Replace String() calls with .toString()/.toLocaleString() per ast-grep rule

- Add formatError() helper in CLI to safely handle unknown error types
- Add uniqueTestId() helper for generating unique test identifiers
- Replace String(id) with id.toString() for database ID conversions
- Replace String(n) with n.toLocaleString() for user-facing number formatting
- Fix TypeScript errors in test files (undefined checks, unused variables)
- Update lint commands to include ast-grep scanning

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
igm
2026-01-12 15:02:46 +08:00
parent 8b63eb3538
commit 2baf10b0cd
30 changed files with 178 additions and 166 deletions

View File

@@ -36,6 +36,7 @@ import {
initTestDb, initTestDb,
TEST_RP, TEST_RP,
truncateAllTables, truncateAllTables,
uniqueTestId,
withTestTransaction, withTestTransaction,
} from "@reviq/test-helpers"; } from "@reviq/test-helpers";
import { router } from "../../router.js"; import { router } from "../../router.js";
@@ -84,7 +85,7 @@ async function createSession(
db: Kysely<Database>, db: Kysely<Database>,
userId: number, userId: number,
): Promise<{ token: string; sessionId: number }> { ): Promise<{ token: string; sessionId: number }> {
const token = `test-session-${String(Date.now())}${String(Math.random())}`; const token = `test-session-${uniqueTestId()}`;
const tokenHashValue = await hashToken(token); const tokenHashValue = await hashToken(token);
const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS); const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS);
@@ -115,9 +116,7 @@ async function createOrg(
logoUrl?: string; logoUrl?: string;
}, },
): Promise<{ id: number; slug: string }> { ): Promise<{ id: number; slug: string }> {
const slug = const slug = options?.slug ?? `org-${uniqueTestId()}`;
options?.slug ??
`org-${String(Date.now())}-${String(Math.random()).slice(2, 8)}`;
const result = await db const result = await db
.insertInto("orgs") .insertInto("orgs")
@@ -183,7 +182,7 @@ async function createLoginRequest(
expiresAt?: Date; expiresAt?: Date;
}, },
): Promise<{ id: number; token: string }> { ): Promise<{ id: number; token: string }> {
const token = `login-${String(Date.now())}${String(Math.random())}`; const token = `login-${uniqueTestId()}`;
const expiresAt = const expiresAt =
options?.expiresAt ?? new Date(Date.now() + LOGIN_REQUEST_EXPIRY_MS); options?.expiresAt ?? new Date(Date.now() + LOGIN_REQUEST_EXPIRY_MS);
@@ -212,7 +211,7 @@ async function createOrgInvite(
email: string, email: string,
invitedBy: number, invitedBy: number,
): Promise<{ id: number }> { ): Promise<{ id: number }> {
const token = `invite-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const token = `invite-${uniqueTestId()}`;
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
const result = await db const result = await db
@@ -461,7 +460,7 @@ describeE2E("admin", () => {
test("creates passwordless user", async () => { test("creates passwordless user", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -492,7 +491,7 @@ describeE2E("admin", () => {
test("creates user with name", async () => { test("creates user with name", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -519,7 +518,7 @@ describeE2E("admin", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -554,7 +553,7 @@ describeE2E("admin", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -588,7 +587,7 @@ describeE2E("admin", () => {
test("normalizes email to lowercase", async () => { test("normalizes email to lowercase", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -615,7 +614,7 @@ describeE2E("admin", () => {
test("throws CONFLICT for duplicate email", async () => { test("throws CONFLICT for duplicate email", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -637,7 +636,7 @@ describeE2E("admin", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1060,7 +1059,7 @@ describeE2E("admin", () => {
test("creates organization with owner", async () => { test("creates organization with owner", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1109,7 +1108,7 @@ describeE2E("admin", () => {
test("normalizes owner email to lowercase", async () => { test("normalizes owner email to lowercase", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1135,7 +1134,7 @@ describeE2E("admin", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1160,7 +1159,7 @@ describeE2E("admin", () => {
test("throws CONFLICT for duplicate slug", async () => { test("throws CONFLICT for duplicate slug", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1284,7 +1283,7 @@ describeE2E("admin", () => {
await createOrg(db, { await createOrg(db, {
slug: "test-org", slug: "test-org",
displayName: "Old", displayName: "Old",
logoUrl: null, logoUrl: undefined,
}); });
const { token: sessionToken } = await createSession(db, admin.id); const { token: sessionToken } = await createSession(db, admin.id);
@@ -1379,7 +1378,7 @@ describeE2E("admin", () => {
test("deletes organization and related records", async () => { test("deletes organization and related records", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1444,7 +1443,7 @@ describeE2E("admin", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1564,7 +1563,7 @@ describeE2E("admin", () => {
test("adds site to organization", async () => { test("adds site to organization", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1596,7 +1595,7 @@ describeE2E("admin", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1617,7 +1616,7 @@ describeE2E("admin", () => {
test("throws CONFLICT for duplicate domain", async () => { test("throws CONFLICT for duplicate domain", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1640,7 +1639,7 @@ describeE2E("admin", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1793,7 +1792,7 @@ describeE2E("admin", () => {
// Verify login request was completed // Verify login request was completed
const request = await db const request = await db
.selectFrom("login_requests") .selectFrom("login_requests")
.where("id", "=", String(loginRequest.id)) .where("id", "=", loginRequest.id.toString())
.select(["completed_at"]) .select(["completed_at"])
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
@@ -1825,7 +1824,7 @@ describeE2E("admin", () => {
const request = await db const request = await db
.selectFrom("login_requests") .selectFrom("login_requests")
.where("id", "=", String(loginRequest.id)) .where("id", "=", loginRequest.id.toString())
.select(["completed_at"]) .select(["completed_at"])
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();

View File

@@ -47,6 +47,7 @@ import {
getSharedDb, getSharedDb,
initTestDb, initTestDb,
TEST_RP, TEST_RP,
uniqueTestId,
withTestTransaction, withTestTransaction,
} from "@reviq/test-helpers"; } from "@reviq/test-helpers";
import { VirtualAuthenticator } from "@reviq/virtual-authenticator"; import { VirtualAuthenticator } from "@reviq/virtual-authenticator";
@@ -146,7 +147,7 @@ async function createSession(
userId: number, userId: number,
options?: { deviceId?: bigint }, options?: { deviceId?: bigint },
): Promise<{ token: string; sessionId: number }> { ): Promise<{ token: string; sessionId: number }> {
const token = `test-session-${String(Date.now())}${String(Math.random())}`; const token = `test-session-${uniqueTestId()}`;
const tokenHashValue = await hashToken(token); const tokenHashValue = await hashToken(token);
const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS); const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS);
@@ -154,7 +155,7 @@ async function createSession(
.insertInto("sessions") .insertInto("sessions")
.values({ .values({
user_id: userId, user_id: userId,
device_id: options?.deviceId ? String(options.deviceId) : null, device_id: options?.deviceId ? options.deviceId.toString() : null,
token_hash: tokenHashValue, token_hash: tokenHashValue,
trusted_mode: false, trusted_mode: false,
expires_at: expiresAt, expires_at: expiresAt,
@@ -178,7 +179,7 @@ async function createLoginRequest(
expiresAt?: Date; expiresAt?: Date;
}, },
): Promise<{ token: string; id: number }> { ): Promise<{ token: string; id: number }> {
const token = `login_test-${String(Date.now())}${String(Math.random())}`; const token = `login_test-${uniqueTestId()}`;
const expiresAt = const expiresAt =
options?.expiresAt ?? new Date(Date.now() + LOGIN_REQUEST_EXPIRY_MS); options?.expiresAt ?? new Date(Date.now() + LOGIN_REQUEST_EXPIRY_MS);
@@ -228,7 +229,7 @@ async function createEmailVerification(
userId: number, userId: number,
options?: { expiresAt?: Date }, options?: { expiresAt?: Date },
): Promise<string> { ): Promise<string> {
const token = `verify-${String(Date.now())}${String(Math.random())}`; const token = `verify-${uniqueTestId()}`;
const expiresAt = const expiresAt =
options?.expiresAt ?? new Date(Date.now() + 24 * 60 * 60 * 1000); options?.expiresAt ?? new Date(Date.now() + 24 * 60 * 60 * 1000);
@@ -252,7 +253,7 @@ async function createPasswordReset(
userId: number, userId: number,
options?: { expiresAt?: Date; usedAt?: Date | null }, options?: { expiresAt?: Date; usedAt?: Date | null },
): Promise<string> { ): Promise<string> {
const token = `reset-${String(Date.now())}${String(Math.random())}`; const token = `reset-${uniqueTestId()}`;
const expiresAt = options?.expiresAt ?? new Date(Date.now() + 60 * 60 * 1000); const expiresAt = options?.expiresAt ?? new Date(Date.now() + 60 * 60 * 1000);
await db await db
@@ -457,7 +458,7 @@ describeE2E("auth", () => {
const challenges = await db const challenges = await db
.selectFrom("webauthn_challenges") .selectFrom("webauthn_challenges")
.selectAll() .selectAll()
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.execute(); .execute();
expect(challenges.length).toBe(0); expect(challenges.length).toBe(0);
}); });
@@ -483,7 +484,7 @@ describeE2E("auth", () => {
await db await db
.updateTable("webauthn_challenges") .updateTable("webauthn_challenges")
.set({ created_at: new Date(Date.now() - 20 * 60 * 1000) }) // 20 minutes ago .set({ created_at: new Date(Date.now() - 20 * 60 * 1000) }) // 20 minutes ago
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.execute(); .execute();
// Step 4: Try to signup with expired challenge // Step 4: Try to signup with expired challenge
@@ -540,7 +541,7 @@ describeE2E("auth", () => {
const challenges = await db const challenges = await db
.selectFrom("webauthn_challenges") .selectFrom("webauthn_challenges")
.selectAll() .selectAll()
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.execute(); .execute();
expect(challenges.length).toBe(0); expect(challenges.length).toBe(0);
}); });
@@ -1072,7 +1073,7 @@ describeE2E("auth", () => {
const loginRequest = await db const loginRequest = await db
.selectFrom("login_requests") .selectFrom("login_requests")
.selectAll() .selectAll()
.where("id", "=", String(loginRequestId)) .where("id", "=", loginRequestId.toString())
.executeTakeFirst(); .executeTakeFirst();
expect(loginRequest).toBeUndefined(); expect(loginRequest).toBeUndefined();
@@ -1152,7 +1153,7 @@ describeE2E("auth", () => {
}); });
// Create login request without device fingerprint // Create login request without device fingerprint
const token = `login_test-${String(Date.now())}`; const token = `login_test-${uniqueTestId()}`;
await db await db
.insertInto("login_requests") .insertInto("login_requests")
.values({ .values({
@@ -1644,7 +1645,7 @@ describeE2E("auth", () => {
const session = await db const session = await db
.selectFrom("sessions") .selectFrom("sessions")
.select(["revoked_at"]) .select(["revoked_at"])
.where("id", "=", String(sessionId)) .where("id", "=", sessionId.toString())
.executeTakeFirst(); .executeTakeFirst();
expect(session?.revoked_at).not.toBeNull(); expect(session?.revoked_at).not.toBeNull();
@@ -1981,7 +1982,7 @@ describeE2E("auth", () => {
// Clean up registration session // Clean up registration session
await db await db
.deleteFrom("sessions") .deleteFrom("sessions")
.where("id", "=", String(regSessionId)) .where("id", "=", regSessionId.toString())
.execute(); .execute();
// Step 1: Create login request // Step 1: Create login request

View File

@@ -29,6 +29,7 @@ import {
getSharedDb, getSharedDb,
initTestDb, initTestDb,
TEST_RP, TEST_RP,
uniqueTestId,
withTestTransaction, withTestTransaction,
} from "@reviq/test-helpers"; } from "@reviq/test-helpers";
import { router } from "../../router.js"; import { router } from "../../router.js";
@@ -92,7 +93,7 @@ async function createSession(
userId: number, userId: number,
options?: { ipAddress?: string; userAgent?: string }, options?: { ipAddress?: string; userAgent?: string },
): Promise<{ token: string; sessionId: number }> { ): Promise<{ token: string; sessionId: number }> {
const token = `test-session-${String(Date.now())}${String(Math.random())}`; const token = `test-session-${uniqueTestId()}`;
const tokenHashValue = await hashToken(token); const tokenHashValue = await hashToken(token);
const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS); const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS);
@@ -125,9 +126,7 @@ async function createDevice(
userAgent?: string; userAgent?: string;
}, },
): Promise<{ fingerprint: string; deviceId: number }> { ): Promise<{ fingerprint: string; deviceId: number }> {
const fingerprint = const fingerprint = options?.fingerprint ?? `test-fp-${uniqueTestId()}`;
options?.fingerprint ??
`test-fp-${String(Date.now())}${String(Math.random())}`;
const result = await db const result = await db
.insertInto("user_devices") .insertInto("user_devices")
@@ -153,7 +152,7 @@ async function createApiToken(
db: Kysely<Database>, db: Kysely<Database>,
userId: number, userId: number,
): Promise<{ token: string; name: string }> { ): Promise<{ token: string; name: string }> {
const token = `test-api-token-${String(Date.now())}${String(Math.random())}`; const token = `test-api-token-${uniqueTestId()}`;
const tokenHashValue = await hashToken(token); const tokenHashValue = await hashToken(token);
const expiresAt = new Date(Date.now() + API_TOKEN_EXPIRY_MS); const expiresAt = new Date(Date.now() + API_TOKEN_EXPIRY_MS);
@@ -224,7 +223,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-${String(Date.now())}`; const token = `expired-session-${uniqueTestId()}`;
const tokenHashValue = await hashToken(token); const tokenHashValue = await hashToken(token);
await db await db
.insertInto("sessions") .insertInto("sessions")
@@ -249,7 +248,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-${String(Date.now())}`; const token = `revoked-session-${uniqueTestId()}`;
const tokenHashValue = await hashToken(token); const tokenHashValue = await hashToken(token);
await db await db
.insertInto("sessions") .insertInto("sessions")
@@ -925,7 +924,7 @@ describeE2E("me", () => {
country: "US", country: "US",
trusted_mode: true, trusted_mode: true,
}) })
.where("id", "=", String(sessionId)) .where("id", "=", sessionId.toString())
.execute(); .execute();
const context = createAPIContext(db, { sessionToken }); const context = createAPIContext(db, { sessionToken });
@@ -968,7 +967,7 @@ describeE2E("me", () => {
const session = await db const session = await db
.selectFrom("sessions") .selectFrom("sessions")
.select(["revoked_at"]) .select(["revoked_at"])
.where("id", "=", String(sessionId2)) .where("id", "=", sessionId2.toString())
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
expect(session.revoked_at).not.toBeNull(); expect(session.revoked_at).not.toBeNull();
@@ -1021,7 +1020,7 @@ describeE2E("me", () => {
await db await db
.updateTable("sessions") .updateTable("sessions")
.set({ revoked_at: new Date() }) .set({ revoked_at: new Date() })
.where("id", "=", String(sessionId2)) .where("id", "=", sessionId2.toString())
.execute(); .execute();
const context = createAPIContext(db, { sessionToken: sessionToken1 }); const context = createAPIContext(db, { sessionToken: sessionToken1 });
@@ -1080,7 +1079,7 @@ describeE2E("me", () => {
const currentSession = await db const currentSession = await db
.selectFrom("sessions") .selectFrom("sessions")
.select(["revoked_at"]) .select(["revoked_at"])
.where("id", "=", String(id1)) .where("id", "=", id1.toString())
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
expect(currentSession.revoked_at).toBeNull(); expect(currentSession.revoked_at).toBeNull();
@@ -1088,7 +1087,7 @@ describeE2E("me", () => {
const otherSessions = await db const otherSessions = await db
.selectFrom("sessions") .selectFrom("sessions")
.select(["id", "revoked_at"]) .select(["id", "revoked_at"])
.where("id", "in", [String(id2), String(id3)]) .where("id", "in", [id2.toString(), id3.toString()])
.execute(); .execute();
for (const session of otherSessions) { for (const session of otherSessions) {
@@ -1116,7 +1115,7 @@ describeE2E("me", () => {
const session = await db const session = await db
.selectFrom("sessions") .selectFrom("sessions")
.select(["revoked_at"]) .select(["revoked_at"])
.where("id", "=", String(sessionId)) .where("id", "=", sessionId.toString())
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
expect(session.revoked_at).toBeNull(); expect(session.revoked_at).toBeNull();
}); });
@@ -1147,7 +1146,7 @@ describeE2E("me", () => {
region: "NY", region: "NY",
country: "US", country: "US",
}) })
.where("id", "=", String(deviceId)) .where("id", "=", deviceId.toString())
.execute(); .execute();
const { token: sessionToken } = await createSession(db, user.id); const { token: sessionToken } = await createSession(db, user.id);
@@ -1256,7 +1255,7 @@ describeE2E("me", () => {
const device = await db const device = await db
.selectFrom("user_devices") .selectFrom("user_devices")
.select(["is_trusted", "name"]) .select(["is_trusted", "name"])
.where("id", "=", String(deviceId)) .where("id", "=", deviceId.toString())
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
expect(device.is_trusted).toBe(true); expect(device.is_trusted).toBe(true);
@@ -1401,7 +1400,7 @@ describeE2E("me", () => {
const device = await db const device = await db
.selectFrom("user_devices") .selectFrom("user_devices")
.select(["is_trusted"]) .select(["is_trusted"])
.where("id", "=", String(deviceId)) .where("id", "=", deviceId.toString())
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
expect(device.is_trusted).toBe(false); expect(device.is_trusted).toBe(false);
@@ -1501,7 +1500,7 @@ async function createTrustedSession(
db: Kysely<Database>, db: Kysely<Database>,
userId: number, userId: number,
): Promise<{ token: string; sessionId: number }> { ): Promise<{ token: string; sessionId: number }> {
const token = `test-session-${String(Date.now())}${String(Math.random())}`; const token = `test-session-${uniqueTestId()}`;
const tokenHashValue = await hashToken(token); const tokenHashValue = await hashToken(token);
const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS); const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS);
@@ -1568,7 +1567,7 @@ async function createOrgInvite(
expiresAt?: Date; expiresAt?: Date;
}, },
): Promise<{ id: number }> { ): Promise<{ id: number }> {
const token = `invite-token-${String(Date.now())}-${Math.random().toString(36).slice(2)}`; const token = `invite-token-${uniqueTestId()}-${Math.random().toString(36).slice(2)}`;
const result = await db const result = await db
.insertInto("org_invites") .insertInto("org_invites")
.values({ .values({
@@ -1693,7 +1692,7 @@ describeE2E("me.apiTokens and me.invites", () => {
}); });
expect(tokens).toHaveLength(1); expect(tokens).toHaveLength(1);
expect(tokens[0].name).toBe("User1 Token"); expect(tokens[0]?.name).toBe("User1 Token");
}); });
}); });
}); });
@@ -1727,7 +1726,7 @@ describeE2E("me.apiTokens and me.invites", () => {
.execute(); .execute();
expect(tokens).toHaveLength(1); expect(tokens).toHaveLength(1);
expect(tokens[0].name).toBe("My New Token"); expect(tokens[0]?.name).toBe("My New Token");
}); });
}); });
@@ -1937,10 +1936,10 @@ describeE2E("me.apiTokens and me.invites", () => {
}); });
expect(invites).toHaveLength(1); expect(invites).toHaveLength(1);
expect(invites[0].org.slug).toBe("invite-org"); expect(invites[0]?.org.slug).toBe("invite-org");
expect(invites[0].org.displayName).toBe("Invite Org"); expect(invites[0]?.org.displayName).toBe("Invite Org");
expect(invites[0].role).toBe("admin"); expect(invites[0]?.role).toBe("admin");
expect(invites[0].invitedBy).toBe("Inviter Person"); expect(invites[0]?.invitedBy).toBe("Inviter Person");
}); });
}); });
@@ -2086,7 +2085,7 @@ describeE2E("me.apiTokens and me.invites", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const inviter = await createTestUser(db, { const inviter = await createTestUser(db, {
email: `inviter-accept-${uniqueId}@example.com`, email: `inviter-accept-${uniqueId}@example.com`,
@@ -2188,7 +2187,7 @@ describeE2E("me.apiTokens and me.invites", () => {
test("returns error if already a member", async () => { test("returns error if already a member", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const inviter = await createTestUser(db, { const inviter = await createTestUser(db, {
email: `inviter-already-${uniqueId}@example.com`, email: `inviter-already-${uniqueId}@example.com`,

View File

@@ -20,6 +20,7 @@ import {
getSharedDb, getSharedDb,
initTestDb, initTestDb,
TEST_RP, TEST_RP,
uniqueTestId,
withTestTransaction, withTestTransaction,
} from "@reviq/test-helpers"; } from "@reviq/test-helpers";
import { router } from "../../router.js"; import { router } from "../../router.js";
@@ -68,7 +69,7 @@ async function createSession(
userId: number, userId: number,
options?: { trustedMode?: boolean }, options?: { trustedMode?: boolean },
): Promise<{ token: string; sessionId: number }> { ): Promise<{ token: string; sessionId: number }> {
const token = `test-session-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const token = `test-session-${uniqueTestId()}`;
const tokenHashValue = await hashToken(token); const tokenHashValue = await hashToken(token);
const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS); const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS);
@@ -166,9 +167,7 @@ async function createOrgInvite(
expiresAt?: Date; expiresAt?: Date;
}, },
): Promise<{ id: number; token: string }> { ): Promise<{ id: number; token: string }> {
const token = const token = options?.token ?? `invite-${uniqueTestId()}`;
options?.token ??
`invite-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
const expiresAt = const expiresAt =
options?.expiresAt ?? new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); options?.expiresAt ?? new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
@@ -319,7 +318,7 @@ describeE2E("orgs", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const user = await createTestUser(db, { const user = await createTestUser(db, {
email: `user-${uniqueId}@example.com`, email: `user-${uniqueId}@example.com`,
@@ -349,7 +348,7 @@ describeE2E("orgs", () => {
test("rejects duplicate slug", async () => { test("rejects duplicate slug", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const user = await createTestUser(db, { const user = await createTestUser(db, {
email: `user-${uniqueId}@example.com`, email: `user-${uniqueId}@example.com`,
@@ -532,7 +531,7 @@ describeE2E("orgs", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const user = await createTestUser(db, { const user = await createTestUser(db, {
email: `user-${uniqueId}@example.com`, email: `user-${uniqueId}@example.com`,
@@ -581,7 +580,7 @@ describeE2E("orgs", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -614,7 +613,7 @@ describeE2E("orgs", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner1 = await createTestUser(db, { const owner1 = await createTestUser(db, {
email: `owner1-${uniqueId}@example.com`, email: `owner1-${uniqueId}@example.com`,
@@ -647,7 +646,7 @@ describeE2E("orgs", () => {
test("prevents only owner from leaving", async () => { test("prevents only owner from leaving", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -770,7 +769,7 @@ describeE2E("orgs", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -803,7 +802,7 @@ describeE2E("orgs", () => {
test("owner can promote member to owner", async () => { test("owner can promote member to owner", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -836,7 +835,7 @@ describeE2E("orgs", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner1 = await createTestUser(db, { const owner1 = await createTestUser(db, {
email: `owner1-${uniqueId}@example.com`, email: `owner1-${uniqueId}@example.com`,
@@ -869,7 +868,7 @@ describeE2E("orgs", () => {
test("prevents demoting the only owner", async () => { test("prevents demoting the only owner", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -916,7 +915,7 @@ describeE2E("orgs", () => {
test("rejects when target member not found", async () => { test("rejects when target member not found", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -942,7 +941,7 @@ describeE2E("orgs", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -975,7 +974,7 @@ describeE2E("orgs", () => {
test("owner can remove admin", async () => { test("owner can remove admin", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -1008,7 +1007,7 @@ describeE2E("orgs", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner1 = await createTestUser(db, { const owner1 = await createTestUser(db, {
email: `owner1-${uniqueId}@example.com`, email: `owner1-${uniqueId}@example.com`,
@@ -1041,7 +1040,7 @@ describeE2E("orgs", () => {
test("prevents removing the only owner", async () => { test("prevents removing the only owner", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -1065,7 +1064,7 @@ describeE2E("orgs", () => {
test("admin can remove member", async () => { test("admin can remove member", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -1102,7 +1101,7 @@ describeE2E("orgs", () => {
test("admin cannot remove owner", async () => { test("admin cannot remove owner", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -1130,7 +1129,7 @@ describeE2E("orgs", () => {
test("admin cannot remove other admin", async () => { test("admin cannot remove other admin", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -1162,7 +1161,7 @@ describeE2E("orgs", () => {
test("member cannot remove anyone", async () => { test("member cannot remove anyone", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -1192,7 +1191,7 @@ describeE2E("orgs", () => {
test("rejects when target member not found", async () => { test("rejects when target member not found", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -1297,7 +1296,7 @@ describeE2E("orgs", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1331,7 +1330,7 @@ describeE2E("orgs", () => {
test("admin can create admin invite", async () => { test("admin can create admin invite", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const admin = await createTestUser(db, { const admin = await createTestUser(db, {
email: `admin-${uniqueId}@example.com`, email: `admin-${uniqueId}@example.com`,
@@ -1385,7 +1384,7 @@ describeE2E("orgs", () => {
test("owner can create owner invite", async () => { test("owner can create owner invite", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -1571,7 +1570,7 @@ describeE2E("orgs", () => {
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 = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,
@@ -1669,9 +1668,7 @@ describeE2E("orgs", () => {
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, { await createTestUser(db, { email: "invitee@example.com" });
email: "invitee@example.com",
});
const wrongUser = await createTestUser(db, { const wrongUser = await createTestUser(db, {
email: "wrong@example.com", email: "wrong@example.com",
}); });
@@ -1701,7 +1698,7 @@ describeE2E("orgs", () => {
test("handles already a member gracefully", async () => { test("handles already a member gracefully", async () => {
const db = getSharedDb(); const db = getSharedDb();
const uniqueId = `${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`; const uniqueId = uniqueTestId();
const owner = await createTestUser(db, { const owner = await createTestUser(db, {
email: `owner-${uniqueId}@example.com`, email: `owner-${uniqueId}@example.com`,

View File

@@ -20,6 +20,7 @@ import {
initTestDb, initTestDb,
KNOWN_AAGUIDS, KNOWN_AAGUIDS,
TEST_RP, TEST_RP,
uniqueTestId,
withTestTransaction, withTestTransaction,
} from "@reviq/test-helpers"; } from "@reviq/test-helpers";
import { VirtualAuthenticator } from "@reviq/virtual-authenticator"; import { VirtualAuthenticator } from "@reviq/virtual-authenticator";
@@ -60,7 +61,7 @@ async function createSession(
db: Kysely<Database>, db: Kysely<Database>,
userId: number, userId: number,
): Promise<string> { ): Promise<string> {
const token = `test-session-${String(Date.now())}${String(Math.random())}`; const token = `test-session-${uniqueTestId()}`;
const tokenHashValue = await hashToken(token); const tokenHashValue = await hashToken(token);
const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS); const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS);
@@ -87,7 +88,7 @@ async function createLoginRequest(
userId: number, userId: number,
email: string, email: string,
): Promise<{ id: number; token: string }> { ): Promise<{ id: number; token: string }> {
const token = `test-login-${String(Date.now())}${String(Math.random())}`; const token = `test-login-${uniqueTestId()}`;
const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
const result = await db const result = await db
@@ -236,7 +237,7 @@ describeE2E("webauthn", () => {
const challengeRow = await db const challengeRow = await db
.selectFrom("webauthn_challenges") .selectFrom("webauthn_challenges")
.select("id") .select("id")
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.executeTakeFirst(); .executeTakeFirst();
expect(challengeRow).toBeDefined(); expect(challengeRow).toBeDefined();
@@ -382,7 +383,7 @@ describeE2E("webauthn", () => {
const challengeRow = await db const challengeRow = await db
.selectFrom("webauthn_challenges") .selectFrom("webauthn_challenges")
.select("id") .select("id")
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.executeTakeFirst(); .executeTakeFirst();
expect(challengeRow).toBeUndefined(); expect(challengeRow).toBeUndefined();
@@ -585,7 +586,7 @@ describeE2E("webauthn", () => {
const challengeRow = await db const challengeRow = await db
.selectFrom("webauthn_challenges") .selectFrom("webauthn_challenges")
.select("id") .select("id")
.where("id", "=", String(authChallengeId)) .where("id", "=", authChallengeId.toString())
.executeTakeFirst(); .executeTakeFirst();
expect(challengeRow).toBeUndefined(); expect(challengeRow).toBeUndefined();

View File

@@ -22,7 +22,7 @@ export const getAllowedOrigins = (): string[] => {
// Default to localhost origins for development // Default to localhost origins for development
return [ return [
`http://localhost:${String(DEFAULT_PORT)}`, `http://localhost:${DEFAULT_PORT.toString()}`,
"http://localhost:6827", "http://localhost:6827",
"http://localhost:6828", "http://localhost:6828",
]; ];

