Files
publisher-dashboard/apps/api-server/src/__tests__/e2e/admin.test.ts
igm 665092464a Fix all linter errors
- Remove unused biome suppression comment in completions.ts
- Remove unnecessary if condition in execute-bootstrap.test.ts
- Add eslint-disable comments for any type assertions in client.test.ts
- Add eslint-disable comments for expect().rejects patterns
- Fix template literal number expression with toString()
- Fix error handling in test-db.ts to avoid object stringify

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 17:30:00 +08:00

1955 lines
59 KiB
TypeScript

/**
* End-to-end tests for Admin procedures (superuser operations)
*
* These tests use a real PostgreSQL database to test:
*
* Users:
* - admin.users.list - list all users
* - admin.users.get - get user by email
* - admin.users.create - create passwordless user with optional org
* - admin.users.update - update user properties (e.g., isSuperuser)
* - admin.users.confirmEmail - confirm a user's email
*
* Organizations:
* - admin.orgs.list - list all organizations
* - admin.orgs.get - get organization by slug
* - admin.orgs.create - create organization with owner
* - admin.orgs.update - update organization properties
* - admin.orgs.delete - delete organization and related records
* - admin.orgs.listSites - list sites for organization
* - admin.orgs.addSite - add site to organization
* - admin.orgs.removeSite - remove site from organization
*
* Auth:
* - admin.auth.completeLogin - complete pending login request
*/
import type { Database } from "@reviq/db-schema";
import type { Kysely } from "kysely";
import type { APIContext } from "../../context.js";
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { call } from "@orpc/server";
import { createLoggingEmailClient } from "@reviq/emails";
import {
createTestUser,
describeE2E,
getSharedDb,
initTestDb,
TEST_RP,
truncateAllTables,
uniqueTestId,
withTestTransaction,
} from "@reviq/test-helpers";
import { router } from "../../router.js";
import { COOKIE_NAMES } from "../../utils/cookies.js";
import { hashToken } from "../../utils/crypto.js";
/** Session expiry duration: 24 hours in milliseconds */
const SESSION_EXPIRY_MS = 24 * 60 * 60 * 1000;
/** Login request expiry: 15 minutes in milliseconds */
const LOGIN_REQUEST_EXPIRY_MS = 15 * 60 * 1000;
/**
* Create an API context with optional authentication
*/
function createAPIContext(
db: Kysely<Database>,
options?: {
sessionToken?: string;
},
): APIContext {
const reqHeaders = new Headers();
const cookies: string[] = [];
if (options?.sessionToken) {
cookies.push(`${COOKIE_NAMES.SESSION_TOKEN}=${options.sessionToken}`);
}
if (cookies.length > 0) {
reqHeaders.set("cookie", cookies.join("; "));
}
return {
db,
origin: TEST_RP.origin,
allowedOrigins: [...TEST_RP.allowedOrigins],
rpName: TEST_RP.rpName,
reqHeaders,
resHeaders: new Headers(),
email: {
client: createLoggingEmailClient(),
fromAddress: "test@example.com",
baseUrl: TEST_RP.origin,
},
};
}
/**
* Create a real session in the database and return the token
*/
async function createSession(
db: Kysely<Database>,
userId: number,
): Promise<{ token: string; sessionId: number }> {
const token = `test-session-${uniqueTestId()}`;
const tokenHashValue = await hashToken(token);
const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS);
const result = await db
.insertInto("sessions")
.values({
user_id: userId,
token_hash: tokenHashValue,
ip_address: "127.0.0.1",
user_agent: "test-agent",
expires_at: expiresAt,
trusted_mode: false,
})
.returning("id")
.executeTakeFirstOrThrow();
return { token, sessionId: Number(result.id) };
}
/**
* Create an organization in the database
*/
async function createOrg(
db: Kysely<Database>,
options?: {
slug?: string;
displayName?: string;
logoUrl?: string;
},
): Promise<{ id: number; slug: string }> {
const slug = options?.slug ?? `org-${uniqueTestId()}`;
const result = await db
.insertInto("orgs")
.values({
slug,
display_name: options?.displayName ?? "Test Organization",
logo_url: options?.logoUrl ?? null,
})
.returning(["id", "slug"])
.executeTakeFirstOrThrow();
return result;
}
/**
* Add a user as a member of an organization
*/
async function addOrgMember(
db: Kysely<Database>,
orgId: number,
userId: number,
role: "owner" | "admin" | "member" = "member",
): Promise<void> {
await db
.insertInto("org_members")
.values({
org_id: orgId,
user_id: userId,
role,
})
.execute();
}
/**
* Create a site for an organization
*/
async function createSite(
db: Kysely<Database>,
orgId: number,
domain: string,
): Promise<{ id: number; domain: string }> {
const result = await db
.insertInto("org_sites")
.values({
org_id: orgId,
domain,
})
.returning(["id", "domain"])
.executeTakeFirstOrThrow();
return result;
}
/**
* Create a login request in the database
*/
async function createLoginRequest(
db: Kysely<Database>,
userId: number,
email: string,
options?: {
completedAt?: Date | null;
expiresAt?: Date;
},
): Promise<{ id: number; token: string }> {
const token = `login-${uniqueTestId()}`;
const expiresAt =
options?.expiresAt ?? new Date(Date.now() + LOGIN_REQUEST_EXPIRY_MS);
const result = await db
.insertInto("login_requests")
.values({
user_id: userId,
email,
token,
device_fingerprint: "test-fingerprint",
expires_at: expiresAt,
completed_at: options?.completedAt ?? null,
})
.returning("id")
.executeTakeFirstOrThrow();
return { id: Number(result.id), token };
}
/**
* Create an organization invite
*/
async function createOrgInvite(
db: Kysely<Database>,
orgId: number,
email: string,
invitedBy: number,
): Promise<{ id: number }> {
const token = `invite-${uniqueTestId()}`;
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
const result = await db
.insertInto("org_invites")
.values({
org_id: orgId,
email,
role: "member",
invited_by: invitedBy,
token,
expires_at: expiresAt,
})
.returning("id")
.executeTakeFirstOrThrow();
return { id: result.id };
}
describeE2E("admin", () => {
beforeAll(async () => {
await initTestDb();
// Ensure clean slate in case other test files left data behind
await truncateAllTables(getSharedDb());
});
// ===== Authorization Tests =====
describe("admin authorization", () => {
test("rejects non-superuser for admin.users.list", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const user = await createTestUser(db, {
email: "regular@example.com",
isSuperuser: false,
});
const { token: sessionToken } = await createSession(db, user.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(router.admin.users.list, undefined, { context }),
).rejects.toThrow("Superuser access required");
});
});
test("rejects unauthenticated request for admin.orgs.list", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const context = createAPIContext(db);
await expect(
call(router.admin.orgs.list, undefined, { context }),
).rejects.toThrow("No session or API key");
});
});
});
// ===== admin.users.list =====
describe("admin.users.list", () => {
test("returns all users", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createTestUser(db, { email: "user1@example.com" });
await createTestUser(db, { email: "user2@example.com" });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const users = await call(router.admin.users.list, undefined, {
context,
});
expect(users.length).toBe(3);
const emails = users.map((u) => u.email).sort();
expect(emails).toContain("admin@example.com");
expect(emails).toContain("user1@example.com");
expect(emails).toContain("user2@example.com");
});
});
test("returns users with correct fields", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
displayName: "Admin User",
fullName: "Admin Full Name",
emailVerifiedAt: new Date(),
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const users = await call(router.admin.users.list, undefined, {
context,
});
const adminUser = users.find((u) => u.email === "admin@example.com");
expect(adminUser).toBeDefined();
expect(adminUser?.displayName).toBe("Admin User");
expect(adminUser?.fullName).toBe("Admin Full Name");
expect(adminUser?.emailVerified).toBe(true);
expect(adminUser?.isSuperuser).toBe(true);
expect(adminUser?.needsSetup).toBe(false);
});
});
test("returns empty array when no users", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
// Create only the admin user
const admin = await createTestUser(db, {
email: "onlyadmin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const users = await call(router.admin.users.list, undefined, {
context,
});
// Only the admin user exists
expect(users.length).toBe(1);
expect(users[0]?.email).toBe("onlyadmin@example.com");
});
});
});
// ===== admin.users.get =====
describe("admin.users.get", () => {
test("returns user by email", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createTestUser(db, {
email: "target@example.com",
displayName: "Target User",
fullName: "Target Full",
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const user = await call(
router.admin.users.get,
{ email: "target@example.com" },
{ context },
);
expect(user.email).toBe("target@example.com");
expect(user.displayName).toBe("Target User");
expect(user.fullName).toBe("Target Full");
});
});
test("normalizes email to lowercase", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createTestUser(db, { email: "test@example.com" });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const user = await call(
router.admin.users.get,
{ email: "TEST@EXAMPLE.COM" },
{ context },
);
expect(user.email).toBe("test@example.com");
});
});
test("throws NOT_FOUND for non-existent user", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.users.get,
{ email: "nonexistent@example.com" },
{ context },
),
).rejects.toThrow("User not found");
});
});
test("returns correct hasPassword and needsSetup flags", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
// User without display name (needs setup)
await createTestUser(db, {
email: "nosetup@example.com",
displayName: undefined,
});
// Set display_name to null
await db
.updateTable("users")
.set({ display_name: null })
.where("email", "=", "nosetup@example.com")
.execute();
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const user = await call(
router.admin.users.get,
{ email: "nosetup@example.com" },
{ context },
);
expect(user.needsSetup).toBe(true);
expect(user.hasPassword).toBe(false);
});
});
});
// ===== admin.users.create =====
// NOTE: These tests don't use withTestTransaction because the procedure uses db.transaction() internally
describe("admin.users.create", () => {
afterAll(async () => {
// Clean up all test data
await truncateAllTables(getSharedDb());
});
test("creates passwordless user", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const result = await call(
router.admin.users.create,
{ email: `newuser-${uniqueId}@example.com` },
{ context },
);
expect(result.success).toBe(true);
// Verify user was created
const user = await db
.selectFrom("users")
.where("email", "=", `newuser-${uniqueId}@example.com`)
.selectAll()
.executeTakeFirst();
expect(user).toBeDefined();
expect(user?.password_hash).toBeNull();
});
test("creates user with name", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.users.create,
{ email: `named-${uniqueId}@example.com`, name: "Named User" },
{ context },
);
const user = await db
.selectFrom("users")
.where("email", "=", `named-${uniqueId}@example.com`)
.selectAll()
.executeTakeFirst();
expect(user?.display_name).toBe("Named User");
});
test("creates user and adds to organization as member", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const org = await createOrg(db, { slug: `test-org-${uniqueId}` });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.users.create,
{
email: `orguser-${uniqueId}@example.com`,
orgSlug: `test-org-${uniqueId}`,
},
{ context },
);
// Verify org membership
const membership = await db
.selectFrom("org_members")
.innerJoin("users", "users.id", "org_members.user_id")
.where("users.email", "=", `orguser-${uniqueId}@example.com`)
.where("org_members.org_id", "=", org.id)
.selectAll()
.executeTakeFirst();
expect(membership).toBeDefined();
expect(membership?.role).toBe("member");
});
test("creates user and adds to organization with custom role", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const org = await createOrg(db, { slug: `test-org-${uniqueId}` });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.users.create,
{
email: `adminuser-${uniqueId}@example.com`,
orgSlug: `test-org-${uniqueId}`,
orgRole: "admin",
},
{ context },
);
const membership = await db
.selectFrom("org_members")
.innerJoin("users", "users.id", "org_members.user_id")
.where("users.email", "=", `adminuser-${uniqueId}@example.com`)
.where("org_members.org_id", "=", org.id)
.selectAll()
.executeTakeFirst();
expect(membership?.role).toBe("admin");
});
test("normalizes email to lowercase", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.users.create,
{ email: `UPPERCASE-${uniqueId}@EXAMPLE.COM` },
{ context },
);
const user = await db
.selectFrom("users")
.where("email", "=", `uppercase-${uniqueId}@example.com`)
.selectAll()
.executeTakeFirst();
expect(user).toBeDefined();
});
test("throws CONFLICT for duplicate email", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
await createTestUser(db, { email: `existing-${uniqueId}@example.com` });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.users.create,
{ email: `existing-${uniqueId}@example.com` },
{ context },
),
).rejects.toThrow("User with this email already exists");
});
test("throws NOT_FOUND for non-existent org", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.users.create,
{
email: `newuser-${uniqueId}@example.com`,
orgSlug: "nonexistent-org",
},
{ context },
),
).rejects.toThrow("Organization not found");
});
});
// ===== admin.users.update =====
describe("admin.users.update", () => {
test("grants superuser status", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createTestUser(db, {
email: "regular@example.com",
isSuperuser: false,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.users.update,
{ email: "regular@example.com", isSuperuser: true },
{ context },
);
const user = await db
.selectFrom("users")
.where("email", "=", "regular@example.com")
.select(["is_superuser"])
.executeTakeFirstOrThrow();
expect(user.is_superuser).toBe(true);
});
});
test("revokes superuser status from another user", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createTestUser(db, {
email: "otheradmin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.users.update,
{ email: "otheradmin@example.com", isSuperuser: false },
{ context },
);
const user = await db
.selectFrom("users")
.where("email", "=", "otheradmin@example.com")
.select(["is_superuser"])
.executeTakeFirstOrThrow();
expect(user.is_superuser).toBe(false);
});
});
test("prevents self-demotion", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.users.update,
{ email: "admin@example.com", isSuperuser: false },
{ context },
),
).rejects.toThrow("Cannot remove your own superuser status");
});
});
test("normalizes email to lowercase", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createTestUser(db, { email: "target@example.com" });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.users.update,
{ email: "TARGET@EXAMPLE.COM", isSuperuser: true },
{ context },
);
const user = await db
.selectFrom("users")
.where("email", "=", "target@example.com")
.select(["is_superuser"])
.executeTakeFirstOrThrow();
expect(user.is_superuser).toBe(true);
});
});
test("throws NOT_FOUND for non-existent user", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.users.update,
{ email: "nonexistent@example.com", isSuperuser: true },
{ context },
),
).rejects.toThrow("User not found");
});
});
test("returns success for no-op update (no fields to update)", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createTestUser(db, { email: "target@example.com" });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const result = await call(
router.admin.users.update,
{ email: "target@example.com" },
{ context },
);
expect(result.success).toBe(true);
});
});
test("throws NOT_FOUND for no-op update on non-existent user", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.users.update,
{ email: "nonexistent@example.com" },
{ context },
),
).rejects.toThrow("User not found");
});
});
});
// ===== admin.users.confirmEmail =====
describe("admin.users.confirmEmail", () => {
test("confirms user email", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createTestUser(db, {
email: "unverified@example.com",
emailVerifiedAt: undefined,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.users.confirmEmail,
{ email: "unverified@example.com" },
{ context },
);
const user = await db
.selectFrom("users")
.where("email", "=", "unverified@example.com")
.select(["email_verified_at"])
.executeTakeFirstOrThrow();
expect(user.email_verified_at).not.toBeNull();
});
});
test("normalizes email to lowercase", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createTestUser(db, { email: "test@example.com" });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.users.confirmEmail,
{ email: "TEST@EXAMPLE.COM" },
{ context },
);
const user = await db
.selectFrom("users")
.where("email", "=", "test@example.com")
.select(["email_verified_at"])
.executeTakeFirstOrThrow();
expect(user.email_verified_at).not.toBeNull();
});
});
test("throws NOT_FOUND for non-existent user", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.users.confirmEmail,
{ email: "nonexistent@example.com" },
{ context },
),
).rejects.toThrow("User not found");
});
});
test("succeeds for already verified user (idempotent)", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createTestUser(db, {
email: "verified@example.com",
emailVerifiedAt: new Date(),
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
// Should not throw
const result = await call(
router.admin.users.confirmEmail,
{ email: "verified@example.com" },
{ context },
);
expect(result.success).toBe(true);
});
});
});
// ===== admin.orgs.list =====
describe("admin.orgs.list", () => {
test("returns all organizations", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createOrg(db, { slug: "org-one", displayName: "Org One" });
await createOrg(db, { slug: "org-two", displayName: "Org Two" });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const orgs = await call(router.admin.orgs.list, undefined, { context });
expect(orgs.length).toBe(2);
const slugs = orgs.map((o) => o.slug).sort();
expect(slugs).toEqual(["org-one", "org-two"]);
});
});
test("returns organizations with correct fields", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createOrg(db, {
slug: "test-org",
displayName: "Test Org",
logoUrl: "https://example.com/logo.png",
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const orgs = await call(router.admin.orgs.list, undefined, { context });
const org = orgs.find((o) => o.slug === "test-org");
expect(org).toBeDefined();
expect(org?.displayName).toBe("Test Org");
expect(org?.logoUrl).toBe("https://example.com/logo.png");
expect(org?.createdAt).toBeInstanceOf(Date);
});
});
test("returns empty array when no organizations", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const orgs = await call(router.admin.orgs.list, undefined, { context });
expect(orgs).toHaveLength(0);
});
});
});
// ===== admin.orgs.get =====
describe("admin.orgs.get", () => {
test("returns organization by slug", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createOrg(db, {
slug: "target-org",
displayName: "Target Organization",
logoUrl: "https://example.com/logo.png",
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const org = await call(
router.admin.orgs.get,
{ slug: "target-org" },
{ context },
);
expect(org.slug).toBe("target-org");
expect(org.displayName).toBe("Target Organization");
expect(org.logoUrl).toBe("https://example.com/logo.png");
});
});
test("throws NOT_FOUND for non-existent organization", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(router.admin.orgs.get, { slug: "nonexistent" }, { context }),
).rejects.toThrow("Organization not found");
});
});
});
// ===== admin.orgs.create =====
// NOTE: These tests don't use withTestTransaction because the procedure uses db.transaction() internally
describe("admin.orgs.create", () => {
afterAll(async () => {
await truncateAllTables(getSharedDb());
});
test("creates organization with owner", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const result = await call(
router.admin.orgs.create,
{
slug: `new-org-${uniqueId}`,
displayName: "New Organization",
ownerEmail: `owner-${uniqueId}@example.com`,
},
{ context },
);
expect(result.slug).toBe(`new-org-${uniqueId}`);
// Verify org was created
const org = await db
.selectFrom("orgs")
.where("slug", "=", `new-org-${uniqueId}`)
.selectAll()
.executeTakeFirst();
expect(org).toBeDefined();
expect(org?.display_name).toBe("New Organization");
// Verify owner membership
if (org) {
const membership = await db
.selectFrom("org_members")
.where("org_id", "=", org.id)
.where("user_id", "=", owner.id)
.selectAll()
.executeTakeFirst();
expect(membership).toBeDefined();
expect(membership?.role).toBe("owner");
}
});
test("normalizes owner email to lowercase", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const result = await call(
router.admin.orgs.create,
{
slug: `new-org-${uniqueId}`,
displayName: "New Organization",
ownerEmail: `OWNER-${uniqueId}@EXAMPLE.COM`,
},
{ context },
);
expect(result.slug).toBe(`new-org-${uniqueId}`);
});
test("throws NOT_FOUND for non-existent owner", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.orgs.create,
{
slug: `new-org-${uniqueId}`,
displayName: "New Organization",
ownerEmail: "nonexistent@example.com",
},
{ context },
),
).rejects.toThrow("User not found");
});
test("throws CONFLICT for duplicate slug", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`,
});
await createOrg(db, { slug: `existing-org-${uniqueId}` });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.orgs.create,
{
slug: `existing-org-${uniqueId}`,
displayName: "New Organization",
ownerEmail: owner.email,
},
{ context },
),
).rejects.toThrow("Organization with this slug already exists");
});
});
// ===== admin.orgs.update =====
describe("admin.orgs.update", () => {
test("updates display name", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createOrg(db, { slug: "test-org", displayName: "Old Name" });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.orgs.update,
{ slug: "test-org", displayName: "New Name" },
{ context },
);
const org = await db
.selectFrom("orgs")
.where("slug", "=", "test-org")
.select(["display_name"])
.executeTakeFirstOrThrow();
expect(org.display_name).toBe("New Name");
});
});
test("updates logo URL", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createOrg(db, { slug: "test-org" });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.orgs.update,
{ slug: "test-org", logoUrl: "https://example.com/newlogo.png" },
{ context },
);
const org = await db
.selectFrom("orgs")
.where("slug", "=", "test-org")
.select(["logo_url"])
.executeTakeFirstOrThrow();
expect(org.logo_url).toBe("https://example.com/newlogo.png");
});
});
test("clears logo URL with empty string", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createOrg(db, {
slug: "test-org",
logoUrl: "https://example.com/logo.png",
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.orgs.update,
{ slug: "test-org", logoUrl: "" },
{ context },
);
const org = await db
.selectFrom("orgs")
.where("slug", "=", "test-org")
.select(["logo_url"])
.executeTakeFirstOrThrow();
expect(org.logo_url).toBeNull();
});
});
test("updates multiple fields at once", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createOrg(db, {
slug: "test-org",
displayName: "Old",
logoUrl: undefined,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.orgs.update,
{
slug: "test-org",
displayName: "New Name",
logoUrl: "https://example.com/logo.png",
},
{ context },
);
const org = await db
.selectFrom("orgs")
.where("slug", "=", "test-org")
.select(["display_name", "logo_url"])
.executeTakeFirstOrThrow();
expect(org.display_name).toBe("New Name");
expect(org.logo_url).toBe("https://example.com/logo.png");
});
});
test("returns success for no-op update", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createOrg(db, { slug: "test-org" });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const result = await call(
router.admin.orgs.update,
{ slug: "test-org" },
{ context },
);
expect(result.success).toBe(true);
});
});
test("throws NOT_FOUND for no-op on non-existent org", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(router.admin.orgs.update, { slug: "nonexistent" }, { context }),
).rejects.toThrow("Organization not found");
});
});
test("throws NOT_FOUND for non-existent organization", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.orgs.update,
{ slug: "nonexistent", displayName: "Test" },
{ context },
),
).rejects.toThrow("Organization not found");
});
});
});
// ===== admin.orgs.delete =====
// NOTE: These tests don't use withTestTransaction because the procedure uses db.transaction() internally
describe("admin.orgs.delete", () => {
afterAll(async () => {
await truncateAllTables(getSharedDb());
});
test("deletes organization and related records", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const member = await createTestUser(db, {
email: `member-${uniqueId}@example.com`,
});
const org = await createOrg(db, { slug: `delete-me-${uniqueId}` });
// Create related records
await addOrgMember(db, org.id, member.id, "owner");
await createSite(db, org.id, `example-${uniqueId}.com`);
await createOrgInvite(
db,
org.id,
`invite-${uniqueId}@example.com`,
admin.id,
);
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const result = await call(
router.admin.orgs.delete,
{ slug: `delete-me-${uniqueId}` },
{ context },
);
expect(result.success).toBe(true);
// Verify org is deleted
const deletedOrg = await db
.selectFrom("orgs")
.where("slug", "=", `delete-me-${uniqueId}`)
.selectAll()
.executeTakeFirst();
expect(deletedOrg).toBeUndefined();
// Verify related records are deleted
const members = await db
.selectFrom("org_members")
.where("org_id", "=", org.id)
.selectAll()
.execute();
expect(members).toHaveLength(0);
const sites = await db
.selectFrom("org_sites")
.where("org_id", "=", org.id)
.selectAll()
.execute();
expect(sites).toHaveLength(0);
const invites = await db
.selectFrom("org_invites")
.where("org_id", "=", org.id)
.selectAll()
.execute();
expect(invites).toHaveLength(0);
});
test("throws NOT_FOUND for non-existent organization", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(router.admin.orgs.delete, { slug: "nonexistent" }, { context }),
).rejects.toThrow("Organization not found");
});
});
// ===== admin.orgs.listSites =====
describe("admin.orgs.listSites", () => {
test("returns sites for organization", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const org = await createOrg(db, { slug: "test-org" });
await createSite(db, org.id, "example.com");
await createSite(db, org.id, "test.com");
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const sites = await call(
router.admin.orgs.listSites,
{ slug: "test-org" },
{ context },
);
expect(sites.length).toBe(2);
const domains = sites.map((s) => s.domain).sort();
expect(domains).toEqual(["example.com", "test.com"]);
});
});
test("returns empty array when no sites", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createOrg(db, { slug: "empty-org" });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const sites = await call(
router.admin.orgs.listSites,
{ slug: "empty-org" },
{ context },
);
expect(sites).toHaveLength(0);
});
});
test("returns sites with correct fields", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const org = await createOrg(db, { slug: "test-org" });
await createSite(db, org.id, "example.com");
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const sites = await call(
router.admin.orgs.listSites,
{ slug: "test-org" },
{ context },
);
expect(sites[0]?.id).toBeDefined();
expect(sites[0]?.domain).toBe("example.com");
expect(sites[0]?.createdAt).toBeInstanceOf(Date);
});
});
test("throws NOT_FOUND for non-existent organization", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.orgs.listSites,
{ slug: "nonexistent" },
{ context },
),
).rejects.toThrow("Organization not found");
});
});
});
// ===== admin.orgs.addSite =====
// NOTE: These tests don't use withTestTransaction because the procedure uses db.transaction() internally
describe("admin.orgs.addSite", () => {
afterAll(async () => {
await truncateAllTables(getSharedDb());
});
test("adds site to organization", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const org = await createOrg(db, { slug: `test-org-${uniqueId}` });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const result = await call(
router.admin.orgs.addSite,
{ slug: `test-org-${uniqueId}`, domain: `newsite-${uniqueId}.com` },
{ context },
);
expect(result.success).toBe(true);
// Verify site was created
const site = await db
.selectFrom("org_sites")
.where("org_id", "=", org.id)
.where("domain", "=", `newsite-${uniqueId}.com`)
.selectAll()
.executeTakeFirst();
expect(site).toBeDefined();
});
test("throws NOT_FOUND for non-existent organization", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.orgs.addSite,
{ slug: "nonexistent", domain: `test-${uniqueId}.com` },
{ context },
),
).rejects.toThrow("Organization not found");
});
test("throws CONFLICT for duplicate domain", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const org = await createOrg(db, { slug: `test-org-${uniqueId}` });
await createSite(db, org.id, `existing-${uniqueId}.com`);
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.orgs.addSite,
{ slug: `test-org-${uniqueId}`, domain: `existing-${uniqueId}.com` },
{ context },
),
).rejects.toThrow("Site with this domain already exists");
});
test("throws CONFLICT for domain in another organization", async () => {
const db = getSharedDb();
const uniqueId = uniqueTestId();
const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`,
isSuperuser: true,
});
const org1 = await createOrg(db, { slug: `org-one-${uniqueId}` });
await createOrg(db, { slug: `org-two-${uniqueId}` });
await createSite(db, org1.id, `shared-${uniqueId}.com`);
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.orgs.addSite,
{ slug: `org-two-${uniqueId}`, domain: `shared-${uniqueId}.com` },
{ context },
),
).rejects.toThrow("Site with this domain already exists");
});
});
// ===== admin.orgs.removeSite =====
describe("admin.orgs.removeSite", () => {
test("removes site from organization", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const org = await createOrg(db, { slug: "test-org" });
await createSite(db, org.id, "remove-me.com");
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const result = await call(
router.admin.orgs.removeSite,
{ slug: "test-org", domain: "remove-me.com" },
{ context },
);
expect(result.success).toBe(true);
// Verify site was removed
const site = await db
.selectFrom("org_sites")
.where("org_id", "=", org.id)
.where("domain", "=", "remove-me.com")
.selectAll()
.executeTakeFirst();
expect(site).toBeUndefined();
});
});
test("throws NOT_FOUND for non-existent organization", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.orgs.removeSite,
{ slug: "nonexistent", domain: "test.com" },
{ context },
),
).rejects.toThrow("Organization not found");
});
});
test("throws NOT_FOUND for non-existent site", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
await createOrg(db, { slug: "test-org" });
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.orgs.removeSite,
{ slug: "test-org", domain: "nonexistent.com" },
{ context },
),
).rejects.toThrow("Site not found");
});
});
test("throws NOT_FOUND for site in another organization", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const org1 = await createOrg(db, { slug: "org-one" });
await createOrg(db, { slug: "org-two" });
await createSite(db, org1.id, "org1-site.com");
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.orgs.removeSite,
{ slug: "org-two", domain: "org1-site.com" },
{ context },
),
).rejects.toThrow("Site not found");
});
});
});
// ===== admin.auth.completeLogin =====
describe("admin.auth.completeLogin", () => {
test("completes pending login request", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const user = await createTestUser(db, { email: "user@example.com" });
const loginRequest = await createLoginRequest(
db,
user.id,
"user@example.com",
);
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
const result = await call(
router.admin.auth.completeLogin,
{ email: "user@example.com" },
{ context },
);
expect(result.success).toBe(true);
// Verify login request was completed
const request = await db
.selectFrom("login_requests")
.where("id", "=", loginRequest.id.toString())
.select(["completed_at"])
.executeTakeFirstOrThrow();
expect(request.completed_at).not.toBeNull();
});
});
test("normalizes email to lowercase", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const user = await createTestUser(db, { email: "user@example.com" });
const loginRequest = await createLoginRequest(
db,
user.id,
"user@example.com",
);
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.auth.completeLogin,
{ email: "USER@EXAMPLE.COM" },
{ context },
);
const request = await db
.selectFrom("login_requests")
.where("id", "=", loginRequest.id.toString())
.select(["completed_at"])
.executeTakeFirstOrThrow();
expect(request.completed_at).not.toBeNull();
});
});
test("throws NOT_FOUND for no login request", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.auth.completeLogin,
{ email: "noRequest@example.com" },
{ context },
),
).rejects.toThrow("No login request found");
});
});
test("throws BAD_REQUEST for already completed request", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const user = await createTestUser(db, { email: "user@example.com" });
await createLoginRequest(db, user.id, "user@example.com", {
completedAt: new Date(),
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.auth.completeLogin,
{ email: "user@example.com" },
{ context },
),
).rejects.toThrow("Login request already completed");
});
});
test("throws BAD_REQUEST for expired request", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const user = await createTestUser(db, { email: "user@example.com" });
await createLoginRequest(db, user.id, "user@example.com", {
expiresAt: new Date(Date.now() - 1000), // Expired
});
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await expect(
call(
router.admin.auth.completeLogin,
{ email: "user@example.com" },
{ context },
),
).rejects.toThrow("Login request expired");
});
});
test("completes most recent login request", async () => {
await withTestTransaction(getSharedDb(), async (db) => {
const admin = await createTestUser(db, {
email: "admin@example.com",
isSuperuser: true,
});
const user = await createTestUser(db, { email: "user@example.com" });
// Create two login requests
await createLoginRequest(db, user.id, "user@example.com");
await createLoginRequest(db, user.id, "user@example.com");
const { token: sessionToken } = await createSession(db, admin.id);
const context = createAPIContext(db, { sessionToken });
await call(
router.admin.auth.completeLogin,
{ email: "user@example.com" },
{ context },
);
// Check that exactly one login request was completed
// (Note: both requests have the same created_at in transaction, so ORDER BY is non-deterministic)
const allRequests = await db
.selectFrom("login_requests")
.where("email", "=", "user@example.com")
.select(["id", "completed_at"])
.execute();
expect(allRequests.length).toBe(2);
const completedCount = allRequests.filter(
(r) => r.completed_at !== null,
).length;
expect(completedCount).toBe(1);
const uncompletedCount = allRequests.filter(
(r) => r.completed_at === null,
).length;
expect(uncompletedCount).toBe(1);
});
});
});
}); // Close describeE2E("admin")