import { join } from 'path' /** * The board save claim on postgres (ADR-0098, architect ruling 2026-09-27): postgres keeps microseconds * while the editor holds milliseconds, and two saves that read the same moment arrive together. Concurrency * is proven on postgres, where the row lock decides; sqlite's single writer cannot show it. * * Runs when TEST_POSTGRES_URL names an empty database; the schema is dropped and rebuilt. */ const POSTGRES = process.env.TEST_POSTGRES_URL require('@things-factory/env').config.set('ormconfig', { type: 'postgres' }) const { DataSource } = require('typeorm') const { entityClosure } = require(join(__dirname, '..', '..', 'ops-master', 'test', 'support', 'database.cjs')) const DIST = join(__dirname, '..', 'dist-server') const { Board } = require(join(DIST, 'service', 'board', 'board.js')) const { claimUnchangedSince } = require(join(DIST, 'service', 'board', 'board-claim.js')) const shell = require('@things-factory/shell') const onPostgres = POSTGRES ? describe : describe.skip onPostgres('board save claim (postgres, TEST_POSTGRES_URL)', () => { let db: any let here: any beforeAll(async () => { db = new DataSource({ type: 'postgres', url: POSTGRES, synchronize: true, dropSchema: true, namingStrategy: new shell.NamingStrategy(), entities: entityClosure([Board]), logging: false }) await db.initialize() shell.addDataSource('default', db) here = await db.getRepository(shell.Domain).save({ name: 'here', subdomain: 'here' }) }) afterAll(async () => { await db?.destroy() }) const aBoard = async (name: string) => { const saved = await db.getRepository(Board).save({ domain: here, name, model: '{"width":1,"height":1}' }) return db.getRepository(Board).findOneBy({ id: saved.id }) } test('★ a stored time with microseconds is claimed by the millisecond the editor holds', async () => { const board = await aBoard('micro') await db.query(`UPDATE boards SET updated_at = '2026-09-27 01:02:03.456789' WHERE id = $1`, [board.id]) const read = (await db.getRepository(Board).findOneBy({ id: board.id })).updatedAt expect(new Date(read).getMilliseconds()).toBe(456) expect(await claimUnchangedSince(undefined, here.id, board.id, new Date(read))).toBe(true) }) test('★ two saves that read the same moment: exactly one is claimed', async () => { const board = await aBoard('race') const read = new Date(board.updatedAt) const attempt = () => db.transaction(async (tx: any) => { const claimed = await claimUnchangedSince(tx, here.id, board.id, read) /* Hold the lock a moment, as a real save does while it writes the model. */ await new Promise(resolve => setTimeout(resolve, 50)) return claimed }) const results = await Promise.all([attempt(), attempt(), attempt()]) expect(results.filter(Boolean).length).toBe(1) }) test('a save that read an older one is refused', async () => { const board = await aBoard('older') const read = new Date(board.updatedAt) expect(await claimUnchangedSince(undefined, here.id, board.id, read)).toBe(true) expect(await claimUnchangedSince(undefined, here.id, board.id, read)).toBe(false) }) })