test: cover saveEach and schedule config normalization

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViTUkt4czrvZdxdWwxqRAQ
This commit is contained in:
Diego Imbert
2026-09-11 17:29:17 +02:00
co-authored by Claude Opus 5
parent 594475815f
commit 92a6ab6c4e
2 changed files with 71 additions and 0 deletions
@@ -0,0 +1,40 @@
import { describe, it, expect } from 'vitest'
import { normalizeScheduleCfg, scheduleCfgOf, scheduleFormOf } from './scheduleCfg'
// A deployed schedule as the API returns it: server fields the form never shows, a short cron.
const deployed = {
path: 'u/me/s',
schedule: '0 0 12 * *',
timezone: 'UTC',
script_path: 'u/me/script',
is_flow: false,
args: { x: 'a' },
enabled: true,
on_failure: 'script/hub/1/workspace-or-schedule-error-handler-slack',
on_failure_extra_args: { channel: '#alerts' },
on_recovery: null,
summary: '',
extra_perms: {},
edited_by: 'me',
edited_at: '2026-09-11T00:00:00Z',
workspace_id: 'w',
email: 'me@example.com'
}
describe('schedule config normalization', () => {
// The deployed side is kept in this shape and compared with what the form hands back: if the
// two drift, a schedule reads as edited the moment it is opened.
it('is what the form hands back after loading it, and idempotent', () => {
const once = normalizeScheduleCfg(deployed)
expect(scheduleCfgOf(scheduleFormOf(once))).toEqual(once)
expect(JSON.stringify(normalizeScheduleCfg(once))).toBe(JSON.stringify(once))
expect(once).toMatchObject({
schedule: '0 0 12 * * *',
on_failure: 'script/hub/1/workspace-or-schedule-error-handler-slack',
on_failure_extra_args: { channel: '#alerts' },
on_recovery: undefined,
summary: undefined
})
expect(once).not.toHaveProperty('edited_by')
})
})
+31
View File
@@ -162,6 +162,37 @@ describe('item store: commands', () => {
expect(a.writes).toHaveLength(1)
})
it('saves items in turn, all busy from the call, and stops at the first failure', async () => {
const rows = fakeRows()
const store = createItemStore(rows.port)
const written: string[] = []
const a = adapter({ deployed: deployedRes }, async (ctx) => {
written.push(ctx.workspace)
if (ctx.workspace === 'w1') throw new Error('refused')
})
const acquire = (ws: string) =>
store.acquire(
{ workspace: ws, kind: 'resource', path: 'u/me/r' },
{ workspace: ws, path: 'u/me/r' },
a
).handle
const [first, second] = [acquire('w1'), acquire('w2')]
await settle()
first.value = { ...deployedRes, description: 'one' }
second.value = { ...deployedRes, description: 'two' }
const saving = store.saveEach([first, second])
expect([first.busy, second.busy]).toEqual([true, true])
const outcomes = await saving
expect(outcomes).toEqual([
{ ok: false, error: 'refused' },
{ ok: false, error: 'Not saved', skipped: true }
])
expect(written).toEqual(['w1'])
expect([first.dirty, second.dirty, second.busy]).toEqual([true, true, false])
})
it('holds a toggle behind a save, and keeps an unrelated edit through the toggle', async () => {
type Sched = { path: string; enabled: boolean; summary: string }
const rows = fakeRows()