Refactor API to use nested sessions/devices routers and fix test infrastructure

- Update API contract to use nested router structure for sessions and devices
  (me.sessions.list, me.devices.getInfo, etc.)
- Update frontend Svelte components to use new nested API paths
- Fix test assertion patterns for consistency (remove async () => wrappers)
- Fix test-db.ts findRepoRoot to use existsSync for directory checking
  (Bun.file().exists() returns false for directories)
- Add ESLint config override for test files to handle expect().rejects patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
RevIQ
2026-01-10 17:17:50 +08:00
110 changed files with 2013 additions and 362 deletions

View File

@@ -31,9 +31,9 @@ import {
} from "bun:test";
import { call } from "@orpc/server";
import { router } from "../../router.js";
import { hashPassword } from "../../utils/password.js";
import { hashToken } from "../../utils/crypto.js";
import { COOKIE_NAMES } from "../../utils/cookies.js";
import { hashToken } from "../../utils/crypto.js";
import { hashPassword } from "../../utils/password.js";
import { TEST_RP } from "../helpers/test-constants.js";
import {
createTestDb,
@@ -76,7 +76,9 @@ function createAPIContext(options?: {
cookies.push(`${COOKIE_NAMES.SESSION_TOKEN}=${options.sessionToken}`);
}
if (options?.deviceFingerprint) {
cookies.push(`${COOKIE_NAMES.DEVICE_FINGERPRINT}=${options.deviceFingerprint}`);
cookies.push(
`${COOKIE_NAMES.DEVICE_FINGERPRINT}=${options.deviceFingerprint}`,
);
}
if (cookies.length > 0) {
reqHeaders.set("cookie", cookies.join("; "));
@@ -102,7 +104,7 @@ async function createSession(
userId: number,
options?: { ipAddress?: string; userAgent?: string },
): Promise<{ token: string; sessionId: number }> {
const token = "test-session-" + String(Date.now()) + String(Math.random());
const token = `test-session-${String(Date.now())}${String(Math.random())}`;
const tokenHashValue = await hashToken(token);
const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS);
@@ -137,6 +139,9 @@ async function createUserAPIContext(
return { context, token };
}
// Export to suppress unused warning - helper available for future tests
void createUserAPIContext;
/**
* Create a device in the database and return the fingerprint
*/
@@ -151,7 +156,7 @@ async function createDevice(
): Promise<{ fingerprint: string; deviceId: number }> {
const fingerprint =
options?.fingerprint ??
"test-fp-" + String(Date.now()) + String(Math.random());
`test-fp-${String(Date.now())}${String(Math.random())}`;
const result = await getDb()
.insertInto("user_devices")
@@ -176,8 +181,7 @@ async function createDevice(
async function createApiToken(
userId: number,
): Promise<{ token: string; name: string }> {
const token =
"test-api-token-" + String(Date.now()) + String(Math.random());
const token = `test-api-token-${String(Date.now())}${String(Math.random())}`;
const tokenHashValue = await hashToken(token);
const expiresAt = new Date(Date.now() + API_TOKEN_EXPIRY_MS);
@@ -633,6 +637,7 @@ describe("me.setPassword", () => {
// Password must be at least 8 chars to pass schema validation
// "password" passes length check but fails zxcvbn strength check
// zxcvbn provides feedback like "This is a top-10 common password"
await expect(
call(
router.me.setPassword,
@@ -741,10 +746,10 @@ describe("me.sessions.list", () => {
});
// Create multiple sessions
const { token: sessionToken1, sessionId: id1 } = await createSession(
user.id,
{ ipAddress: "192.168.1.1", userAgent: "Chrome/1.0" },
);
const { token: sessionToken1 } = await createSession(user.id, {
ipAddress: "192.168.1.1",
userAgent: "Chrome/1.0",
});
await createSession(user.id, {
ipAddress: "192.168.1.2",
userAgent: "Firefox/1.0",
@@ -838,7 +843,11 @@ describe("me.sessions.revoke", () => {
const { sessionId: sessionId2 } = await createSession(user.id);
const context = createAPIContext({ sessionToken: sessionToken1 });
await call(router.me.sessions.revoke, { sessionId: sessionId2 }, { context });
await call(
router.me.sessions.revoke,
{ sessionId: sessionId2 },
{ context },
);
// Verify session is revoked
const session = await getDb()
@@ -1265,9 +1274,9 @@ describe("me.devices.revokeAll", () => {
email: "revokealldevices@example.com",
});
const { deviceId: id1 } = await createDevice(user.id, { isTrusted: true });
const { deviceId: id2 } = await createDevice(user.id, { isTrusted: true });
const { deviceId: id3 } = await createDevice(user.id, { isTrusted: false });
await createDevice(user.id, { isTrusted: true });
await createDevice(user.id, { isTrusted: true });
await createDevice(user.id, { isTrusted: false });
const { token: sessionToken } = await createSession(user.id);
const context = createAPIContext({ sessionToken });
@@ -1282,7 +1291,7 @@ describe("me.devices.revokeAll", () => {
.execute();
expect(devices).toHaveLength(3);
expect(devices.every((d) => d.is_trusted === false)).toBe(true);
expect(devices.every((d) => !d.is_trusted)).toBe(true);
});
test("works when no devices exist", async () => {