/** * authServiceDb.test.ts * * DB-centric unit tests for authService.ts using a real in-memory SQLite database. * Pure function tests live in authService.test.ts (stub DB); this file covers * functions that require actual DB queries to exercise their logic. */ // --------------------------------------------------------------------------- // vi.hoisted: build the real in-memory DB and the module mock before any import // --------------------------------------------------------------------------- const { testDb, dbMock } = vi.hoisted(() => { const Database = require('better-sqlite3'); const db = new Database(':memory:'); db.exec('PRAGMA journal_mode = WAL'); db.exec('PRAGMA foreign_keys = ON'); db.exec('PRAGMA busy_timeout = 5000'); const mock = { db, closeDb: () => {}, reinitialize: () => {}, canAccessTrip: (tripId: any, userId: number) => db .prepare( `SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)` ) .get(userId, tripId, userId), isOwner: (tripId: any, userId: number) => !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), }; return { testDb: db, dbMock: mock }; }); vi.mock('../../../src/db/database', () => dbMock); vi.mock('../../../src/config', () => ({ JWT_SECRET: 'test-secret', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', updateJwtSecret: () => {}, })); vi.mock('../../../src/services/mfaCrypto', () => ({ encryptMfaSecret: vi.fn((s) => `enc:${s}`), decryptMfaSecret: vi.fn((s: string) => s.replace('enc:', '')), })); vi.mock('../../../src/services/apiKeyCrypto', () => ({ decrypt_api_key: vi.fn((v) => v), maybe_encrypt_api_key: vi.fn((v) => v), mask_stored_api_key: vi.fn((v: string | null | undefined) => (v ? '••••••••' : null)), encrypt_api_key: vi.fn((v) => v), })); vi.mock('../../../src/services/permissions', () => ({ getAllPermissions: vi.fn(() => ({})), checkPermission: vi.fn(), })); vi.mock('../../../src/services/ephemeralTokens', () => ({ createEphemeralToken: vi.fn() })); vi.mock('../../../src/mcp', () => ({ revokeUserSessions: vi.fn() })); vi.mock('../../../src/scheduler', () => ({ startTripReminders: vi.fn(), buildCronExpression: vi.fn(), loadSettings: vi.fn(() => ({ enabled: false })), VALID_INTERVALS: ['daily', 'weekly', 'monthly'], })); // --------------------------------------------------------------------------- // Imports (after mocks) // --------------------------------------------------------------------------- import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from 'vitest'; import { createTables } from '../../../src/db/schema'; import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createAdmin, createInviteToken } from '../../helpers/factories'; import { updateSettings, getSettings, listUsers, getAppSettings, validateKeys, isOidcOnlyMode, resolveAuthToggles, setupMfa, enableMfa, disableMfa, validateInviteToken, registerUser, loginUser, changePassword, verifyMfaLogin, createMcpToken, deleteMcpToken, } from '../../../src/services/authService'; // --------------------------------------------------------------------------- // Lifecycle // --------------------------------------------------------------------------- beforeAll(() => { createTables(testDb); runMigrations(testDb); }); beforeEach(() => resetTestDb(testDb)); afterAll(() => testDb.close()); // --------------------------------------------------------------------------- // updateSettings // --------------------------------------------------------------------------- describe('updateSettings', () => { it('AUTH-DB-001: updates username successfully', () => { const { user } = createUser(testDb); const result = updateSettings(user.id, { username: 'newname' }); expect(result.success).toBe(true); expect(result.user?.username).toBe('newname'); }); it('AUTH-DB-002: returns 400 when username is too short (< 2 chars)', () => { const { user } = createUser(testDb); const result = updateSettings(user.id, { username: 'x' }); expect(result.status).toBe(400); expect(result.error).toMatch(/between 2 and 50/i); }); it('AUTH-DB-003: returns 400 when username has invalid characters (spaces)', () => { const { user } = createUser(testDb); const result = updateSettings(user.id, { username: 'bad name' }); expect(result.status).toBe(400); expect(result.error).toMatch(/only contain/i); }); it('AUTH-DB-004: returns 409 when username is already taken by another user', () => { const { user: user1 } = createUser(testDb, { username: 'alice' }); const { user: user2 } = createUser(testDb, { username: 'bob' }); const result = updateSettings(user2.id, { username: user1.username }); expect(result.status).toBe(409); expect(result.error).toMatch(/already taken/i); }); it('AUTH-DB-005: updates email successfully', () => { const { user } = createUser(testDb); const result = updateSettings(user.id, { email: 'new@example.com' }); expect(result.success).toBe(true); expect(result.user?.email).toBe('new@example.com'); }); it('AUTH-DB-006: returns 400 for invalid email format', () => { const { user } = createUser(testDb); const result = updateSettings(user.id, { email: 'not-an-email' }); expect(result.status).toBe(400); expect(result.error).toMatch(/invalid email/i); }); it('AUTH-DB-007: returns 409 when email is already taken by another user', () => { const { user: user1 } = createUser(testDb, { email: 'taken@example.com' }); const { user: user2 } = createUser(testDb); const result = updateSettings(user2.id, { email: user1.email }); expect(result.status).toBe(409); expect(result.error).toMatch(/already taken/i); }); it('AUTH-DB-008: returns success with no field changes when empty body is passed', () => { const { user } = createUser(testDb); const result = updateSettings(user.id, {}); expect(result.success).toBe(true); }); }); // --------------------------------------------------------------------------- // getSettings // --------------------------------------------------------------------------- describe('getSettings', () => { it('AUTH-DB-009: returns 403 for non-admin user', () => { const { user } = createUser(testDb); const result = getSettings(user.id); expect(result.status).toBe(403); expect(result.error).toMatch(/admin/i); }); it('AUTH-DB-010: returns maps_api_key and openweather_api_key for admin', () => { const { user } = createAdmin(testDb); testDb .prepare('UPDATE users SET maps_api_key = ?, openweather_api_key = ? WHERE id = ?') .run('maps-key-value', 'weather-key-value', user.id); const result = getSettings(user.id); expect(result.status).toBeUndefined(); expect(result.settings).toBeDefined(); expect(result.settings).toHaveProperty('maps_api_key'); expect(result.settings).toHaveProperty('openweather_api_key'); }); }); // --------------------------------------------------------------------------- // listUsers // --------------------------------------------------------------------------- describe('listUsers', () => { it('AUTH-DB-011: returns all users except self, sorted by username', () => { const { user: self } = createUser(testDb, { username: 'zzself' }); createUser(testDb, { username: 'alice' }); createUser(testDb, { username: 'charlie' }); createUser(testDb, { username: 'bob' }); const result = listUsers(self.id); expect(result).toHaveLength(3); const names = result.map((u) => u.username); expect(names).toEqual([...names].sort()); expect(names).not.toContain('zzself'); }); it('AUTH-DB-012: returns empty array when only one user exists', () => { const { user } = createUser(testDb); const result = listUsers(user.id); expect(result).toHaveLength(0); }); }); // --------------------------------------------------------------------------- // getAppSettings // --------------------------------------------------------------------------- describe('getAppSettings', () => { it('AUTH-DB-013: returns 403 for non-admin', () => { const { user } = createUser(testDb); const result = getAppSettings(user.id); expect(result.status).toBe(403); expect(result.error).toMatch(/admin/i); }); it('AUTH-DB-014: returns settings object for admin with known key allow_registration', () => { const { user } = createAdmin(testDb); testDb .prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allow_registration', 'true')") .run(); const result = getAppSettings(user.id); expect(result.status).toBeUndefined(); expect(result.data).toBeDefined(); expect(result.data).toHaveProperty('allow_registration', 'true'); }); }); // --------------------------------------------------------------------------- // validateKeys // --------------------------------------------------------------------------- describe('validateKeys', () => { it('AUTH-DB-015: returns 403 for non-admin', async () => { const { user } = createUser(testDb); const result = await validateKeys(user.id); expect(result.status).toBe(403); expect(result.error).toMatch(/admin/i); expect(result.maps).toBe(false); expect(result.weather).toBe(false); }); it('AUTH-DB-016: returns { maps: false, weather: false } when no API keys are stored', async () => { const { user } = createAdmin(testDb); const result = await validateKeys(user.id); expect(result.maps).toBe(false); expect(result.weather).toBe(false); expect(result.maps_details).toBeNull(); }); it('AUTH-DB-017: returns { maps: true } when fetch returns 200', async () => { const { user } = createAdmin(testDb); testDb.prepare('UPDATE users SET maps_api_key = ? WHERE id = ?').run('test-key', user.id); const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValueOnce({ status: 200, statusText: 'OK', text: async () => '', } as Response); const result = await validateKeys(user.id); expect(result.maps).toBe(true); expect(result.maps_details?.ok).toBe(true); fetchSpy.mockRestore(); }); it('AUTH-DB-018: returns { maps: false } when fetch throws a network error', async () => { const { user } = createAdmin(testDb); testDb.prepare('UPDATE users SET maps_api_key = ? WHERE id = ?').run('test-key', user.id); const fetchSpy = vi .spyOn(global, 'fetch') .mockRejectedValueOnce(new Error('Network failure')); const result = await validateKeys(user.id); expect(result.maps).toBe(false); expect(result.maps_details?.error_status).toBe('FETCH_ERROR'); expect(result.maps_details?.error_message).toBe('Network failure'); fetchSpy.mockRestore(); }); }); // --------------------------------------------------------------------------- // isOidcOnlyMode // --------------------------------------------------------------------------- describe('isOidcOnlyMode', () => { it('AUTH-DB-019: returns false when OIDC_ONLY env var is not set', () => { vi.stubEnv('OIDC_ONLY', ''); expect(isOidcOnlyMode()).toBe(false); vi.unstubAllEnvs(); }); it('AUTH-DB-020: returns false when OIDC_ONLY=true but no OIDC_ISSUER configured', () => { vi.stubEnv('OIDC_ONLY', 'true'); vi.stubEnv('OIDC_ISSUER', ''); vi.stubEnv('OIDC_CLIENT_ID', ''); expect(isOidcOnlyMode()).toBe(false); vi.unstubAllEnvs(); }); it('AUTH-DB-021: returns true when OIDC_ONLY=true AND OIDC_ISSUER AND OIDC_CLIENT_ID are set', () => { vi.stubEnv('OIDC_ONLY', 'true'); vi.stubEnv('OIDC_ISSUER', 'https://sso.example.com'); vi.stubEnv('OIDC_CLIENT_ID', 'trek-client'); expect(isOidcOnlyMode()).toBe(true); vi.unstubAllEnvs(); }); }); // --------------------------------------------------------------------------- // resolveAuthToggles // --------------------------------------------------------------------------- describe('resolveAuthToggles', () => { afterEach(() => { vi.unstubAllEnvs(); testDb.prepare("DELETE FROM app_settings WHERE key IN ('password_login','password_registration','oidc_login','oidc_registration','oidc_only','allow_registration')").run(); }); it('AUTH-DB-022a: returns all true by default (no DB keys, no env override)', () => { vi.stubEnv('OIDC_ONLY', ''); const t = resolveAuthToggles(); expect(t.password_login).toBe(true); expect(t.password_registration).toBe(true); expect(t.oidc_login).toBe(true); expect(t.oidc_registration).toBe(true); }); it('AUTH-DB-022b: legacy — OIDC_ONLY=true with OIDC configured disables password_login and password_registration', () => { vi.stubEnv('OIDC_ONLY', 'true'); vi.stubEnv('OIDC_ISSUER', 'https://sso.example.com'); vi.stubEnv('OIDC_CLIENT_ID', 'trek-client'); const t = resolveAuthToggles(); expect(t.password_login).toBe(false); expect(t.password_registration).toBe(false); expect(t.oidc_login).toBe(true); expect(t.oidc_registration).toBe(true); }); it('AUTH-DB-022c: legacy — allow_registration=false disables both password and oidc registration', () => { vi.stubEnv('OIDC_ONLY', ''); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allow_registration', 'false')").run(); const t = resolveAuthToggles(); expect(t.password_login).toBe(true); expect(t.password_registration).toBe(false); expect(t.oidc_login).toBe(true); expect(t.oidc_registration).toBe(false); }); it('AUTH-DB-022d: new granular keys take precedence over legacy keys', () => { vi.stubEnv('OIDC_ONLY', ''); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allow_registration', 'false')").run(); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('password_registration', 'true')").run(); const t = resolveAuthToggles(); // New key present → use new keys, allow_registration ignored expect(t.password_registration).toBe(true); expect(t.oidc_registration).toBe(true); // defaults to true when key not set }); it('AUTH-DB-022e: OIDC_ONLY env var overrides new granular keys for password toggles', () => { vi.stubEnv('OIDC_ONLY', 'true'); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('password_login', 'true')").run(); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('password_registration', 'true')").run(); const t = resolveAuthToggles(); // OIDC_ONLY forces password toggles off even when DB says true expect(t.password_login).toBe(false); expect(t.password_registration).toBe(false); }); it('AUTH-DB-022f: individual granular keys can be set independently', () => { vi.stubEnv('OIDC_ONLY', ''); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('password_login', 'true')").run(); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('password_registration', 'false')").run(); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_login', 'true')").run(); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_registration', 'false')").run(); const t = resolveAuthToggles(); expect(t.password_login).toBe(true); expect(t.password_registration).toBe(false); expect(t.oidc_login).toBe(true); expect(t.oidc_registration).toBe(false); }); }); // --------------------------------------------------------------------------- // setupMfa // --------------------------------------------------------------------------- describe('setupMfa', () => { it('AUTH-DB-022: returns 403 in demo mode for demo@nomad.app', () => { vi.stubEnv('DEMO_MODE', 'true'); const { user } = createUser(testDb, { email: 'demo@nomad.app' }); const result = setupMfa(user.id, 'demo@nomad.app'); expect(result.status).toBe(403); expect(result.error).toMatch(/demo mode/i); vi.unstubAllEnvs(); }); it('AUTH-DB-023: returns 400 when MFA is already enabled', () => { const { user } = createUser(testDb); testDb.prepare('UPDATE users SET mfa_enabled = 1 WHERE id = ?').run(user.id); const result = setupMfa(user.id, user.email); expect(result.status).toBe(400); expect(result.error).toMatch(/already enabled/i); }); it('AUTH-DB-024: returns secret and otpauth_url when MFA setup starts successfully', () => { const { user } = createUser(testDb); const result = setupMfa(user.id, user.email); expect(result.error).toBeUndefined(); expect(typeof result.secret).toBe('string'); expect(result.secret!.length).toBeGreaterThan(0); expect(typeof result.otpauth_url).toBe('string'); expect(result.otpauth_url).toMatch(/^otpauth:\/\/totp\//); expect(result.qrPromise).toBeInstanceOf(Promise); }); }); // --------------------------------------------------------------------------- // enableMfa // --------------------------------------------------------------------------- describe('enableMfa', () => { it('AUTH-DB-025: returns 400 when no verification code is provided', () => { const { user } = createUser(testDb); const result = enableMfa(user.id, undefined); expect(result.status).toBe(400); expect(result.error).toMatch(/code is required/i); }); it('AUTH-DB-026: returns 400 when there is no pending MFA setup', () => { const { user } = createUser(testDb); // No setupMfa called first, so no pending entry exists const result = enableMfa(user.id, '123456'); expect(result.status).toBe(400); expect(result.error).toMatch(/no mfa setup in progress/i); }); }); // --------------------------------------------------------------------------- // disableMfa // --------------------------------------------------------------------------- describe('disableMfa', () => { it('AUTH-DB-027: returns 403 in demo mode for demo@nomad.app', () => { vi.stubEnv('DEMO_MODE', 'true'); const { user } = createUser(testDb, { email: 'demo@nomad.app' }); const result = disableMfa(user.id, 'demo@nomad.app', { password: 'password123', code: '000000', }); expect(result.status).toBe(403); expect(result.error).toMatch(/demo mode/i); vi.unstubAllEnvs(); }); it('AUTH-DB-028: returns 400 when password or code is missing', () => { const { user } = createUser(testDb); const missingCode = disableMfa(user.id, user.email, { password: 'pass', code: undefined }); expect(missingCode.status).toBe(400); expect(missingCode.error).toMatch(/password and authenticator code/i); const missingPassword = disableMfa(user.id, user.email, { password: undefined, code: '123456' }); expect(missingPassword.status).toBe(400); expect(missingPassword.error).toMatch(/password and authenticator code/i); }); it('AUTH-DB-029: returns 400 when MFA is not enabled on the account', () => { const { user } = createUser(testDb); // mfa_enabled defaults to 0 / not set const result = disableMfa(user.id, user.email, { password: 'password123', code: '000000' }); expect(result.status).toBe(400); expect(result.error).toMatch(/not enabled/i); }); }); // --------------------------------------------------------------------------- // validateInviteToken // --------------------------------------------------------------------------- describe('validateInviteToken', () => { it('AUTH-DB-030: returns 404 for unknown token', () => { const result = validateInviteToken('no-such-token'); expect(result.status).toBe(404); }); it('AUTH-DB-031: returns 410 when max_uses exceeded', () => { // createInviteToken with used_count already at max const invite = createInviteToken(testDb, { max_uses: 1 }); // manually set used_count = 1 to simulate exhaustion testDb.prepare('UPDATE invite_tokens SET used_count = 1 WHERE id = ?').run(invite.id); const result = validateInviteToken(invite.token); expect(result.status).toBe(410); }); it('AUTH-DB-032: returns 410 when expired', () => { const invite = createInviteToken(testDb, { expires_at: '2000-01-01T00:00:00.000Z' }); const result = validateInviteToken(invite.token); expect(result.status).toBe(410); }); }); // --------------------------------------------------------------------------- // registerUser — OIDC-only / registration-disabled // --------------------------------------------------------------------------- describe('registerUser — OIDC-only / registration-disabled', () => { it('AUTH-DB-033: returns 403 when oidc_only=true and not first user', () => { createUser(testDb); // ensure userCount > 0 testDb.prepare("INSERT INTO app_settings (key, value) VALUES ('oidc_only', 'true')").run(); testDb.prepare("INSERT INTO app_settings (key, value) VALUES ('oidc_issuer', 'https://x')").run(); testDb.prepare("INSERT INTO app_settings (key, value) VALUES ('oidc_client_id', 'id')").run(); const result = registerUser({ username: 'u', email: 'new@x.com', password: 'Secure123!' }); expect(result.status).toBe(403); expect(result.error).toMatch(/password registration is disabled/i); }); it('AUTH-DB-034: returns 403 when registration is disabled and no invite', () => { createUser(testDb); // ensure userCount > 0 testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allow_registration', 'false')").run(); const result = registerUser({ username: 'u2', email: 'n2@x.com', password: 'Secure123!' }); expect(result.status).toBe(403); }); }); // --------------------------------------------------------------------------- // loginUser — OIDC-only mode // --------------------------------------------------------------------------- describe('loginUser — OIDC-only mode', () => { it('AUTH-DB-035: returns 403 when oidc_only=true', () => { const { user, password } = createUser(testDb); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_only', 'true')").run(); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_issuer', 'https://x')").run(); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_client_id', 'id')").run(); const result = loginUser({ email: user.email, password }); expect(result.status).toBe(403); }); }); // --------------------------------------------------------------------------- // changePassword — OIDC-only mode // --------------------------------------------------------------------------- describe('changePassword — OIDC-only mode', () => { it('AUTH-DB-036: returns 403 when oidc_only=true', () => { const { user, password } = createUser(testDb); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_only', 'true')").run(); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_issuer', 'https://x')").run(); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('oidc_client_id', 'id')").run(); const result = changePassword(user.id, user.email, { current_password: password, new_password: 'New1234!' }); expect(result.status).toBe(403); }); }); // --------------------------------------------------------------------------- // disableMfa — require_mfa policy // --------------------------------------------------------------------------- describe('disableMfa — require_mfa policy', () => { it('AUTH-DB-037: returns 403 when require_mfa=true is set globally', () => { const { user } = createUser(testDb); testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('require_mfa', 'true')").run(); const result = disableMfa(user.id, user.email, { password: 'pass', code: '123456' }); expect(result.status).toBe(403); expect(result.error).toMatch(/cannot be disabled/i); }); }); // --------------------------------------------------------------------------- // verifyMfaLogin — validation // --------------------------------------------------------------------------- describe('verifyMfaLogin — validation', () => { it('AUTH-DB-038: returns 400 when mfa_token or code is missing', () => { const result = verifyMfaLogin({ mfa_token: undefined, code: undefined }); expect(result.status).toBe(400); }); it('AUTH-DB-039: returns 401 when mfa_token has wrong purpose', () => { // eslint-disable-next-line @typescript-eslint/no-require-imports const jwt = require('jsonwebtoken'); const tok = jwt.sign({ id: 1, purpose: 'wrong' }, 'test-secret', { expiresIn: '5m', algorithm: 'HS256' }); const result = verifyMfaLogin({ mfa_token: tok, code: '123456' }); expect(result.status).toBe(401); expect(result.error).toMatch(/invalid/i); }); it('AUTH-DB-040: returns 401 when user not found for valid mfa_token', () => { // eslint-disable-next-line @typescript-eslint/no-require-imports const jwt = require('jsonwebtoken'); const tok = jwt.sign({ id: 99999, purpose: 'mfa_login' }, 'test-secret', { expiresIn: '5m', algorithm: 'HS256' }); const result = verifyMfaLogin({ mfa_token: tok, code: '123456' }); expect(result.status).toBe(401); }); }); // --------------------------------------------------------------------------- // MCP token service // --------------------------------------------------------------------------- describe('MCP token service', () => { it('AUTH-DB-041: createMcpToken returns 400 when name is missing', () => { const { user } = createUser(testDb); const result = createMcpToken(user.id, undefined); expect(result.status).toBe(400); }); it('AUTH-DB-042: createMcpToken returns 400 when name exceeds 100 chars', () => { const { user } = createUser(testDb); const result = createMcpToken(user.id, 'a'.repeat(101)); expect(result.status).toBe(400); }); it('AUTH-DB-043: createMcpToken creates token and returns raw_token', () => { const { user } = createUser(testDb); const result = createMcpToken(user.id, 'My Token'); expect(result.token).toBeDefined(); expect((result.token as any).raw_token).toMatch(/^trek_/); }); it('AUTH-DB-044: createMcpToken returns 400 when user has 10 tokens already', () => { const { user } = createUser(testDb); for (let i = 0; i < 10; i++) { testDb.prepare( 'INSERT INTO mcp_tokens (user_id, name, token_hash, token_prefix) VALUES (?, ?, ?, ?)' ).run(user.id, `Token ${i}`, `hash${i}`, `trek_prefix${i}`); } const result = createMcpToken(user.id, 'One More'); expect(result.status).toBe(400); }); it('AUTH-DB-045: deleteMcpToken returns 404 for non-existent token', () => { const { user } = createUser(testDb); const result = deleteMcpToken(user.id, '99999'); expect(result.status).toBe(404); }); it('AUTH-DB-046: deleteMcpToken deletes the token and returns success', () => { const { user } = createUser(testDb); const created = createMcpToken(user.id, 'Deletable Token'); const tokenId = String((created.token as any).id); const result = deleteMcpToken(user.id, tokenId); expect(result).toEqual({ success: true }); const row = testDb.prepare('SELECT id FROM mcp_tokens WHERE id = ?').get(tokenId); expect(row).toBeUndefined(); }); });