fix: 유저 장수 preset 아이콘 적용 차단

사람 장수는 활성 전용 아이콘 ID를 명시한 경우에만 사용자 아이콘을 사용한다. 일반 생성, 선택 pool, Gateway 서버 동기화의 대표·후보 preset fallback을 제거하고 회귀 검증을 추가한다.
This commit is contained in:
2026-08-24 08:38:50 +00:00
parent 379312470e
commit 833ade670c
13 changed files with 227 additions and 76 deletions
@@ -40,6 +40,16 @@ const profileInputAt = (body: string, index: number): string | null => {
}
};
const adjustIconInputAt = (body: string, index: number): Record<string, unknown> => {
try {
const parsed = JSON.parse(body) as Record<string, Record<string, unknown> & { json?: Record<string, unknown> }>;
const input = parsed[String(index)];
return input?.json ?? input ?? {};
} catch {
return {};
}
};
type FixtureOptions = {
failHweAdjustOnce?: boolean;
delayHweAdjust?: boolean;
@@ -65,6 +75,7 @@ const installFixture = async (page: Page, options: FixtureOptions = {}) => {
let preferredIconCount = 0;
let retireIconCount = 0;
let hweAdjustCount = 0;
const adjustIconInputs: Array<Record<string, unknown>> = [];
const operations = new Map<string, string[]>([
['che:903', []],
['hwe:903', []],
@@ -199,7 +210,8 @@ const installFixture = async (page: Page, options: FixtureOptions = {}) => {
`/${profileName.split(':')[0]}/api/trpc/${operationNames(route).join(',')}`
);
const results = [];
for (const operation of operationNames(route)) {
const body = route.request().postData() ?? '';
for (const [index, operation] of operationNames(route).entries()) {
if (operation === 'auth.exchangeGatewayToken') {
operations.get(profileName)?.push('exchangeGatewayToken');
results.push(
@@ -213,6 +225,11 @@ const installFixture = async (page: Page, options: FixtureOptions = {}) => {
}
if (operation === 'general.adjustIcon') {
operations.get(profileName)?.push('adjustIcon');
const input = adjustIconInputAt(body, index);
adjustIconInputs.push(input);
if (input.resetToDefault !== true) {
expect(input.iconId).toBe('3f804277-584f-4f44-b39c-9ecf40d1ed31');
}
if (profileName === 'hwe:903') {
hweAdjustCount += 1;
if (options.delayHweAdjust) {
@@ -242,6 +259,7 @@ const installFixture = async (page: Page, options: FixtureOptions = {}) => {
deleteIconCount: () => deleteIconCount,
preferredIconCount: () => preferredIconCount,
retireIconCount: () => retireIconCount,
adjustIconInputs: () => adjustIconInputs,
};
};
@@ -558,6 +576,9 @@ test('uses the Ref delete confirmation and opens the modal only after acceptance
.toBe('none');
await page.keyboard.press('Escape');
await expect(page.getByTestId('icon-server-modal')).toBeVisible();
await apply.click();
await expect(page.getByTestId('icon-server-result-hwe:903')).toContainText('적용됨');
expect(fixture.adjustIconInputs().at(-1)).toEqual({ resetToDefault: true });
});
test('contains focus and long failure content inside a 320px viewport', async ({ page }) => {
+10 -1
View File
@@ -442,7 +442,16 @@ const syncIconToServer = async (row: IconSyncRow, token: string): Promise<void>
gatewayToken: issued.gameToken,
});
const gameTrpc = createGameTrpc(row.profile, row.apiPort, exchanged.accessToken);
await gameTrpc.general.adjustIcon.mutate();
if (!account.value?.iconUrl) {
await gameTrpc.general.adjustIcon.mutate({ resetToDefault: true });
row.state = 'success';
return;
}
const selectedIconId = account.value?.icons.find(
(icon) => icon.picture === account.value?.preferredPicture
)?.id;
if (!selectedIconId) throw new Error('적용할 활성 전용 아이콘을 찾을 수 없습니다.');
await gameTrpc.general.adjustIcon.mutate({ iconId: selectedIconId });
row.state = 'success';
} catch (error) {
row.state = 'error';