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,215 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import {
|
||||
defaultGlobalSettings,
|
||||
loadGlobalSettings,
|
||||
migrateGlobalSettings,
|
||||
normalizeGlobalSettings,
|
||||
saveGlobalSettings,
|
||||
} from '../src/global-config';
|
||||
|
||||
describe('global-config (config/snowluma.json)', () => {
|
||||
let tempDir: string;
|
||||
let prevCwd: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'snowluma-global-config-'));
|
||||
prevCwd = process.cwd();
|
||||
process.chdir(tempDir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.chdir(prevCwd);
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('defaults to an empty fallback list (feature off)', () => {
|
||||
expect(defaultGlobalSettings()).toEqual({
|
||||
rkey: { fallbackServers: [] },
|
||||
musicSignUrl: '',
|
||||
videoUploadConcurrency: 1,
|
||||
});
|
||||
expect(loadGlobalSettings()).toEqual({
|
||||
rkey: { fallbackServers: [] },
|
||||
musicSignUrl: '',
|
||||
videoUploadConcurrency: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps only well-formed http(s) URLs, trims, and dedupes', () => {
|
||||
const out = normalizeGlobalSettings({
|
||||
rkey: {
|
||||
fallbackServers: [
|
||||
' https://a.example/r ',
|
||||
'ftp://x',
|
||||
'not-a-url',
|
||||
'https://',
|
||||
'https://a.example/r',
|
||||
'http://b.example/r',
|
||||
42,
|
||||
],
|
||||
},
|
||||
});
|
||||
// 'https://' (no host) and non-http schemes are dropped.
|
||||
expect(out.rkey.fallbackServers).toEqual(['https://a.example/r', 'http://b.example/r']);
|
||||
});
|
||||
|
||||
it('ignores a malformed rkey block', () => {
|
||||
expect(normalizeGlobalSettings({ rkey: 'nope' })).toEqual({
|
||||
rkey: { fallbackServers: [] },
|
||||
musicSignUrl: '',
|
||||
videoUploadConcurrency: 1,
|
||||
});
|
||||
expect(normalizeGlobalSettings(null)).toEqual({
|
||||
rkey: { fallbackServers: [] },
|
||||
musicSignUrl: '',
|
||||
videoUploadConcurrency: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('round-trips through save → disk → load', () => {
|
||||
const saved = saveGlobalSettings({
|
||||
rkey: { fallbackServers: ['https://r.example/rkey', 'bogus'] },
|
||||
});
|
||||
expect(saved.rkey.fallbackServers).toEqual(['https://r.example/rkey']);
|
||||
|
||||
const onDisk = JSON.parse(
|
||||
fs.readFileSync(path.join(tempDir, 'config', 'snowluma.json'), 'utf8'),
|
||||
);
|
||||
expect(onDisk).toEqual({
|
||||
rkey: { fallbackServers: ['https://r.example/rkey'] },
|
||||
musicSignUrl: '',
|
||||
videoUploadConcurrency: 1,
|
||||
});
|
||||
|
||||
expect(loadGlobalSettings().rkey.fallbackServers).toEqual(['https://r.example/rkey']);
|
||||
});
|
||||
|
||||
it('section-merges on save: a partial write never wipes a sibling section', () => {
|
||||
saveGlobalSettings({ rkey: { fallbackServers: ['https://r.example/rkey'] } });
|
||||
// A save touching only musicSignUrl must preserve the rkey servers, and
|
||||
// vice-versa.
|
||||
const after = saveGlobalSettings({ musicSignUrl: 'https://sign.example/card' });
|
||||
expect(after.rkey.fallbackServers).toEqual(['https://r.example/rkey']);
|
||||
expect(after.musicSignUrl).toBe('https://sign.example/card');
|
||||
expect(saveGlobalSettings({ rkey: { fallbackServers: [] } }).musicSignUrl).toBe(
|
||||
'https://sign.example/card',
|
||||
);
|
||||
});
|
||||
|
||||
it('normalizes + round-trips musicSignUrl (trimmed)', () => {
|
||||
expect(
|
||||
normalizeGlobalSettings({ musicSignUrl: ' https://s.example/c ' }).musicSignUrl,
|
||||
).toBe('https://s.example/c');
|
||||
const saved = saveGlobalSettings({ musicSignUrl: 'https://s.example/c' });
|
||||
expect(saved.musicSignUrl).toBe('https://s.example/c');
|
||||
expect(loadGlobalSettings().musicSignUrl).toBe('https://s.example/c');
|
||||
});
|
||||
|
||||
it.each([1, 2, 3, 4])('persists %i video connections and preserves partial saves', (value) => {
|
||||
expect(saveGlobalSettings({ videoUploadConcurrency: value }).videoUploadConcurrency).toBe(
|
||||
value,
|
||||
);
|
||||
saveGlobalSettings({ musicSignUrl: 'https://sign.example/card' });
|
||||
expect(loadGlobalSettings().videoUploadConcurrency).toBe(value);
|
||||
});
|
||||
|
||||
it.each([0, 5, -1, 1.5, '2', null, false])(
|
||||
'rejects invalid video concurrency %j without writing',
|
||||
(value) => {
|
||||
saveGlobalSettings({ videoUploadConcurrency: 3 });
|
||||
const file = path.join(tempDir, 'config', 'snowluma.json');
|
||||
const before = fs.readFileSync(file, 'utf8');
|
||||
expect(() => saveGlobalSettings({ videoUploadConcurrency: value })).toThrow(
|
||||
/integer from 1 to 4/,
|
||||
);
|
||||
expect(fs.readFileSync(file, 'utf8')).toBe(before);
|
||||
expect(() => normalizeGlobalSettings({ videoUploadConcurrency: value })).toThrow();
|
||||
},
|
||||
);
|
||||
|
||||
it('reports an invalid persisted concurrency instead of silently ignoring it', () => {
|
||||
fs.mkdirSync(path.join(tempDir, 'config'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'config', 'snowluma.json'),
|
||||
JSON.stringify({ videoUploadConcurrency: 99 }),
|
||||
);
|
||||
expect(() => loadGlobalSettings()).toThrow(/videoUploadConcurrency/);
|
||||
});
|
||||
|
||||
describe('migrateGlobalSettings (musicSignUrl copy-up)', () => {
|
||||
function writeOnebot(file: string, body: unknown) {
|
||||
const dir = path.join(tempDir, 'config');
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, file), JSON.stringify(body), 'utf8');
|
||||
}
|
||||
|
||||
it('lifts a legacy per-UIN musicSignUrl into the global store', () => {
|
||||
writeOnebot('onebot_123.json', { musicSignUrl: 'https://legacy.example/sign' });
|
||||
migrateGlobalSettings();
|
||||
expect(loadGlobalSettings().musicSignUrl).toBe('https://legacy.example/sign');
|
||||
});
|
||||
|
||||
it('is a no-op when the global value is already set', () => {
|
||||
saveGlobalSettings({ musicSignUrl: 'https://kept.example/sign' });
|
||||
writeOnebot('onebot_123.json', { musicSignUrl: 'https://legacy.example/sign' });
|
||||
migrateGlobalSettings();
|
||||
expect(loadGlobalSettings().musicSignUrl).toBe('https://kept.example/sign');
|
||||
});
|
||||
|
||||
it('takes the first file and tolerates differing/empty values across files', () => {
|
||||
writeOnebot('onebot.json', { musicSignUrl: '' }); // empty → skipped
|
||||
writeOnebot('onebot_111.json', { musicSignUrl: 'https://a.example/s' });
|
||||
writeOnebot('onebot_222.json', { musicSignUrl: 'https://b.example/s' });
|
||||
migrateGlobalSettings();
|
||||
// onebot.json sorts first but is empty; onebot_111 is the first non-empty.
|
||||
expect(loadGlobalSettings().musicSignUrl).toBe('https://a.example/s');
|
||||
});
|
||||
|
||||
it('does nothing when no legacy value exists', () => {
|
||||
writeOnebot('onebot_123.json', { networks: {} });
|
||||
migrateGlobalSettings();
|
||||
expect(loadGlobalSettings().musicSignUrl).toBe('');
|
||||
});
|
||||
|
||||
it('migrates a value carried by the base onebot.json', () => {
|
||||
writeOnebot('onebot.json', { musicSignUrl: 'https://base.example/s' });
|
||||
migrateGlobalSettings();
|
||||
expect(loadGlobalSettings().musicSignUrl).toBe('https://base.example/s');
|
||||
});
|
||||
|
||||
it('skips an invalid earlier value and migrates the first VALID one', () => {
|
||||
writeOnebot('onebot.json', { musicSignUrl: 'not-a-url' }); // earlier-sorting garbage
|
||||
writeOnebot('onebot_111.json', { musicSignUrl: 'https://real.example/s' });
|
||||
migrateGlobalSettings();
|
||||
expect(loadGlobalSettings().musicSignUrl).toBe('https://real.example/s');
|
||||
});
|
||||
|
||||
it('does NOT re-copy after an intentional clear-to-empty (presence guard)', () => {
|
||||
// Operator cleared the global value to '' (the key is present in the file)…
|
||||
saveGlobalSettings({ musicSignUrl: '' });
|
||||
// …while a stale legacy value still lingers.
|
||||
writeOnebot('onebot_123.json', { musicSignUrl: 'https://legacy.example/sign' });
|
||||
migrateGlobalSettings();
|
||||
expect(loadGlobalSettings().musicSignUrl).toBe(''); // stays cleared, not reverted
|
||||
});
|
||||
|
||||
it('is a safe no-op when config/ does not exist', () => {
|
||||
// fresh cwd, no config dir at all
|
||||
expect(() => migrateGlobalSettings()).not.toThrow();
|
||||
expect(loadGlobalSettings().musicSignUrl).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to defaults when the file is corrupt', () => {
|
||||
fs.mkdirSync(path.join(tempDir, 'config'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tempDir, 'config', 'snowluma.json'), '{ not json', 'utf8');
|
||||
expect(loadGlobalSettings()).toEqual({
|
||||
rkey: { fallbackServers: [] },
|
||||
musicSignUrl: '',
|
||||
videoUploadConcurrency: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user