init
This commit is contained in:
commit
0964819cdc
34 files changed
+7912
No files matched your search
@@ -0,0 +1,97 @@
|
||||
import { strict as assert } from 'node:assert'
|
||||
import { test } from 'node:test'
|
||||
|
||||
import { Config, SETTING_KEYS, validateSettings } from '../index.js'
|
||||
|
||||
/**
|
||||
* The load-time contract.
|
||||
*
|
||||
* `apply` never runs when Cordis rejects a plugin's `Config`, so these are the
|
||||
* only failures a plugin cannot report from inside itself — the profile simply
|
||||
* refuses to boot. The rest of the suite calls `apply` directly and would never
|
||||
* notice, which is exactly why this file exists separately.
|
||||
*/
|
||||
|
||||
test('Config is a Standard Schema, which is what Cordis requires', () => {
|
||||
assert.ok(Config !== undefined, 'Config must be exported')
|
||||
assert.ok(Config['~standard'] !== undefined, 'Config must implement the Standard Schema interface')
|
||||
})
|
||||
|
||||
test('Config fills every field from an empty input, so apply can rely on defaults', () => {
|
||||
const filled = Config({})
|
||||
for (const key of SETTING_KEYS) {
|
||||
assert.ok(key in filled, `${key} has no default and would be undefined in apply`)
|
||||
}
|
||||
assert.equal(filled.embeddingApiBase, 'http://127.0.0.1:11434')
|
||||
assert.equal(filled.embeddingDimensions, 768)
|
||||
assert.equal(filled.syncBudgetMs, 20_000)
|
||||
assert.deepEqual(filled.exclude, [])
|
||||
assert.deepEqual(filled.extensions, [])
|
||||
})
|
||||
|
||||
test('Config keeps an explicit value and fills the rest', () => {
|
||||
const filled = Config({ embeddingApiBase: 'http://10.50.1.2:11434', embeddingConcurrency: 8 })
|
||||
assert.equal(filled.embeddingApiBase, 'http://10.50.1.2:11434')
|
||||
assert.equal(filled.embeddingConcurrency, 8)
|
||||
assert.equal(filled.embeddingBatchSize, 16)
|
||||
assert.equal(filled.embeddingTimeoutMs, 30_000)
|
||||
assert.equal(filled.embeddingKeepAlive, '5m')
|
||||
// The per-excerpt cap equals the chunk cap, so chunks arrive whole.
|
||||
assert.equal(filled.excerptLines, 80)
|
||||
assert.equal(filled.totalExcerptLines, 400)
|
||||
assert.equal(filled.maxChunkLines, 80)
|
||||
})
|
||||
|
||||
test('a wrongly typed value fails loudly at load rather than silently in apply', () => {
|
||||
assert.throws(() => Config({ embeddingDimensions: 'seven hundred' }))
|
||||
assert.throws(() => Config({ exclude: 'src/generated/' }))
|
||||
assert.throws(() => Config({ embeddingApiBase: { host: 'localhost' } }))
|
||||
})
|
||||
|
||||
test('every declared setting is a real schema key, so none is dead config', () => {
|
||||
const filled = Config({})
|
||||
for (const key of SETTING_KEYS) {
|
||||
assert.ok(Object.hasOwn(filled, key), `${key} is listed in SETTING_KEYS but is not in the schema`)
|
||||
}
|
||||
// And the reverse: no schema field is missing from the documented list, or
|
||||
// `configure` would reject a setting the schema happily accepts.
|
||||
for (const key of Object.keys(filled)) {
|
||||
assert.ok(SETTING_KEYS.includes(key), `${key} is in the schema but not in SETTING_KEYS`)
|
||||
}
|
||||
})
|
||||
|
||||
/** The defaults plus an override, as the settings provider would resolve them. */
|
||||
function settingsWith(patch) {
|
||||
return { ...Config({}), ...patch }
|
||||
}
|
||||
|
||||
test('validateSettings accepts the defaults', () => {
|
||||
assert.doesNotThrow(() => validateSettings(Config({})))
|
||||
})
|
||||
|
||||
test('validateSettings rejects an endpoint that is not a URL', () => {
|
||||
// A bare host:port parses successfully — `localhost:` is read as the scheme —
|
||||
// so it is the protocol check that actually catches the common mistake.
|
||||
assert.throws(() => validateSettings(settingsWith({ embeddingApiBase: 'localhost:11434' })), /must be http or https/)
|
||||
assert.throws(() => validateSettings(settingsWith({ embeddingApiBase: '' })), /not a URL/)
|
||||
assert.doesNotThrow(() => validateSettings(settingsWith({ embeddingApiBase: 'http://10.50.1.2:11434' })))
|
||||
})
|
||||
|
||||
test('validateSettings rejects a non-http protocol', () => {
|
||||
assert.throws(() => validateSettings(settingsWith({ embeddingApiBase: 'file:///tmp/socket' })), /must be http or https/)
|
||||
})
|
||||
|
||||
test('validateSettings rejects values that would break the pipeline', () => {
|
||||
assert.throws(() => validateSettings(settingsWith({ embeddingDimensions: 0 })), /positive number/)
|
||||
assert.throws(() => validateSettings(settingsWith({ embeddingConcurrency: 0 })), /at least 1/)
|
||||
assert.throws(() => validateSettings(settingsWith({ excerptLines: 0 })), /at least 1/)
|
||||
assert.throws(() => validateSettings(settingsWith({ syncBudgetMs: -1 })), /cannot be negative/)
|
||||
})
|
||||
|
||||
test('validateSettings rejects cross-field contradictions the schema cannot see', () => {
|
||||
assert.throws(() => validateSettings(settingsWith({ defaultLimit: 50, maxLimit: 10 })), /cannot exceed maxLimit/)
|
||||
assert.throws(() => validateSettings(settingsWith({ chunkLines: 100, maxChunkLines: 80 })), /below chunkLines/)
|
||||
// An overlap at or above the cap means a split never advances.
|
||||
assert.throws(() => validateSettings(settingsWith({ chunkLines: 10, maxChunkLines: 10, overlapLines: 10 })), /never advances/)
|
||||
assert.throws(() => validateSettings(settingsWith({ minSimilarity: 1.5 })), /between 0 and 1/)
|
||||
})
|
||||
Reference in new issue
Block a user