SnowLuma v2.4.2 source snapshot
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run

This commit is contained in:
TanTan committed 2026-11-13 09:02:00 +08:00
commit fa9c73363d
1367 files changed
+355735

No files matched your search

@@ -0,0 +1,295 @@
import { describe, expect, it } from 'vitest';
import type { LogStoragePolicy, LogStorageStatus } from '@snowluma/common/log-file-transport';
import type { RuntimeConfig } from '@snowluma/common/runtime';
import {
LogStorageSettingsManager,
StorageSettingsInputError,
StorageSettingsLockedError,
StorageSettingsTransactionError,
} from '../src/webui/storage-settings';
const persisted: RuntimeConfig = {
webuiPort: 5099,
hookAutoLoad: false,
webuiHost: '0.0.0.0',
webuiTls: { enabled: false },
trustProxy: '',
logMaxTotalMb: 1024,
logRetainDays: 7,
logPerUin: false,
};
const persistedPolicy: LogStoragePolicy = {
maxTotalMb: 1024,
retainDays: 7,
perUinEnabled: false,
};
function status(
policy: LogStoragePolicy,
state: LogStorageStatus['state'] = 'healthy',
): LogStorageStatus {
return {
state,
directory: '/private/logs',
totalBytes: state === 'degraded' ? policy.maxTotalMb * 1024 * 1024 + 1 : 0,
maxTotalBytes: policy.maxTotalMb * 1024 * 1024,
retainDays: policy.retainDays,
perUinEnabled: policy.perUinEnabled,
fileCount: 0,
activeFileCount: 0,
droppedLines: 0,
...(state === 'healthy' ? {} : { lastError: 'quota could not be enforced' }),
};
}
describe('LogStorageSettingsManager', () => {
it('reports persisted, effective, and environment-locked settings separately', () => {
const manager = new LogStorageSettingsManager({
readPersisted: () => persisted,
readEnvOverrides: () => ({ logRetainDays: 0, logPerUin: true }),
readEffective: () =>
status({
maxTotalMb: 1024,
retainDays: 0,
perUinEnabled: true,
}),
persist: () => {
throw new Error('not used');
},
apply: async (policy) => status(policy),
});
expect(manager.read()).toEqual({
saved: {
logMaxTotalMb: 1024,
logRetainDays: 7,
logPerUin: false,
},
effective: {
logMaxTotalMb: 1024,
logRetainDays: 0,
logPerUin: true,
},
envOverrides: ['logRetainDays', 'logPerUin'],
});
});
it('reports the actual live policy separately from externally changed disk values', () => {
const manager = new LogStorageSettingsManager({
readPersisted: () => ({ ...persisted, logRetainDays: 30 }),
readEnvOverrides: () => ({}),
readEffective: () => status(persistedPolicy),
persist: () => {
throw new Error('not used');
},
apply: async (policy) => status(policy),
});
expect(manager.read()).toMatchObject({
saved: { logRetainDays: 30 },
effective: { logRetainDays: 7 },
});
});
it('strictly rejects malformed, unknown, and empty patches', async () => {
const manager = new LogStorageSettingsManager({
readPersisted: () => persisted,
readEnvOverrides: () => ({}),
readEffective: () => status(persistedPolicy),
persist: () => persisted,
apply: async (policy) => status(policy),
});
await expect(manager.update(null)).rejects.toThrow(StorageSettingsInputError);
await expect(manager.update({})).rejects.toThrow(StorageSettingsInputError);
await expect(manager.update({ unknown: 1 })).rejects.toThrow(StorageSettingsInputError);
await expect(manager.update({ logMaxTotalMb: 0 })).rejects.toThrow(
StorageSettingsInputError,
);
await expect(manager.update({ logRetainDays: -1 })).rejects.toThrow(
StorageSettingsInputError,
);
await expect(manager.update({ logPerUin: 1 })).rejects.toThrow(StorageSettingsInputError);
});
it('rejects edits to fields pinned by environment variables', async () => {
let applied = false;
const manager = new LogStorageSettingsManager({
readPersisted: () => persisted,
readEnvOverrides: () => ({ logMaxTotalMb: 2048 }),
readEffective: () =>
status({
maxTotalMb: 2048,
retainDays: 7,
perUinEnabled: false,
}),
persist: () => persisted,
apply: async (policy) => {
applied = true;
return status(policy);
},
});
await expect(manager.update({ logMaxTotalMb: 512 })).rejects.toThrow(
StorageSettingsLockedError,
);
expect(applied).toBe(false);
});
it('applies before persisting and returns the new effective settings', async () => {
const order: string[] = [];
let current = persisted;
const manager = new LogStorageSettingsManager({
readPersisted: () => current,
readEnvOverrides: () => ({}),
readEffective: () =>
status({
maxTotalMb: current.logMaxTotalMb ?? 1024,
retainDays: current.logRetainDays ?? 7,
perUinEnabled: current.logPerUin ?? false,
}),
persist: (patch) => {
order.push('persist');
current = { ...current, ...patch };
return current;
},
apply: async (policy) => {
order.push('apply');
return status(policy);
},
});
await expect(
manager.update({
logMaxTotalMb: 512,
logRetainDays: 0,
logPerUin: true,
}),
).resolves.toMatchObject({
settings: {
saved: {
logMaxTotalMb: 512,
logRetainDays: 0,
logPerUin: true,
},
effective: {
logMaxTotalMb: 512,
logRetainDays: 0,
logPerUin: true,
},
},
status: {
state: 'healthy',
maxTotalBytes: 512 * 1024 * 1024,
},
});
expect(order).toEqual(['apply', 'persist']);
});
it('does not persist a policy that cannot be enforced and rolls runtime state back', async () => {
const applied: LogStoragePolicy[] = [];
let persistCalls = 0;
const manager = new LogStorageSettingsManager({
readPersisted: () => persisted,
readEnvOverrides: () => ({}),
readEffective: () => status(persistedPolicy),
persist: () => {
persistCalls += 1;
return persisted;
},
apply: async (policy) => {
applied.push(policy);
return status(policy, applied.length === 1 ? 'degraded' : 'healthy');
},
});
await expect(manager.update({ logMaxTotalMb: 1 })).rejects.toThrow(
StorageSettingsTransactionError,
);
expect(persistCalls).toBe(0);
expect(applied).toEqual([
{ maxTotalMb: 1, retainDays: 7, perUinEnabled: false },
{ maxTotalMb: 1024, retainDays: 7, perUinEnabled: false },
]);
});
it('restores both runtime state and persisted settings when persistence fails', async () => {
const applied: LogStoragePolicy[] = [];
const persistedPatches: Partial<RuntimeConfig>[] = [];
let persistCalls = 0;
const manager = new LogStorageSettingsManager({
readPersisted: () => persisted,
readEnvOverrides: () => ({}),
readEffective: () => status(persistedPolicy),
persist: (patch) => {
persistedPatches.push(patch);
persistCalls += 1;
if (persistCalls === 1) {
throw new Error('disk full');
}
return persisted;
},
apply: async (policy) => {
applied.push(policy);
return status(policy);
},
});
await expect(manager.update({ logRetainDays: 30 })).rejects.toThrow(
StorageSettingsTransactionError,
);
expect(applied).toEqual([
{ maxTotalMb: 1024, retainDays: 30, perUinEnabled: false },
{ maxTotalMb: 1024, retainDays: 7, perUinEnabled: false },
]);
expect(persistedPatches).toEqual([{ logRetainDays: 30 }, persisted]);
});
it('serializes concurrent updates so invocation order remains authoritative', async () => {
let current = persisted;
let livePolicy = persistedPolicy;
const applied: LogStoragePolicy[] = [];
const releases: Array<() => void> = [];
let notifySecondStarted!: () => void;
const secondStarted = new Promise<void>((resolve) => {
notifySecondStarted = resolve;
});
const manager = new LogStorageSettingsManager({
readPersisted: () => current,
readEnvOverrides: () => ({}),
readEffective: () => status(livePolicy),
persist: (patch) => {
current = { ...current, ...patch };
return current;
},
apply: async (policy) => {
applied.push(policy);
if (applied.length === 2) {
notifySecondStarted();
}
await new Promise<void>((resolve) => releases.push(resolve));
livePolicy = policy;
return status(policy);
},
});
const first = manager.update({ logRetainDays: 30 });
const second = manager.update({ logRetainDays: 60 });
await Promise.resolve();
expect(applied).toHaveLength(1);
releases.shift()?.();
await secondStarted;
expect(applied).toHaveLength(2);
releases.shift()?.();
await Promise.all([first, second]);
expect(current.logRetainDays).toBe(60);
expect(applied.map((policy) => policy.retainDays)).toEqual([30, 60]);
});
});