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
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:
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]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user