kt-template-online-api/test/qqbot/plugins/bangDream/tsugu/policy.spec.ts

161 lines
4.7 KiB
TypeScript

jest.mock('@/qqbot/plugins/bangDream/tsugu/models/main-data-store', () => ({
__esModule: true,
default: {},
}));
import {
calculateCnEventEstimateStartAt,
getBangDreamOccupiedDays,
type BangDreamEventSchedule,
} from '@/qqbot/plugins/bangDream/tsugu/models/cn-event-estimate-policy';
import {
getCutoffDateByServerTimezone,
getCutoffDayIndex,
getCutoffEventStatus,
getCutoffPredictionWindow,
getCutoffTierList,
isCutoffDailyCheckpoint,
isCutoffTierSupported,
selectRecentCutoffEventIds,
} from '@/qqbot/plugins/bangDream/tsugu/models/cutoff-policy';
import {
getBangDreamServerUtcOffset,
normalizeBangDreamTimestamp,
} from '@/qqbot/plugins/bangDream/tsugu/models/server-policy';
import {
BangDreamEventStatus,
BangDreamServerId as Server,
} from '@/qqbot/plugins/bangDream/tsugu/models/bangdream-protocol';
const DAY_MS = 24 * 60 * 60 * 1000;
describe('BangDream server policy', () => {
it('normalizes timestamps and applies server UTC offsets', () => {
expect(normalizeBangDreamTimestamp(1_700_000_000)).toBe(1_700_000_000_000);
expect(normalizeBangDreamTimestamp(1_700_000_000_000)).toBe(
1_700_000_000_000,
);
expect(getBangDreamServerUtcOffset(Server.cn)).toBe(8);
expect(getBangDreamServerUtcOffset(Server.jp)).toBe(9);
expect(getBangDreamServerUtcOffset(Server.en)).toBe(0);
});
});
describe('BangDream CN event estimate policy', () => {
it('keeps occupied day calculation inclusive', () => {
expect(
getBangDreamOccupiedDays(Date.UTC(2026, 0, 1), Date.UTC(2026, 0, 1)),
).toBe(1);
expect(
getBangDreamOccupiedDays(Date.UTC(2026, 0, 1), Date.UTC(2026, 0, 3)),
).toBe(3);
});
it('calculates the next missing CN event start from pure schedules', () => {
const schedules: Record<number, BangDreamEventSchedule> = {
100: {
endAt: [Date.UTC(2026, 0, 5), null, null, Date.UTC(2026, 0, 5), null],
startAt: [Date.UTC(2026, 0, 1), null, null, Date.UTC(2026, 0, 1), null],
},
101: {
endAt: [Date.UTC(2026, 0, 10), null, null, null, null],
startAt: [Date.UTC(2026, 0, 6), null, null, null, null],
},
};
const result = calculateCnEventEstimateStartAt({
currentEvent: { eventId: 100, ...schedules[100] },
eventId: 101,
getSchedule: (eventId) => schedules[eventId] || null,
options: {
defaultNoBangDays: 1,
estimateStartEventId: 100,
},
presentJpEventId: 101,
});
expect(result).toBe(Date.UTC(2026, 0, 6));
});
});
describe('BangDream cutoff policy', () => {
it('checks tier support by server', () => {
expect(getCutoffTierList(Server.cn)).toContain(1500);
expect(isCutoffTierSupported(Server.cn, 1500)).toBe(true);
expect(isCutoffTierSupported(Server.jp, 1500)).toBe(false);
});
it('resolves event status and prediction windows', () => {
const startAt = Date.UTC(2026, 0, 1);
const endAt = Date.UTC(2026, 0, 5);
expect(getCutoffEventStatus(startAt, endAt, startAt - 1)).toBe(
BangDreamEventStatus.notStart,
);
expect(getCutoffEventStatus(startAt, endAt, startAt + 1)).toBe(
BangDreamEventStatus.inProgress,
);
expect(getCutoffEventStatus(startAt, endAt, endAt + 1)).toBe(
BangDreamEventStatus.ended,
);
expect(getCutoffPredictionWindow(startAt, endAt)).toEqual({
endTs: Math.floor(endAt / 1000),
startTs: Math.floor(startAt / 1000),
});
});
it('calculates daily checkpoints and event day index', () => {
const checkpoint = getCutoffDateByServerTimezone(
Date.UTC(2026, 0, 1, 18, 45),
Server.jp,
);
expect(isCutoffDailyCheckpoint(Server.jp, checkpoint)).toBe(true);
expect(isCutoffDailyCheckpoint(Server.en, checkpoint)).toBe(false);
const eventStartAt = Date.UTC(2026, 0, 1, 12);
expect(getCutoffDayIndex(Server.jp, eventStartAt, eventStartAt + 1)).toBe(
0,
);
expect(
getCutoffDayIndex(Server.jp, eventStartAt, eventStartAt + DAY_MS),
).toBe(1);
});
it('selects recent comparable events by type and server start time', () => {
const candidates = [
eventCandidate(1, 'story', [Date.UTC(2026, 0, 1)]),
eventCandidate(2, 'festival', [Date.UTC(2026, 0, 5)]),
eventCandidate(3, 'story', [Date.UTC(2026, 0, 10)]),
eventCandidate(4, 'story', [Date.UTC(2026, 0, 15)]),
];
expect(
selectRecentCutoffEventIds({
candidates,
count: 2,
event: candidates[3],
sameType: true,
server: Server.jp,
}),
).toEqual([3, 4]);
});
});
function eventCandidate(
eventId: number,
eventType: string,
jpStartAt: number[],
) {
return {
eventId,
eventType,
startAt: [
jpStartAt[0],
null,
null,
jpStartAt[Server.cn] ?? jpStartAt[0],
null,
],
};
}