vpp-ai-platform/packages/services/test/relational.test.ts
Thomas Bayes e7196bc88a M1: close gaps — CI, time-series/relational stores, ingestion skeleton, Python 3.11 pin
- .github/workflows/ci.yml: TS job (typecheck, re-export contracts, drift
  check, vitest) and Python job (3.11, regenerate pydantic models, drift
  check, pytest) — the dual-side contract test now actually runs in CI.
- packages/services: MemoryTimeSeriesStore (append-only daily-curve
  revisions), MemoryRepository + ResourceRegistry (schema-validated,
  optimistic versioning), IngestionPipeline (snapshot → quality gate →
  store or quarantine). In-memory reference semantics; persistent adapters
  arrive with M3 like the ledger.
- skills-py: .python-version + pyproject requires-python >=3.11; codegen
  script asserts interpreter version and runs the generator as a module.
- typecheck scripts per package and in root `check`; README status and dev
  setup; CLAUDE.md current-phase note.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoYoGYzHkFyv3ALenkRPhA
2026-09-01 21:58:49 -04:00

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)
})
})