Revert "PHP Rebuild"

This reverts commit 2ef3654841.
This commit is contained in:
Stefan Lazic committed 2026-09-05 09:32:05 +01:00
1 parent a59f998f2b
commit 2aa8405006
690 files changed
+12045 -33943

No files matched your search

@@ -0,0 +1,229 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { prisma } from "@/services/scheduling/__tests__/fixtures";
import { InstallationStatus } from "@/generated/prisma/enums";
import { installApplication } from "../installer.service";
import type { InstallInput } from "../schema";
/**
* Exercises the real transactional installer against a real MySQL
* database — see installer.service.ts's own header for why the
* ApplicationInstallation singleton row is the concurrency lock. These
* tests own that row exclusively: any pre-existing installation state in
* this database is removed in `beforeEach` and never restored, since a
* test database is expected to be disposable (see
* docs/phase-8-summary.md's testing section) — do not point this at a
* real deployment's database.
*/
const INSTALLATION_TOKEN = "integration-test-install-token";
function buildInput(overrides: Partial<InstallInput> = {}): InstallInput {
return {
installationToken: INSTALLATION_TOKEN,
administrator: {
firstName: "Test",
lastName: "Owner",
email: `install-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}@example.com`,
phone: "",
password: "correct-horse-battery",
confirmPassword: "correct-horse-battery",
},
business: {
name: "Integration Test Barbershop",
shortName: "",
description: "",
email: "",
phone: "",
website: "",
},
location: {
addressLine1: "1 Test Street",
addressLine2: "",
city: "Testville",
region: "",
postalCode: "T1 1TT",
country: "GB",
timezone: "Europe/London",
currency: "GBP",
locale: "en-GB",
},
hours: (["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"] as const).map((dayOfWeek) => ({
dayOfWeek,
isClosed: dayOfWeek === "SUNDAY",
opensAtMinutes: dayOfWeek === "SUNDAY" ? undefined : 540,
closesAtMinutes: dayOfWeek === "SUNDAY" ? undefined : 1080,
})),
bookingRules: {
allowGuestBooking: false,
minNoticeMinutes: 120,
maxAdvanceDays: 60,
cancellationWindowHours: 24,
rescheduleWindowHours: 24,
slotIntervalMinutes: 30,
bufferBeforeMinutes: 0,
bufferAfterMinutes: 5,
maxAppointmentsPerDay: null,
maxAppointmentsPerStaffPerDay: null,
customerCanChooseStaff: true,
requireApproval: false,
},
branding: { logoUrl: "", coverImageUrl: "", themeColor: "", backgroundColor: "" },
starterServices: [
{ categoryName: "Haircuts", name: "Classic Cut", description: "", price: "20.00", durationMinutes: 30, active: true, allowOnlineBooking: true },
],
...overrides,
};
}
async function resetInstallationSingleton(): Promise<void> {
await prisma.applicationInstallation.deleteMany({ where: { id: "singleton" } });
}
async function cleanupInstalledBusiness(businessId: string | undefined): Promise<void> {
if (!businessId) return;
const staff = await prisma.staffProfile.findMany({ where: { businessId }, select: { id: true, userId: true } });
const userIds = staff.map((s) => s.userId);
await prisma.staffService.deleteMany({ where: { staffId: { in: staff.map((s) => s.id) } } });
await prisma.staffProfile.deleteMany({ where: { businessId } });
await prisma.service.deleteMany({ where: { businessId } });
await prisma.serviceCategory.deleteMany({ where: { businessId } });
await prisma.rolePermission.deleteMany({ where: { role: { businessId } } });
await prisma.role.deleteMany({ where: { businessId } });
await prisma.auditLog.deleteMany({ where: { entityId: businessId } });
await prisma.loyaltyProgramme.deleteMany({ where: { businessId } });
await prisma.bookingRule.deleteMany({ where: { businessId } });
await prisma.businessSettings.deleteMany({ where: { businessId } });
await prisma.businessHours.deleteMany({ where: { businessId } });
await prisma.businessLocation.deleteMany({ where: { businessId } });
await prisma.business.deleteMany({ where: { id: businessId } });
await prisma.user.deleteMany({ where: { id: { in: userIds } } });
await resetInstallationSingleton();
}
describe("installApplication (integration)", () => {
let createdBusinessId: string | undefined;
beforeEach(async () => {
await resetInstallationSingleton();
createdBusinessId = undefined;
});
afterEach(async () => {
await cleanupInstalledBusiness(createdBusinessId);
});
it("rejects an incorrect installation token without creating anything", async () => {
const originalToken = process.env.INSTALLATION_TOKEN;
process.env.INSTALLATION_TOKEN = INSTALLATION_TOKEN;
try {
const result = await installApplication(prisma, {
input: buildInput({ installationToken: "wrong-token" }),
installedVersion: "test",
});
expect(result.success).toBe(false);
if (!result.success) expect(result.code).toBe("INVALID_TOKEN");
const business = await prisma.business.findFirst({ where: { name: "Integration Test Barbershop" } });
expect(business).toBeNull();
const installation = await prisma.applicationInstallation.findUnique({ where: { id: "singleton" } });
expect(installation).toBeNull();
} finally {
process.env.INSTALLATION_TOKEN = originalToken;
}
});
it("creates the business, administrator, permissions, booking rules, and starter services transactionally", async () => {
const originalToken = process.env.INSTALLATION_TOKEN;
process.env.INSTALLATION_TOKEN = INSTALLATION_TOKEN;
try {
const result = await installApplication(prisma, { input: buildInput(), installedVersion: "test" });
expect(result.success).toBe(true);
if (!result.success) return;
createdBusinessId = result.businessId;
const installation = await prisma.applicationInstallation.findUnique({ where: { id: "singleton" } });
expect(installation?.status).toBe(InstallationStatus.INSTALLED);
expect(installation?.primaryBusinessId).toBe(result.businessId);
expect(installation?.initialAdminUserId).toBe(result.userId);
const business = await prisma.business.findUnique({
where: { id: result.businessId },
include: { bookingRule: true, locations: true, hours: true, loyaltyProgramme: true },
});
expect(business?.name).toBe("Integration Test Barbershop");
// Guest booking off by default, per the brief — confirmed end to end here.
expect(business?.bookingRule?.allowGuestBooking).toBe(false);
expect(business?.locations).toHaveLength(1);
expect(business?.hours).toHaveLength(7);
expect(business?.loyaltyProgramme?.isEnabled).toBe(false);
const roles = await prisma.role.findMany({ where: { businessId: result.businessId }, include: { permissions: true } });
const ownerRole = roles.find((r) => r.name === "Owner");
const barberRole = roles.find((r) => r.name === "Barber");
expect(ownerRole).toBeDefined();
expect(barberRole).toBeDefined();
expect(ownerRole!.permissions.length).toBeGreaterThan(barberRole!.permissions.length);
const staffProfile = await prisma.staffProfile.findUnique({ where: { userId: result.userId }, include: { services: true } });
expect(staffProfile?.roleId).toBe(ownerRole!.id);
// The initial owner is eligible for every starter service — see the brief's "initial owner is service-eligible."
expect(staffProfile?.services).toHaveLength(1);
const services = await prisma.service.findMany({ where: { businessId: result.businessId } });
expect(services).toHaveLength(1);
expect(services[0]?.name).toBe("Classic Cut");
const auditLog = await prisma.auditLog.findFirst({ where: { entityId: result.businessId, action: "APPLICATION_INSTALLED" } });
expect(auditLog).toBeTruthy();
} finally {
process.env.INSTALLATION_TOKEN = originalToken;
}
});
it("refuses to install a second time once already installed", async () => {
const originalToken = process.env.INSTALLATION_TOKEN;
process.env.INSTALLATION_TOKEN = INSTALLATION_TOKEN;
try {
const first = await installApplication(prisma, { input: buildInput(), installedVersion: "test" });
expect(first.success).toBe(true);
if (first.success) createdBusinessId = first.businessId;
const second = await installApplication(prisma, {
input: buildInput({
administrator: { ...buildInput().administrator, email: `second-${Date.now()}@example.com` },
}),
installedVersion: "test",
});
expect(second.success).toBe(false);
if (!second.success) expect(second.code).toBe("ALREADY_INSTALLED");
} finally {
process.env.INSTALLATION_TOKEN = originalToken;
}
});
it("prevents two concurrent installation attempts from both succeeding", async () => {
const originalToken = process.env.INSTALLATION_TOKEN;
process.env.INSTALLATION_TOKEN = INSTALLATION_TOKEN;
try {
const inputA = buildInput({ administrator: { ...buildInput().administrator, email: `concurrent-a-${Date.now()}@example.com` } });
const inputB = buildInput({ administrator: { ...buildInput().administrator, email: `concurrent-b-${Date.now()}@example.com` } });
const [resultA, resultB] = await Promise.all([
installApplication(prisma, { input: inputA, installedVersion: "test" }),
installApplication(prisma, { input: inputB, installedVersion: "test" }),
]);
const successes = [resultA, resultB].filter((r) => r.success);
expect(successes).toHaveLength(1);
const winner = successes[0];
if (winner?.success) createdBusinessId = winner.businessId;
const businessCount = await prisma.business.count({
where: { name: "Integration Test Barbershop" },
});
expect(businessCount).toBe(1);
} finally {
process.env.INSTALLATION_TOKEN = originalToken;
}
});
});