Files
deepseek-harness/packages/settings/settings/tests/invariant.spec.ts
Yichen Jiang f44b4db1f2 fix(settings): harden seam and provider per review findings
Confirmed and fixed, each with a regression test that failed first:

- Concurrent update() lost patches (merge over one stale snapshot):
  per-namespace serialized write queues; a failed write cannot poison
  the queue for later writers.
- Fixed-name .tmp write followed planted symlinks and kept stale modes:
  random-suffix sibling, exclusive-create (wx), 0600, cleanup on
  failure, then rename.
- A throwing settings/updated listener escaped commit and permanently
  wedged the provider reload chain (rejected refreshTask): commit now
  contains listener failures (INVARIANT-coded errors still propagate),
  async watcher rejections are adopted and contained
  (watch callbacks are officially void | Promise<void>), and the
  provider chains refreshes on a settled tail with an error log.
- No way to remove a user override: scope/service replace(section)
  sets the user section wholesale; replace({}) re-inherits base and
  schema defaults.
- The three-primitive provider contract did not hold (base never
  called load()): the base Service.init loads and publishes once;
  settings-local delegates via yield* super[Service.init]().
- Dispose did not quiesce: teardown flags closed, closes the watcher,
  then awaits queued/in-flight reloads; closed is re-checked across
  await points.
- Invariant now checks the authoritative relation with the seam's own
  deepEqualJson: emitted next must equal settings.get(ns), and
  next/prev must differ structurally (cosmokit dependency dropped).
- New docs/core-data-structures/settings.{md,zh.md} with type-equiv
  blocks + manifest entries; catalog types moved from exemptions to
  LINK_MAP; website page registered.

Both packages stay at per-file 100% coverage.
2026-07-29 10:19:32 +08:00

53 lines
2.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import z from 'schemastery'
import InvariantService from '@deepseek-ai/dsh-invariants'
import * as SettingsInvariant from '../src/invariant.ts'
import { settingsNamespace } from '../src/index.ts'
import { MemorySettings } from './memory.ts'
async function setup(withProvider: boolean): Promise<Context> {
const ctx = new Context()
await ctx.plugin(InvariantService)
await ctx.plugin(SettingsInvariant)
if (withProvider) await ctx.plugin(MemorySettings)
return ctx
}
describe('settings invariants', () => {
it('fails a settings/updated emission without a live settings service', async () => {
const ctx = await setup(false)
expect(() => {
ctx.emit('settings/updated', settingsNamespace('ghost'), { a: 1 }, { a: 2 }, 'provider')
}).toThrow(/without a live settings service/)
})
it('fails a settings/updated emission for an unregistered namespace', async () => {
const ctx = await setup(true)
expect(() => {
ctx.emit('settings/updated', settingsNamespace('ghost'), { a: 1 }, { a: 2 }, 'provider')
}).toThrow(/unregistered/)
})
it('fails a settings/updated emission without a resolved-value change', async () => {
const ctx = await setup(true)
ctx.settings.register(settingsNamespace('ui-theme'), z.object({
theme: z.string().default('dark'),
}))
expect(() => {
ctx.emit('settings/updated', settingsNamespace('ui-theme'), { theme: 'dark' }, { theme: 'dark' }, 'update')
}).toThrow(/without a resolved-value change/)
})
it('fails a settings/updated emission whose value diverges from the authoritative state', async () => {
const ctx = await setup(true)
ctx.settings.register(settingsNamespace('ui-theme'), z.object({
theme: z.string().default('dark'),
}))
// Fabricated next ≠ the service's current resolved value ({theme: 'dark'}).
expect(() => {
ctx.emit('settings/updated', settingsNamespace('ui-theme'), { theme: 'forged' }, { theme: 'dark' }, 'update')
}).toThrow(/authoritative/)
})
})