Fix lint errors and add ast-grep rule for countAll
- Fix template literal expressions: wrap Date.now() in String() - Add missing afterAll import in admin.test.ts - Fix countOwners to use countAll() without misleading <number> type - Add ast-grep rule to prevent countAll<number>() usage - Fix formatting issues from merge conflict resolution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
8
.ast-grep/rules/no-countall-number.yml
Normal file
8
.ast-grep/rules/no-countall-number.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
id: no-countall-number
|
||||||
|
language: typescript
|
||||||
|
severity: error
|
||||||
|
message: "Don't use countAll<number>() - use countAll() instead. PostgreSQL COUNT returns bigint (string), so the type annotation is misleading."
|
||||||
|
note: "Use Number() to convert the result if you need a number type."
|
||||||
|
rule:
|
||||||
|
pattern: countAll<number>()
|
||||||
|
fix: countAll()
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
import type { Database } from "@reviq/db-schema";
|
import type { Database } from "@reviq/db-schema";
|
||||||
import type { Kysely } from "kysely";
|
import type { Kysely } from "kysely";
|
||||||
import type { APIContext } from "../../context.js";
|
import type { APIContext } from "../../context.js";
|
||||||
import { beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { call } from "@orpc/server";
|
import { call } from "@orpc/server";
|
||||||
import {
|
import {
|
||||||
createTestUser,
|
createTestUser,
|
||||||
@@ -115,7 +115,9 @@ async function createOrg(
|
|||||||
logoUrl?: string;
|
logoUrl?: string;
|
||||||
},
|
},
|
||||||
): Promise<{ id: number; slug: string }> {
|
): Promise<{ id: number; slug: string }> {
|
||||||
const slug = options?.slug ?? `org-${String(Date.now())}-${String(Math.random()).slice(2, 8)}`;
|
const slug =
|
||||||
|
options?.slug ??
|
||||||
|
`org-${String(Date.now())}-${String(Math.random()).slice(2, 8)}`;
|
||||||
|
|
||||||
const result = await db
|
const result = await db
|
||||||
.insertInto("orgs")
|
.insertInto("orgs")
|
||||||
@@ -182,7 +184,8 @@ async function createLoginRequest(
|
|||||||
},
|
},
|
||||||
): Promise<{ id: number; token: string }> {
|
): Promise<{ id: number; token: string }> {
|
||||||
const token = `login-${String(Date.now())}${String(Math.random())}`;
|
const token = `login-${String(Date.now())}${String(Math.random())}`;
|
||||||
const expiresAt = options?.expiresAt ?? new Date(Date.now() + LOGIN_REQUEST_EXPIRY_MS);
|
const expiresAt =
|
||||||
|
options?.expiresAt ?? new Date(Date.now() + LOGIN_REQUEST_EXPIRY_MS);
|
||||||
|
|
||||||
const result = await db
|
const result = await db
|
||||||
.insertInto("login_requests")
|
.insertInto("login_requests")
|
||||||
@@ -225,7 +228,7 @@ async function createOrgInvite(
|
|||||||
.returning("id")
|
.returning("id")
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
return { id: Number(result.id) };
|
return { id: result.id };
|
||||||
}
|
}
|
||||||
|
|
||||||
describeE2E("admin", () => {
|
describeE2E("admin", () => {
|
||||||
@@ -280,7 +283,9 @@ describe("admin.users.list", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, admin.id);
|
const { token: sessionToken } = await createSession(db, admin.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const users = await call(router.admin.users.list, undefined, { context });
|
const users = await call(router.admin.users.list, undefined, {
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
expect(users.length).toBe(3);
|
expect(users.length).toBe(3);
|
||||||
const emails = users.map((u) => u.email).sort();
|
const emails = users.map((u) => u.email).sort();
|
||||||
@@ -303,7 +308,9 @@ describe("admin.users.list", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, admin.id);
|
const { token: sessionToken } = await createSession(db, admin.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const users = await call(router.admin.users.list, undefined, { context });
|
const users = await call(router.admin.users.list, undefined, {
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
const adminUser = users.find((u) => u.email === "admin@example.com");
|
const adminUser = users.find((u) => u.email === "admin@example.com");
|
||||||
expect(adminUser).toBeDefined();
|
expect(adminUser).toBeDefined();
|
||||||
@@ -326,7 +333,9 @@ describe("admin.users.list", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, admin.id);
|
const { token: sessionToken } = await createSession(db, admin.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const users = await call(router.admin.users.list, undefined, { context });
|
const users = await call(router.admin.users.list, undefined, {
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
// Only the admin user exists
|
// Only the admin user exists
|
||||||
expect(users.length).toBe(1);
|
expect(users.length).toBe(1);
|
||||||
@@ -452,7 +461,7 @@ describe("admin.users.create", () => {
|
|||||||
|
|
||||||
test("creates passwordless user", async () => {
|
test("creates passwordless user", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -483,7 +492,7 @@ describe("admin.users.create", () => {
|
|||||||
|
|
||||||
test("creates user with name", async () => {
|
test("creates user with name", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -510,7 +519,7 @@ describe("admin.users.create", () => {
|
|||||||
|
|
||||||
test("creates user and adds to organization as member", async () => {
|
test("creates user and adds to organization as member", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -523,7 +532,10 @@ describe("admin.users.create", () => {
|
|||||||
|
|
||||||
await call(
|
await call(
|
||||||
router.admin.users.create,
|
router.admin.users.create,
|
||||||
{ email: `orguser-${uniqueId}@example.com`, orgSlug: `test-org-${uniqueId}` },
|
{
|
||||||
|
email: `orguser-${uniqueId}@example.com`,
|
||||||
|
orgSlug: `test-org-${uniqueId}`,
|
||||||
|
},
|
||||||
{ context },
|
{ context },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -542,7 +554,7 @@ describe("admin.users.create", () => {
|
|||||||
|
|
||||||
test("creates user and adds to organization with custom role", async () => {
|
test("creates user and adds to organization with custom role", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -555,7 +567,11 @@ describe("admin.users.create", () => {
|
|||||||
|
|
||||||
await call(
|
await call(
|
||||||
router.admin.users.create,
|
router.admin.users.create,
|
||||||
{ email: `adminuser-${uniqueId}@example.com`, orgSlug: `test-org-${uniqueId}`, orgRole: "admin" },
|
{
|
||||||
|
email: `adminuser-${uniqueId}@example.com`,
|
||||||
|
orgSlug: `test-org-${uniqueId}`,
|
||||||
|
orgRole: "admin",
|
||||||
|
},
|
||||||
{ context },
|
{ context },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -572,7 +588,7 @@ describe("admin.users.create", () => {
|
|||||||
|
|
||||||
test("normalizes email to lowercase", async () => {
|
test("normalizes email to lowercase", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -599,7 +615,7 @@ describe("admin.users.create", () => {
|
|||||||
|
|
||||||
test("throws CONFLICT for duplicate email", async () => {
|
test("throws CONFLICT for duplicate email", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -621,7 +637,7 @@ describe("admin.users.create", () => {
|
|||||||
|
|
||||||
test("throws NOT_FOUND for non-existent org", async () => {
|
test("throws NOT_FOUND for non-existent org", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -634,7 +650,10 @@ describe("admin.users.create", () => {
|
|||||||
await expect(
|
await expect(
|
||||||
call(
|
call(
|
||||||
router.admin.users.create,
|
router.admin.users.create,
|
||||||
{ email: `newuser-${uniqueId}@example.com`, orgSlug: "nonexistent-org" },
|
{
|
||||||
|
email: `newuser-${uniqueId}@example.com`,
|
||||||
|
orgSlug: "nonexistent-org",
|
||||||
|
},
|
||||||
{ context },
|
{ context },
|
||||||
),
|
),
|
||||||
).rejects.toThrow("Organization not found");
|
).rejects.toThrow("Organization not found");
|
||||||
@@ -1041,13 +1060,15 @@ describe("admin.orgs.create", () => {
|
|||||||
|
|
||||||
test("creates organization with owner", async () => {
|
test("creates organization with owner", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
isSuperuser: true,
|
isSuperuser: true,
|
||||||
});
|
});
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, admin.id);
|
const { token: sessionToken } = await createSession(db, admin.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
@@ -1077,7 +1098,7 @@ describe("admin.orgs.create", () => {
|
|||||||
// Verify owner membership
|
// Verify owner membership
|
||||||
const membership = await db
|
const membership = await db
|
||||||
.selectFrom("org_members")
|
.selectFrom("org_members")
|
||||||
.where("org_id", "=", org!.id)
|
.where("org_id", "=", org?.id)
|
||||||
.where("user_id", "=", owner.id)
|
.where("user_id", "=", owner.id)
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
@@ -1088,7 +1109,7 @@ describe("admin.orgs.create", () => {
|
|||||||
|
|
||||||
test("normalizes owner email to lowercase", async () => {
|
test("normalizes owner email to lowercase", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -1114,7 +1135,7 @@ describe("admin.orgs.create", () => {
|
|||||||
|
|
||||||
test("throws NOT_FOUND for non-existent owner", async () => {
|
test("throws NOT_FOUND for non-existent owner", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -1139,13 +1160,15 @@ describe("admin.orgs.create", () => {
|
|||||||
|
|
||||||
test("throws CONFLICT for duplicate slug", async () => {
|
test("throws CONFLICT for duplicate slug", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
isSuperuser: true,
|
isSuperuser: true,
|
||||||
});
|
});
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
await createOrg(db, { slug: `existing-org-${uniqueId}` });
|
await createOrg(db, { slug: `existing-org-${uniqueId}` });
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, admin.id);
|
const { token: sessionToken } = await createSession(db, admin.id);
|
||||||
@@ -1356,19 +1379,26 @@ describe("admin.orgs.delete", () => {
|
|||||||
|
|
||||||
test("deletes organization and related records", async () => {
|
test("deletes organization and related records", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
isSuperuser: true,
|
isSuperuser: true,
|
||||||
});
|
});
|
||||||
const member = await createTestUser(db, { email: `member-${uniqueId}@example.com` });
|
const member = await createTestUser(db, {
|
||||||
|
email: `member-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `delete-me-${uniqueId}` });
|
const org = await createOrg(db, { slug: `delete-me-${uniqueId}` });
|
||||||
|
|
||||||
// Create related records
|
// Create related records
|
||||||
await addOrgMember(db, org.id, member.id, "owner");
|
await addOrgMember(db, org.id, member.id, "owner");
|
||||||
await createSite(db, org.id, `example-${uniqueId}.com`);
|
await createSite(db, org.id, `example-${uniqueId}.com`);
|
||||||
await createOrgInvite(db, org.id, `invite-${uniqueId}@example.com`, admin.id);
|
await createOrgInvite(
|
||||||
|
db,
|
||||||
|
org.id,
|
||||||
|
`invite-${uniqueId}@example.com`,
|
||||||
|
admin.id,
|
||||||
|
);
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, admin.id);
|
const { token: sessionToken } = await createSession(db, admin.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
@@ -1414,7 +1444,7 @@ describe("admin.orgs.delete", () => {
|
|||||||
|
|
||||||
test("throws NOT_FOUND for non-existent organization", async () => {
|
test("throws NOT_FOUND for non-existent organization", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -1514,7 +1544,11 @@ describe("admin.orgs.listSites", () => {
|
|||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
call(router.admin.orgs.listSites, { slug: "nonexistent" }, { context }),
|
call(
|
||||||
|
router.admin.orgs.listSites,
|
||||||
|
{ slug: "nonexistent" },
|
||||||
|
{ context },
|
||||||
|
),
|
||||||
).rejects.toThrow("Organization not found");
|
).rejects.toThrow("Organization not found");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1530,7 +1564,7 @@ describe("admin.orgs.addSite", () => {
|
|||||||
|
|
||||||
test("adds site to organization", async () => {
|
test("adds site to organization", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -1562,7 +1596,7 @@ describe("admin.orgs.addSite", () => {
|
|||||||
|
|
||||||
test("throws NOT_FOUND for non-existent organization", async () => {
|
test("throws NOT_FOUND for non-existent organization", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -1583,7 +1617,7 @@ describe("admin.orgs.addSite", () => {
|
|||||||
|
|
||||||
test("throws CONFLICT for duplicate domain", async () => {
|
test("throws CONFLICT for duplicate domain", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -1606,7 +1640,7 @@ describe("admin.orgs.addSite", () => {
|
|||||||
|
|
||||||
test("throws CONFLICT for domain in another organization", async () => {
|
test("throws CONFLICT for domain in another organization", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, {
|
const admin = await createTestUser(db, {
|
||||||
email: `admin-${uniqueId}@example.com`,
|
email: `admin-${uniqueId}@example.com`,
|
||||||
@@ -1739,7 +1773,11 @@ describe("admin.auth.completeLogin", () => {
|
|||||||
isSuperuser: true,
|
isSuperuser: true,
|
||||||
});
|
});
|
||||||
const user = await createTestUser(db, { email: "user@example.com" });
|
const user = await createTestUser(db, { email: "user@example.com" });
|
||||||
const loginRequest = await createLoginRequest(db, user.id, "user@example.com");
|
const loginRequest = await createLoginRequest(
|
||||||
|
db,
|
||||||
|
user.id,
|
||||||
|
"user@example.com",
|
||||||
|
);
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, admin.id);
|
const { token: sessionToken } = await createSession(db, admin.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
@@ -1770,7 +1808,11 @@ describe("admin.auth.completeLogin", () => {
|
|||||||
isSuperuser: true,
|
isSuperuser: true,
|
||||||
});
|
});
|
||||||
const user = await createTestUser(db, { email: "user@example.com" });
|
const user = await createTestUser(db, { email: "user@example.com" });
|
||||||
const loginRequest = await createLoginRequest(db, user.id, "user@example.com");
|
const loginRequest = await createLoginRequest(
|
||||||
|
db,
|
||||||
|
user.id,
|
||||||
|
"user@example.com",
|
||||||
|
);
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, admin.id);
|
const { token: sessionToken } = await createSession(db, admin.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
@@ -1890,10 +1932,14 @@ describe("admin.auth.completeLogin", () => {
|
|||||||
|
|
||||||
expect(allRequests.length).toBe(2);
|
expect(allRequests.length).toBe(2);
|
||||||
|
|
||||||
const completedCount = allRequests.filter((r) => r.completed_at !== null).length;
|
const completedCount = allRequests.filter(
|
||||||
|
(r) => r.completed_at !== null,
|
||||||
|
).length;
|
||||||
expect(completedCount).toBe(1);
|
expect(completedCount).toBe(1);
|
||||||
|
|
||||||
const uncompletedCount = allRequests.filter((r) => r.completed_at === null).length;
|
const uncompletedCount = allRequests.filter(
|
||||||
|
(r) => r.completed_at === null,
|
||||||
|
).length;
|
||||||
expect(uncompletedCount).toBe(1);
|
expect(uncompletedCount).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ describeE2E("me", () => {
|
|||||||
const user = await createTestUser(db, { email: "expired@example.com" });
|
const user = await createTestUser(db, { email: "expired@example.com" });
|
||||||
|
|
||||||
// Create an expired session
|
// Create an expired session
|
||||||
const token = `expired-session-${Date.now()}`;
|
const token = `expired-session-${String(Date.now())}`;
|
||||||
const tokenHashValue = await hashToken(token);
|
const tokenHashValue = await hashToken(token);
|
||||||
await db
|
await db
|
||||||
.insertInto("sessions")
|
.insertInto("sessions")
|
||||||
@@ -249,7 +249,7 @@ describeE2E("me", () => {
|
|||||||
const user = await createTestUser(db, { email: "revoked@example.com" });
|
const user = await createTestUser(db, { email: "revoked@example.com" });
|
||||||
|
|
||||||
// Create a revoked session
|
// Create a revoked session
|
||||||
const token = `revoked-session-${Date.now()}`;
|
const token = `revoked-session-${String(Date.now())}`;
|
||||||
const tokenHashValue = await hashToken(token);
|
const tokenHashValue = await hashToken(token);
|
||||||
await db
|
await db
|
||||||
.insertInto("sessions")
|
.insertInto("sessions")
|
||||||
@@ -1583,7 +1583,7 @@ async function createOrgInvite(
|
|||||||
.returning("id")
|
.returning("id")
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
return { id: Number(result.id) };
|
return { id: result.id };
|
||||||
}
|
}
|
||||||
|
|
||||||
describeE2E("me.apiTokens and me.invites", () => {
|
describeE2E("me.apiTokens and me.invites", () => {
|
||||||
@@ -1598,7 +1598,9 @@ describeE2E("me.apiTokens and me.invites", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, user.id);
|
const { token: sessionToken } = await createSession(db, user.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const tokens = await call(router.me.apiTokens.list, undefined, { context });
|
const tokens = await call(router.me.apiTokens.list, undefined, {
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
expect(tokens).toHaveLength(0);
|
expect(tokens).toHaveLength(0);
|
||||||
});
|
});
|
||||||
@@ -1619,15 +1621,27 @@ describeE2E("me.apiTokens and me.invites", () => {
|
|||||||
await db
|
await db
|
||||||
.insertInto("api_tokens")
|
.insertInto("api_tokens")
|
||||||
.values([
|
.values([
|
||||||
{ user_id: user.id, token_hash: tokenHash1, name: "Token One", expires_at: expiresAt },
|
{
|
||||||
{ user_id: user.id, token_hash: tokenHash2, name: "Token Two", expires_at: expiresAt },
|
user_id: user.id,
|
||||||
|
token_hash: tokenHash1,
|
||||||
|
name: "Token One",
|
||||||
|
expires_at: expiresAt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user_id: user.id,
|
||||||
|
token_hash: tokenHash2,
|
||||||
|
name: "Token Two",
|
||||||
|
expires_at: expiresAt,
|
||||||
|
},
|
||||||
])
|
])
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, user.id);
|
const { token: sessionToken } = await createSession(db, user.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const tokens = await call(router.me.apiTokens.list, undefined, { context });
|
const tokens = await call(router.me.apiTokens.list, undefined, {
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
expect(tokens).toHaveLength(2);
|
expect(tokens).toHaveLength(2);
|
||||||
const names = tokens.map((t) => t.name).sort();
|
const names = tokens.map((t) => t.name).sort();
|
||||||
@@ -1640,8 +1654,14 @@ describeE2E("me.apiTokens and me.invites", () => {
|
|||||||
|
|
||||||
test("only returns current user tokens", async () => {
|
test("only returns current user tokens", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const user1 = await createTestUser(db, { email: "user1@example.com", isSuperuser: true });
|
const user1 = await createTestUser(db, {
|
||||||
const user2 = await createTestUser(db, { email: "user2@example.com", isSuperuser: true });
|
email: "user1@example.com",
|
||||||
|
isSuperuser: true,
|
||||||
|
});
|
||||||
|
const user2 = await createTestUser(db, {
|
||||||
|
email: "user2@example.com",
|
||||||
|
isSuperuser: true,
|
||||||
|
});
|
||||||
|
|
||||||
const tokenHash1 = await hashToken("token1");
|
const tokenHash1 = await hashToken("token1");
|
||||||
const tokenHash2 = await hashToken("token2");
|
const tokenHash2 = await hashToken("token2");
|
||||||
@@ -1650,15 +1670,27 @@ describeE2E("me.apiTokens and me.invites", () => {
|
|||||||
await db
|
await db
|
||||||
.insertInto("api_tokens")
|
.insertInto("api_tokens")
|
||||||
.values([
|
.values([
|
||||||
{ user_id: user1.id, token_hash: tokenHash1, name: "User1 Token", expires_at: expiresAt },
|
{
|
||||||
{ user_id: user2.id, token_hash: tokenHash2, name: "User2 Token", expires_at: expiresAt },
|
user_id: user1.id,
|
||||||
|
token_hash: tokenHash1,
|
||||||
|
name: "User1 Token",
|
||||||
|
expires_at: expiresAt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user_id: user2.id,
|
||||||
|
token_hash: tokenHash2,
|
||||||
|
name: "User2 Token",
|
||||||
|
expires_at: expiresAt,
|
||||||
|
},
|
||||||
])
|
])
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, user1.id);
|
const { token: sessionToken } = await createSession(db, user1.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const tokens = await call(router.me.apiTokens.list, undefined, { context });
|
const tokens = await call(router.me.apiTokens.list, undefined, {
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
expect(tokens).toHaveLength(1);
|
expect(tokens).toHaveLength(1);
|
||||||
expect(tokens[0].name).toBe("User1 Token");
|
expect(tokens[0].name).toBe("User1 Token");
|
||||||
@@ -1746,7 +1778,12 @@ describe("me.apiTokens.delete", () => {
|
|||||||
|
|
||||||
const insertResult = await db
|
const insertResult = await db
|
||||||
.insertInto("api_tokens")
|
.insertInto("api_tokens")
|
||||||
.values({ user_id: user.id, token_hash: tokenHash, name: "To Delete", expires_at: expiresAt })
|
.values({
|
||||||
|
user_id: user.id,
|
||||||
|
token_hash: tokenHash,
|
||||||
|
name: "To Delete",
|
||||||
|
expires_at: expiresAt,
|
||||||
|
})
|
||||||
.returning("id")
|
.returning("id")
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
@@ -1774,15 +1811,26 @@ describe("me.apiTokens.delete", () => {
|
|||||||
|
|
||||||
test("cannot delete other user token", async () => {
|
test("cannot delete other user token", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const user1 = await createTestUser(db, { email: "owner@example.com", isSuperuser: true });
|
const user1 = await createTestUser(db, {
|
||||||
const user2 = await createTestUser(db, { email: "other@example.com", isSuperuser: true });
|
email: "owner@example.com",
|
||||||
|
isSuperuser: true,
|
||||||
|
});
|
||||||
|
const user2 = await createTestUser(db, {
|
||||||
|
email: "other@example.com",
|
||||||
|
isSuperuser: true,
|
||||||
|
});
|
||||||
|
|
||||||
const tokenHash = await hashToken("other-token");
|
const tokenHash = await hashToken("other-token");
|
||||||
const expiresAt = new Date(Date.now() + API_TOKEN_EXPIRY_MS);
|
const expiresAt = new Date(Date.now() + API_TOKEN_EXPIRY_MS);
|
||||||
|
|
||||||
const insertResult = await db
|
const insertResult = await db
|
||||||
.insertInto("api_tokens")
|
.insertInto("api_tokens")
|
||||||
.values({ user_id: user1.id, token_hash: tokenHash, name: "User1 Token", expires_at: expiresAt })
|
.values({
|
||||||
|
user_id: user1.id,
|
||||||
|
token_hash: tokenHash,
|
||||||
|
name: "User1 Token",
|
||||||
|
expires_at: expiresAt,
|
||||||
|
})
|
||||||
.returning("id")
|
.returning("id")
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
@@ -1790,7 +1838,11 @@ describe("me.apiTokens.delete", () => {
|
|||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
call(router.me.apiTokens.delete, { tokenId: Number(insertResult.id) }, { context }),
|
call(
|
||||||
|
router.me.apiTokens.delete,
|
||||||
|
{ tokenId: Number(insertResult.id) },
|
||||||
|
{ context },
|
||||||
|
),
|
||||||
).rejects.toThrow("API token not found");
|
).rejects.toThrow("API token not found");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1823,11 +1875,16 @@ describe("me.invites.list", () => {
|
|||||||
email: "inviter@example.com",
|
email: "inviter@example.com",
|
||||||
emailVerifiedAt: new Date(),
|
emailVerifiedAt: new Date(),
|
||||||
});
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org", displayName: "Test Org" });
|
const org = await createOrg(db, {
|
||||||
|
slug: "test-org",
|
||||||
|
displayName: "Test Org",
|
||||||
|
});
|
||||||
await addOrgMember(db, org.id, inviter.id, "owner");
|
await addOrgMember(db, org.id, inviter.id, "owner");
|
||||||
|
|
||||||
// User without verified email
|
// User without verified email
|
||||||
const user = await createTestUser(db, { email: "unverified@example.com" });
|
const user = await createTestUser(db, {
|
||||||
|
email: "unverified@example.com",
|
||||||
|
});
|
||||||
|
|
||||||
// Create an invite for the unverified user
|
// Create an invite for the unverified user
|
||||||
await createOrgInvite(db, {
|
await createOrgInvite(db, {
|
||||||
@@ -1839,7 +1896,9 @@ describe("me.invites.list", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, user.id);
|
const { token: sessionToken } = await createSession(db, user.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const invites = await call(router.me.invites.list, undefined, { context });
|
const invites = await call(router.me.invites.list, undefined, {
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
expect(invites).toHaveLength(0);
|
expect(invites).toHaveLength(0);
|
||||||
});
|
});
|
||||||
@@ -1852,7 +1911,10 @@ describe("me.invites.list", () => {
|
|||||||
emailVerifiedAt: new Date(),
|
emailVerifiedAt: new Date(),
|
||||||
displayName: "Inviter Person",
|
displayName: "Inviter Person",
|
||||||
});
|
});
|
||||||
const org = await createOrg(db, { slug: "invite-org", displayName: "Invite Org" });
|
const org = await createOrg(db, {
|
||||||
|
slug: "invite-org",
|
||||||
|
displayName: "Invite Org",
|
||||||
|
});
|
||||||
await addOrgMember(db, org.id, inviter.id, "owner");
|
await addOrgMember(db, org.id, inviter.id, "owner");
|
||||||
|
|
||||||
const user = await createTestUser(db, {
|
const user = await createTestUser(db, {
|
||||||
@@ -1870,7 +1932,9 @@ describe("me.invites.list", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, user.id);
|
const { token: sessionToken } = await createSession(db, user.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const invites = await call(router.me.invites.list, undefined, { context });
|
const invites = await call(router.me.invites.list, undefined, {
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
expect(invites).toHaveLength(1);
|
expect(invites).toHaveLength(1);
|
||||||
expect(invites[0].org.slug).toBe("invite-org");
|
expect(invites[0].org.slug).toBe("invite-org");
|
||||||
@@ -1905,7 +1969,9 @@ describe("me.invites.list", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, user.id);
|
const { token: sessionToken } = await createSession(db, user.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const invites = await call(router.me.invites.list, undefined, { context });
|
const invites = await call(router.me.invites.list, undefined, {
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
expect(invites).toHaveLength(0);
|
expect(invites).toHaveLength(0);
|
||||||
});
|
});
|
||||||
@@ -1920,7 +1986,10 @@ describe("me.invites.get", () => {
|
|||||||
emailVerifiedAt: new Date(),
|
emailVerifiedAt: new Date(),
|
||||||
displayName: "The Inviter",
|
displayName: "The Inviter",
|
||||||
});
|
});
|
||||||
const org = await createOrg(db, { slug: "get-invite-org", displayName: "Get Invite Org" });
|
const org = await createOrg(db, {
|
||||||
|
slug: "get-invite-org",
|
||||||
|
displayName: "Get Invite Org",
|
||||||
|
});
|
||||||
await addOrgMember(db, org.id, inviter.id, "owner");
|
await addOrgMember(db, org.id, inviter.id, "owner");
|
||||||
|
|
||||||
const user = await createTestUser(db, {
|
const user = await createTestUser(db, {
|
||||||
@@ -1960,7 +2029,9 @@ describe("me.invites.get", () => {
|
|||||||
const org = await createOrg(db, { slug: "unverified-get-org" });
|
const org = await createOrg(db, { slug: "unverified-get-org" });
|
||||||
await addOrgMember(db, org.id, inviter.id, "owner");
|
await addOrgMember(db, org.id, inviter.id, "owner");
|
||||||
|
|
||||||
const user = await createTestUser(db, { email: "unverified2@example.com" });
|
const user = await createTestUser(db, {
|
||||||
|
email: "unverified2@example.com",
|
||||||
|
});
|
||||||
|
|
||||||
const invite = await createOrgInvite(db, {
|
const invite = await createOrgInvite(db, {
|
||||||
orgId: org.id,
|
orgId: org.id,
|
||||||
@@ -2015,7 +2086,7 @@ describe("me.invites.get", () => {
|
|||||||
describe("me.invites.accept", () => {
|
describe("me.invites.accept", () => {
|
||||||
test("accepts invite and adds user to org", async () => {
|
test("accepts invite and adds user to org", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const inviter = await createTestUser(db, {
|
const inviter = await createTestUser(db, {
|
||||||
email: `inviter-accept-${uniqueId}@example.com`,
|
email: `inviter-accept-${uniqueId}@example.com`,
|
||||||
@@ -2069,9 +2140,18 @@ describe("me.invites.accept", () => {
|
|||||||
expect(inviteCheck).toBeUndefined();
|
expect(inviteCheck).toBeUndefined();
|
||||||
} finally {
|
} finally {
|
||||||
// Cleanup
|
// Cleanup
|
||||||
await db.deleteFrom("org_members").where("org_id", "=", org.id).execute();
|
await db
|
||||||
await db.deleteFrom("org_invites").where("org_id", "=", org.id).execute();
|
.deleteFrom("org_members")
|
||||||
await db.deleteFrom("sessions").where("user_id", "=", user.id).execute();
|
.where("org_id", "=", org.id)
|
||||||
|
.execute();
|
||||||
|
await db
|
||||||
|
.deleteFrom("org_invites")
|
||||||
|
.where("org_id", "=", org.id)
|
||||||
|
.execute();
|
||||||
|
await db
|
||||||
|
.deleteFrom("sessions")
|
||||||
|
.where("user_id", "=", user.id)
|
||||||
|
.execute();
|
||||||
await db.deleteFrom("orgs").where("id", "=", org.id).execute();
|
await db.deleteFrom("orgs").where("id", "=", org.id).execute();
|
||||||
await db.deleteFrom("users").where("id", "=", user.id).execute();
|
await db.deleteFrom("users").where("id", "=", user.id).execute();
|
||||||
await db.deleteFrom("users").where("id", "=", inviter.id).execute();
|
await db.deleteFrom("users").where("id", "=", inviter.id).execute();
|
||||||
@@ -2087,7 +2167,9 @@ describe("me.invites.accept", () => {
|
|||||||
const org = await createOrg(db, { slug: "unverified-accept-org" });
|
const org = await createOrg(db, { slug: "unverified-accept-org" });
|
||||||
await addOrgMember(db, org.id, inviter.id, "owner");
|
await addOrgMember(db, org.id, inviter.id, "owner");
|
||||||
|
|
||||||
const user = await createTestUser(db, { email: "unverified3@example.com" });
|
const user = await createTestUser(db, {
|
||||||
|
email: "unverified3@example.com",
|
||||||
|
});
|
||||||
|
|
||||||
const invite = await createOrgInvite(db, {
|
const invite = await createOrgInvite(db, {
|
||||||
orgId: org.id,
|
orgId: org.id,
|
||||||
@@ -2106,13 +2188,15 @@ describe("me.invites.accept", () => {
|
|||||||
|
|
||||||
test("returns error if already a member", async () => {
|
test("returns error if already a member", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const inviter = await createTestUser(db, {
|
const inviter = await createTestUser(db, {
|
||||||
email: `inviter-already-${uniqueId}@example.com`,
|
email: `inviter-already-${uniqueId}@example.com`,
|
||||||
emailVerifiedAt: new Date(),
|
emailVerifiedAt: new Date(),
|
||||||
});
|
});
|
||||||
const org = await createOrg(db, { slug: `already-member-org-${uniqueId}` });
|
const org = await createOrg(db, {
|
||||||
|
slug: `already-member-org-${uniqueId}`,
|
||||||
|
});
|
||||||
await addOrgMember(db, org.id, inviter.id, "owner");
|
await addOrgMember(db, org.id, inviter.id, "owner");
|
||||||
|
|
||||||
const user = await createTestUser(db, {
|
const user = await createTestUser(db, {
|
||||||
@@ -2139,9 +2223,18 @@ describe("me.invites.accept", () => {
|
|||||||
).rejects.toThrow("You are already a member of this organization");
|
).rejects.toThrow("You are already a member of this organization");
|
||||||
} finally {
|
} finally {
|
||||||
// Cleanup
|
// Cleanup
|
||||||
await db.deleteFrom("org_members").where("org_id", "=", org.id).execute();
|
await db
|
||||||
await db.deleteFrom("org_invites").where("org_id", "=", org.id).execute();
|
.deleteFrom("org_members")
|
||||||
await db.deleteFrom("sessions").where("user_id", "=", user.id).execute();
|
.where("org_id", "=", org.id)
|
||||||
|
.execute();
|
||||||
|
await db
|
||||||
|
.deleteFrom("org_invites")
|
||||||
|
.where("org_id", "=", org.id)
|
||||||
|
.execute();
|
||||||
|
await db
|
||||||
|
.deleteFrom("sessions")
|
||||||
|
.where("user_id", "=", user.id)
|
||||||
|
.execute();
|
||||||
await db.deleteFrom("orgs").where("id", "=", org.id).execute();
|
await db.deleteFrom("orgs").where("id", "=", org.id).execute();
|
||||||
await db.deleteFrom("users").where("id", "=", user.id).execute();
|
await db.deleteFrom("users").where("id", "=", user.id).execute();
|
||||||
await db.deleteFrom("users").where("id", "=", inviter.id).execute();
|
await db.deleteFrom("users").where("id", "=", inviter.id).execute();
|
||||||
@@ -2256,4 +2349,5 @@ describe("me.invites.decline", () => {
|
|||||||
).rejects.toThrow("Invitation not found");
|
).rejects.toThrow("Invitation not found");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}); // Close describe for me.invites.decline
|
||||||
}); // Close describeE2E for me.apiTokens and me.invites
|
}); // Close describeE2E for me.apiTokens and me.invites
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
import type { Database } from "@reviq/db-schema";
|
import type { Database } from "@reviq/db-schema";
|
||||||
import type { Kysely } from "kysely";
|
import type { Kysely } from "kysely";
|
||||||
|
import type { APIContext } from "../../context.js";
|
||||||
import { beforeAll, describe, expect, test } from "bun:test";
|
import { beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { call } from "@orpc/server";
|
import { call } from "@orpc/server";
|
||||||
import {
|
import {
|
||||||
@@ -19,10 +20,8 @@ import {
|
|||||||
getSharedDb,
|
getSharedDb,
|
||||||
initTestDb,
|
initTestDb,
|
||||||
TEST_RP,
|
TEST_RP,
|
||||||
truncateAllTables,
|
|
||||||
withTestTransaction,
|
withTestTransaction,
|
||||||
} from "@reviq/test-helpers";
|
} from "@reviq/test-helpers";
|
||||||
import type { APIContext } from "../../context.js";
|
|
||||||
import { router } from "../../router.js";
|
import { router } from "../../router.js";
|
||||||
import { COOKIE_NAMES } from "../../utils/cookies.js";
|
import { COOKIE_NAMES } from "../../utils/cookies.js";
|
||||||
import { hashToken } from "../../utils/crypto.js";
|
import { hashToken } from "../../utils/crypto.js";
|
||||||
@@ -167,8 +166,11 @@ async function createOrgInvite(
|
|||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
},
|
},
|
||||||
): Promise<{ id: number; token: string }> {
|
): Promise<{ id: number; token: string }> {
|
||||||
const token = options?.token ?? `invite-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
const token =
|
||||||
const expiresAt = options?.expiresAt ?? new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
options?.token ??
|
||||||
|
`invite-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
const expiresAt =
|
||||||
|
options?.expiresAt ?? new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
const result = await db
|
const result = await db
|
||||||
.insertInto("org_invites")
|
.insertInto("org_invites")
|
||||||
@@ -209,7 +211,11 @@ describeE2E("orgs", () => {
|
|||||||
const context = createAPIContext(db);
|
const context = createAPIContext(db);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
call(router.orgs.create, { slug: "test", displayName: "Test" }, { context }),
|
call(
|
||||||
|
router.orgs.create,
|
||||||
|
{ slug: "test", displayName: "Test" },
|
||||||
|
{ context },
|
||||||
|
),
|
||||||
).rejects.toThrow("No session or API key");
|
).rejects.toThrow("No session or API key");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -243,8 +249,14 @@ describe("orgs.list", () => {
|
|||||||
test("returns orgs where user is a member", async () => {
|
test("returns orgs where user is a member", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const user = await createTestUser(db, { email: "user@example.com" });
|
const user = await createTestUser(db, { email: "user@example.com" });
|
||||||
const org1 = await createOrg(db, { slug: "org-one", displayName: "Org One" });
|
const org1 = await createOrg(db, {
|
||||||
const org2 = await createOrg(db, { slug: "org-two", displayName: "Org Two" });
|
slug: "org-one",
|
||||||
|
displayName: "Org One",
|
||||||
|
});
|
||||||
|
const org2 = await createOrg(db, {
|
||||||
|
slug: "org-two",
|
||||||
|
displayName: "Org Two",
|
||||||
|
});
|
||||||
await addOrgMember(db, org1.id, user.id, "owner");
|
await addOrgMember(db, org1.id, user.id, "owner");
|
||||||
await addOrgMember(db, org2.id, user.id, "member");
|
await addOrgMember(db, org2.id, user.id, "member");
|
||||||
|
|
||||||
@@ -262,7 +274,9 @@ describe("orgs.list", () => {
|
|||||||
test("does not return orgs where user is not a member", async () => {
|
test("does not return orgs where user is not a member", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const user = await createTestUser(db, { email: "user@example.com" });
|
const user = await createTestUser(db, { email: "user@example.com" });
|
||||||
const otherUser = await createTestUser(db, { email: "other@example.com" });
|
const otherUser = await createTestUser(db, {
|
||||||
|
email: "other@example.com",
|
||||||
|
});
|
||||||
const org1 = await createOrg(db, { slug: "my-org" });
|
const org1 = await createOrg(db, { slug: "my-org" });
|
||||||
const org2 = await createOrg(db, { slug: "other-org" });
|
const org2 = await createOrg(db, { slug: "other-org" });
|
||||||
await addOrgMember(db, org1.id, user.id, "owner");
|
await addOrgMember(db, org1.id, user.id, "owner");
|
||||||
@@ -305,9 +319,11 @@ describe("orgs.list", () => {
|
|||||||
describe("orgs.create", () => {
|
describe("orgs.create", () => {
|
||||||
test("creates org and makes user owner", async () => {
|
test("creates org and makes user owner", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const user = await createTestUser(db, { email: `user-${uniqueId}@example.com` });
|
const user = await createTestUser(db, {
|
||||||
|
email: `user-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const { token: sessionToken } = await createSession(db, user.id);
|
const { token: sessionToken } = await createSession(db, user.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
@@ -333,9 +349,11 @@ describe("orgs.create", () => {
|
|||||||
|
|
||||||
test("rejects duplicate slug", async () => {
|
test("rejects duplicate slug", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const user = await createTestUser(db, { email: `user-${uniqueId}@example.com` });
|
const user = await createTestUser(db, {
|
||||||
|
email: `user-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
await createOrg(db, { slug: `existing-${uniqueId}` });
|
await createOrg(db, { slug: `existing-${uniqueId}` });
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, user.id);
|
const { token: sessionToken } = await createSession(db, user.id);
|
||||||
@@ -367,7 +385,11 @@ describe("orgs.get", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, user.id);
|
const { token: sessionToken } = await createSession(db, user.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const result = await call(router.orgs.get, { slug: "test-org" }, { context });
|
const result = await call(
|
||||||
|
router.orgs.get,
|
||||||
|
{ slug: "test-org" },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
expect(result.slug).toBe("test-org");
|
expect(result.slug).toBe("test-org");
|
||||||
expect(result.displayName).toBe("Test Org");
|
expect(result.displayName).toBe("Test Org");
|
||||||
@@ -378,7 +400,9 @@ describe("orgs.get", () => {
|
|||||||
test("rejects when user is not a member", async () => {
|
test("rejects when user is not a member", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const user = await createTestUser(db, { email: "user@example.com" });
|
const user = await createTestUser(db, { email: "user@example.com" });
|
||||||
const otherUser = await createTestUser(db, { email: "other@example.com" });
|
const otherUser = await createTestUser(db, {
|
||||||
|
email: "other@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, otherUser.id, "owner");
|
await addOrgMember(db, org.id, otherUser.id, "owner");
|
||||||
|
|
||||||
@@ -411,7 +435,10 @@ describe("orgs.update", () => {
|
|||||||
test("updates display name when user is admin", async () => {
|
test("updates display name when user is admin", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const user = await createTestUser(db, { email: "user@example.com" });
|
const user = await createTestUser(db, { email: "user@example.com" });
|
||||||
const org = await createOrg(db, { slug: "test-org", displayName: "Old Name" });
|
const org = await createOrg(db, {
|
||||||
|
slug: "test-org",
|
||||||
|
displayName: "Old Name",
|
||||||
|
});
|
||||||
await addOrgMember(db, org.id, user.id, "admin");
|
await addOrgMember(db, org.id, user.id, "admin");
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, user.id);
|
const { token: sessionToken } = await createSession(db, user.id);
|
||||||
@@ -480,7 +507,9 @@ describe("orgs.update", () => {
|
|||||||
test("rejects when user is not a member", async () => {
|
test("rejects when user is not a member", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const user = await createTestUser(db, { email: "user@example.com" });
|
const user = await createTestUser(db, { email: "user@example.com" });
|
||||||
const otherUser = await createTestUser(db, { email: "other@example.com" });
|
const otherUser = await createTestUser(db, {
|
||||||
|
email: "other@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, otherUser.id, "owner");
|
await addOrgMember(db, org.id, otherUser.id, "owner");
|
||||||
|
|
||||||
@@ -503,16 +532,22 @@ describe("orgs.update", () => {
|
|||||||
describe("orgs.delete", () => {
|
describe("orgs.delete", () => {
|
||||||
test("deletes org when user is owner", async () => {
|
test("deletes org when user is owner", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const user = await createTestUser(db, { email: `user-${uniqueId}@example.com` });
|
const user = await createTestUser(db, {
|
||||||
|
email: `user-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `delete-org-${uniqueId}` });
|
const org = await createOrg(db, { slug: `delete-org-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, user.id, "owner");
|
await addOrgMember(db, org.id, user.id, "owner");
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, user.id);
|
const { token: sessionToken } = await createSession(db, user.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
await call(router.orgs.delete, { slug: `delete-org-${uniqueId}` }, { context });
|
await call(
|
||||||
|
router.orgs.delete,
|
||||||
|
{ slug: `delete-org-${uniqueId}` },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
const deleted = await db
|
const deleted = await db
|
||||||
.selectFrom("orgs")
|
.selectFrom("orgs")
|
||||||
@@ -546,10 +581,14 @@ describe("orgs.delete", () => {
|
|||||||
describe("orgs.leave", () => {
|
describe("orgs.leave", () => {
|
||||||
test("allows member to leave org", async () => {
|
test("allows member to leave org", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const member = await createTestUser(db, { email: `member-${uniqueId}@example.com` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const member = await createTestUser(db, {
|
||||||
|
email: `member-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `leave-org-${uniqueId}` });
|
const org = await createOrg(db, { slug: `leave-org-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
@@ -557,7 +596,11 @@ describe("orgs.leave", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, member.id);
|
const { token: sessionToken } = await createSession(db, member.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
await call(router.orgs.leave, { slug: `leave-org-${uniqueId}` }, { context });
|
await call(
|
||||||
|
router.orgs.leave,
|
||||||
|
{ slug: `leave-org-${uniqueId}` },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
const membership = await db
|
const membership = await db
|
||||||
.selectFrom("org_members")
|
.selectFrom("org_members")
|
||||||
@@ -571,10 +614,14 @@ describe("orgs.leave", () => {
|
|||||||
|
|
||||||
test("allows owner to leave when there are other owners", async () => {
|
test("allows owner to leave when there are other owners", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner1 = await createTestUser(db, { email: `owner1-${uniqueId}@example.com` });
|
const owner1 = await createTestUser(db, {
|
||||||
const owner2 = await createTestUser(db, { email: `owner2-${uniqueId}@example.com` });
|
email: `owner1-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const owner2 = await createTestUser(db, {
|
||||||
|
email: `owner2-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `leave-org-${uniqueId}` });
|
const org = await createOrg(db, { slug: `leave-org-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner1.id, "owner");
|
await addOrgMember(db, org.id, owner1.id, "owner");
|
||||||
await addOrgMember(db, org.id, owner2.id, "owner");
|
await addOrgMember(db, org.id, owner2.id, "owner");
|
||||||
@@ -582,7 +629,11 @@ describe("orgs.leave", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, owner1.id);
|
const { token: sessionToken } = await createSession(db, owner1.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
await call(router.orgs.leave, { slug: `leave-org-${uniqueId}` }, { context });
|
await call(
|
||||||
|
router.orgs.leave,
|
||||||
|
{ slug: `leave-org-${uniqueId}` },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
const membership = await db
|
const membership = await db
|
||||||
.selectFrom("org_members")
|
.selectFrom("org_members")
|
||||||
@@ -596,9 +647,11 @@ describe("orgs.leave", () => {
|
|||||||
|
|
||||||
test("prevents only owner from leaving", async () => {
|
test("prevents only owner from leaving", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `leave-only-owner-${uniqueId}` });
|
const org = await createOrg(db, { slug: `leave-only-owner-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
|
|
||||||
@@ -606,7 +659,11 @@ describe("orgs.leave", () => {
|
|||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
call(router.orgs.leave, { slug: `leave-only-owner-${uniqueId}` }, { context }),
|
call(
|
||||||
|
router.orgs.leave,
|
||||||
|
{ slug: `leave-only-owner-${uniqueId}` },
|
||||||
|
{ context },
|
||||||
|
),
|
||||||
).rejects.toThrow("Cannot leave as the only owner");
|
).rejects.toThrow("Cannot leave as the only owner");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -632,9 +689,18 @@ describe("orgs.leave", () => {
|
|||||||
describe("orgs.members.list", () => {
|
describe("orgs.members.list", () => {
|
||||||
test("returns all members of org", async () => {
|
test("returns all members of org", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const owner = await createTestUser(db, { email: "owner@example.com", displayName: "Owner" });
|
const owner = await createTestUser(db, {
|
||||||
const admin = await createTestUser(db, { email: "admin@example.com", displayName: "Admin" });
|
email: "owner@example.com",
|
||||||
const member = await createTestUser(db, { email: "member@example.com", displayName: "Member" });
|
displayName: "Owner",
|
||||||
|
});
|
||||||
|
const admin = await createTestUser(db, {
|
||||||
|
email: "admin@example.com",
|
||||||
|
displayName: "Admin",
|
||||||
|
});
|
||||||
|
const member = await createTestUser(db, {
|
||||||
|
email: "member@example.com",
|
||||||
|
displayName: "Member",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, admin.id, "admin");
|
await addOrgMember(db, org.id, admin.id, "admin");
|
||||||
@@ -643,23 +709,38 @@ describe("orgs.members.list", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, member.id);
|
const { token: sessionToken } = await createSession(db, member.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const members = await call(router.orgs.members.list, { slug: "test-org" }, { context });
|
const members = await call(
|
||||||
|
router.orgs.members.list,
|
||||||
|
{ slug: "test-org" },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
expect(members.length).toBe(3);
|
expect(members.length).toBe(3);
|
||||||
expect(members.map((m) => m.role).sort()).toEqual(["admin", "member", "owner"]);
|
expect(members.map((m) => m.role).sort()).toEqual([
|
||||||
|
"admin",
|
||||||
|
"member",
|
||||||
|
"owner",
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("includes user details", async () => {
|
test("includes user details", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const owner = await createTestUser(db, { email: "owner@example.com", displayName: "Test Owner" });
|
const owner = await createTestUser(db, {
|
||||||
|
email: "owner@example.com",
|
||||||
|
displayName: "Test Owner",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, owner.id);
|
const { token: sessionToken } = await createSession(db, owner.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const members = await call(router.orgs.members.list, { slug: "test-org" }, { context });
|
const members = await call(
|
||||||
|
router.orgs.members.list,
|
||||||
|
{ slug: "test-org" },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
expect(members[0]?.email).toBe("owner@example.com");
|
expect(members[0]?.email).toBe("owner@example.com");
|
||||||
expect(members[0]?.displayName).toBe("Test Owner");
|
expect(members[0]?.displayName).toBe("Test Owner");
|
||||||
@@ -689,10 +770,14 @@ describe("orgs.members.list", () => {
|
|||||||
describe("orgs.members.updateRole", () => {
|
describe("orgs.members.updateRole", () => {
|
||||||
test("owner can promote member to admin", async () => {
|
test("owner can promote member to admin", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const member = await createTestUser(db, { email: `member-${uniqueId}@example.com` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const member = await createTestUser(db, {
|
||||||
|
email: `member-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `update-role-${uniqueId}` });
|
const org = await createOrg(db, { slug: `update-role-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
@@ -718,10 +803,14 @@ describe("orgs.members.updateRole", () => {
|
|||||||
|
|
||||||
test("owner can promote member to owner", async () => {
|
test("owner can promote member to owner", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const member = await createTestUser(db, { email: `member-${uniqueId}@example.com` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const member = await createTestUser(db, {
|
||||||
|
email: `member-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `update-role-${uniqueId}` });
|
const org = await createOrg(db, { slug: `update-role-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
@@ -747,10 +836,14 @@ describe("orgs.members.updateRole", () => {
|
|||||||
|
|
||||||
test("owner can demote owner to admin when multiple owners exist", async () => {
|
test("owner can demote owner to admin when multiple owners exist", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner1 = await createTestUser(db, { email: `owner1-${uniqueId}@example.com` });
|
const owner1 = await createTestUser(db, {
|
||||||
const owner2 = await createTestUser(db, { email: `owner2-${uniqueId}@example.com` });
|
email: `owner1-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const owner2 = await createTestUser(db, {
|
||||||
|
email: `owner2-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `update-role-${uniqueId}` });
|
const org = await createOrg(db, { slug: `update-role-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner1.id, "owner");
|
await addOrgMember(db, org.id, owner1.id, "owner");
|
||||||
await addOrgMember(db, org.id, owner2.id, "owner");
|
await addOrgMember(db, org.id, owner2.id, "owner");
|
||||||
@@ -776,9 +869,11 @@ describe("orgs.members.updateRole", () => {
|
|||||||
|
|
||||||
test("prevents demoting the only owner", async () => {
|
test("prevents demoting the only owner", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `update-role-${uniqueId}` });
|
const org = await createOrg(db, { slug: `update-role-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
|
|
||||||
@@ -798,7 +893,9 @@ describe("orgs.members.updateRole", () => {
|
|||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const owner = await createTestUser(db, { email: "owner@example.com" });
|
const owner = await createTestUser(db, { email: "owner@example.com" });
|
||||||
const admin = await createTestUser(db, { email: "admin@example.com" });
|
const admin = await createTestUser(db, { email: "admin@example.com" });
|
||||||
const member = await createTestUser(db, { email: "member@example.com" });
|
const member = await createTestUser(db, {
|
||||||
|
email: "member@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, admin.id, "admin");
|
await addOrgMember(db, org.id, admin.id, "admin");
|
||||||
@@ -819,9 +916,11 @@ describe("orgs.members.updateRole", () => {
|
|||||||
|
|
||||||
test("rejects when target member not found", async () => {
|
test("rejects when target member not found", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `update-role-${uniqueId}` });
|
const org = await createOrg(db, { slug: `update-role-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
|
|
||||||
@@ -843,10 +942,14 @@ describe("orgs.members.updateRole", () => {
|
|||||||
describe("orgs.members.remove", () => {
|
describe("orgs.members.remove", () => {
|
||||||
test("owner can remove member", async () => {
|
test("owner can remove member", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const member = await createTestUser(db, { email: `member-${uniqueId}@example.com` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const member = await createTestUser(db, {
|
||||||
|
email: `member-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `remove-member-${uniqueId}` });
|
const org = await createOrg(db, { slug: `remove-member-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
@@ -872,10 +975,14 @@ describe("orgs.members.remove", () => {
|
|||||||
|
|
||||||
test("owner can remove admin", async () => {
|
test("owner can remove admin", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const admin = await createTestUser(db, { email: `admin-${uniqueId}@example.com` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const admin = await createTestUser(db, {
|
||||||
|
email: `admin-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `remove-admin-${uniqueId}` });
|
const org = await createOrg(db, { slug: `remove-admin-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, admin.id, "admin");
|
await addOrgMember(db, org.id, admin.id, "admin");
|
||||||
@@ -901,10 +1008,14 @@ describe("orgs.members.remove", () => {
|
|||||||
|
|
||||||
test("owner can remove other owner when multiple owners exist", async () => {
|
test("owner can remove other owner when multiple owners exist", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner1 = await createTestUser(db, { email: `owner1-${uniqueId}@example.com` });
|
const owner1 = await createTestUser(db, {
|
||||||
const owner2 = await createTestUser(db, { email: `owner2-${uniqueId}@example.com` });
|
email: `owner1-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const owner2 = await createTestUser(db, {
|
||||||
|
email: `owner2-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `remove-owner-${uniqueId}` });
|
const org = await createOrg(db, { slug: `remove-owner-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner1.id, "owner");
|
await addOrgMember(db, org.id, owner1.id, "owner");
|
||||||
await addOrgMember(db, org.id, owner2.id, "owner");
|
await addOrgMember(db, org.id, owner2.id, "owner");
|
||||||
@@ -930,10 +1041,14 @@ describe("orgs.members.remove", () => {
|
|||||||
|
|
||||||
test("prevents removing the only owner", async () => {
|
test("prevents removing the only owner", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const org = await createOrg(db, { slug: `remove-only-owner-${uniqueId}` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const org = await createOrg(db, {
|
||||||
|
slug: `remove-only-owner-${uniqueId}`,
|
||||||
|
});
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, owner.id);
|
const { token: sessionToken } = await createSession(db, owner.id);
|
||||||
@@ -950,11 +1065,17 @@ describe("orgs.members.remove", () => {
|
|||||||
|
|
||||||
test("admin can remove member", async () => {
|
test("admin can remove member", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const admin = await createTestUser(db, { email: `admin-${uniqueId}@example.com` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
const member = await createTestUser(db, { email: `member-${uniqueId}@example.com` });
|
});
|
||||||
|
const admin = await createTestUser(db, {
|
||||||
|
email: `admin-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const member = await createTestUser(db, {
|
||||||
|
email: `member-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `admin-remove-${uniqueId}` });
|
const org = await createOrg(db, { slug: `admin-remove-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, admin.id, "admin");
|
await addOrgMember(db, org.id, admin.id, "admin");
|
||||||
@@ -981,11 +1102,17 @@ describe("orgs.members.remove", () => {
|
|||||||
|
|
||||||
test("admin cannot remove owner", async () => {
|
test("admin cannot remove owner", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const admin = await createTestUser(db, { email: `admin-${uniqueId}@example.com` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
const org = await createOrg(db, { slug: `admin-no-remove-owner-${uniqueId}` });
|
});
|
||||||
|
const admin = await createTestUser(db, {
|
||||||
|
email: `admin-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const org = await createOrg(db, {
|
||||||
|
slug: `admin-no-remove-owner-${uniqueId}`,
|
||||||
|
});
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, admin.id, "admin");
|
await addOrgMember(db, org.id, admin.id, "admin");
|
||||||
|
|
||||||
@@ -1003,12 +1130,20 @@ describe("orgs.members.remove", () => {
|
|||||||
|
|
||||||
test("admin cannot remove other admin", async () => {
|
test("admin cannot remove other admin", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const admin1 = await createTestUser(db, { email: `admin1-${uniqueId}@example.com` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
const admin2 = await createTestUser(db, { email: `admin2-${uniqueId}@example.com` });
|
});
|
||||||
const org = await createOrg(db, { slug: `admin-no-remove-admin-${uniqueId}` });
|
const admin1 = await createTestUser(db, {
|
||||||
|
email: `admin1-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const admin2 = await createTestUser(db, {
|
||||||
|
email: `admin2-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const org = await createOrg(db, {
|
||||||
|
slug: `admin-no-remove-admin-${uniqueId}`,
|
||||||
|
});
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, admin1.id, "admin");
|
await addOrgMember(db, org.id, admin1.id, "admin");
|
||||||
await addOrgMember(db, org.id, admin2.id, "admin");
|
await addOrgMember(db, org.id, admin2.id, "admin");
|
||||||
@@ -1027,11 +1162,17 @@ describe("orgs.members.remove", () => {
|
|||||||
|
|
||||||
test("member cannot remove anyone", async () => {
|
test("member cannot remove anyone", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const member1 = await createTestUser(db, { email: `member1-${uniqueId}@example.com` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
const member2 = await createTestUser(db, { email: `member2-${uniqueId}@example.com` });
|
});
|
||||||
|
const member1 = await createTestUser(db, {
|
||||||
|
email: `member1-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const member2 = await createTestUser(db, {
|
||||||
|
email: `member2-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `member-no-remove-${uniqueId}` });
|
const org = await createOrg(db, { slug: `member-no-remove-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, member1.id, "member");
|
await addOrgMember(db, org.id, member1.id, "member");
|
||||||
@@ -1051,9 +1192,11 @@ describe("orgs.members.remove", () => {
|
|||||||
|
|
||||||
test("rejects when target member not found", async () => {
|
test("rejects when target member not found", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `remove-not-found-${uniqueId}` });
|
const org = await createOrg(db, { slug: `remove-not-found-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
|
|
||||||
@@ -1075,19 +1218,33 @@ describe("orgs.members.remove", () => {
|
|||||||
describe("orgs.invites.list", () => {
|
describe("orgs.invites.list", () => {
|
||||||
test("returns pending invites for org", async () => {
|
test("returns pending invites for org", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const admin = await createTestUser(db, { email: "admin@example.com", displayName: "Admin User" });
|
const admin = await createTestUser(db, {
|
||||||
|
email: "admin@example.com",
|
||||||
|
displayName: "Admin User",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, admin.id, "admin");
|
await addOrgMember(db, org.id, admin.id, "admin");
|
||||||
await createOrgInvite(db, org.id, "invite1@example.com", admin.id, { role: "member" });
|
await createOrgInvite(db, org.id, "invite1@example.com", admin.id, {
|
||||||
await createOrgInvite(db, org.id, "invite2@example.com", admin.id, { role: "admin" });
|
role: "member",
|
||||||
|
});
|
||||||
|
await createOrgInvite(db, org.id, "invite2@example.com", admin.id, {
|
||||||
|
role: "admin",
|
||||||
|
});
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, admin.id);
|
const { token: sessionToken } = await createSession(db, admin.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const invites = await call(router.orgs.invites.list, { slug: "test-org" }, { context });
|
const invites = await call(
|
||||||
|
router.orgs.invites.list,
|
||||||
|
{ slug: "test-org" },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
expect(invites.length).toBe(2);
|
expect(invites.length).toBe(2);
|
||||||
expect(invites.map((i) => i.email).sort()).toEqual(["invite1@example.com", "invite2@example.com"]);
|
expect(invites.map((i) => i.email).sort()).toEqual([
|
||||||
|
"invite1@example.com",
|
||||||
|
"invite2@example.com",
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1104,7 +1261,11 @@ describe("orgs.invites.list", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, admin.id);
|
const { token: sessionToken } = await createSession(db, admin.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const invites = await call(router.orgs.invites.list, { slug: "test-org" }, { context });
|
const invites = await call(
|
||||||
|
router.orgs.invites.list,
|
||||||
|
{ slug: "test-org" },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
expect(invites.length).toBe(1);
|
expect(invites.length).toBe(1);
|
||||||
expect(invites[0]?.email).toBe("active@example.com");
|
expect(invites[0]?.email).toBe("active@example.com");
|
||||||
@@ -1114,7 +1275,9 @@ describe("orgs.invites.list", () => {
|
|||||||
test("member cannot list invites", async () => {
|
test("member cannot list invites", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const owner = await createTestUser(db, { email: "owner@example.com" });
|
const owner = await createTestUser(db, { email: "owner@example.com" });
|
||||||
const member = await createTestUser(db, { email: "member@example.com" });
|
const member = await createTestUser(db, {
|
||||||
|
email: "member@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
@@ -1134,9 +1297,11 @@ describe("orgs.invites.list", () => {
|
|||||||
describe("orgs.invites.create", () => {
|
describe("orgs.invites.create", () => {
|
||||||
test("admin can create member invite", async () => {
|
test("admin can create member invite", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, { email: `admin-${uniqueId}@example.com` });
|
const admin = await createTestUser(db, {
|
||||||
|
email: `admin-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `invite-org-${uniqueId}` });
|
const org = await createOrg(db, { slug: `invite-org-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, admin.id, "admin");
|
await addOrgMember(db, org.id, admin.id, "admin");
|
||||||
|
|
||||||
@@ -1145,7 +1310,11 @@ describe("orgs.invites.create", () => {
|
|||||||
|
|
||||||
await call(
|
await call(
|
||||||
router.orgs.invites.create,
|
router.orgs.invites.create,
|
||||||
{ slug: `invite-org-${uniqueId}`, email: `new-${uniqueId}@example.com`, role: "member" },
|
{
|
||||||
|
slug: `invite-org-${uniqueId}`,
|
||||||
|
email: `new-${uniqueId}@example.com`,
|
||||||
|
role: "member",
|
||||||
|
},
|
||||||
{ context },
|
{ context },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1162,9 +1331,11 @@ describe("orgs.invites.create", () => {
|
|||||||
|
|
||||||
test("admin can create admin invite", async () => {
|
test("admin can create admin invite", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const admin = await createTestUser(db, { email: `admin-${uniqueId}@example.com` });
|
const admin = await createTestUser(db, {
|
||||||
|
email: `admin-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `invite-org-${uniqueId}` });
|
const org = await createOrg(db, { slug: `invite-org-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, admin.id, "admin");
|
await addOrgMember(db, org.id, admin.id, "admin");
|
||||||
|
|
||||||
@@ -1173,7 +1344,11 @@ describe("orgs.invites.create", () => {
|
|||||||
|
|
||||||
await call(
|
await call(
|
||||||
router.orgs.invites.create,
|
router.orgs.invites.create,
|
||||||
{ slug: `invite-org-${uniqueId}`, email: `new-${uniqueId}@example.com`, role: "admin" },
|
{
|
||||||
|
slug: `invite-org-${uniqueId}`,
|
||||||
|
email: `new-${uniqueId}@example.com`,
|
||||||
|
role: "admin",
|
||||||
|
},
|
||||||
{ context },
|
{ context },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1210,9 +1385,11 @@ describe("orgs.invites.create", () => {
|
|||||||
|
|
||||||
test("owner can create owner invite", async () => {
|
test("owner can create owner invite", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `invite-org-${uniqueId}` });
|
const org = await createOrg(db, { slug: `invite-org-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
|
|
||||||
@@ -1221,7 +1398,11 @@ describe("orgs.invites.create", () => {
|
|||||||
|
|
||||||
await call(
|
await call(
|
||||||
router.orgs.invites.create,
|
router.orgs.invites.create,
|
||||||
{ slug: `invite-org-${uniqueId}`, email: `new-${uniqueId}@example.com`, role: "owner" },
|
{
|
||||||
|
slug: `invite-org-${uniqueId}`,
|
||||||
|
email: `new-${uniqueId}@example.com`,
|
||||||
|
role: "owner",
|
||||||
|
},
|
||||||
{ context },
|
{ context },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1238,7 +1419,9 @@ describe("orgs.invites.create", () => {
|
|||||||
test("rejects invite for existing member", async () => {
|
test("rejects invite for existing member", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const owner = await createTestUser(db, { email: "owner@example.com" });
|
const owner = await createTestUser(db, { email: "owner@example.com" });
|
||||||
const member = await createTestUser(db, { email: "member@example.com" });
|
const member = await createTestUser(db, {
|
||||||
|
email: "member@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
@@ -1279,7 +1462,9 @@ describe("orgs.invites.create", () => {
|
|||||||
test("member cannot create invite", async () => {
|
test("member cannot create invite", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const owner = await createTestUser(db, { email: "owner@example.com" });
|
const owner = await createTestUser(db, { email: "owner@example.com" });
|
||||||
const member = await createTestUser(db, { email: "member@example.com" });
|
const member = await createTestUser(db, {
|
||||||
|
email: "member@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
@@ -1306,7 +1491,12 @@ describe("orgs.invites.cancel", () => {
|
|||||||
const admin = await createTestUser(db, { email: "admin@example.com" });
|
const admin = await createTestUser(db, { email: "admin@example.com" });
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, admin.id, "admin");
|
await addOrgMember(db, org.id, admin.id, "admin");
|
||||||
const invite = await createOrgInvite(db, org.id, "invited@example.com", admin.id);
|
const invite = await createOrgInvite(
|
||||||
|
db,
|
||||||
|
org.id,
|
||||||
|
"invited@example.com",
|
||||||
|
admin.id,
|
||||||
|
);
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, admin.id);
|
const { token: sessionToken } = await createSession(db, admin.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
@@ -1349,11 +1539,18 @@ describe("orgs.invites.cancel", () => {
|
|||||||
test("member cannot cancel invite", async () => {
|
test("member cannot cancel invite", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const owner = await createTestUser(db, { email: "owner@example.com" });
|
const owner = await createTestUser(db, { email: "owner@example.com" });
|
||||||
const member = await createTestUser(db, { email: "member@example.com" });
|
const member = await createTestUser(db, {
|
||||||
|
email: "member@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
const invite = await createOrgInvite(db, org.id, "invited@example.com", owner.id);
|
const invite = await createOrgInvite(
|
||||||
|
db,
|
||||||
|
org.id,
|
||||||
|
"invited@example.com",
|
||||||
|
owner.id,
|
||||||
|
);
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, member.id);
|
const { token: sessionToken } = await createSession(db, member.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
@@ -1374,18 +1571,32 @@ describe("orgs.invites.cancel", () => {
|
|||||||
describe("orgs.invites.accept", () => {
|
describe("orgs.invites.accept", () => {
|
||||||
test("accepts invite and adds user to org", async () => {
|
test("accepts invite and adds user to org", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const invitee = await createTestUser(db, { email: `invitee-${uniqueId}@example.com` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const invitee = await createTestUser(db, {
|
||||||
|
email: `invitee-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `accept-org-${uniqueId}` });
|
const org = await createOrg(db, { slug: `accept-org-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
const invite = await createOrgInvite(db, org.id, `invitee-${uniqueId}@example.com`, owner.id, { role: "admin" });
|
const invite = await createOrgInvite(
|
||||||
|
db,
|
||||||
|
org.id,
|
||||||
|
`invitee-${uniqueId}@example.com`,
|
||||||
|
owner.id,
|
||||||
|
{ role: "admin" },
|
||||||
|
);
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, invitee.id);
|
const { token: sessionToken } = await createSession(db, invitee.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
await call(router.orgs.invites.accept, { token: invite.token }, { context });
|
await call(
|
||||||
|
router.orgs.invites.accept,
|
||||||
|
{ token: invite.token },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
// Verify membership
|
// Verify membership
|
||||||
const membership = await db
|
const membership = await db
|
||||||
@@ -1410,18 +1621,30 @@ describe("orgs.invites.accept", () => {
|
|||||||
test("rejects expired invite", async () => {
|
test("rejects expired invite", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const owner = await createTestUser(db, { email: "owner@example.com" });
|
const owner = await createTestUser(db, { email: "owner@example.com" });
|
||||||
const invitee = await createTestUser(db, { email: "invitee@example.com" });
|
const invitee = await createTestUser(db, {
|
||||||
|
email: "invitee@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
const invite = await createOrgInvite(db, org.id, "invitee@example.com", owner.id, {
|
const invite = await createOrgInvite(
|
||||||
|
db,
|
||||||
|
org.id,
|
||||||
|
"invitee@example.com",
|
||||||
|
owner.id,
|
||||||
|
{
|
||||||
expiresAt: new Date(Date.now() - 1000), // expired
|
expiresAt: new Date(Date.now() - 1000), // expired
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, invitee.id);
|
const { token: sessionToken } = await createSession(db, invitee.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
call(router.orgs.invites.accept, { token: invite.token }, { context }),
|
call(
|
||||||
|
router.orgs.invites.accept,
|
||||||
|
{ token: invite.token },
|
||||||
|
{ context },
|
||||||
|
),
|
||||||
).rejects.toThrow("Invalid or expired invitation");
|
).rejects.toThrow("Invalid or expired invitation");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1434,7 +1657,11 @@ describe("orgs.invites.accept", () => {
|
|||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
call(router.orgs.invites.accept, { token: "invalid-token" }, { context }),
|
call(
|
||||||
|
router.orgs.invites.accept,
|
||||||
|
{ token: "invalid-token" },
|
||||||
|
{ context },
|
||||||
|
),
|
||||||
).rejects.toThrow("Invalid or expired invitation");
|
).rejects.toThrow("Invalid or expired invitation");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1442,31 +1669,55 @@ describe("orgs.invites.accept", () => {
|
|||||||
test("rejects when email doesn't match", async () => {
|
test("rejects when email doesn't match", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const owner = await createTestUser(db, { email: "owner@example.com" });
|
const owner = await createTestUser(db, { email: "owner@example.com" });
|
||||||
const invitee = await createTestUser(db, { email: "invitee@example.com" });
|
const _invitee = await createTestUser(db, {
|
||||||
const wrongUser = await createTestUser(db, { email: "wrong@example.com" });
|
email: "invitee@example.com",
|
||||||
|
});
|
||||||
|
const wrongUser = await createTestUser(db, {
|
||||||
|
email: "wrong@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
const invite = await createOrgInvite(db, org.id, "invitee@example.com", owner.id);
|
const invite = await createOrgInvite(
|
||||||
|
db,
|
||||||
|
org.id,
|
||||||
|
"invitee@example.com",
|
||||||
|
owner.id,
|
||||||
|
);
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, wrongUser.id);
|
const { token: sessionToken } = await createSession(db, wrongUser.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
call(router.orgs.invites.accept, { token: invite.token }, { context }),
|
call(
|
||||||
).rejects.toThrow("This invitation was sent to a different email address");
|
router.orgs.invites.accept,
|
||||||
|
{ token: invite.token },
|
||||||
|
{ context },
|
||||||
|
),
|
||||||
|
).rejects.toThrow(
|
||||||
|
"This invitation was sent to a different email address",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("handles already a member gracefully", async () => {
|
test("handles already a member gracefully", async () => {
|
||||||
const db = getSharedDb();
|
const db = getSharedDb();
|
||||||
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
const owner = await createTestUser(db, { email: `owner-${uniqueId}@example.com` });
|
const owner = await createTestUser(db, {
|
||||||
const member = await createTestUser(db, { email: `member-${uniqueId}@example.com` });
|
email: `owner-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
|
const member = await createTestUser(db, {
|
||||||
|
email: `member-${uniqueId}@example.com`,
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: `test-org-${uniqueId}` });
|
const org = await createOrg(db, { slug: `test-org-${uniqueId}` });
|
||||||
await addOrgMember(db, org.id, owner.id, "owner");
|
await addOrgMember(db, org.id, owner.id, "owner");
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
const invite = await createOrgInvite(db, org.id, `member-${uniqueId}@example.com`, owner.id);
|
const invite = await createOrgInvite(
|
||||||
|
db,
|
||||||
|
org.id,
|
||||||
|
`member-${uniqueId}@example.com`,
|
||||||
|
owner.id,
|
||||||
|
);
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, member.id);
|
const { token: sessionToken } = await createSession(db, member.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
@@ -1491,7 +1742,9 @@ describe("orgs.invites.accept", () => {
|
|||||||
describe("orgs.sites.list", () => {
|
describe("orgs.sites.list", () => {
|
||||||
test("returns sites for org", async () => {
|
test("returns sites for org", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const member = await createTestUser(db, { email: "member@example.com" });
|
const member = await createTestUser(db, {
|
||||||
|
email: "member@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
await createSite(db, org.id, "example.com");
|
await createSite(db, org.id, "example.com");
|
||||||
@@ -1500,23 +1753,36 @@ describe("orgs.sites.list", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, member.id);
|
const { token: sessionToken } = await createSession(db, member.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const sites = await call(router.orgs.sites.list, { slug: "test-org" }, { context });
|
const sites = await call(
|
||||||
|
router.orgs.sites.list,
|
||||||
|
{ slug: "test-org" },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
expect(sites.length).toBe(2);
|
expect(sites.length).toBe(2);
|
||||||
expect(sites.map((s) => s.domain).sort()).toEqual(["example.com", "test.com"]);
|
expect(sites.map((s) => s.domain).sort()).toEqual([
|
||||||
|
"example.com",
|
||||||
|
"test.com",
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("returns empty array when no sites", async () => {
|
test("returns empty array when no sites", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const member = await createTestUser(db, { email: "member@example.com" });
|
const member = await createTestUser(db, {
|
||||||
|
email: "member@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
|
|
||||||
const { token: sessionToken } = await createSession(db, member.id);
|
const { token: sessionToken } = await createSession(db, member.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const sites = await call(router.orgs.sites.list, { slug: "test-org" }, { context });
|
const sites = await call(
|
||||||
|
router.orgs.sites.list,
|
||||||
|
{ slug: "test-org" },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
expect(sites).toHaveLength(0);
|
expect(sites).toHaveLength(0);
|
||||||
});
|
});
|
||||||
@@ -1524,7 +1790,9 @@ describe("orgs.sites.list", () => {
|
|||||||
|
|
||||||
test("returns site details including id and createdAt", async () => {
|
test("returns site details including id and createdAt", async () => {
|
||||||
await withTestTransaction(getSharedDb(), async (db) => {
|
await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
const member = await createTestUser(db, { email: "member@example.com" });
|
const member = await createTestUser(db, {
|
||||||
|
email: "member@example.com",
|
||||||
|
});
|
||||||
const org = await createOrg(db, { slug: "test-org" });
|
const org = await createOrg(db, { slug: "test-org" });
|
||||||
await addOrgMember(db, org.id, member.id, "member");
|
await addOrgMember(db, org.id, member.id, "member");
|
||||||
await createSite(db, org.id, "example.com");
|
await createSite(db, org.id, "example.com");
|
||||||
@@ -1532,7 +1800,11 @@ describe("orgs.sites.list", () => {
|
|||||||
const { token: sessionToken } = await createSession(db, member.id);
|
const { token: sessionToken } = await createSession(db, member.id);
|
||||||
const context = createAPIContext(db, { sessionToken });
|
const context = createAPIContext(db, { sessionToken });
|
||||||
|
|
||||||
const sites = await call(router.orgs.sites.list, { slug: "test-org" }, { context });
|
const sites = await call(
|
||||||
|
router.orgs.sites.list,
|
||||||
|
{ slug: "test-org" },
|
||||||
|
{ context },
|
||||||
|
);
|
||||||
|
|
||||||
expect(sites[0]?.id).toBeDefined();
|
expect(sites[0]?.id).toBeDefined();
|
||||||
expect(sites[0]?.domain).toBe("example.com");
|
expect(sites[0]?.domain).toBe("example.com");
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ export async function signupWithPassword(
|
|||||||
// Handle duplicate email (unique constraint violation)
|
// Handle duplicate email (unique constraint violation)
|
||||||
// Use generic error to prevent email enumeration
|
// Use generic error to prevent email enumeration
|
||||||
if (error instanceof Error && error.message.includes("users_email_key")) {
|
if (error instanceof Error && error.message.includes("users_email_key")) {
|
||||||
throw new ORPCError("BAD_REQUEST", { message: "Unable to create account" });
|
throw new ORPCError("BAD_REQUEST", {
|
||||||
|
message: "Unable to create account",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -209,7 +211,9 @@ export async function signupWithPasskey(
|
|||||||
// Handle duplicate email (unique constraint violation)
|
// Handle duplicate email (unique constraint violation)
|
||||||
// Use generic error to prevent email enumeration
|
// Use generic error to prevent email enumeration
|
||||||
if (error instanceof Error && error.message.includes("users_email_key")) {
|
if (error instanceof Error && error.message.includes("users_email_key")) {
|
||||||
throw new ORPCError("BAD_REQUEST", { message: "Unable to create account" });
|
throw new ORPCError("BAD_REQUEST", {
|
||||||
|
message: "Unable to create account",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,11 +115,11 @@ export async function countOwners(
|
|||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const result = await db
|
const result = await db
|
||||||
.selectFrom("org_members")
|
.selectFrom("org_members")
|
||||||
.select((eb) => eb.fn.countAll<number>().as("count"))
|
.select((eb) => eb.fn.countAll().as("count"))
|
||||||
.where("org_id", "=", orgId)
|
.where("org_id", "=", orgId)
|
||||||
.where("role", "=", "owner")
|
.where("role", "=", "owner")
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
// PostgreSQL COUNT returns bigint which may be a string; ensure numeric comparison works
|
// PostgreSQL COUNT returns bigint (string), convert to number
|
||||||
return Number(result.count);
|
return Number(result.count);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
\restrict 7omiXDURqmmr2m2jWDDMoltRzeUAT80fRWiPifpD7IpQGCLgxQNBFsA5uBgakPg
|
\restrict NNYnwssF6iMx0TXsk1nTprUEDwxna9uejAmsIiUlMLcPLlQlnnRVCusYtzweHXM
|
||||||
|
|
||||||
-- Dumped from database version 17.7
|
-- Dumped from database version 17.7
|
||||||
-- Dumped by pg_dump version 17.7
|
-- Dumped by pg_dump version 17.7
|
||||||
@@ -1084,7 +1084,7 @@ ALTER TABLE ONLY public.user_devices
|
|||||||
-- PostgreSQL database dump complete
|
-- PostgreSQL database dump complete
|
||||||
--
|
--
|
||||||
|
|
||||||
\unrestrict 7omiXDURqmmr2m2jWDDMoltRzeUAT80fRWiPifpD7IpQGCLgxQNBFsA5uBgakPg
|
\unrestrict NNYnwssF6iMx0TXsk1nTprUEDwxna9uejAmsIiUlMLcPLlQlnnRVCusYtzweHXM
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
ruleDirs:
|
ruleDirs:
|
||||||
- /Users/igm/proj/reviq/publisher-dashboard/.ast-grep/rules/
|
- .ast-grep/rules/
|
||||||
testConfigs:
|
testConfigs:
|
||||||
- testDir: /Users/igm/proj/reviq/publisher-dashboard/.ast-grep/rule-tests/
|
- testDir: .ast-grep/rule-tests/
|
||||||
utilDirs:
|
utilDirs:
|
||||||
- /Users/igm/proj/reviq/publisher-dashboard/.ast-grep/utils/
|
- .ast-grep/utils/
|
||||||
|
|||||||
Reference in New Issue
Block a user