View File

@@ -45,7 +45,7 @@ Bun.serve({
if (url.pathname.startsWith("/api/v1/rpc")) { if (url.pathname.startsWith("/api/v1/rpc")) {
// Build context for the request // Build context for the request
const origin = const origin =
request.headers.get("origin") ?? `http://localhost:${String(port)}`; request.headers.get("origin") ?? `http://localhost:${port.toString()}`;
// Create response headers for setting cookies // Create response headers for setting cookies
const resHeaders = new Headers(); const resHeaders = new Headers();

View File

@@ -108,7 +108,7 @@ export async function signupWithPasskey(
const challengeRow = await db const challengeRow = await db
.selectFrom("webauthn_challenges") .selectFrom("webauthn_challenges")
.select("options") .select("options")
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.where("created_at", ">", fifteenMinutesAgo) .where("created_at", ">", fifteenMinutesAgo)
.executeTakeFirst(); .executeTakeFirst();
@@ -134,7 +134,7 @@ export async function signupWithPasskey(
// Delete the challenge // Delete the challenge
await db await db
.deleteFrom("webauthn_challenges") .deleteFrom("webauthn_challenges")
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.execute(); .execute();
// Log error for debugging but don't expose to client // Log error for debugging but don't expose to client
@@ -149,7 +149,7 @@ export async function signupWithPasskey(
// Delete the challenge // Delete the challenge
await db await db
.deleteFrom("webauthn_challenges") .deleteFrom("webauthn_challenges")
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.execute(); .execute();
throw new ORPCError("BAD_REQUEST", { throw new ORPCError("BAD_REQUEST", {
@@ -200,7 +200,7 @@ export async function signupWithPasskey(
// Delete the challenge // Delete the challenge
await trx await trx
.deleteFrom("webauthn_challenges") .deleteFrom("webauthn_challenges")
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.execute(); .execute();
return { userId: newUserId }; return { userId: newUserId };

View File

@@ -95,7 +95,7 @@ export const deleteApiToken = os.me.apiTokens.delete
.handler(async ({ input, context }) => { .handler(async ({ input, context }) => {
const result = await context.db const result = await context.db
.deleteFrom("api_tokens") .deleteFrom("api_tokens")
.where("id", "=", String(input.tokenId)) .where("id", "=", input.tokenId.toString())
.where("user_id", "=", context.user.id) .where("user_id", "=", context.user.id)
.executeTakeFirst(); .executeTakeFirst();

View File

@@ -108,7 +108,7 @@ export const untrustDevice = os.me.devices.untrust
const result = await context.db const result = await context.db
.updateTable("user_devices") .updateTable("user_devices")
.set({ is_trusted: false }) .set({ is_trusted: false })
.where("id", "=", String(input.deviceId)) .where("id", "=", input.deviceId.toString())
.where("user_id", "=", context.user.id) .where("user_id", "=", context.user.id)
.executeTakeFirst(); .executeTakeFirst();

View File

@@ -38,7 +38,7 @@ export const renamePasskey = os.me.passkeys.rename
const result = await context.db const result = await context.db
.updateTable("passkeys") .updateTable("passkeys")
.set({ name }) .set({ name })
.where("id", "=", String(passkeyId)) .where("id", "=", passkeyId.toString())
.where("user_id", "=", context.user.id) .where("user_id", "=", context.user.id)
.executeTakeFirst(); .executeTakeFirst();
@@ -86,7 +86,7 @@ export const deletePasskey = os.me.passkeys.delete
const result = await trx const result = await trx
.deleteFrom("passkeys") .deleteFrom("passkeys")
.where("id", "=", String(passkeyId)) .where("id", "=", passkeyId.toString())
.where("user_id", "=", context.user.id) .where("user_id", "=", context.user.id)
.executeTakeFirst(); .executeTakeFirst();

View File

@@ -48,7 +48,7 @@ export const revokeSession = os.me.sessions.revoke
const { sessionId } = input; const { sessionId } = input;
// Prevent revoking current session (use logout instead) // Prevent revoking current session (use logout instead)
if (String(sessionId) === context.session.id) { if (sessionId.toString() === context.session.id) {
throw new ORPCError("BAD_REQUEST", { throw new ORPCError("BAD_REQUEST", {
message: "Cannot revoke current session. Use logout instead.", message: "Cannot revoke current session. Use logout instead.",
}); });
@@ -57,7 +57,7 @@ export const revokeSession = os.me.sessions.revoke
const result = await context.db const result = await context.db
.updateTable("sessions") .updateTable("sessions")
.set({ revoked_at: new Date() }) .set({ revoked_at: new Date() })
.where("id", "=", String(sessionId)) .where("id", "=", sessionId.toString())
.where("user_id", "=", context.user.id) .where("user_id", "=", context.user.id)
.where("revoked_at", "is", null) .where("revoked_at", "is", null)
.executeTakeFirst(); .executeTakeFirst();

View File

@@ -139,7 +139,7 @@ const verifyAuthentication = os.auth.webauthn.verifyAuthentication
await context.db await context.db
.updateTable("login_requests") .updateTable("login_requests")
.set({ completed_at: new Date() }) .set({ completed_at: new Date() })
.where("id", "=", String(context.loginRequestId)) .where("id", "=", context.loginRequestId.toString())
.execute(); .execute();
return { success: true }; return { success: true };

View File

@@ -162,7 +162,7 @@ export const verifyRegistration = async (
const challengeRow = await db const challengeRow = await db
.selectFrom("webauthn_challenges") .selectFrom("webauthn_challenges")
.select("options") .select("options")
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.executeTakeFirst(); .executeTakeFirst();
if (!challengeRow) { if (!challengeRow) {
@@ -189,7 +189,7 @@ export const verifyRegistration = async (
// Always delete the challenge // Always delete the challenge
await db await db
.deleteFrom("webauthn_challenges") .deleteFrom("webauthn_challenges")
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.execute(); .execute();
} }
@@ -278,7 +278,7 @@ export const verifyAuthentication = async (
const challengeRow = await db const challengeRow = await db
.selectFrom("webauthn_challenges") .selectFrom("webauthn_challenges")
.select("options") .select("options")
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.executeTakeFirst(); .executeTakeFirst();
if (!challengeRow) { if (!challengeRow) {
@@ -321,7 +321,7 @@ export const verifyAuthentication = async (
counter: verification.authenticationInfo.newCounter.toString(), counter: verification.authenticationInfo.newCounter.toString(),
last_used_at: new Date(), last_used_at: new Date(),
}) })
.where("id", "=", String(passkey.id)) .where("id", "=", passkey.id.toString())
.execute(); .execute();
return true; return true;
@@ -329,7 +329,7 @@ export const verifyAuthentication = async (
// Always delete the challenge // Always delete the challenge
await db await db
.deleteFrom("webauthn_challenges") .deleteFrom("webauthn_challenges")
.where("id", "=", String(challengeId)) .where("id", "=", challengeId.toString())
.execute(); .execute();
} }
}; };

View File

@@ -2,6 +2,7 @@ import type { LocalContext } from "../../context.js";
import { ORPCError } from "@orpc/client"; import { ORPCError } from "@orpc/client";
import { buildCommand } from "@stricli/core"; import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js"; import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
interface CompleteLoginFlags { interface CompleteLoginFlags {
email: string; email: string;
@@ -21,12 +22,10 @@ async function completeLogin(
console.log(`Completed login request for: ${flags.email}`); console.log(`Completed login request for: ${flags.email}`);
} catch (error) { } catch (error) {
if (error instanceof ORPCError) { if (error instanceof ORPCError) {
console.error(`Error [${String(error.code)}]:`, error.message); // eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- ORPCError.code is typed as any
console.error(`Error [${error.code}]:`, error.message);
} else { } else {
console.error( console.error("Error:", formatError(error));
"Error:",
error instanceof Error ? error.message : String(error),
);
} }
this.process.exit(1); this.process.exit(1);
} }

View File

@@ -2,6 +2,7 @@ import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core"; import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js"; import { createApiClient } from "../../utils/api-client.js";
import { readConfig, writeConfig } from "../../utils/config.js"; import { readConfig, writeConfig } from "../../utils/config.js";
import { formatError } from "../../utils/format-error.js";
interface LoginFlags { interface LoginFlags {
token: string; token: string;
@@ -47,10 +48,7 @@ async function login(this: LocalContext, flags: LoginFlags): Promise<void> {
console.log(`Logged in as ${authStatus.user.email}`); console.log(`Logged in as ${authStatus.user.email}`);
console.log("Credentials saved to ~/.config/reviq/credentials.json"); console.log("Credentials saved to ~/.config/reviq/credentials.json");
} catch (error) { } catch (error) {
console.error( console.error("Login failed:", formatError(error));
"Login failed:",
error instanceof Error ? error.message : String(error),
);
console.log("\nMake sure your API token is valid."); console.log("\nMake sure your API token is valid.");
console.log("You can create a new token at: /account/api-tokens"); console.log("You can create a new token at: /account/api-tokens");
this.process.exit(1); this.process.exit(1);

View File

@@ -2,6 +2,7 @@ import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core"; import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js"; import { createApiClient } from "../../utils/api-client.js";
import { getConfigPath, readConfig } from "../../utils/config.js"; import { getConfigPath, readConfig } from "../../utils/config.js";
import { formatError } from "../../utils/format-error.js";
import { TOKEN_PREFIX } from "../../utils/token.js"; import { TOKEN_PREFIX } from "../../utils/token.js";
function formatDate(date: Date): string { function formatDate(date: Date): string {
@@ -14,19 +15,19 @@ function formatRelativeTime(date: Date): string {
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays < 0) { if (diffDays < 0) {
return `${String(Math.abs(diffDays))} days ago`; return `${Math.abs(diffDays).toLocaleString()} days ago`;
} }
if (diffDays === 0) { if (diffDays === 0) {
const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
if (diffHours <= 0) { if (diffHours <= 0) {
return "expired"; return "expired";
} }
return `in ${String(diffHours)} hours`; return `in ${diffHours.toLocaleString()} hours`;
} }
if (diffDays === 1) { if (diffDays === 1) {
return "tomorrow"; return "tomorrow";
} }
return `in ${String(diffDays)} days`; return `in ${diffDays.toLocaleString()} days`;
} }
async function status(this: LocalContext): Promise<void> { async function status(this: LocalContext): Promise<void> {
@@ -96,9 +97,7 @@ async function status(this: LocalContext): Promise<void> {
); );
} }
} catch (error) { } catch (error) {
console.log( console.log(` Error: ${formatError(error)}`);
` Error: ${error instanceof Error ? error.message : String(error)}`,
);
console.log( console.log(
"\n Unable to connect to API. Local credentials may be invalid.", "\n Unable to connect to API. Local credentials may be invalid.",
); );

View File

@@ -2,6 +2,7 @@ import type { LocalContext } from "../context.js";
import { createDb, executeBootstrap } from "@reviq/db"; import { createDb, executeBootstrap } from "@reviq/db";
import { buildCommand } from "@stricli/core"; import { buildCommand } from "@stricli/core";
import { writeConfig } from "../utils/config.js"; import { writeConfig } from "../utils/config.js";
import { formatError } from "../utils/format-error.js";
interface BootstrapFlags { interface BootstrapFlags {
email: string; email: string;
@@ -47,10 +48,7 @@ async function bootstrap(
await db.destroy(); await db.destroy();
} catch (error) { } catch (error) {
console.error( console.error("Error:", formatError(error));
"Error:",
error instanceof Error ? error.message : String(error),
);
await db.destroy(); await db.destroy();
this.process.exit(1); this.process.exit(1);
} }

View File

@@ -1,6 +1,7 @@
import type { LocalContext } from "../../context.js"; import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core"; import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js"; import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
interface AddSiteFlags { interface AddSiteFlags {
org: string; org: string;
@@ -18,10 +19,7 @@ async function addSite(this: LocalContext, flags: AddSiteFlags): Promise<void> {
console.log(`Added site ${flags.domain} to org ${flags.org}`); console.log(`Added site ${flags.domain} to org ${flags.org}`);
} catch (error) { } catch (error) {
console.error( console.error("Error:", formatError(error));
"Error:",
error instanceof Error ? error.message : String(error),
);
this.process.exit(1); this.process.exit(1);
} }
} }

View File

@@ -1,6 +1,7 @@
import type { LocalContext } from "../../context.js"; import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core"; import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js"; import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
interface CreateOrgFlags { interface CreateOrgFlags {
slug: string; slug: string;
@@ -24,10 +25,7 @@ async function create(
console.log(`Created org: ${result.slug}`); console.log(`Created org: ${result.slug}`);
console.log(`Owner: ${flags.owner}`); console.log(`Owner: ${flags.owner}`);
} catch (error) { } catch (error) {
console.error( console.error("Error:", formatError(error));
"Error:",
error instanceof Error ? error.message : String(error),
);
this.process.exit(1); this.process.exit(1);
} }
} }

View File

@@ -1,6 +1,7 @@
import type { LocalContext } from "../../context.js"; import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core"; import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js"; import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
async function list(this: LocalContext): Promise<void> { async function list(this: LocalContext): Promise<void> {
try { try {
@@ -23,12 +24,9 @@ async function list(this: LocalContext): Promise<void> {
console.log(); console.log();
} }
console.log(`Total: ${String(orgs.length)} organization(s)`); console.log(`Total: ${orgs.length.toLocaleString()} organization(s)`);
} catch (error) { } catch (error) {
console.error( console.error("Error:", formatError(error));
"Error:",
error instanceof Error ? error.message : String(error),
);
this.process.exit(1); this.process.exit(1);
} }
} }

View File

@@ -1,6 +1,7 @@
import type { LocalContext } from "../../context.js"; import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core"; import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js"; import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
interface ConfirmEmailFlags { interface ConfirmEmailFlags {
email: string; email: string;
@@ -19,10 +20,7 @@ async function confirmEmail(
console.log(`Confirmed email for: ${flags.email}`); console.log(`Confirmed email for: ${flags.email}`);
} catch (error) { } catch (error) {
console.error( console.error("Error:", formatError(error));
"Error:",
error instanceof Error ? error.message : String(error),
);
this.process.exit(1); this.process.exit(1);
} }
} }

View File

@@ -1,6 +1,7 @@
import type { LocalContext } from "../../context.js"; import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core"; import { buildCommand } from "@stricli/core";
import { createApiClient } from "../../utils/api-client.js"; import { createApiClient } from "../../utils/api-client.js";
import { formatError } from "../../utils/format-error.js";
type OrgRole = "owner" | "admin" | "member"; type OrgRole = "owner" | "admin" | "member";
@@ -45,10 +46,7 @@ async function create(
console.log(`Added to org: ${flags.org} as ${flags.role ?? "member"}`); console.log(`Added to org: ${flags.org} as ${flags.role ?? "member"}`);
} }
} catch (error) { } catch (error) {
console.error( console.error("Error:", formatError(error));
"Error:",
error instanceof Error ? error.message : String(error),
);
this.process.exit(1); this.process.exit(1);
} }
} }

View File

@@ -0,0 +1,14 @@
/**
* Format an unknown error value into a string message.
* Handles Error instances, strings, and other types safely.
*/
export function formatError(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
if (typeof error === "string") {
return error;
}
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- intentional unknown coercion
return `${error}`;
}

View File

@@ -11,8 +11,8 @@
"build": "turbo build", "build": "turbo build",
"build:watch:packages": "turbo watch build --filter=./packages/*", "build:watch:packages": "turbo watch build --filter=./packages/*",
"build:packages": "turbo build --filter=./packages/*", "build:packages": "turbo build --filter=./packages/*",
"lint": "biome check && turbo run lint", "lint": "biome check && ast-grep scan && turbo run lint",
"lint:fix": "biome check --write --unsafe && turbo run lint -- --fix", "lint:fix": "biome check --write --unsafe && ast-grep scan --update-all && turbo run lint -- --fix",
"typecheck": "turbo typecheck", "typecheck": "turbo typecheck",
"clean": "turbo clean", "clean": "turbo clean",
"test": "turbo test", "test": "turbo test",

View File

@@ -95,11 +95,11 @@ export function formatRelativeDate(
return "Yesterday"; return "Yesterday";
} }
if (diffDays < 7) { if (diffDays < 7) {
return `${String(diffDays)} days ago`; return `${diffDays.toLocaleString()} days ago`;
} }
if (diffDays < 30) { if (diffDays < 30) {
const weeks = Math.floor(diffDays / 7); const weeks = Math.floor(diffDays / 7);
return weeks === 1 ? "1 week ago" : `${String(weeks)} weeks ago`; return weeks === 1 ? "1 week ago" : `${weeks.toLocaleString()} weeks ago`;
} }
// For older dates, show the actual date // For older dates, show the actual date

View File

@@ -16,3 +16,4 @@ export {
truncateAllTables, truncateAllTables,
} from "./test-db.js"; } from "./test-db.js";
export { withTestTransaction } from "./test-transaction.js"; export { withTestTransaction } from "./test-transaction.js";
export { uniqueTestId } from "./test-utils.js";

View File

@@ -9,6 +9,7 @@ import { join } from "node:path";
import { createDb } from "@reviq/db"; import { createDb } from "@reviq/db";
import { sql } from "kysely"; import { sql } from "kysely";
import pg from "pg"; import pg from "pg";
import { uniqueTestId } from "./test-utils.js";
const { Client } = pg; const { Client } = pg;
@@ -192,7 +193,7 @@ export async function runMigrations(): Promise<void> {
if (exitCode !== 0) { if (exitCode !== 0) {
const stderr = await new Response(proc.stderr).text(); const stderr = await new Response(proc.stderr).text();
throw new Error( throw new Error(
`Migration failed with code ${String(exitCode)}: ${stderr}`, `Migration failed with code ${exitCode.toString()}: ${stderr}`,
); );
} }
} }
@@ -224,7 +225,7 @@ export async function createTestUser(
isSuperuser: boolean; isSuperuser: boolean;
}> = {}, }> = {},
): Promise<{ id: number; email: string }> { ): Promise<{ id: number; email: string }> {
const email = overrides.email ?? `test-${String(Date.now())}@example.com`; const email = overrides.email ?? `test-${uniqueTestId()}@example.com`;
const result = await db const result = await db
.insertInto("users") .insertInto("users")

View File

@@ -0,0 +1,15 @@
/**
* Test utility functions
*/
/**
* Generates a unique test ID using timestamp and random string.
* Useful for creating unique emails, slugs, tokens, etc. in tests.
*
* @example
* const email = `user-${uniqueTestId()}@example.com`
* const slug = `org-${uniqueTestId()}`
*/
export function uniqueTestId(): string {
return `${Date.now().toString()}-${Math.random().toString(36).slice(2, 8)}`;
}

View File

@@ -63,7 +63,7 @@ export const hashPassword = async (password: string): Promise<string> => {
const saltB64 = toBase64(salt.buffer); const saltB64 = toBase64(salt.buffer);
const hashB64 = toBase64(derivedBits); const hashB64 = toBase64(derivedBits);
return `$${ALGORITHM}$${String(ITERATIONS)}$${saltB64}$${hashB64}`; return `$${ALGORITHM}$${ITERATIONS.toString()}$${saltB64}$${hashB64}`;
}; };
export const verifyPassword = async ( export const verifyPassword = async (