fix reserved turn daemon api serialization

This commit is contained in:
2026-07-26 19:23:25 +00:00
parent d1e9d5fc75
commit 8f1fe73bad
12 changed files with 1003 additions and 74 deletions
@@ -11,6 +11,7 @@ import {
const databaseUrl = process.env.RESERVED_TURN_DATABASE_URL;
const describeIntegration = databaseUrl ? describe : describe.skip;
const GENERAL_ID = 2_147_400_001;
const REVISION_ZERO_GENERAL_ID = 2_147_400_002;
describeIntegration('reserved turn queue revision integration', () => {
const connector = databaseUrl ? createGamePostgresConnector({ url: databaseUrl }) : null;
@@ -20,16 +21,18 @@ describeIntegration('reserved turn queue revision integration', () => {
return;
}
await connector.connect();
await connector.prisma.generalTurn.deleteMany({ where: { generalId: GENERAL_ID } });
await connector.prisma.generalTurnRevision.deleteMany({ where: { generalId: GENERAL_ID } });
const generalIds = [GENERAL_ID, REVISION_ZERO_GENERAL_ID];
await connector.prisma.generalTurn.deleteMany({ where: { generalId: { in: generalIds } } });
await connector.prisma.generalTurnRevision.deleteMany({ where: { generalId: { in: generalIds } } });
});
afterAll(async () => {
if (!connector) {
return;
}
await connector.prisma.generalTurn.deleteMany({ where: { generalId: GENERAL_ID } });
await connector.prisma.generalTurnRevision.deleteMany({ where: { generalId: GENERAL_ID } });
const generalIds = [GENERAL_ID, REVISION_ZERO_GENERAL_ID];
await connector.prisma.generalTurn.deleteMany({ where: { generalId: { in: generalIds } } });
await connector.prisma.generalTurnRevision.deleteMany({ where: { generalId: { in: generalIds } } });
await connector.disconnect();
});
@@ -60,4 +63,46 @@ describeIntegration('reserved turn queue revision integration', () => {
expect(['che_훈련', 'che_사기진작']).toContain(snapshot.turns[0]?.action);
expect(await connector.prisma.generalTurn.count({ where: { generalId: GENERAL_ID } })).toBe(30);
});
it('uses an unlocked revision-zero lease row as the first API revision', async () => {
if (!connector) {
throw new Error('integration connector is unavailable');
}
await connector.prisma.generalTurnRevision.create({
data: {
generalId: REVISION_ZERO_GENERAL_ID,
revision: 0,
leaseOwner: null,
leaseExpiresAt: null,
},
});
const result = await connector.prisma.$transaction((transaction) =>
setGeneralTurn(
transaction as unknown as DatabaseClient,
REVISION_ZERO_GENERAL_ID,
0,
'che_훈련',
{},
0
)
);
expect(result.revision).toBe(1);
expect(
await connector.prisma.generalTurn.count({
where: { generalId: REVISION_ZERO_GENERAL_ID },
})
).toBe(30);
expect(
await connector.prisma.generalTurnRevision.findUnique({
where: { generalId: REVISION_ZERO_GENERAL_ID },
})
).toMatchObject({
revision: 1,
leaseOwner: null,
leaseExpiresAt: null,
});
});
});
+30 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import type { DatabaseClient, GeneralTurnRow, NationTurnRow } from '../src/context.js';
import {
@@ -338,4 +338,33 @@ describe('reservedTurns', () => {
const noOpPush = await shiftNationTurns(db, 5, 12, 12, repeated.revision);
expect(noOpPush).toEqual(repeated);
});
it('rejects an API writer while the daemon holds the queue lease without touching turns', async () => {
const deleteMany = vi.fn(async () => ({}));
const createMany = vi.fn(async () => ({}));
const db = {
generalTurnRevision: {
updateMany: vi.fn(async () => ({ count: 0 })),
createMany: vi.fn(async () => ({ count: 0 })),
findUnique: vi.fn(async () => ({
generalId: 9,
revision: 0,
leaseOwner: 'daemon-1',
leaseExpiresAt: new Date(Date.now() + 60_000),
updatedAt: new Date(),
})),
},
generalTurn: {
findMany: vi.fn(async () => []),
deleteMany,
createMany,
},
} as unknown as DatabaseClient;
await expect(setGeneralTurn(db, 9, 0, 'che_훈련', {}, 0)).rejects.toBeInstanceOf(
ReservedTurnRevisionConflictError
);
expect(deleteMany).not.toHaveBeenCalled();
expect(createMany).not.toHaveBeenCalled();
});
});