import { createGatewayPostgresConnector } from '@sammo-ts/infra'; import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; import { createGatewayReleaseRepository } from '../src/orchestrator/gatewayReleaseRepository.js'; const databaseUrl = process.env.GATEWAY_RELEASE_DATABASE_URL; const describeDatabase = describe.runIf(Boolean(databaseUrl)); describeDatabase('gateway release operation persistence', () => { const connector = createGatewayPostgresConnector({ url: databaseUrl ?? '' }); const repository = createGatewayReleaseRepository(connector.prisma); beforeAll(async () => { await connector.connect(); }); afterEach(async () => { await connector.prisma.gatewayReleaseOperation.deleteMany(); await connector.prisma.gatewayReleaseState.deleteMany(); }); afterAll(async () => { await connector.disconnect(); }); it('serializes releases, fences publication by lease owner, and records rollback state', async () => { const operation = await repository.createOperation({ type: 'DEPLOY', sourceMode: 'BRANCH', sourceRef: 'main', requestedBy: 'admin-a', }); await expect( repository.createOperation({ type: 'ROLLBACK', sourceMode: 'COMMIT', sourceRef: '2222222222222222222222222222222222222222', requestedBy: 'admin-b', }) ).rejects.toMatchObject({ code: 'P2002' }); const now = new Date('2030-01-01T00:00:00.000Z'); await expect( repository.claimNextOperation(now, { ownerId: 'controller-a', durationMs: 1_000 }) ).resolves.toMatchObject({ id: operation.id, attempts: 1, leaseOwner: 'controller-a', }); await expect(repository.pinOperationResolvedCommit(operation.id, 'controller-a', 'a'.repeat(40))).resolves.toBe( true ); await expect( repository.publishRelease(operation.id, 'stale-controller', { commitSha: 'a'.repeat(40), workspace: '/srv/sammo/new', }) ).rejects.toThrow('lease lost before publish'); await expect( repository.publishRelease(operation.id, 'controller-a', { commitSha: 'a'.repeat(40), workspace: '/srv/sammo/new', previousCommitSha: 'b'.repeat(40), previousWorkspace: '/srv/sammo/old', }) ).resolves.toMatchObject({ activeCommitSha: 'a'.repeat(40), activeWorkspace: '/srv/sammo/new', previousCommitSha: 'b'.repeat(40), previousWorkspace: '/srv/sammo/old', }); await expect( repository.completeOperation( operation.id, 'SUCCEEDED', { resolvedCommitSha: 'a'.repeat(40), error: null }, 'controller-a' ) ).resolves.toMatchObject({ status: 'SUCCEEDED' }); }); it('reclaims an expired release while preserving its pinned commit', async () => { const operation = await repository.createOperation({ type: 'DEPLOY', sourceMode: 'BRANCH', sourceRef: 'main', requestedBy: 'admin', }); const now = new Date('2030-01-01T00:00:00.000Z'); await repository.claimNextOperation(now, { ownerId: 'controller-a', durationMs: 1_000 }); await repository.pinOperationResolvedCommit(operation.id, 'controller-a', 'c'.repeat(40)); await expect( repository.claimNextOperation(new Date(now.getTime() + 1_001), { ownerId: 'controller-b', durationMs: 1_000, }) ).resolves.toMatchObject({ id: operation.id, attempts: 2, leaseOwner: 'controller-b', resolvedCommitSha: 'c'.repeat(40), }); await expect(repository.renewOperationLease(operation.id, 'controller-a', now, 1_000)).resolves.toBe(false); }); });