Files
HealthLog/tests/integration/managed-profile-lifecycle.test.ts
Marc-André Bombeck b6da360856 refactor(db): drop the runtime schema mirror
ensureDbCompatibility ran ALTER TABLE / CREATE TABLE IF NOT EXISTS on the
login, register, token and session paths, mirroring columns every
migration under prisma/migrations already creates. The entrypoint applies
those migrations before the server listens and stops the boot when one
fails, so the mirror never had work to do, and DDL on a request path
needs the table's owner: a connection that may only read and write rows
fails every login on it. Drop the module, its call sites and the mocks
that stubbed it out.
2026-11-03 20:32:35 +01:00

904 lines
32 KiB
TypeScript

import { NextRequest } from "next/server";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { cookieJar, headerJar, queuedSessionIds } from "./mock-next-headers";
import { getPrismaClient, truncateAllTables, switchSessionTo } from "./setup";
import { createManagedProfile } from "@/lib/managed-profiles/create";
import { acceptGrant, inviteGrant } from "@/lib/sharing/grants";
import { findActiveGrant } from "@/lib/sharing/grant-access";
import {
clearManagedProfileMarker,
ManagedProfileLifecycleError,
} from "@/lib/managed-profiles/lifecycle";
vi.mock("next/headers", async () => {
const { cookieJar, headerJar, queuedSessionIds } =
await import("./mock-next-headers");
return {
headers: vi.fn(async () => ({
get: (name: string) => headerJar.get(name.toLowerCase()) ?? null,
})),
cookies: vi.fn(async () => {
const snapshot = new Map(cookieJar);
const queuedSessionId = queuedSessionIds.shift();
if (queuedSessionId) {
snapshot.set("healthlog_session", queuedSessionId);
}
return {
get: (name: string) => {
const value = snapshot.get(name);
return value ? { name, value } : undefined;
},
set: (name: string, value: string) => {
cookieJar.set(name, value);
},
delete: (name: string) => {
cookieJar.delete(name);
},
};
}),
};
});
let sequence = 0;
async function createFreshMfaUser(label = "guardian") {
const suffix = sequence++;
const prisma = getPrismaClient();
const user = await prisma.user.create({
data: {
username: `${label}-${suffix}`,
email: `${label}-${suffix}@example.test`,
totpConfirmedAt: new Date(),
},
});
const session = await prisma.session.create({
data: {
userId: user.id,
expiresAt: new Date(Date.now() + 60 * 60 * 1000),
mfaVerifiedAt: new Date(),
},
});
return { user, sessionId: session.id };
}
async function signInWithFreshMfa() {
const { user, sessionId } = await createFreshMfaUser();
cookieJar.set("healthlog_session", sessionId);
return user;
}
async function makeAdult(label: string) {
const suffix = sequence++;
return getPrismaClient().user.create({
data: {
username: `${label}-${suffix}`,
email: `${label}-${suffix}@example.test`,
},
});
}
async function createTwoGuardians() {
const creator = await createFreshMfaUser("creator");
const secondGuardian = await createFreshMfaUser("second-guardian");
const { profile, creatorGrant } = await createManagedProfile({
creatorId: creator.user.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const invitation = await inviteGrant({
grantorId: profile.id,
granteeId: secondGuardian.user.id,
access: "MANAGE",
scope: null,
});
const secondGrant = await acceptGrant({
grantId: invitation.id,
granteeId: secondGuardian.user.id,
});
return { creator, secondGuardian, profile, creatorGrant, secondGrant };
}
function createRequest(body: unknown): NextRequest {
return new NextRequest("http://localhost/api/managed-profiles", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});
}
beforeEach(async () => {
await truncateAllTables(getPrismaClient());
cookieJar.clear();
headerJar.clear();
queuedSessionIds.length = 0;
});
afterEach(async () => {
const prisma = getPrismaClient();
await prisma.$executeRawUnsafe(
'DROP TRIGGER IF EXISTS managed_profile_creation_failure ON "account_grants"',
);
await prisma.$executeRawUnsafe(
"DROP FUNCTION IF EXISTS fail_managed_profile_creation()",
);
await prisma.$executeRawUnsafe(
'DROP TRIGGER IF EXISTS managed_profile_acceptance_failure ON "account_grants"',
);
await prisma.$executeRawUnsafe(
"DROP FUNCTION IF EXISTS fail_managed_profile_acceptance()",
);
await prisma.$executeRawUnsafe(
"DROP TRIGGER IF EXISTS managed_profile_delete_audit_failure ON audit_logs",
);
await prisma.$executeRawUnsafe(
"DROP FUNCTION IF EXISTS fail_managed_profile_delete_audit()",
);
await prisma.$executeRawUnsafe(
"DROP TRIGGER IF EXISTS managed_guardian_audit_failure ON audit_logs",
);
await prisma.$executeRawUnsafe(
"DROP FUNCTION IF EXISTS fail_managed_guardian_audit()",
);
});
describe("managed profile lifecycle (real Postgres)", () => {
it("creation records a complete DOB and one accepted non-expiring Guardian grant", async () => {
const guardian = await signInWithFreshMfa();
const { POST } = await import("@/app/api/managed-profiles/route");
const response = await POST(
createRequest({
displayName: "Managed profile",
dateOfBirth: "2012-03-04",
locale: "de",
timezone: "Europe/Zurich",
}),
);
expect(response.status).toBe(201);
const body = await response.json();
const profile = await getPrismaClient().user.findUniqueOrThrow({
where: { id: body.data.id },
});
expect(profile.username).toMatch(/^managed-/);
expect(profile.username).not.toContain("Managed profile");
expect(profile.email).toBeNull();
expect(profile.passwordHash).toBeNull();
expect(profile.dateOfBirth?.toISOString().slice(0, 10)).toBe("2012-03-04");
expect(profile.locale).toBe("de");
expect(profile.timezone).toBe("Europe/Zurich");
expect(profile.managedProfileAt).not.toBeNull();
const grant = await getPrismaClient().accountGrant.findFirstOrThrow({
where: { grantorId: profile.id, granteeId: guardian.id },
});
expect(grant.access).toBe("MANAGE");
expect(grant.acceptedAt).not.toBeNull();
expect(grant.expiresAt).toBeNull();
});
it("creation accepts an absent DOB without fabricating one", async () => {
await signInWithFreshMfa();
const { POST } = await import("@/app/api/managed-profiles/route");
const response = await POST(
createRequest({
displayName: "No birth date",
locale: "en",
timezone: "UTC",
}),
);
expect(response.status).toBe(201);
const body = await response.json();
const profile = await getPrismaClient().user.findUniqueOrThrow({
where: { id: body.data.id },
});
expect(profile.dateOfBirth).toBeNull();
});
it("creation rolls the profile back when its Guardian grant fails", async () => {
await signInWithFreshMfa();
const prisma = getPrismaClient();
await prisma.$executeRawUnsafe(
"CREATE FUNCTION fail_managed_profile_creation() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'managed profile grant failure'; END; $$",
);
await prisma.$executeRawUnsafe(
'CREATE TRIGGER managed_profile_creation_failure BEFORE INSERT ON "account_grants" FOR EACH ROW EXECUTE FUNCTION fail_managed_profile_creation()',
);
const { POST } = await import("@/app/api/managed-profiles/route");
const response = await POST(
createRequest({
displayName: "Must roll back",
locale: "en",
timezone: "UTC",
}),
);
expect(response.status).toBe(500);
expect(
await prisma.user.count({
where: { managedProfileAt: { not: null } },
}),
).toBe(0);
expect(await prisma.accountGrant.count()).toBe(0);
});
it("second Guardian accepts through the ordinary invitation and does not expire", async () => {
const creator = await signInWithFreshMfa();
const secondGuardian = await makeAdult("second-guardian");
const { profile } = await createManagedProfile({
creatorId: creator.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const invitationExpiry = new Date(Date.now() + 60 * 60 * 1000);
const invitation = await inviteGrant({
grantorId: profile.id,
granteeId: secondGuardian.id,
access: "MANAGE",
scope: null,
expiresAt: invitationExpiry,
});
expect(invitation.acceptedAt).toBeNull();
expect(invitation.expiresAt).toEqual(invitationExpiry);
const accepted = await acceptGrant({
grantId: invitation.id,
granteeId: secondGuardian.id,
});
expect(accepted.acceptedAt).not.toBeNull();
expect(accepted.expiresAt).toBeNull();
expect(
await findActiveGrant(
{ grantorId: profile.id, granteeId: secondGuardian.id },
getPrismaClient(),
new Date(Date.now() + 24 * 60 * 60 * 1000),
),
).not.toBeNull();
});
it("lets a cookie-authenticated Guardian invite and revoke another Guardian", async () => {
const creator = await createFreshMfaUser("creator");
const secondGuardian = await createFreshMfaUser("second-guardian");
cookieJar.set("healthlog_session", creator.sessionId);
const { profile } = await createManagedProfile({
creatorId: creator.user.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const invitationExpiry = new Date(Date.now() + 60 * 60 * 1000);
const { POST: inviteGuardian } =
await import("@/app/api/managed-profiles/[id]/guardians/route");
const invited = await inviteGuardian(
new NextRequest(
`http://localhost/api/managed-profiles/${profile.id}/guardians`,
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
identifier: secondGuardian.user.username,
expiresAt: invitationExpiry.toISOString(),
}),
},
),
{ params: Promise.resolve({ id: profile.id }) },
);
expect(invited.status).toBe(201);
const invitation = (await invited.json()).data;
expect(invitation.acceptedAt).toBeNull();
// The expiry is read off the stored row: the route answers with the entry
// the roster publishes, and that entry describes who looks after the
// record rather than the terms of one invitation.
expect(
(
await getPrismaClient().accountGrant.findUniqueOrThrow({
where: { id: invitation.grantId },
})
).expiresAt?.getTime(),
).toBe(invitationExpiry.getTime());
cookieJar.set("healthlog_session", secondGuardian.sessionId);
const { POST: acceptInvitation } =
await import("@/app/api/account/grants/[id]/accept/route");
const accepted = await acceptInvitation(
new NextRequest(
`http://localhost/api/account/grants/${invitation.grantId}/accept`,
{
method: "POST",
},
),
{ params: Promise.resolve({ id: invitation.grantId }) },
);
expect(accepted.status).toBe(200);
expect(
await getPrismaClient().accountGrant.findUniqueOrThrow({
where: { id: invitation.grantId },
}),
).toMatchObject({ acceptedAt: expect.any(Date), expiresAt: null });
await switchSessionTo(secondGuardian.sessionId, profile.id);
cookieJar.set("healthlog_session", creator.sessionId);
const { DELETE: revokeGuardian } =
await import("@/app/api/managed-profiles/[id]/guardians/[grantId]/route");
const revoked = await revokeGuardian(
new NextRequest(
`http://localhost/api/managed-profiles/${profile.id}/guardians/${invitation.grantId}`,
{ method: "DELETE" },
),
{
params: Promise.resolve({
id: profile.id,
grantId: invitation.grantId,
}),
},
);
expect(revoked.status).toBe(200);
expect(
await getPrismaClient().accountGrant.findUniqueOrThrow({
where: { id: invitation.grantId },
}),
).toMatchObject({ revokedBy: "GRANTOR" });
expect(
await getPrismaClient().session.findUniqueOrThrow({
where: { id: secondGuardian.sessionId },
}),
).toMatchObject({ actingAsUserId: null });
const audit = await getPrismaClient().auditLog.findMany({
where: {
userId: profile.id,
action: {
in: [
"managed_profile.guardian.invited",
"managed_profile.guardian.revoked",
],
},
},
orderBy: { createdAt: "asc" },
});
expect(audit).toHaveLength(2);
expect(audit.map((entry) => entry.actorUserId)).toEqual([
creator.user.id,
creator.user.id,
]);
expect(audit.map((entry) => JSON.parse(entry.details ?? "{}"))).toEqual([
{ grantId: invitation.grantId },
{ grantId: invitation.grantId },
]);
});
it("withdraws a pending Guardian invitation without touching the active count and permits re-invitation", async () => {
const creator = await createFreshMfaUser("creator");
const secondGuardian = await createFreshMfaUser("second-guardian");
cookieJar.set("healthlog_session", creator.sessionId);
const { profile } = await createManagedProfile({
creatorId: creator.user.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const { POST: inviteGuardian } =
await import("@/app/api/managed-profiles/[id]/guardians/route");
const { DELETE: revokeGuardian } =
await import("@/app/api/managed-profiles/[id]/guardians/[grantId]/route");
const invite = async () => {
const response = await inviteGuardian(
new NextRequest(
`http://localhost/api/managed-profiles/${profile.id}/guardians`,
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ identifier: secondGuardian.user.username }),
},
),
{ params: Promise.resolve({ id: profile.id }) },
);
expect(response.status).toBe(201);
return (await response.json()).data;
};
const pending = await invite();
const withdrawn = await revokeGuardian(
new NextRequest(
`http://localhost/api/managed-profiles/${profile.id}/guardians/${pending.grantId}`,
{ method: "DELETE" },
),
{ params: Promise.resolve({ id: profile.id, grantId: pending.grantId }) },
);
expect(withdrawn.status).toBe(200);
expect(
await getPrismaClient().accountGrant.findUniqueOrThrow({
where: { id: pending.grantId },
}),
).toMatchObject({ acceptedAt: null, revokedAt: expect.any(Date) });
const replacement = await invite();
expect(replacement.grantId).not.toBe(pending.grantId);
expect(replacement.acceptedAt).toBeNull();
});
it("refuses a managed profile as a Guardian invitee", async () => {
const creator = await createFreshMfaUser("creator");
cookieJar.set("healthlog_session", creator.sessionId);
const { profile } = await createManagedProfile({
creatorId: creator.user.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const { profile: managedInvitee } = await createManagedProfile({
creatorId: creator.user.id,
displayName: "Another managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const { POST: inviteGuardian } =
await import("@/app/api/managed-profiles/[id]/guardians/route");
const response = await inviteGuardian(
new NextRequest(
`http://localhost/api/managed-profiles/${profile.id}/guardians`,
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ identifier: managedInvitee.username }),
},
),
{ params: Promise.resolve({ id: profile.id }) },
);
expect(response.status).toBe(422);
expect(
await getPrismaClient().accountGrant.count({
where: { grantorId: profile.id },
}),
).toBe(1);
});
it("rolls Guardian invitations back when their audit entry fails", async () => {
const creator = await createFreshMfaUser("creator");
const secondGuardian = await createFreshMfaUser("second-guardian");
cookieJar.set("healthlog_session", creator.sessionId);
const { profile } = await createManagedProfile({
creatorId: creator.user.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const prisma = getPrismaClient();
await prisma.$executeRawUnsafe(
"CREATE FUNCTION fail_managed_guardian_audit() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW.action = 'managed_profile.guardian.invited' THEN RAISE EXCEPTION 'managed Guardian audit failure'; END IF; RETURN NEW; END; $$",
);
await prisma.$executeRawUnsafe(
"CREATE TRIGGER managed_guardian_audit_failure BEFORE INSERT ON audit_logs FOR EACH ROW EXECUTE FUNCTION fail_managed_guardian_audit()",
);
const { POST: inviteGuardian } =
await import("@/app/api/managed-profiles/[id]/guardians/route");
const response = await inviteGuardian(
new NextRequest(
`http://localhost/api/managed-profiles/${profile.id}/guardians`,
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ identifier: secondGuardian.user.username }),
},
),
{ params: Promise.resolve({ id: profile.id }) },
);
expect(response.status).toBe(500);
expect(
await prisma.accountGrant.count({ where: { grantorId: profile.id } }),
).toBe(1);
});
it("rolls active Guardian revocation back when its audit entry fails", async () => {
const { creator, profile, secondGuardian, secondGrant } =
await createTwoGuardians();
cookieJar.set("healthlog_session", creator.sessionId);
await switchSessionTo(secondGuardian.sessionId, profile.id);
const prisma = getPrismaClient();
await prisma.$executeRawUnsafe(
"CREATE FUNCTION fail_managed_guardian_audit() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW.action = 'managed_profile.guardian.revoked' THEN RAISE EXCEPTION 'managed Guardian audit failure'; END IF; RETURN NEW; END; $$",
);
await prisma.$executeRawUnsafe(
"CREATE TRIGGER managed_guardian_audit_failure BEFORE INSERT ON audit_logs FOR EACH ROW EXECUTE FUNCTION fail_managed_guardian_audit()",
);
const { DELETE: revokeGuardian } =
await import("@/app/api/managed-profiles/[id]/guardians/[grantId]/route");
const response = await revokeGuardian(
new NextRequest(
`http://localhost/api/managed-profiles/${profile.id}/guardians/${secondGrant.id}`,
{ method: "DELETE" },
),
{ params: Promise.resolve({ id: profile.id, grantId: secondGrant.id }) },
);
expect(response.status).toBe(500);
expect(
await prisma.accountGrant.findUniqueOrThrow({
where: { id: secondGrant.id },
}),
).toMatchObject({ revokedAt: null });
expect(
await prisma.session.findUniqueOrThrow({
where: { id: secondGuardian.sessionId },
}),
).toMatchObject({ actingAsUserId: profile.id });
});
it("rolls managed Guardian acceptance back when the grant update fails", async () => {
const creator = await signInWithFreshMfa();
const secondGuardian = await makeAdult("second-guardian");
const { profile } = await createManagedProfile({
creatorId: creator.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
const invitation = await inviteGrant({
grantorId: profile.id,
granteeId: secondGuardian.id,
access: "MANAGE",
scope: null,
expiresAt,
});
const prisma = getPrismaClient();
await prisma.$executeRawUnsafe(
"CREATE FUNCTION fail_managed_profile_acceptance() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW.accepted_at IS NOT NULL AND OLD.accepted_at IS NULL THEN RAISE EXCEPTION 'managed profile acceptance failure'; END IF; RETURN NEW; END; $$",
);
await prisma.$executeRawUnsafe(
'CREATE TRIGGER managed_profile_acceptance_failure BEFORE UPDATE ON "account_grants" FOR EACH ROW EXECUTE FUNCTION fail_managed_profile_acceptance()',
);
await expect(
acceptGrant({ grantId: invitation.id, granteeId: secondGuardian.id }),
).rejects.toThrow("managed profile acceptance failure");
expect(
await prisma.accountGrant.findUniqueOrThrow({
where: { id: invitation.id },
}),
).toMatchObject({ acceptedAt: null, expiresAt });
expect(
await prisma.user.findUniqueOrThrow({ where: { id: profile.id } }),
).toMatchObject({ managedProfileAt: expect.any(Date) });
});
it("serializes Guardian acceptance against marker clearing", async () => {
const creator = await signInWithFreshMfa();
const secondGuardian = await makeAdult("second-guardian");
const { profile } = await createManagedProfile({
creatorId: creator.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
const invitation = await inviteGrant({
grantorId: profile.id,
granteeId: secondGuardian.id,
access: "MANAGE",
scope: null,
expiresAt,
});
await Promise.all([
acceptGrant({ grantId: invitation.id, granteeId: secondGuardian.id }),
clearManagedProfileMarker({ profileId: profile.id }),
]);
const [profileAfter, grantAfter] = await Promise.all([
getPrismaClient().user.findUniqueOrThrow({ where: { id: profile.id } }),
getPrismaClient().accountGrant.findUniqueOrThrow({
where: { id: invitation.id },
}),
]);
expect(profileAfter.managedProfileAt).toBeNull();
expect(grantAfter.acceptedAt).toEqual(expect.any(Date));
expect([null, expiresAt.getTime()]).toContain(
grantAfter.expiresAt?.getTime() ?? null,
);
});
it("refuses to delete a Guardian account when it would leave a managed profile behind", async () => {
const guardian = await signInWithFreshMfa();
const { profile } = await createManagedProfile({
creatorId: guardian.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const { DELETE } = await import("@/app/api/settings/account/route");
const response = await DELETE(
new NextRequest("http://localhost/api/settings/account", {
method: "DELETE",
headers: { "content-type": "application/json" },
body: JSON.stringify({ confirm: "DELETE_ACCOUNT" }),
}),
);
expect(response.status).toBe(409);
expect(
await getPrismaClient().user.findUnique({ where: { id: guardian.id } }),
).not.toBeNull();
expect(
await getPrismaClient().user.findUnique({ where: { id: profile.id } }),
).not.toBeNull();
});
it("preserves account, session, and audit rows when last-Guardian deletion is refused", async () => {
const guardian = await signInWithFreshMfa();
const prisma = getPrismaClient();
await createManagedProfile({
creatorId: guardian.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
await prisma.auditLog.create({
data: { action: "test.before.guardian.refusal", userId: guardian.id },
});
const [sessionsBefore, auditsBefore] = await Promise.all([
prisma.session.count({ where: { userId: guardian.id } }),
prisma.auditLog.count({ where: { userId: guardian.id } }),
]);
const { DELETE } = await import("@/app/api/settings/account/route");
const response = await DELETE(
new NextRequest("http://localhost/api/settings/account", {
method: "DELETE",
headers: { "content-type": "application/json" },
body: JSON.stringify({ confirm: "DELETE_ACCOUNT" }),
}),
);
expect(response.status).toBe(409);
await expect(
Promise.all([
prisma.session.count({ where: { userId: guardian.id } }),
prisma.auditLog.count({ where: { userId: guardian.id } }),
prisma.auditLog.count({
where: { userId: guardian.id, action: "user.account.delete" },
}),
]),
).resolves.toEqual([sessionsBefore, auditsBefore, 0]);
});
it("deletes a managed profile through its fresh-MFA Guardian route", async () => {
const guardian = await signInWithFreshMfa();
const { profile } = await createManagedProfile({
creatorId: guardian.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const { DELETE } = await import("@/app/api/managed-profiles/[id]/route");
const response = await DELETE(
new NextRequest(`http://localhost/api/managed-profiles/${profile.id}`, {
method: "DELETE",
}),
{ params: Promise.resolve({ id: profile.id }) },
);
expect(response.status).toBe(200);
expect(
await getPrismaClient().user.findUnique({ where: { id: profile.id } }),
).toBeNull();
});
it("rolls profile deletion back when its audit entry fails", async () => {
const guardian = await signInWithFreshMfa();
const { profile } = await createManagedProfile({
creatorId: guardian.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const prisma = getPrismaClient();
await prisma.$executeRawUnsafe(
"CREATE FUNCTION fail_managed_profile_delete_audit() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW.action = 'managed_profile.deleted' THEN RAISE EXCEPTION 'managed profile deletion audit failure'; END IF; RETURN NEW; END; $$",
);
await prisma.$executeRawUnsafe(
"CREATE TRIGGER managed_profile_delete_audit_failure BEFORE INSERT ON audit_logs FOR EACH ROW EXECUTE FUNCTION fail_managed_profile_delete_audit()",
);
const { DELETE } = await import("@/app/api/managed-profiles/[id]/route");
const response = await DELETE(
new NextRequest(`http://localhost/api/managed-profiles/${profile.id}`, {
method: "DELETE",
}),
{ params: Promise.resolve({ id: profile.id }) },
);
expect(response.status).toBe(500);
expect(
await prisma.user.findUnique({ where: { id: profile.id } }),
).not.toBeNull();
expect(
await prisma.accountGrant.count({ where: { grantorId: profile.id } }),
).toBe(1);
});
it("serializes a Guardian renunciation against that Guardian's account deletion", async () => {
const { profile, secondGuardian, secondGrant } = await createTwoGuardians();
const { POST: renounce } =
await import("@/app/api/account/grants/[id]/renounce/route");
const { DELETE: deleteAccount } =
await import("@/app/api/settings/account/route");
queuedSessionIds.push(
secondGuardian.sessionId,
secondGuardian.sessionId,
secondGuardian.sessionId,
);
const [renounced, deleted] = await Promise.all([
renounce(
new NextRequest(
`http://localhost/api/account/grants/${secondGrant.id}/renounce`,
{ method: "POST" },
),
{ params: Promise.resolve({ id: secondGrant.id }) },
),
deleteAccount(
new NextRequest("http://localhost/api/settings/account", {
method: "DELETE",
headers: { "content-type": "application/json" },
body: JSON.stringify({ confirm: "DELETE_ACCOUNT" }),
}),
),
]);
// Either order is a correct serialization: the renunciation lands first
// (200), or the deletion ends the grant first and the renunciation finds
// it gone (404) or already ended (409 `sharing.renounce.already_ended`).
// Which one wins depends only on timing, and the write ceiling charged
// ahead of the renunciation shifts that timing.
expect([200, 404, 409]).toContain(renounced.status);
if (renounced.status === 409) {
const body = (await renounced.clone().json()) as {
meta?: { errorCode?: string };
};
expect(body.meta?.errorCode).toBe("sharing.renounce.already_ended");
}
expect(deleted.status).toBe(200);
await expect(
Promise.all([
getPrismaClient().user.findUnique({
where: { id: secondGuardian.user.id },
}),
getPrismaClient().session.count({
where: { userId: secondGuardian.user.id },
}),
getPrismaClient().accountGrant.count({
where: {
grantorId: profile.id,
access: "MANAGE",
acceptedAt: { not: null },
revokedAt: null,
},
}),
]),
).resolves.toEqual([null, 0, 1]);
});
it("serializes a Guardian renunciation against managed profile deletion", async () => {
const { creator, profile, secondGuardian, secondGrant } =
await createTwoGuardians();
const { POST: renounce } =
await import("@/app/api/account/grants/[id]/renounce/route");
const { DELETE: deleteProfile } =
await import("@/app/api/managed-profiles/[id]/route");
queuedSessionIds.push(secondGuardian.sessionId, creator.sessionId);
const [renounced, deleted] = await Promise.all([
renounce(
new NextRequest(
`http://localhost/api/account/grants/${secondGrant.id}/renounce`,
{ method: "POST" },
),
{ params: Promise.resolve({ id: secondGrant.id }) },
),
deleteProfile(
new NextRequest(`http://localhost/api/managed-profiles/${profile.id}`, {
method: "DELETE",
}),
{ params: Promise.resolve({ id: profile.id }) },
),
]);
// Either order is a correct serialization: the renunciation lands first
// (200), or the deletion ends the grant first and the renunciation finds
// it gone (404) or already ended (409 `sharing.renounce.already_ended`).
// Which one wins depends only on timing, and the write ceiling charged
// ahead of the renunciation shifts that timing.
expect([200, 404, 409]).toContain(renounced.status);
if (renounced.status === 409) {
const body = (await renounced.clone().json()) as {
meta?: { errorCode?: string };
};
expect(body.meta?.errorCode).toBe("sharing.renounce.already_ended");
}
expect(deleted.status).toBe(200);
await expect(
Promise.all([
getPrismaClient().user.findUnique({ where: { id: profile.id } }),
getPrismaClient().accountGrant.count({
where: { grantorId: profile.id },
}),
getPrismaClient().auditLog.count({
where: {
userId: creator.user.id,
action: "managed_profile.deleted",
},
}),
]),
).resolves.toEqual([null, 0, 1]);
});
it("serializes a Guardian revocation route against internal marker clearing", async () => {
const { creator, profile, secondGrant } = await createTwoGuardians();
const { DELETE: revokeGuardian } =
await import("@/app/api/managed-profiles/[id]/guardians/[grantId]/route");
queuedSessionIds.push(creator.sessionId);
const [revoked] = await Promise.all([
revokeGuardian(
new NextRequest(
`http://localhost/api/managed-profiles/${profile.id}/guardians/${secondGrant.id}`,
{ method: "DELETE" },
),
{
params: Promise.resolve({ id: profile.id, grantId: secondGrant.id }),
},
),
clearManagedProfileMarker({ profileId: profile.id }),
]);
expect([200, 404]).toContain(revoked.status);
const [profileAfter, grantAfter] = await Promise.all([
getPrismaClient().user.findUniqueOrThrow({ where: { id: profile.id } }),
getPrismaClient().accountGrant.findUniqueOrThrow({
where: { id: secondGrant.id },
}),
]);
expect(profileAfter.managedProfileAt).toBeNull();
expect([null, "GRANTOR"]).toContain(grantAfter.revokedBy);
});
it("keeps marker clearing internal while stopping managed behavior", async () => {
const guardian = await signInWithFreshMfa();
const { profile } = await createManagedProfile({
creatorId: guardian.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
await clearManagedProfileMarker({ profileId: profile.id });
const cleared = await getPrismaClient().user.findUniqueOrThrow({
where: { id: profile.id },
});
expect(cleared.managedProfileAt).toBeNull();
await expect(
clearManagedProfileMarker({ profileId: "missing-profile" }),
).rejects.toBeInstanceOf(ManagedProfileLifecycleError);
});
});