Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/server/routes/api/settings.events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ describe('settings and auth events', () => {
(config as any).telegramChatId = '';
(config as any).telegramUseSystemProxy = false;
(config as any).telegramMessageThreadId = '';
config.globalBlockedBrands = [];
config.globalAllowedModels = [];
});

afterAll(async () => {
Expand Down Expand Up @@ -745,6 +747,32 @@ describe('settings and auth events', () => {
expect(runtime.proxyEmptyContentFailEnabled).toBe(true);
});

it('persists global model and brand filters as JSON arrays', async () => {
const updateResponse = await app.inject({
method: 'PUT',
url: '/api/settings/runtime',
payload: {
globalAllowedModels: ['model-alpha', ' model-beta ', 'model-alpha', 'model-gamma'],
globalBlockedBrands: ['Codex', ' Codex ', 'Gemini'],
},
});

expect(updateResponse.statusCode).toBe(200);
const updated = updateResponse.json() as {
globalAllowedModels?: string[];
globalBlockedBrands?: string[];
};
expect(updated.globalAllowedModels).toEqual(['model-alpha', 'model-beta', 'model-gamma']);
expect(updated.globalBlockedBrands).toEqual(['Codex', 'Gemini']);

const rows = await db.select().from(schema.settings).all();
const settingsMap = new Map(rows.map((row) => [row.key, row.value]));
expect(settingsMap.get('global_allowed_models')).toBe(JSON.stringify(['model-alpha', 'model-beta', 'model-gamma']));
expect(JSON.parse(settingsMap.get('global_allowed_models') || 'null')).toEqual(['model-alpha', 'model-beta', 'model-gamma']);
expect(settingsMap.get('global_blocked_brands')).toBe(JSON.stringify(['Codex', 'Gemini']));
expect(JSON.parse(settingsMap.get('global_blocked_brands') || 'null')).toEqual(['Codex', 'Gemini']);
});

it('persists and returns log cleanup settings from runtime settings', async () => {
const updateResponse = await app.inject({
method: 'PUT',
Expand Down
4 changes: 2 additions & 2 deletions src/server/routes/api/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1425,7 +1425,7 @@ export async function settingsRoutes(app: FastifyInstance) {
changedLabels.push('全局品牌屏蔽');
}
config.globalBlockedBrands = uniqueBrands;
upsertSetting('global_blocked_brands', JSON.stringify(uniqueBrands));
upsertSetting('global_blocked_brands', uniqueBrands);
if (prev !== next) {
startBackgroundTask(
{
Expand All @@ -1450,7 +1450,7 @@ export async function settingsRoutes(app: FastifyInstance) {
changedLabels.push('全局模型白名单');
}
config.globalAllowedModels = uniqueModels;
upsertSetting('global_allowed_models', JSON.stringify(uniqueModels));
upsertSetting('global_allowed_models', uniqueModels);
if (prev !== next) {
startBackgroundTask(
{
Expand Down
10 changes: 10 additions & 0 deletions src/server/runtimeSettingsHydration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,14 @@ describe('applyRuntimeSettings', () => {

expect(config.smtpPort).toBe(587);
});

it('hydrates legacy double-encoded global model allowlist values', () => {
config.globalAllowedModels = [];

applyRuntimeSettings(new Map([
['global_allowed_models', JSON.stringify(JSON.stringify(['model-alpha', ' model-beta ', 'model-gamma']))],
]));

expect(config.globalAllowedModels).toEqual(['model-alpha', 'model-beta', 'model-gamma']);
});
});
8 changes: 8 additions & 0 deletions src/server/runtimeSettingsHydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ function toStringList(value: unknown): string[] {
.filter((item) => item.length > 0);
}
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value);
if (Array.isArray(parsed)) {
return toStringList(parsed);
}
} catch {
// Fall back to comma splitting for legacy plain-string lists.
}
return value
.split(',')
.map((item) => item.trim())
Expand Down
Loading