feat: complete map and trend frontend flows

This commit is contained in:
2026-07-26 08:39:35 +00:00
parent 8ac1ad4059
commit 477862464e
15 changed files with 645 additions and 36 deletions
@@ -186,6 +186,42 @@ describe('in-game my information ownership', () => {
})
);
});
it('returns the three legacy front-page record streams for the session-owned general', async () => {
const fixture = createContext({});
const caller = appRouter.createCaller(fixture.context);
await expect(caller.general.getFrontRecords()).resolves.toEqual({
global: [{ id: 1, text: '기록' }],
general: [{ id: 1, text: '기록' }],
history: [{ id: 1, text: '기록' }],
});
expect(fixture.db.logEntry.findMany).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
where: { scope: 'SYSTEM', category: 'ACTION' },
orderBy: { id: 'desc' },
take: 15,
})
);
expect(fixture.db.logEntry.findMany).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
where: { scope: 'GENERAL', category: 'ACTION', generalId: 7 },
orderBy: { id: 'desc' },
take: 15,
})
);
expect(fixture.db.logEntry.findMany).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
where: { scope: 'SYSTEM', category: 'HISTORY' },
orderBy: { id: 'desc' },
take: 15,
})
);
});
});
describe('battle-center general and user permissions', () => {
@@ -0,0 +1,90 @@
import { describe, expect, it, vi } from 'vitest';
import type { GameSessionTokenPayload } from '@sammo-ts/common/auth/gameToken';
import type { RedisConnector } from '@sammo-ts/infra';
import { RedisAccessTokenStore } from '../src/auth/accessTokenStore.js';
import { InMemoryBattleSimTransport } from '../src/battleSim/inMemoryTransport.js';
import { InMemoryFlushStore } from '../src/auth/flushStore.js';
import type { DatabaseClient, GameApiContext, GameProfile } from '../src/context.js';
import { InMemoryTurnDaemonTransport } from '../src/daemon/inMemoryTransport.js';
import { appRouter } from '../src/router.js';
const profile: GameProfile = {
id: 'che',
scenario: 'default',
name: 'che:default',
};
const buildContext = () => {
const redis = {
get: vi.fn(async () => null),
set: vi.fn(async () => 'OK'),
};
const db = {
worldState: {
findFirst: vi.fn(async () => ({
currentYear: 190,
currentMonth: 3,
config: {},
meta: { scenarioMeta: { startYear: 184 } },
})),
},
logEntry: {
findMany: vi.fn(async () => [
{ id: 9, text: '<Y>최근 정세</>' },
{ id: 8, text: '이전 정세' },
]),
},
$queryRaw: vi
.fn()
.mockResolvedValueOnce([
{ id: 1, level: 5, nationId: 1, region: 1, supplyState: 1, meta: { state: 0 } },
])
.mockResolvedValueOnce([
{ id: 1, name: '촉', color: '#ff0000', capitalCityId: 1, meta: {} },
]),
};
const context: GameApiContext = {
db: db as unknown as DatabaseClient,
redis: redis as unknown as RedisConnector['client'],
turnDaemon: new InMemoryTurnDaemonTransport(),
battleSim: new InMemoryBattleSimTransport(),
profile,
auth: null as GameSessionTokenPayload | null,
uploadDir: 'uploads',
uploadPath: '/uploads',
uploadPublicUrl: null,
accessTokenStore: new RedisAccessTokenStore(redis as unknown as RedisConnector['client'], profile.name),
flushStore: new InMemoryFlushStore(),
gameTokenSecret: 'test-secret',
};
return { context, db, redis };
};
describe('public.getCachedMap', () => {
it('caches the neutral map and ten latest public history rows as one snapshot', async () => {
const fixture = buildContext();
const result = await appRouter.createCaller(fixture.context).public.getCachedMap();
expect(result).toMatchObject({
year: 190,
month: 3,
history: [
{ id: 9, text: '<Y>최근 정세</>' },
{ id: 8, text: '이전 정세' },
],
});
expect(fixture.db.logEntry.findMany).toHaveBeenCalledWith({
where: { scope: 'SYSTEM', category: 'HISTORY' },
select: { id: true, text: true },
orderBy: { id: 'desc' },
take: 10,
});
expect(fixture.redis.set).toHaveBeenCalledWith(
'sammo:public:cachedMapWithHistory:che:default',
expect.stringContaining('최근 정세'),
{ EX: 600 }
);
});
});