mirror of
https://github.com/fancive/obsidian-parallel-reader.git
synced 2026-07-22 06:53:43 +00:00
P1: - cli: pin codex --sandbox read-only (defeat prompt injection) - settings: clear apiKey/apiHeaders on provider preset switch - main/view: render error state when LLM returns empty cards - schema/cli: do not log raw model output; UI errors expose length only P2: - cli: pass --model to Claude Code when settings.model set - settings-tab: add CLI timeout input + normalizeCliTimeoutMs (min 1s) - view: export source link uses [[path|basename]] - view: export failures surface a localized Notice P3: - schema: cardUntitled fallback now respects uiLanguage - generation-job-manager: error codes localized via main.ts - batch: settled guard on promptForBatchFolder modal - README: drop stale e2e validator install note Codex review pass: - provider-parsers: forward settings to normalizeCardsPayload - generation-job-manager: classifyGenerationError regex covers new EN/zh schema-error messages - cli: do NOT pass --model to codex (DEFAULT_SETTINGS.model is a Claude name and would break codex; codex relies on its own config) Tests: cli (codex sandbox + claude --model + classify zh/en), settings (applyApiProviderPreset isolation + normalizeCliTimeoutMs), schema (language-aware fallback title), test-exports (new symbols). Change-Id: I9f4c21f9299e1a9bf166f69f712859854482fe92
112 lines
4.7 KiB
JavaScript
112 lines
4.7 KiB
JavaScript
const { assert, t, baseSettings } = require('./test-setup');
|
|
|
|
// modelForApi strips provider prefix
|
|
assert.strictEqual(t.modelForApi({ ...baseSettings, model: 'openai/gpt-5.1' }), 'gpt-5.1', 'strip openai prefix');
|
|
assert.strictEqual(t.modelForApi({ ...baseSettings, model: 'gpt-5.1' }), 'gpt-5.1', 'no prefix pass-through');
|
|
assert.throws(() => t.modelForApi({ ...baseSettings, model: '' }), /Model/, 'empty model throws');
|
|
|
|
// getApiBaseUrl
|
|
assert.strictEqual(
|
|
t.getApiBaseUrl({ ...baseSettings, apiBaseUrl: 'https://custom.ai/v1/' }),
|
|
'https://custom.ai/v1',
|
|
'strips trailing slash',
|
|
);
|
|
assert.throws(
|
|
() => t.getApiBaseUrl({ ...baseSettings, apiProvider: 'custom-openai', apiBaseUrl: '' }),
|
|
/Custom provider/,
|
|
'custom provider without url throws',
|
|
);
|
|
|
|
// generationFingerprint stability
|
|
const fp1 = t.generationFingerprint(baseSettings);
|
|
const fp2 = t.generationFingerprint({ ...baseSettings });
|
|
assert.strictEqual(fp1, fp2, 'same settings = same fingerprint');
|
|
assert.notStrictEqual(
|
|
fp1,
|
|
t.generationFingerprint({ ...baseSettings, model: 'other' }),
|
|
'different model = different fp',
|
|
);
|
|
assert.notStrictEqual(
|
|
fp1,
|
|
t.generationFingerprint({ ...baseSettings, apiMaxTokens: 8192 }),
|
|
'different tokens = different fp',
|
|
);
|
|
assert.strictEqual(t.normalizeStreamingTimeoutMs('30000'), 30000, 'streaming timeout accepts numeric strings');
|
|
assert.strictEqual(t.normalizeStreamingTimeoutMs(999), 120000, 'streaming timeout below minimum falls back');
|
|
assert.strictEqual(t.normalizeStreamingTimeoutMs('bad'), 120000, 'streaming timeout rejects non-numeric values');
|
|
assert.strictEqual(
|
|
t.normalizeSettings({ ...baseSettings, streamingTimeoutMs: 500 }).streamingTimeoutMs,
|
|
120000,
|
|
'normalizeSettings protects invalid streaming timeout values',
|
|
);
|
|
|
|
// normalizeSettings does not mutate its input
|
|
const settingsInput = { ...baseSettings, streamingTimeoutMs: 500, minCards: -1, maxCards: 999 };
|
|
const settingsInputCopy = JSON.parse(JSON.stringify(settingsInput));
|
|
const normalized = t.normalizeSettings(settingsInput);
|
|
assert.deepStrictEqual(settingsInput, settingsInputCopy, 'normalizeSettings does not mutate input object');
|
|
assert.notStrictEqual(normalized, settingsInput, 'normalizeSettings returns a new object');
|
|
assert.strictEqual(normalized.minCards, 1, 'normalizeSettings clamps minCards');
|
|
assert.strictEqual(normalized.maxCards, 30, 'normalizeSettings clamps maxCards to max 30');
|
|
|
|
// cacheEntryMatches
|
|
const crypto = require('crypto');
|
|
const hash = crypto.createHash('sha1').update('test', 'utf8').digest('hex');
|
|
assert.strictEqual(
|
|
t.cacheEntryMatches(
|
|
{
|
|
schemaVersion: t.CACHE_SCHEMA_VERSION,
|
|
contentHash: hash,
|
|
settingsHash: t.generationFingerprint(baseSettings),
|
|
},
|
|
'test',
|
|
baseSettings,
|
|
),
|
|
true,
|
|
'matching entry returns true',
|
|
);
|
|
assert.strictEqual(t.cacheEntryMatches(null, 'test', baseSettings), false, 'null entry returns false');
|
|
assert.strictEqual(
|
|
t.shouldSkipBatchFile(
|
|
{
|
|
schemaVersion: t.CACHE_SCHEMA_VERSION,
|
|
contentHash: hash,
|
|
settingsHash: t.generationFingerprint(baseSettings),
|
|
},
|
|
'test',
|
|
baseSettings,
|
|
),
|
|
true,
|
|
'fresh cache entry is skipped during batch',
|
|
);
|
|
assert.strictEqual(t.shouldSkipBatchFile(null, 'test', baseSettings), false, 'missing cache entry is not skipped');
|
|
|
|
// normalizeCliTimeoutMs (mirrors normalizeStreamingTimeoutMs)
|
|
assert.strictEqual(t.normalizeCliTimeoutMs('60000'), 60000, 'cli timeout accepts numeric strings');
|
|
assert.strictEqual(t.normalizeCliTimeoutMs(999), 120000, 'cli timeout below minimum falls back to default');
|
|
assert.strictEqual(t.normalizeCliTimeoutMs('bad'), 120000, 'cli timeout rejects non-numeric values');
|
|
assert.strictEqual(
|
|
t.normalizeSettings({ ...baseSettings, cliTimeoutMs: 500 }).cliTimeoutMs,
|
|
120000,
|
|
'normalizeSettings protects invalid cli timeout values',
|
|
);
|
|
|
|
// applyApiProviderPreset clears credentials but keeps preset baseUrl (security: prevent cross-provider key leak)
|
|
{
|
|
const previous = {
|
|
...baseSettings,
|
|
apiProvider: 'anthropic',
|
|
apiKey: 'sk-ant-secret',
|
|
apiHeaders: 'Authorization: Bearer leaked',
|
|
apiBaseUrl: 'https://api.anthropic.com/v1',
|
|
};
|
|
const switched = t.applyApiProviderPreset(previous, 'openai');
|
|
assert.strictEqual(switched.apiKey, '', 'switching provider clears apiKey');
|
|
assert.strictEqual(switched.apiHeaders, '', 'switching provider clears apiHeaders');
|
|
assert.notStrictEqual(switched.apiBaseUrl, '', 'switching provider keeps preset baseUrl (not blank)');
|
|
assert.ok(switched.apiBaseUrl.includes('openai'), 'switched apiBaseUrl matches new openai preset');
|
|
assert.notStrictEqual(switched, previous, 'applyApiProviderPreset returns new object');
|
|
assert.strictEqual(previous.apiKey, 'sk-ant-secret', 'applyApiProviderPreset does not mutate input');
|
|
}
|
|
|
|
console.log('settings tests passed');
|