61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
|
|
import { readFileSync } from 'node:fs'
|
||
|
|
import { fileURLToPath } from 'node:url'
|
||
|
|
import { describe, expect, it } from 'vitest'
|
||
|
|
import type { ResourceProfile } from '@vpp/domain'
|
||
|
|
import { RepositoryConcurrencyError, createResourceRegistry } from '../src/relational.js'
|
||
|
|
|
||
|
|
const fixture: ResourceProfile = JSON.parse(
|
||
|
|
readFileSync(
|
||
|
|
fileURLToPath(new URL('../../../contracts/fixtures/resource_profile/storage.json', import.meta.url)),
|
||
|
|
'utf8',
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
const clock = () => '2026-03-14T08:00:00Z'
|
||
|
|
|
||
|
|
describe('relational store: resource registry (资源台账)', () => {
|
||
|
|
it('puts and gets a schema-valid row with version 1', () => {
|
||
|
|
const reg = createResourceRegistry(clock)
|
||
|
|
const row = reg.put(fixture)
|
||
|
|
expect(row.key).toBe('res-storage-01')
|
||
|
|
expect(row.version).toBe(1)
|
||
|
|
expect(reg.get('res-storage-01')?.value).toEqual(fixture)
|
||
|
|
expect(reg.get('missing')).toBeUndefined()
|
||
|
|
})
|
||
|
|
|
||
|
|
it('rejects rows that fail the domain schema (no unvalidated master data)', () => {
|
||
|
|
const reg = createResourceRegistry(clock)
|
||
|
|
expect(() => reg.put({ ...fixture, rated_power_mw: 10 as unknown as string })).toThrow()
|
||
|
|
expect(() => reg.put({ ...fixture, type: 'WINDMILL' as never })).toThrow()
|
||
|
|
expect(reg.list()).toHaveLength(0)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('enforces optimistic concurrency when expectedVersion is supplied', () => {
|
||
|
|
const reg = createResourceRegistry(clock)
|
||
|
|
reg.put(fixture, 0)
|
||
|
|
expect(() => reg.put({ ...fixture, reliability_score: '0.90' }, 0)).toThrow(
|
||
|
|
RepositoryConcurrencyError,
|
||
|
|
)
|
||
|
|
const updated = reg.put({ ...fixture, reliability_score: '0.90' }, 1)
|
||
|
|
expect(updated.version).toBe(2)
|
||
|
|
expect(updated.value.reliability_score).toBe('0.90')
|
||
|
|
})
|
||
|
|
|
||
|
|
it('allows unconditional replace for master-data loads', () => {
|
||
|
|
const reg = createResourceRegistry(clock)
|
||
|
|
reg.put(fixture)
|
||
|
|
const row = reg.put({ ...fixture, name: 'renamed' })
|
||
|
|
expect(row.version).toBe(2)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('lists rows sorted by key and isolates stored values from mutation', () => {
|
||
|
|
const reg = createResourceRegistry(clock)
|
||
|
|
reg.put({ ...fixture, resource_id: 'res-b' })
|
||
|
|
reg.put({ ...fixture, resource_id: 'res-a' })
|
||
|
|
expect(reg.list().map((r) => r.key)).toEqual(['res-a', 'res-b'])
|
||
|
|
const row = reg.get('res-a')!
|
||
|
|
row.value.name = 'mutated'
|
||
|
|
expect(reg.get('res-a')!.value.name).toBe(fixture.name)
|
||
|
|
})
|
||
|
|
})
|