fix: repair existing test failures
This commit is contained in:
@@ -524,10 +524,12 @@ liveDescribe('auction worker durable recovery', () => {
|
||||
expect(reopened).toMatchObject({ status: 'OPEN' });
|
||||
expect(reopened!.closeAt.getTime()).toBeGreaterThan(extensionAuction.closeAt.getTime());
|
||||
|
||||
const secondCloseAt = new Date(Date.now() - 1_000);
|
||||
const secondWallNow = new Date();
|
||||
const secondCloseAt = new Date(secondWallNow.getTime() - 1_000);
|
||||
const secondCloseTick = world.dateToGameTick(secondCloseAt);
|
||||
await connector.prisma.auction.update({
|
||||
where: { id: extensionAuction.id },
|
||||
data: { closeAt: secondCloseAt },
|
||||
data: { closeAt: secondCloseAt, closeTick: BigInt(secondCloseTick) },
|
||||
});
|
||||
const secondExtensionRequestId = requestIdFor({ id: extensionAuction.id, closeAt: secondCloseAt });
|
||||
await processDueAuctionId({
|
||||
@@ -536,7 +538,8 @@ liveDescribe('auction worker durable recovery', () => {
|
||||
timerKey: 'timer',
|
||||
historyKey: 'history',
|
||||
id: String(extensionAuction.id),
|
||||
nowMs: Date.now(),
|
||||
nowMs: world.getGameNow(secondWallNow).getTime(),
|
||||
nowTick: world.dateToGameTick(secondWallNow),
|
||||
});
|
||||
|
||||
for (let attempt = 0; attempt < 200; attempt += 1) {
|
||||
@@ -601,6 +604,10 @@ liveDescribe('auction worker durable recovery', () => {
|
||||
{ timeout: 15_000 },
|
||||
async () => {
|
||||
const poisonedAuction = await createAuction('OPEN');
|
||||
await connector.prisma.auction.update({
|
||||
where: { id: poisonedAuction.id },
|
||||
data: { closeTick: 0n },
|
||||
});
|
||||
const poisonedRequestId = requestIdFor(poisonedAuction);
|
||||
await connector.prisma.inputEvent.create({
|
||||
data: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from 'vitest';
|
||||
import type { GamePrismaClient } from '@sammo-ts/infra';
|
||||
|
||||
import { processDueAuctionId } from '../src/auction/worker.js';
|
||||
import { resolveAuctionSeedScore } from '../src/auction/scheduler.js';
|
||||
|
||||
const buildRedis = () => ({
|
||||
zRangeByScore: vi.fn(async () => []),
|
||||
@@ -52,6 +53,34 @@ const buildDb = (options: {
|
||||
};
|
||||
|
||||
describe('auction worker clock-shift race', () => {
|
||||
it('seeds OPEN at its deadline but retries FINALIZING at the current logical tick', () => {
|
||||
const now = new Date('2026-07-30T12:00:00.000Z');
|
||||
const time = {
|
||||
now,
|
||||
tick: 36_000_000,
|
||||
mode: 'manual' as const,
|
||||
dateToTick: () => 72_000_000,
|
||||
};
|
||||
const closeAt = new Date('2099-01-01T00:00:00.000Z');
|
||||
|
||||
expect(
|
||||
resolveAuctionSeedScore(time, {
|
||||
id: 7,
|
||||
status: 'OPEN',
|
||||
closeAt,
|
||||
closeTick: 72_000_000n,
|
||||
})
|
||||
).toBe(72_000_000);
|
||||
expect(
|
||||
resolveAuctionSeedScore(time, {
|
||||
id: 7,
|
||||
status: 'FINALIZING',
|
||||
closeAt,
|
||||
closeTick: 72_000_000n,
|
||||
})
|
||||
).toBe(36_000_000);
|
||||
});
|
||||
|
||||
it('requeues an OPEN auction at its current DB deadline when an old due score loses the race', async () => {
|
||||
const redis = buildRedis();
|
||||
const closeAt = new Date('2026-07-30T12:15:00.000Z');
|
||||
@@ -125,6 +154,26 @@ describe('auction worker clock-shift race', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('records operational history time separately from logical settlement time', async () => {
|
||||
const redis = buildRedis();
|
||||
const closeAt = new Date('2026-07-30T11:00:00.000Z');
|
||||
const { db } = buildDb({ updated: 1, auction: { status: 'FINALIZING', closeAt } });
|
||||
const logicalNowMs = new Date('0190-01-01T00:00:00.000Z').getTime();
|
||||
const operationalNowMs = new Date('2026-07-30T12:00:00.000Z').getTime();
|
||||
|
||||
await processDueAuctionId({
|
||||
db,
|
||||
redis,
|
||||
timerKey: 'timer',
|
||||
historyKey: 'history',
|
||||
id: '7',
|
||||
nowMs: logicalNowMs,
|
||||
historyNowMs: operationalNowMs,
|
||||
});
|
||||
|
||||
expect(redis.zAdd).toHaveBeenCalledWith('history', [{ score: operationalNowMs, value: '7' }]);
|
||||
});
|
||||
|
||||
it('repairs a pre-existing FINALIZING auction without creating a duplicate command', async () => {
|
||||
const redis = buildRedis();
|
||||
const closeAt = new Date('2026-07-30T11:00:00.000Z');
|
||||
|
||||
@@ -345,7 +345,7 @@ integration('generic general creation through the durable turn daemon', () => {
|
||||
attempts: 1,
|
||||
actorUserId: userId,
|
||||
});
|
||||
expect(access.lastRefresh?.getTime()).toBe(event.createdAt.getTime());
|
||||
expect(access.lastRefresh?.getTime()).toBe(runtime!.world.getGameNow(event.createdAt).getTime());
|
||||
const turnGridOffsetSeconds =
|
||||
((created.turnTime.getTime() - runtime!.world.getState().lastTurnTime.getTime()) / 1000 + 300) % 300;
|
||||
expect(turnGridOffsetSeconds).toBeGreaterThanOrEqual(35);
|
||||
|
||||
@@ -321,7 +321,7 @@ integration('mode 1 NPC possession through token reservation and the durable dae
|
||||
attempts: 1,
|
||||
actorUserId: userId,
|
||||
});
|
||||
expect(access.lastRefresh?.getTime()).toBe(event.createdAt.getTime());
|
||||
expect(access.lastRefresh?.getTime()).toBe(runtime!.world.getGameNow(event.createdAt).getTime());
|
||||
const logs = await db.logEntry.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
@@ -362,7 +362,7 @@ integration('mode 1 NPC possession through token reservation and the durable dae
|
||||
});
|
||||
}, 45_000);
|
||||
|
||||
it('keeps an accepted token through wall-clock expiry until the queued ENGINE event finishes', async () => {
|
||||
it('keeps a token accepted in logical time until the queued ENGINE event finishes', async () => {
|
||||
const reservation = await appRouter
|
||||
.createCaller(buildContext('npc-possession-delayed-token', delayedAuth))
|
||||
.join.listPossessCandidates({});
|
||||
@@ -382,12 +382,17 @@ integration('mode 1 NPC possession through token reservation and the durable dae
|
||||
).rejects.toMatchObject({ code: 'TIMEOUT' });
|
||||
|
||||
const event = await db.inputEvent.findUniqueOrThrow({ where: { requestId } });
|
||||
const acceptedSecond = new Date(Math.floor(event.createdAt.getTime() / 1000) * 1000);
|
||||
const acceptedGameAt = new Date(
|
||||
(event.payload as { acceptedGameAt?: string }).acceptedGameAt ?? 'invalid accepted game time'
|
||||
);
|
||||
expect(acceptedGameAt.toString()).not.toBe('Invalid Date');
|
||||
await db.npcSelectionToken.update({
|
||||
where: { ownerUserId: delayedUserId },
|
||||
data: { validUntil: acceptedSecond },
|
||||
data: { validUntil: acceptedGameAt },
|
||||
});
|
||||
await db.worldState.updateMany({
|
||||
data: { clockTick: { increment: 1 } },
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 1_100));
|
||||
|
||||
await appRouter
|
||||
.createCaller(buildContext('npc-possession-cleanup-token', cleanupAuth))
|
||||
|
||||
@@ -434,6 +434,7 @@ integration('scenario 903 select pool through the durable turn daemon', () => {
|
||||
}, 30_000);
|
||||
|
||||
it('keeps a stable ENGINE event for retries and rejects reservation bypasses', async () => {
|
||||
const logicalNowMs = runtime!.world.getGameNow(new Date()).getTime();
|
||||
const reservation = await appRouter
|
||||
.createCaller(buildContext('select-pool-other-reserve', otherAuth))
|
||||
.join.getSelectionPool();
|
||||
@@ -449,7 +450,7 @@ integration('scenario 903 select pool through the durable turn daemon', () => {
|
||||
|
||||
await db.selectPoolEntry.update({
|
||||
where: { uniqueName: candidate.uniqueName },
|
||||
data: { reservedUntil: new Date(Date.now() - 60_000) },
|
||||
data: { reservedUntil: new Date(logicalNowMs - 60_000) },
|
||||
});
|
||||
await expect(
|
||||
appRouter.createCaller(buildContext('select-pool-expired-token', otherAuth)).join.selectPoolGeneral({
|
||||
@@ -476,7 +477,7 @@ integration('scenario 903 select pool through the durable turn daemon', () => {
|
||||
};
|
||||
await db.selectPoolEntry.updateMany({
|
||||
where: { ownerUserId: otherUserId, generalId: null },
|
||||
data: { reservedUntil: new Date(Date.now() + 60_000) },
|
||||
data: { reservedUntil: new Date(logicalNowMs + 60_000) },
|
||||
});
|
||||
const runtimeAllocatorBefore = runtime!.world.getState().meta.lastGeneralId;
|
||||
const persistedAllocatorBefore = (
|
||||
|
||||
Reference in New Issue
Block a user