- Updated all package.json name fields and dependency references - Updated all TypeScript/JavaScript imports - Updated .woodpecker/publish.yml filters and registry paths - Updated tools/install.sh scope default - Updated .npmrc registry paths (worktree + host) - Enhanced update-checker.ts with checkForAllUpdates() multi-package support - Updated CLI update command to show table of all packages - Added KNOWN_PACKAGES, formatAllPackagesTable, getInstallAllCommand - Marked checkForUpdate() with @deprecated JSDoc Closes #391
153 lines
5.5 KiB
TypeScript
153 lines
5.5 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { PreferencesService, PLATFORM_DEFAULTS, IMMUTABLE_KEYS } from './preferences.service.js';
|
|
import type { Db } from '@mosaicstack/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);
|
|
}
|
|
});
|
|
});
|
|
});
|