test: cover managed profile Guardian races

- Exercise concurrent final-Guardian reductions against Postgres\n- Cover recoverable account and profile deletion paths
This commit is contained in:
Marc-André Bombeck committed 2026-08-06 02:36:08 +02:00
1 parent e5e3590ca3
commit 5059bc9b69
3 files changed
+174

No files matched your search

@@ -31,4 +31,25 @@ describe("managed-profile contract", () => {
routes.some((entry) => entry.toLowerCase().includes("emancip")),
).toBe(false);
});
it("uses the same record lock for every Guardian-reducing transition", async () => {
const lifecycle = await readFile(
path.join(root, "src/lib/managed-profiles/lifecycle.ts"),
"utf8",
);
const grants = await readFile(
path.join(root, "src/lib/sharing/grants.ts"),
"utf8",
);
const accountDeletion = await readFile(
path.join(root, "src/app/api/settings/account/route.ts"),
"utf8",
);
expect(lifecycle).toContain("pg_advisory_xact_lock");
expect(lifecycle).toContain("clearManagedProfileMarker");
expect(lifecycle).toContain("deleteManagedProfile");
expect(grants).toContain("reduceManagedProfileGuardian");
expect(accountDeletion).toContain("deleteGuardianAccountWithLifecycle");
});
});
@@ -0,0 +1,82 @@
import { beforeEach, describe, expect, it } from "vitest";
import { createManagedProfile } from "@/lib/managed-profiles/create";
import {
acceptGrant,
inviteGrant,
renounceGrant,
revokeGrant,
} from "@/lib/sharing/grants";
import { getPrismaClient, truncateAllTables } from "./setup";
let sequence = 0;
async function makeAdult(label: string) {
const suffix = sequence++;
return getPrismaClient().user.create({
data: {
username: `${label}-${suffix}`,
email: `${label}-${suffix}@example.test`,
},
});
}
async function twoGuardians() {
const creator = await makeAdult("creator");
const secondGuardian = await makeAdult("second-guardian");
const { profile, creatorGrant } = await createManagedProfile({
creatorId: creator.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
const invitation = await inviteGrant({
grantorId: profile.id,
granteeId: secondGuardian.id,
access: "MANAGE",
scope: null,
});
const secondGrant = await acceptGrant({
grantId: invitation.id,
granteeId: secondGuardian.id,
});
return { creator, secondGuardian, profile, creatorGrant, secondGrant };
}
beforeEach(async () => {
await truncateAllTables(getPrismaClient());
});
describe("managed profile Guardian concurrency (real Postgres)", () => {
it("allows only one concurrent reduction of the final two Guardians", async () => {
const { creatorGrant, secondGuardian, secondGrant, profile } =
await twoGuardians();
const results = await Promise.allSettled([
revokeGrant({ grantId: creatorGrant.id, grantorId: profile.id }),
renounceGrant({
grantId: secondGrant.id,
granteeId: secondGuardian.id,
}),
]);
expect(
results.filter((result) => result.status === "fulfilled"),
).toHaveLength(1);
expect(
results.filter((result) => result.status === "rejected"),
).toHaveLength(1);
expect(
await getPrismaClient().accountGrant.count({
where: {
grantorId: profile.id,
access: "MANAGE",
acceptedAt: { not: null },
revokedAt: null,
},
}),
).toBe(1);
});
});
@@ -9,6 +9,11 @@ import {
findActiveGrant,
inviteGrant,
} from "@/lib/sharing/grants";
import {
LastManagedGuardianError,
clearManagedProfileMarker,
deleteManagedProfile,
} from "@/lib/managed-profiles/lifecycle";
vi.mock("next/headers", async () => {
const { cookieJar, headerJar } = await import("./mock-next-headers");
@@ -210,4 +215,70 @@ describe("managed profile lifecycle (real Postgres)", () => {
),
).not.toBeNull();
});
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("deletes a managed profile only through its fresh-MFA Guardian path", async () => {
const guardian = await signInWithFreshMfa();
const { profile } = await createManagedProfile({
creatorId: guardian.id,
displayName: "Managed profile",
dateOfBirth: null,
locale: "en",
timezone: "UTC",
});
await deleteManagedProfile({
profileId: profile.id,
guardianId: guardian.id,
});
expect(
await getPrismaClient().user.findUnique({ where: { id: profile.id } }),
).toBeNull();
});
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(LastManagedGuardianError);
});
});