import { beforeEach, describe, expect, test } from "vitest"; import { db, sqlite } from "~/server/db/db"; import { member, organization, sessionsTable, usersTable } from "~/server/db/schema"; import { createMembership, createOrganization, createSession, createUser, dropTrigger, escapeSqlLiteral, randomSlug, } from "~/test/helpers/user-org"; import { authService } from "../auth.service"; const CLEANUP_USER_ORGS_ROLLBACK_TRIGGER = "cleanup_user_orgs_final_session_abort"; describe("authService.cleanupUserOrganizations", () => { beforeEach(async () => { dropTrigger(CLEANUP_USER_ORGS_ROLLBACK_TRIGGER); await db.delete(member); await db.delete(sessionsTable); await db.delete(organization); await db.delete(usersTable); }); test("reassigns active organization for members whose active org gets deleted", async () => { const adminUserId = await createUser(`${randomSlug("admin")}@example.com`); const deletedUserId = await createUser(`${randomSlug("deleted")}@example.com`); const adminWorkspaceId = await createOrganization("Admin Workspace"); const deletedWorkspaceId = await createOrganization("Deleted Workspace"); await createMembership({ userId: adminUserId, organizationId: adminWorkspaceId, role: "owner" }); await createMembership({ userId: deletedUserId, organizationId: deletedWorkspaceId, role: "owner" }); await createMembership({ userId: adminUserId, organizationId: deletedWorkspaceId, role: "member" }); const adminSessionId = await createSession({ userId: adminUserId, activeOrganizationId: deletedWorkspaceId, }); await authService.cleanupUserOrganizations(deletedUserId); const remainingSession = await db.query.sessionsTable.findFirst({ where: { id: adminSessionId }, columns: { activeOrganizationId: true }, }); const deletedWorkspace = await db.query.organization.findFirst({ where: { id: deletedWorkspaceId }, columns: { id: true }, }); expect(deletedWorkspace).toBeUndefined(); expect(remainingSession?.activeOrganizationId).toBe(adminWorkspaceId); const deletedMembership = await db.query.member.findFirst({ where: { AND: [{ organizationId: deletedWorkspaceId }, { userId: adminUserId }] }, columns: { id: true }, }); expect(deletedMembership).toBeUndefined(); }); test("sets active organization to null when user has no other memberships", async () => { const deletedOnlyUserId = await createUser(`${randomSlug("deleted")}@example.com`); const deletedWorkspaceId = await createOrganization("Deleted Workspace"); await createMembership({ userId: deletedOnlyUserId, organizationId: deletedWorkspaceId, role: "owner", }); const userSessionId = await createSession({ userId: deletedOnlyUserId, activeOrganizationId: deletedWorkspaceId, }); await authService.cleanupUserOrganizations(deletedOnlyUserId); const deletedWorkspace = await db.query.organization.findFirst({ where: { id: deletedWorkspaceId }, columns: { id: true }, }); const updatedSession = await db.query.sessionsTable.findFirst({ where: { id: userSessionId }, columns: { activeOrganizationId: true }, }); expect(deletedWorkspace).toBeUndefined(); expect(updatedSession?.activeOrganizationId).toBeNull(); const membership = await db.query.member.findFirst({ where: { userId: deletedOnlyUserId }, columns: { id: true }, }); expect(membership).toBeUndefined(); }); test("sets active organization to null for affected members without a fallback organization", async () => { const deletedUserId = await createUser(`${randomSlug("deleted")}@example.com`); const affectedUserId = await createUser(`${randomSlug("affected")}@example.com`); const deletedWorkspaceId = await createOrganization("Deleted Workspace"); await createMembership({ userId: deletedUserId, organizationId: deletedWorkspaceId, role: "owner" }); await createMembership({ userId: affectedUserId, organizationId: deletedWorkspaceId, role: "member" }); const affectedSessionId = await createSession({ userId: affectedUserId, activeOrganizationId: deletedWorkspaceId, }); await authService.cleanupUserOrganizations(deletedUserId); const updatedSession = await db.query.sessionsTable.findFirst({ where: { id: affectedSessionId }, columns: { activeOrganizationId: true }, }); const deletedWorkspace = await db.query.organization.findFirst({ where: { id: deletedWorkspaceId }, columns: { id: true }, }); const removedMembership = await db.query.member.findFirst({ where: { AND: [{ organizationId: deletedWorkspaceId }, { userId: affectedUserId }] }, columns: { id: true }, }); expect(deletedWorkspace).toBeUndefined(); expect(updatedSession?.activeOrganizationId).toBeNull(); expect(removedMembership).toBeUndefined(); }); test("rolls back organization cleanup when session nulling fails", async () => { const affectedUserId = await createUser(`${randomSlug("admin")}@example.com`); const deletedUserId = await createUser(`${randomSlug("deleted")}@example.com`); const fallbackWorkspaceId = await createOrganization("Fallback Workspace"); const deletedWorkspaceId = await createOrganization("Deleted Workspace"); await createMembership({ userId: affectedUserId, organizationId: fallbackWorkspaceId, role: "owner" }); await createMembership({ userId: deletedUserId, organizationId: deletedWorkspaceId, role: "owner" }); await createMembership({ userId: affectedUserId, organizationId: deletedWorkspaceId, role: "member" }); const affectedSessionId = await createSession({ userId: affectedUserId, activeOrganizationId: deletedWorkspaceId, }); const deletedUserSessionId = await createSession({ userId: deletedUserId, activeOrganizationId: deletedWorkspaceId, }); sqlite.exec(` CREATE TRIGGER ${CLEANUP_USER_ORGS_ROLLBACK_TRIGGER} BEFORE UPDATE OF active_organization_id ON sessions_table WHEN OLD.user_id = '${escapeSqlLiteral(deletedUserId)}' AND NEW.active_organization_id IS NULL BEGIN SELECT RAISE(ABORT, 'forced cleanup rollback'); END; `); try { await expect(authService.cleanupUserOrganizations(deletedUserId)).rejects.toThrow("forced cleanup rollback"); } finally { dropTrigger(CLEANUP_USER_ORGS_ROLLBACK_TRIGGER); } const deletedWorkspace = await db.query.organization.findFirst({ where: { id: deletedWorkspaceId }, columns: { id: true }, }); const affectedSession = await db.query.sessionsTable.findFirst({ where: { id: affectedSessionId }, columns: { activeOrganizationId: true }, }); const deletedUserSession = await db.query.sessionsTable.findFirst({ where: { id: deletedUserSessionId }, columns: { activeOrganizationId: true }, }); const affectedMembership = await db.query.member.findFirst({ where: { AND: [{ organizationId: deletedWorkspaceId }, { userId: affectedUserId }] }, columns: { id: true }, }); expect(deletedWorkspace).toEqual({ id: deletedWorkspaceId }); expect(affectedSession?.activeOrganizationId).toBe(deletedWorkspaceId); expect(deletedUserSession?.activeOrganizationId).toBe(deletedWorkspaceId); expect(affectedMembership).not.toBeUndefined(); }); });