감찰부 개인 기록 권한 거부 시 허용된 기록 표시 복구
This commit is contained in:
@@ -945,6 +945,52 @@ describe('in-game my information ownership', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('battle-center general and user permissions', () => {
|
describe('battle-center general and user permissions', () => {
|
||||||
|
it.each([
|
||||||
|
{ role: '연수 미달', officerLevel: 1, belong: 2, permission: 'normal', penalty: {}, level: 0 },
|
||||||
|
{ role: '연수 충족', officerLevel: 1, belong: 3, permission: 'normal', penalty: {}, level: 1 },
|
||||||
|
{ role: '연수 초과', officerLevel: 1, belong: 4, permission: 'normal', penalty: {}, level: 1 },
|
||||||
|
{ role: '지방 관직', officerLevel: 2, belong: 0, permission: 'normal', penalty: {}, level: 1 },
|
||||||
|
{ role: '수뇌', officerLevel: 5, belong: 0, permission: 'normal', penalty: {}, level: 2 },
|
||||||
|
{ role: '군주', officerLevel: 12, belong: 0, permission: 'normal', penalty: {}, level: 4 },
|
||||||
|
{ role: '외교권자', officerLevel: 1, belong: 0, permission: 'ambassador', penalty: {}, level: 4 },
|
||||||
|
{ role: '조언자', officerLevel: 1, belong: 0, permission: 'auditor', penalty: {}, level: 3 },
|
||||||
|
{ role: '수뇌 금지', officerLevel: 12, belong: 4, permission: 'normal', penalty: { noChief: true }, level: 0 },
|
||||||
|
{
|
||||||
|
role: '기밀 제한',
|
||||||
|
officerLevel: 12,
|
||||||
|
belong: 4,
|
||||||
|
permission: 'normal',
|
||||||
|
penalty: { noTopSecret: true },
|
||||||
|
level: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: '외교 제한',
|
||||||
|
officerLevel: 12,
|
||||||
|
belong: 4,
|
||||||
|
permission: 'normal',
|
||||||
|
penalty: { noAmbassador: true },
|
||||||
|
level: 2,
|
||||||
|
},
|
||||||
|
])('Ref 감찰부 권한: $role', async ({ officerLevel, belong, permission, penalty, level }) => {
|
||||||
|
const me = buildGeneral({ officerLevel, meta: { belong, permission }, penalty });
|
||||||
|
const other = buildGeneral({ id: 8, userId: 'user-8' });
|
||||||
|
const fixture = createContext({ me, targets: [me, other], nationMeta: { secretLimit: 3 } });
|
||||||
|
const caller = appRouter.createCaller(fixture.context);
|
||||||
|
if (level < 1) {
|
||||||
|
await expect(caller.nation.getBattleCenter()).rejects.toMatchObject({ code: 'FORBIDDEN' });
|
||||||
|
} else {
|
||||||
|
await expect(caller.nation.getBattleCenter()).resolves.toMatchObject({ me: { permissionLevel: level } });
|
||||||
|
}
|
||||||
|
for (const type of ['generalHistory', 'battleDetail', 'battleResult', 'generalAction'] as const) {
|
||||||
|
const request = caller.nation.getGeneralLog({ generalId: other.id, type });
|
||||||
|
if (level < 1 || (type === 'generalAction' && level < 2)) {
|
||||||
|
await expect(request).rejects.toMatchObject({ code: 'FORBIDDEN' });
|
||||||
|
} else {
|
||||||
|
await expect(request).resolves.toMatchObject({ generalId: other.id, type });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('distinguishes an ordinary member, a tenured member, and an auditor', async () => {
|
it('distinguishes an ordinary member, a tenured member, and an auditor', async () => {
|
||||||
const ordinary = createContext({
|
const ordinary = createContext({
|
||||||
me: buildGeneral({ officerLevel: 1, meta: { belong: 1, permission: 'normal' } }),
|
me: buildGeneral({ officerLevel: 1, meta: { belong: 1, permission: 'normal' } }),
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ const readGeneralSummaryRows = async (summary: Locator) =>
|
|||||||
|
|
||||||
type FixtureState = {
|
type FixtureState = {
|
||||||
permission: 'head' | 'member';
|
permission: 'head' | 'member';
|
||||||
|
battleTenured?: boolean;
|
||||||
myset: number;
|
myset: number;
|
||||||
scenarioEffect?: string | null;
|
scenarioEffect?: string | null;
|
||||||
instantRetreatEnabled?: boolean;
|
instantRetreatEnabled?: boolean;
|
||||||
@@ -295,7 +296,7 @@ const battleCenter = (state: FixtureState) => ({
|
|||||||
me: {
|
me: {
|
||||||
id: 7,
|
id: 7,
|
||||||
officerLevel: state.permission === 'head' ? 9 : 1,
|
officerLevel: state.permission === 'head' ? 9 : 1,
|
||||||
permissionLevel: state.permission === 'head' ? 2 : 0,
|
permissionLevel: state.battleTenured ? 1 : state.permission === 'head' ? 2 : 0,
|
||||||
},
|
},
|
||||||
nation: { id: 1, name: '위', color: '#777777', level: 3 },
|
nation: { id: 1, name: '위', color: '#777777', level: 3 },
|
||||||
currentYear: 185,
|
currentYear: 185,
|
||||||
@@ -344,7 +345,7 @@ const battleCenter = (state: FixtureState) => ({
|
|||||||
{
|
{
|
||||||
id: 8,
|
id: 8,
|
||||||
name: '다른장수',
|
name: '다른장수',
|
||||||
npcState: 2,
|
npcState: state.battleTenured ? 0 : 2,
|
||||||
officerLevel: 1,
|
officerLevel: 1,
|
||||||
officerLevelText: '일반',
|
officerLevelText: '일반',
|
||||||
cityId: 1,
|
cityId: 1,
|
||||||
@@ -644,7 +645,7 @@ const install = async (page: Page, state: FixtureState) => {
|
|||||||
return response({ recorded: true });
|
return response({ recorded: true });
|
||||||
}
|
}
|
||||||
if (operation === 'nation.getBattleCenter') {
|
if (operation === 'nation.getBattleCenter') {
|
||||||
if (state.permission === 'member') {
|
if (state.permission === 'member' && !state.battleTenured) {
|
||||||
return {
|
return {
|
||||||
error: {
|
error: {
|
||||||
message: '권한이 부족합니다.',
|
message: '권한이 부족합니다.',
|
||||||
@@ -661,9 +662,18 @@ const install = async (page: Page, state: FixtureState) => {
|
|||||||
['generalHistory', 'battleDetail', 'battleResult', 'generalAction'].includes(jsonInput.type)
|
['generalHistory', 'battleDetail', 'battleResult', 'generalAction'].includes(jsonInput.type)
|
||||||
? jsonInput.type
|
? jsonInput.type
|
||||||
: 'generalAction';
|
: 'generalAction';
|
||||||
|
if (state.battleTenured && jsonInput.generalId === 8 && type === 'generalAction') {
|
||||||
|
return {
|
||||||
|
error: {
|
||||||
|
message: '권한이 부족합니다. 유저 장수의 개인 기록은 수뇌만 열람 가능합니다.',
|
||||||
|
code: -32000,
|
||||||
|
data: { code: 'FORBIDDEN', httpStatus: 403, path: operation },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
return response({
|
return response({
|
||||||
type,
|
type,
|
||||||
generalId: 7,
|
generalId: jsonInput.generalId,
|
||||||
logs: [{ id: 1, text: state.hiddenSeedLogText ?? `<Y>${type} 감찰 기록</>` }],
|
logs: [{ id: 1, text: state.hiddenSeedLogText ?? `<Y>${type} 감찰 기록</>` }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2574,3 +2584,46 @@ test('감찰부 하단 창 닫기 버튼은 상단 버튼과 같은 크기를
|
|||||||
const mobile = await measure();
|
const mobile = await measure();
|
||||||
expect(mobile.bottom).toEqual(mobile.top);
|
expect(mobile.bottom).toEqual(mobile.top);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('감찰부 사관 연수를 채운 일반 장수는 개인 기록 거부에도 열전과 전투 기록을 본다', async ({ page }) => {
|
||||||
|
const state: FixtureState = {
|
||||||
|
permission: 'member',
|
||||||
|
battleTenured: true,
|
||||||
|
myset: 3,
|
||||||
|
settingMutations: [],
|
||||||
|
accessPages: [],
|
||||||
|
};
|
||||||
|
await install(page, state);
|
||||||
|
for (const width of [1000, 500]) {
|
||||||
|
await page.setViewportSize({ width, height: 900 });
|
||||||
|
await page.goto('battle-center');
|
||||||
|
const selector = page.locator('.selector-row select').nth(1);
|
||||||
|
await expect(selector).toHaveValue('8');
|
||||||
|
for (const type of ['generalHistory', 'battleResult', 'battleDetail']) {
|
||||||
|
await expect(page.locator(`[data-log-type="${type}"]`)).toContainText(`${type} 감찰 기록`);
|
||||||
|
}
|
||||||
|
await expect(page.getByRole('alert')).toContainText('개인 기록은 수뇌만');
|
||||||
|
await expect(page.locator('[data-log-type="generalAction"]')).not.toContainText('generalAction 감찰 기록');
|
||||||
|
await expect(page.locator('.battle-general-card')).toBeVisible();
|
||||||
|
await waitForVisualAssets(page);
|
||||||
|
await persistParityArtifact(
|
||||||
|
page,
|
||||||
|
`core-battle-permission-${width}`,
|
||||||
|
await page.locator('.log-block').evaluateAll((elements) =>
|
||||||
|
elements.map((element) => ({
|
||||||
|
type: element.getAttribute('data-log-type'),
|
||||||
|
text: element.textContent,
|
||||||
|
rect: element.getBoundingClientRect().toJSON(),
|
||||||
|
font: getComputedStyle(element).font,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await selector.selectOption('7');
|
||||||
|
await expect(page.locator('[data-log-type="generalAction"]')).toContainText('generalAction 감찰 기록');
|
||||||
|
await expect(page.getByRole('alert')).toHaveCount(0);
|
||||||
|
await selector.selectOption('8');
|
||||||
|
await expect(page.getByRole('alert')).toContainText('개인 기록은 수뇌만');
|
||||||
|
await expect(page.locator('[data-log-type="generalAction"]')).not.toContainText('generalAction 감찰 기록');
|
||||||
|
await expect(page.locator('[data-log-type="battleResult"]')).toContainText('battleResult 감찰 기록');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -153,16 +153,27 @@ const loadLogs = async (generalId: number) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logLoading.value = true;
|
logLoading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
// 대상 전환 시 이전 장수의 기록이 새 장수 정보와 섞이지 않게 비운다.
|
||||||
|
for (const type of logTypes) {
|
||||||
|
logs[type] = [];
|
||||||
|
}
|
||||||
const requestId = (logRequestId += 1);
|
const requestId = (logRequestId += 1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const responses = await Promise.all(
|
const responses = await Promise.allSettled(
|
||||||
logTypes.map((type) => trpc.nation.getGeneralLog.query({ generalId, type }))
|
logTypes.map((type) => trpc.nation.getGeneralLog.query({ generalId, type }))
|
||||||
);
|
);
|
||||||
if (requestId !== logRequestId || selectedGeneralId.value !== generalId) {
|
if (requestId !== logRequestId || selectedGeneralId.value !== generalId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const response of responses) {
|
// Ref처럼 개인 기록의 권한 거부가 열전·전투 기록까지 지우지 않게 한다.
|
||||||
|
for (const result of responses) {
|
||||||
|
if (result.status === 'rejected') {
|
||||||
|
error.value = resolveErrorMessage(result.reason);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const response = result.value;
|
||||||
const formatted = response.logs.map((entry) => {
|
const formatted = response.logs.map((entry) => {
|
||||||
const eventTime =
|
const eventTime =
|
||||||
response.type === 'generalAction'
|
response.type === 'generalAction'
|
||||||
|
|||||||
Reference in New Issue
Block a user