feat: port NPC possession through turn daemon
This commit is contained in:
@@ -39,9 +39,7 @@ const resetScenario = async (page: Page, scenarioId: string, sourceCommit: strin
|
||||
const previousLatestOperation = await latestOperation.textContent();
|
||||
await page.getByTestId('request-reset').click();
|
||||
await expect(page.getByText('초기화 작업을 시작했습니다.')).toBeVisible();
|
||||
await expect
|
||||
.poll(() => latestOperation.textContent(), { timeout: 15_000 })
|
||||
.not.toBe(previousLatestOperation);
|
||||
await expect.poll(() => latestOperation.textContent(), { timeout: 15_000 }).not.toBe(previousLatestOperation);
|
||||
await expect(latestOperation).toContainText(sourceCommit, { timeout: 15_000 });
|
||||
await expect(latestOperation.locator('td').nth(3)).toHaveText('SUCCEEDED', {
|
||||
timeout: 300_000,
|
||||
@@ -71,7 +69,9 @@ const resetScenario = async (page: Page, scenarioId: string, sourceCommit: strin
|
||||
|
||||
type PossessCandidateBatch = Array<{
|
||||
result?: {
|
||||
data?: Array<{ picture?: string | null }>;
|
||||
data?: {
|
||||
candidates?: Array<{ picture?: string | null }>;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
|
||||
@@ -99,11 +99,10 @@ const inspectScenarioIcons = async (
|
||||
await page.getByRole('button', { name: 'NPC 빙의' }).click();
|
||||
const payload = (await (await candidatesResponse).json()) as PossessCandidateBatch;
|
||||
const iconPaths = payload
|
||||
.flatMap((entry) => entry.result?.data ?? [])
|
||||
.flatMap((entry) => entry.result?.data?.candidates ?? [])
|
||||
.map((candidate) => candidate.picture)
|
||||
.filter(
|
||||
(picture): picture is string =>
|
||||
typeof picture === 'string' && picture.startsWith(`${expectedDirectory}/`)
|
||||
(picture): picture is string => typeof picture === 'string' && picture.startsWith(`${expectedDirectory}/`)
|
||||
);
|
||||
expect(iconPaths.length).toBeGreaterThan(0);
|
||||
|
||||
|
||||
@@ -18,12 +18,37 @@ const fulfillTrpc = async (route: Route, results: unknown[]): Promise<void> => {
|
||||
});
|
||||
};
|
||||
|
||||
const installFixture = async (page: Page) => {
|
||||
type LobbyFixtureOptions = {
|
||||
canCreateGeneral?: boolean;
|
||||
myGeneral?: {
|
||||
name: string;
|
||||
picture: string;
|
||||
imageServer: number;
|
||||
} | null;
|
||||
selectionPoolEnabled?: boolean;
|
||||
npcPossessionEnabled?: boolean;
|
||||
userCnt?: number;
|
||||
maxUserCnt?: number;
|
||||
};
|
||||
|
||||
const installFixture = async (page: Page, options: LobbyFixtureOptions = {}) => {
|
||||
const {
|
||||
canCreateGeneral = true,
|
||||
myGeneral = {
|
||||
name: '선택장수',
|
||||
picture: 'account-hash.png',
|
||||
imageServer: 1,
|
||||
},
|
||||
selectionPoolEnabled = true,
|
||||
npcPossessionEnabled = false,
|
||||
userCnt = 1,
|
||||
maxUserCnt = 500,
|
||||
} = options;
|
||||
const gameOperations: Array<{ operation: string; authorization: string | undefined }> = [];
|
||||
await page.addInitScript(() => {
|
||||
window.localStorage.setItem('sammo-session-token', 'gateway-lobby-session');
|
||||
});
|
||||
await page.route('http://127.0.0.1:15130/api/trpc/**', async (route) => {
|
||||
await page.route('**/gateway/api/trpc/**', async (route) => {
|
||||
const results = operationNames(route).map((operation) => {
|
||||
if (operation === 'me') {
|
||||
return response({
|
||||
@@ -57,7 +82,7 @@ const installFixture = async (page: Page) => {
|
||||
color: '#ffffff',
|
||||
localAccountPolicy: {
|
||||
accessAllowed: true,
|
||||
canCreateGeneral: true,
|
||||
canCreateGeneral,
|
||||
requiresKakaoVerification: false,
|
||||
graceEndsAt: null,
|
||||
},
|
||||
@@ -90,8 +115,8 @@ const installFixture = async (page: Page) => {
|
||||
return response({
|
||||
year: 180,
|
||||
month: 1,
|
||||
userCnt: 1,
|
||||
maxUserCnt: 500,
|
||||
userCnt,
|
||||
maxUserCnt,
|
||||
npcCnt: 0,
|
||||
nationCnt: 0,
|
||||
turnTerm: 5,
|
||||
@@ -101,12 +126,9 @@ const installFixture = async (page: Page) => {
|
||||
turntime: '2026-07-30 00:05:00',
|
||||
otherTextInfo: '',
|
||||
isUnited: 0,
|
||||
selectionPoolEnabled: true,
|
||||
myGeneral: {
|
||||
name: '선택장수',
|
||||
picture: 'account-hash.png',
|
||||
imageServer: 1,
|
||||
},
|
||||
selectionPoolEnabled,
|
||||
npcPossessionEnabled,
|
||||
myGeneral,
|
||||
});
|
||||
}
|
||||
if (operation === 'public.getMapLayout') {
|
||||
@@ -132,9 +154,7 @@ const installFixture = async (page: Page) => {
|
||||
return gameOperations;
|
||||
};
|
||||
|
||||
test('exchanges the gateway token before loading authenticated lobby general data', async ({
|
||||
page,
|
||||
}) => {
|
||||
test('exchanges the gateway token before loading authenticated lobby general data', async ({ page }) => {
|
||||
const gameOperations = await installFixture(page);
|
||||
|
||||
await page.goto('lobby');
|
||||
@@ -142,10 +162,7 @@ test('exchanges the gateway token before loading authenticated lobby general dat
|
||||
await expect(row).toContainText('선택장수');
|
||||
await expect(row.getByRole('button', { name: '입장' })).toBeVisible();
|
||||
const portrait = row.locator('img');
|
||||
await expect(portrait).toHaveAttribute(
|
||||
'src',
|
||||
'/gateway/api/user-icons/account-hash.png'
|
||||
);
|
||||
await expect(portrait).toHaveAttribute('src', '/gateway/api/user-icons/account-hash.png');
|
||||
await expect.poll(() => portrait.evaluate((image: HTMLImageElement) => image.naturalWidth)).toBe(1);
|
||||
|
||||
expect(gameOperations.find(({ operation }) => operation === 'auth.exchangeGatewayToken')).toEqual({
|
||||
@@ -157,3 +174,59 @@ test('exchanges the gateway token before loading authenticated lobby general dat
|
||||
authorization: 'Bearer ga_lobby-access-token',
|
||||
});
|
||||
});
|
||||
|
||||
test('applies the signed general-acquisition policy to both create and possession actions', async ({ page }) => {
|
||||
await installFixture(page, {
|
||||
canCreateGeneral: false,
|
||||
myGeneral: null,
|
||||
selectionPoolEnabled: false,
|
||||
npcPossessionEnabled: true,
|
||||
});
|
||||
|
||||
await page.goto('lobby');
|
||||
const row = page.locator('tbody tr').filter({ hasText: 'hwe섭' });
|
||||
await expect(row.getByRole('button', { name: '인증 필요' })).toBeDisabled();
|
||||
await expect(row.getByRole('button', { name: '장수빙의' })).toBeDisabled();
|
||||
});
|
||||
|
||||
test('opens the mode-1 possession tab with a fresh gateway game token', async ({ page }) => {
|
||||
await installFixture(page, {
|
||||
myGeneral: null,
|
||||
selectionPoolEnabled: false,
|
||||
npcPossessionEnabled: true,
|
||||
});
|
||||
await page.route('**/hwe/join?**', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'text/html',
|
||||
body: '<title>NPC possession target</title>',
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('lobby');
|
||||
const row = page.locator('tbody tr').filter({ hasText: 'hwe섭' });
|
||||
await expect(row.getByRole('button', { name: '장수빙의' })).toBeEnabled();
|
||||
await row.getByRole('button', { name: '장수빙의' }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/hwe\/join\?/);
|
||||
const target = new URL(page.url());
|
||||
expect(target.searchParams.get('tab')).toBe('possess');
|
||||
expect(target.searchParams.get('profile')).toBe('hwe:903');
|
||||
expect(target.searchParams.get('gameToken')).toBe('encrypted-gateway-game-token');
|
||||
});
|
||||
|
||||
test('shows registration closed instead of acquisition actions at the Ref capacity boundary', async ({ page }) => {
|
||||
await installFixture(page, {
|
||||
myGeneral: null,
|
||||
selectionPoolEnabled: false,
|
||||
npcPossessionEnabled: true,
|
||||
userCnt: 300,
|
||||
maxUserCnt: 300,
|
||||
});
|
||||
|
||||
await page.goto('lobby');
|
||||
const row = page.locator('tbody tr').filter({ hasText: 'hwe섭' });
|
||||
await expect(row).toContainText('장수 등록 마감');
|
||||
await expect(row.getByRole('button', { name: '장수생성' })).toHaveCount(0);
|
||||
await expect(row.getByRole('button', { name: '장수빙의' })).toHaveCount(0);
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ export default defineConfig({
|
||||
},
|
||||
webServer: {
|
||||
command:
|
||||
'VITE_APP_BASE_PATH=/gateway pnpm --filter @sammo-ts/gateway-frontend preview --host 127.0.0.1 --port 15130',
|
||||
"VITE_APP_BASE_PATH=/gateway VITE_GAME_WEB_URL_TEMPLATE='/{profile}/' pnpm --filter @sammo-ts/gateway-frontend preview --host 127.0.0.1 --port 15130",
|
||||
cwd: repositoryRoot,
|
||||
url: 'http://127.0.0.1:15130/gateway/',
|
||||
reuseExistingServer: false,
|
||||
|
||||
Reference in New Issue
Block a user