import { describe, it, expect, vi } from 'vitest'; import { PreferencesService, PLATFORM_DEFAULTS, IMMUTABLE_KEYS } from './preferences.service.js'; import type { Db } from '@mosaic/db'; /** * Build a mock Drizzle DB where the select chain supports: * db.select().from().where() → resolves to `listRows` * db.insert().values().onConflictDoUpdate() → resolves to [] */ function makeMockDb(listRows: Array<{ key: string; value: unknown }> = []): Db { const chainWithLimit = { limit: vi.fn().mockResolvedValue([]), then: (resolve: (v: typeof listRows) => unknown) => Promise.resolve(listRows).then(resolve), }; const selectFrom = { from: vi.fn().mockReturnThis(), where: vi.fn().mockReturnValue(chainWithLimit), }; const deleteResult = { where: vi.fn().mockResolvedValue([]), }; // Single-round-trip upsert chain: insert().values().onConflictDoUpdate() const insertResult = { values: vi.fn().mockReturnThis(), onConflictDoUpdate: vi.fn().mockResolvedValue([]), }; return { select: vi.fn().mockReturnValue(selectFrom), delete: vi.fn().mockReturnValue(deleteResult), insert: vi.fn().mockReturnValue(insertResult), } as unknown as Db; } describe('PreferencesService', () => { describe('getEffective', () => { it('returns platform defaults when user has no overrides', async () => { const db = makeMockDb([]); const service = new PreferencesService(db); const result = await service.getEffective('user-1'); expect(result['agent.thinkingLevel']).toBe('auto'); expect(result['agent.streamingEnabled']).toBe(true); expect(result['session.autoCompactEnabled']).toBe(true); expect(result['session.autoCompactThreshold']).toBe(0.8); }); it('applies user overrides for mutable keys', async () => { const db = makeMockDb([ { key: 'agent.thinkingLevel', value: 'high' }, { key: 'response.language', value: 'es' }, ]); const service = new PreferencesService(db); const result = await service.getEffective('user-1'); expect(result['agent.thinkingLevel']).toBe('high'); expect(result['response.language']).toBe('es'); }); it('ignores user overrides for immutable keys — enforcement always wins', async () => { const db = makeMockDb([ { key: 'limits.maxThinkingLevel', value: 'high' }, { key: 'limits.rateLimit', value: 9999 }, ]); const service = new PreferencesService(db); const result = await service.getEffective('user-1'); // Should still be null (platform default), not the user-supplied values expect(result['limits.maxThinkingLevel']).toBeNull(); expect(result['limits.rateLimit']).toBeNull(); }); }); describe('set', () => { it('returns error when attempting to override an immutable key', async () => { const db = makeMockDb(); const service = new PreferencesService(db); const result = await service.set('user-1', 'limits.maxThinkingLevel', 'high'); expect(result.success).toBe(false); expect(result.message).toContain('platform enforcement'); }); it('returns error when attempting to override limits.rateLimit', async () => { const db = makeMockDb(); const service = new PreferencesService(db); const result = await service.set('user-1', 'limits.rateLimit', 100); expect(result.success).toBe(false); expect(result.message).toContain('platform enforcement'); }); it('upserts a mutable preference and returns success', async () => { // Single-round-trip INSERT … ON CONFLICT DO UPDATE path. const db = makeMockDb([]); const service = new PreferencesService(db); const result = await service.set('user-1', 'agent.thinkingLevel', 'high'); expect(result.success).toBe(true); expect(result.message).toContain('"agent.thinkingLevel"'); }); }); describe('reset', () => { it('returns error when attempting to reset an immutable key', async () => { const db = makeMockDb(); const service = new PreferencesService(db); const result = await service.reset('user-1', 'limits.rateLimit'); expect(result.success).toBe(false); expect(result.message).toContain('platform enforcement'); }); it('deletes user override and returns default value in message', async () => { const db = makeMockDb(); const service = new PreferencesService(db); const result = await service.reset('user-1', 'agent.thinkingLevel'); expect(result.success).toBe(true); expect(result.message).toContain('"auto"'); // platform default for agent.thinkingLevel }); }); describe('IMMUTABLE_KEYS', () => { it('contains only the enforcement keys', () => { expect(IMMUTABLE_KEYS.has('limits.maxThinkingLevel')).toBe(true); expect(IMMUTABLE_KEYS.has('limits.rateLimit')).toBe(true); expect(IMMUTABLE_KEYS.has('agent.thinkingLevel')).toBe(false); }); }); describe('PLATFORM_DEFAULTS', () => { it('has all expected keys', () => { const expectedKeys = [ 'agent.defaultModel', 'agent.thinkingLevel', 'agent.streamingEnabled', 'response.language', 'response.codeAnnotations', 'safety.confirmDestructiveTools', 'session.autoCompactThreshold', 'session.autoCompactEnabled', 'limits.maxThinkingLevel', 'limits.rateLimit', ]; for (const key of expectedKeys) { expect(Object.prototype.hasOwnProperty.call(PLATFORM_DEFAULTS, key)).toBe(true); } }); }); });