fix: 불가침 제의 기한을 20년으로 제한
This commit is contained in:
@@ -80,6 +80,7 @@ export interface TurnCommandInputField {
|
|||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
step?: number;
|
step?: number;
|
||||||
|
defaultValue?: TurnCommandOptionValue | boolean;
|
||||||
constValue?: TurnCommandOptionValue;
|
constValue?: TurnCommandOptionValue;
|
||||||
options?: TurnCommandOption[];
|
options?: TurnCommandOption[];
|
||||||
optionSource?: TurnCommandOptionSource;
|
optionSource?: TurnCommandOptionSource;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import type {
|
|||||||
TriggerValue,
|
TriggerValue,
|
||||||
UnitSetDefinition,
|
UnitSetDefinition,
|
||||||
} from '@sammo-ts/logic';
|
} from '@sammo-ts/logic';
|
||||||
import { evaluateConstraints, LEGACY_DEFAULT_MAX_LEVEL } from '@sammo-ts/logic';
|
import { evaluateConstraints, LEGACY_DEFAULT_MAX_LEVEL, resolveNonAggressionMaxEndYear } from '@sammo-ts/logic';
|
||||||
import type { GeneralActionModule } from '@sammo-ts/logic/actionModules/general.js';
|
import type { GeneralActionModule } from '@sammo-ts/logic/actionModules/general.js';
|
||||||
import { CommandResolver as RecruitmentCommandResolver } from '@sammo-ts/logic/actions/turn/general/che_징병.js';
|
import { CommandResolver as RecruitmentCommandResolver } from '@sammo-ts/logic/actions/turn/general/che_징병.js';
|
||||||
import { projectItemSlots, readItemInventoryFromMeta } from '@sammo-ts/logic/items/index.js';
|
import { projectItemSlots, readItemInventoryFromMeta } from '@sammo-ts/logic/items/index.js';
|
||||||
@@ -656,18 +656,33 @@ const FOUNDING_COMMAND_KEYS = new Set(['che_건국', 'cr_건국', 'che_무작위
|
|||||||
const buildEntries = (
|
const buildEntries = (
|
||||||
env: CommandEnv,
|
env: CommandEnv,
|
||||||
specs: TurnCommandSpec[],
|
specs: TurnCommandSpec[],
|
||||||
options: { foundingAvailable?: boolean } = {}
|
options: { foundingAvailable?: boolean; currentYear?: number; currentMonth?: number } = {}
|
||||||
): CommandEntry[] => {
|
): CommandEntry[] => {
|
||||||
const entries: CommandEntry[] = [];
|
const entries: CommandEntry[] = [];
|
||||||
|
|
||||||
for (const spec of specs) {
|
for (const spec of specs) {
|
||||||
const definition = spec.createDefinition(env);
|
const definition = spec.createDefinition(env);
|
||||||
|
const inputFields = buildTurnCommandInputFields(spec).map((field) => {
|
||||||
|
if (spec.key !== 'che_불가침제의') return field;
|
||||||
|
if (field.key === 'year' && options.currentYear !== undefined) {
|
||||||
|
return {
|
||||||
|
...field,
|
||||||
|
min: options.currentYear + 1,
|
||||||
|
max: resolveNonAggressionMaxEndYear(options.currentYear),
|
||||||
|
defaultValue: options.currentYear + 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (field.key === 'month' && options.currentMonth !== undefined) {
|
||||||
|
return { ...field, defaultValue: options.currentMonth };
|
||||||
|
}
|
||||||
|
return field;
|
||||||
|
});
|
||||||
const entry: CommandEntry = {
|
const entry: CommandEntry = {
|
||||||
category: spec.category,
|
category: spec.category,
|
||||||
definition,
|
definition,
|
||||||
reqArg: spec.reqArg,
|
reqArg: spec.reqArg,
|
||||||
availabilityArgs: spec.reqArg ? spec.availabilityArgs : {},
|
availabilityArgs: spec.reqArg ? spec.availabilityArgs : {},
|
||||||
inputFields: buildTurnCommandInputFields(spec),
|
inputFields,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (spec.key === 'che_포상') {
|
if (spec.key === 'che_포상') {
|
||||||
@@ -817,7 +832,10 @@ export const buildTurnCommandTable = async (options: {
|
|||||||
? undefined
|
? undefined
|
||||||
: options.realNationCount < resolveMaxNation(options.worldState),
|
: options.realNationCount < resolveMaxNation(options.worldState),
|
||||||
});
|
});
|
||||||
const nationEntries = buildEntries(env, nationSpecs);
|
const nationEntries = buildEntries(env, nationSpecs, {
|
||||||
|
currentYear: options.worldState.currentYear,
|
||||||
|
currentMonth: options.worldState.currentMonth,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
general: buildGroups(
|
general: buildGroups(
|
||||||
|
|||||||
@@ -337,6 +337,27 @@ describe('buildTurnCommandTable', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('projects the Ref twenty-year non-aggression end-year window', async () => {
|
||||||
|
const table = await buildTurnCommandTable({
|
||||||
|
worldState: buildWorldState(),
|
||||||
|
general: buildGeneral(),
|
||||||
|
city: buildCity(),
|
||||||
|
nation: buildNation(),
|
||||||
|
nationGenerals: null,
|
||||||
|
});
|
||||||
|
const proposal = table.nation
|
||||||
|
.flatMap((group) => group.values)
|
||||||
|
.find((command) => command.key === 'che_불가침제의');
|
||||||
|
|
||||||
|
expect(proposal?.inputFields.find((field) => field.key === 'year')).toMatchObject({
|
||||||
|
kind: 'number',
|
||||||
|
min: 4,
|
||||||
|
max: 23,
|
||||||
|
defaultValue: 4,
|
||||||
|
});
|
||||||
|
expect(proposal?.inputFields.find((field) => field.key === 'month')).toMatchObject({ defaultValue: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
it('projects the Ref availability boundaries for force move, retirement, and resignation', async () => {
|
it('projects the Ref availability boundaries for force move, retirement, and resignation', async () => {
|
||||||
const buildTable = (general: GeneralRow, nation: NationRow | null = buildNation()) =>
|
const buildTable = (general: GeneralRow, nation: NationRow | null = buildNation()) =>
|
||||||
buildTurnCommandTable({
|
buildTurnCommandTable({
|
||||||
|
|||||||
@@ -1462,6 +1462,28 @@ describe('appRouter', () => {
|
|||||||
})
|
})
|
||||||
).resolves.toMatchObject({ ok: true });
|
).resolves.toMatchObject({ ok: true });
|
||||||
expect(validTermWrites).toHaveLength(1);
|
expect(validTermWrites).toHaveLength(1);
|
||||||
|
|
||||||
|
const excessiveTermWrites: unknown[] = [];
|
||||||
|
const excessiveTermCaller = appRouter.createCaller(
|
||||||
|
buildContext({
|
||||||
|
state: buildWorldState(),
|
||||||
|
general,
|
||||||
|
nationTurnWrites: excessiveTermWrites,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
excessiveTermCaller.turns.reserved.setNation({
|
||||||
|
generalId: general.id,
|
||||||
|
turnIndex: 0,
|
||||||
|
action: 'che_불가침제의',
|
||||||
|
args: { destNationId: 2, year: 211, month: 1 },
|
||||||
|
expectedRevision: 0,
|
||||||
|
})
|
||||||
|
).rejects.toMatchObject({
|
||||||
|
code: 'PRECONDITION_FAILED',
|
||||||
|
message: expect.stringContaining('기한은 210년 이하'),
|
||||||
|
});
|
||||||
|
expect(excessiveTermWrites).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('validates every bulk reservation permission before writing any turn', async () => {
|
it('validates every bulk reservation permission before writing any turn', async () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { GeneralAI } from '../core.js';
|
import type { GeneralAI } from '../core.js';
|
||||||
import { asRecord, joinYearMonth, parseYearMonth, readMetaNumber } from '../../aiUtils.js';
|
import { asRecord, joinYearMonth, parseYearMonth, readMetaNumber } from '../../aiUtils.js';
|
||||||
import { resolveDiplomacyMessageValidUntilTick } from '@sammo-ts/logic';
|
import { resolveDiplomacyMessageValidUntilTick, resolveNonAggressionMaxEndYear } from '@sammo-ts/logic';
|
||||||
import { isNeighbor } from '@sammo-ts/logic/world/distance.js';
|
import { isNeighbor } from '@sammo-ts/logic/world/distance.js';
|
||||||
import { resolveNationIncome } from './helpers.js';
|
import { resolveNationIncome } from './helpers.js';
|
||||||
|
|
||||||
@@ -99,7 +99,8 @@ export const do불가침제의 = (ai: GeneralAI) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [targetYear, targetMonth] = parseYearMonth(Math.floor(yearMonth + diplomatMonth));
|
const maxEndMonth = joinYearMonth(resolveNonAggressionMaxEndYear(ai.world.currentYear), 12);
|
||||||
|
const [targetYear, targetMonth] = parseYearMonth(Math.min(Math.floor(yearMonth + diplomatMonth), maxEndMonth));
|
||||||
const result = ai.buildNationCandidate(
|
const result = ai.buildNationCandidate(
|
||||||
'che_불가침제의',
|
'che_불가침제의',
|
||||||
{ destNationId, year: targetYear, month: targetMonth },
|
{ destNationId, year: targetYear, month: targetMonth },
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ describe('NPC 원조 기반 불가침 제의 lifecycle', () => {
|
|||||||
.peekDirtyState()
|
.peekDirtyState()
|
||||||
.messages.filter((message) => message.msgType === 'diplomacy' && message.option?.action === 'noAggression');
|
.messages.filter((message) => message.msgType === 'diplomacy' && message.option?.action === 'noAggression');
|
||||||
expect(firstMessages).toHaveLength(1);
|
expect(firstMessages).toHaveLength(1);
|
||||||
|
expect(firstMessages[0]?.option).toMatchObject({ year: 210, month: 12 });
|
||||||
const tryEntry = (world.getNationById(1)!.meta.resp_assist_try as Record<string, number[]>).n2!;
|
const tryEntry = (world.getNationById(1)!.meta.resp_assist_try as Record<string, number[]>).n2!;
|
||||||
expect(tryEntry).toHaveLength(3);
|
expect(tryEntry).toHaveLength(3);
|
||||||
const validUntilTick = tryEntry[2]!;
|
const validUntilTick = tryEntry[2]!;
|
||||||
|
|||||||
@@ -504,7 +504,32 @@ const commandTable = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
buildNationCommand('che_불가침제의', '불가침 제의'),
|
{
|
||||||
|
...buildNationCommand('che_불가침제의', '불가침 제의'),
|
||||||
|
inputFields: [
|
||||||
|
...buildNationCommand('che_불가침제의', '불가침 제의').inputFields,
|
||||||
|
{
|
||||||
|
key: 'year',
|
||||||
|
label: '기간(년)',
|
||||||
|
kind: 'number',
|
||||||
|
required: true,
|
||||||
|
min: 191,
|
||||||
|
max: 210,
|
||||||
|
step: 1,
|
||||||
|
defaultValue: 191,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'month',
|
||||||
|
label: '기간(월)',
|
||||||
|
kind: 'number',
|
||||||
|
required: true,
|
||||||
|
min: 1,
|
||||||
|
max: 12,
|
||||||
|
step: 1,
|
||||||
|
defaultValue: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
buildNationCommand('che_선전포고', '선전포고'),
|
buildNationCommand('che_선전포고', '선전포고'),
|
||||||
buildNationCommand('che_종전제의', '종전 제의'),
|
buildNationCommand('che_종전제의', '종전 제의'),
|
||||||
buildNationCommand('che_불가침파기제의', '불가침 파기 제의'),
|
buildNationCommand('che_불가침파기제의', '불가침 파기 제의'),
|
||||||
@@ -2158,6 +2183,42 @@ test('touch command maps select city and nation on the first tap without changin
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('limits non-aggression end years to the Ref twenty-year window on desktop and mobile', async ({
|
||||||
|
browser,
|
||||||
|
}, testInfo) => {
|
||||||
|
for (const viewport of [
|
||||||
|
{ name: 'desktop', width: 1200, height: 900 },
|
||||||
|
{ name: 'mobile', width: 500, height: 900 },
|
||||||
|
]) {
|
||||||
|
const context = await browser.newContext({ viewport });
|
||||||
|
const page = await context.newPage();
|
||||||
|
try {
|
||||||
|
await install(page);
|
||||||
|
await page.goto('/che/chief-center');
|
||||||
|
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
||||||
|
const picker = page.getByTestId('command-picker');
|
||||||
|
await picker.getByRole('button', { name: /^(?:국가:)?외교$/, exact: true }).click();
|
||||||
|
await picker.getByRole('button', { name: /불가침 제의/ }).click();
|
||||||
|
|
||||||
|
const form = picker.getByTestId('command-argument-form');
|
||||||
|
const yearInput = form.locator('#command-arg-year');
|
||||||
|
await expect(yearInput).toHaveAttribute('min', '191');
|
||||||
|
await expect(yearInput).toHaveAttribute('max', '210');
|
||||||
|
await expect(yearInput).toHaveValue('191');
|
||||||
|
|
||||||
|
const submit = picker.getByRole('button', { name: '입력', exact: true });
|
||||||
|
await yearInput.fill('211');
|
||||||
|
await expect(submit).toBeDisabled();
|
||||||
|
await yearInput.fill('210');
|
||||||
|
await expect(submit).toBeEnabled();
|
||||||
|
|
||||||
|
await picker.screenshot({ path: testInfo.outputPath(`non-aggression-year-limit-${viewport.name}.png`) });
|
||||||
|
} finally {
|
||||||
|
await context.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('shows a map and target details for every city or nation argument chief command except assignment', async ({
|
test('shows a map and target details for every city or nation argument chief command except assignment', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -2544,7 +2605,9 @@ test('keeps the entered command visible and reports a server validation error',
|
|||||||
await expect(page.getByRole('alert')).toContainText('대상 도시를 선택할 수 없습니다.');
|
await expect(page.getByRole('alert')).toContainText('대상 도시를 선택할 수 없습니다.');
|
||||||
await expect(page.getByTestId('command-argument-form').locator('select')).toHaveValue('2');
|
await expect(page.getByTestId('command-argument-form').locator('select')).toHaveValue('2');
|
||||||
await expect(submit).toBeEnabled();
|
await expect(submit).toBeEnabled();
|
||||||
await expect(page.getByTestId('command-picker').getByRole('button', { name: '저장 중', exact: true })).toHaveCount(0);
|
await expect(page.getByTestId('command-picker').getByRole('button', { name: '저장 중', exact: true })).toHaveCount(
|
||||||
|
0
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('keeps Ref command briefs and autonomous-action state after a turn mutation', async ({ page, context }) => {
|
test('keeps Ref command briefs and autonomous-action state after a turn mutation', async ({ page, context }) => {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export type CommandInputField = {
|
|||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
step?: number;
|
step?: number;
|
||||||
|
defaultValue?: string | number | boolean;
|
||||||
constValue?: string | number;
|
constValue?: string | number;
|
||||||
options?: CommandOption[];
|
options?: CommandOption[];
|
||||||
optionSource?: 'cities' | 'nations' | 'generals' | 'crewTypes' | 'armTypes' | 'nationTypes' | 'colors' | 'items';
|
optionSource?: 'cities' | 'nations' | 'generals' | 'crewTypes' | 'armTypes' | 'nationTypes' | 'colors' | 'items';
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ const optionsFor = (field: CommandInputField): CommandOption[] => {
|
|||||||
|
|
||||||
const defaultValue = (field: CommandInputField): unknown => {
|
const defaultValue = (field: CommandInputField): unknown => {
|
||||||
if (field.kind === 'hidden') return field.constValue;
|
if (field.kind === 'hidden') return field.constValue;
|
||||||
|
if (field.defaultValue !== undefined) return field.defaultValue;
|
||||||
if (field.kind === 'boolean') return true;
|
if (field.kind === 'boolean') return true;
|
||||||
if (field.kind === 'numberTuple') {
|
if (field.kind === 'numberTuple') {
|
||||||
const value = amountPreset.value?.defaultValue ?? field.min ?? 0;
|
const value = amountPreset.value?.defaultValue ?? field.min ?? 0;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import type { NationTurnCommandSpec } from './index.js';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { parseArgsWithSchema } from '../parseArgs.js';
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
import { resolveDiplomacyMessageValidMinutes } from '../../../diplomacy/messageValidity.js';
|
import { resolveDiplomacyMessageValidMinutes } from '../../../diplomacy/messageValidity.js';
|
||||||
|
import { NON_AGGRESSION_MIN_TERM_MONTHS, resolveNonAggressionMaxEndYear } from '../../../diplomacy/treatyTerm.js';
|
||||||
import { resolveMessageTargetIcon } from '@sammo-ts/logic/messages/message.js';
|
import { resolveMessageTargetIcon } from '@sammo-ts/logic/messages/message.js';
|
||||||
|
|
||||||
const ARGS_SCHEMA = z.object({
|
const ARGS_SCHEMA = z.object({
|
||||||
@@ -38,12 +39,11 @@ interface NonAggressionProposalContext<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ACTION_NAME = '불가침 제의';
|
const ACTION_NAME = '불가침 제의';
|
||||||
const MIN_TERM_MONTHS = 6;
|
|
||||||
|
|
||||||
const resolveMonthIndex = (year: number, month: number): number => year * 12 + month - 1;
|
const resolveMonthIndex = (year: number, month: number): number => year * 12 + month - 1;
|
||||||
|
|
||||||
const reqMinimumTreatyTerm = (minMonths: number): Constraint => ({
|
const reqTreatyTermRange = (minMonths: number): Constraint => ({
|
||||||
name: 'reqMinimumTreatyTerm',
|
name: 'reqTreatyTermRange',
|
||||||
requires: () => [
|
requires: () => [
|
||||||
{ kind: 'arg', key: 'year' },
|
{ kind: 'arg', key: 'year' },
|
||||||
{ kind: 'arg', key: 'month' },
|
{ kind: 'arg', key: 'month' },
|
||||||
@@ -100,6 +100,13 @@ const reqMinimumTreatyTerm = (minMonths: number): Constraint => ({
|
|||||||
reason: `기한은 ${minMonths}개월 이상이어야 합니다.`,
|
reason: `기한은 ${minMonths}개월 이상이어야 합니다.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const maxEndYear = resolveNonAggressionMaxEndYear(envYearValue);
|
||||||
|
if (yearValue > maxEndYear) {
|
||||||
|
return {
|
||||||
|
kind: 'deny',
|
||||||
|
reason: `기한은 ${maxEndYear}년 이하여야 합니다.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
return allow();
|
return allow();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -120,7 +127,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildPermissionConstraints(_ctx: ConstraintContext, _args: NonAggressionProposalArgs): Constraint[] {
|
buildPermissionConstraints(_ctx: ConstraintContext, _args: NonAggressionProposalArgs): Constraint[] {
|
||||||
return [reqMinimumTreatyTerm(MIN_TERM_MONTHS)];
|
return [reqTreatyTermRange(NON_AGGRESSION_MIN_TERM_MONTHS)];
|
||||||
}
|
}
|
||||||
|
|
||||||
buildMinConstraints(_ctx: ConstraintContext, _args: NonAggressionProposalArgs): Constraint[] {
|
buildMinConstraints(_ctx: ConstraintContext, _args: NonAggressionProposalArgs): Constraint[] {
|
||||||
@@ -133,7 +140,7 @@ export class ActionDefinition<
|
|||||||
notBeNeutral(),
|
notBeNeutral(),
|
||||||
existsDestNation(),
|
existsDestNation(),
|
||||||
differentDestNation(),
|
differentDestNation(),
|
||||||
reqMinimumTreatyTerm(MIN_TERM_MONTHS),
|
reqTreatyTermRange(NON_AGGRESSION_MIN_TERM_MONTHS),
|
||||||
disallowDiplomacyBetweenStatus({
|
disallowDiplomacyBetweenStatus({
|
||||||
0: '아국과 이미 교전중입니다.',
|
0: '아국과 이미 교전중입니다.',
|
||||||
1: '아국과 이미 선포중입니다.',
|
1: '아국과 이미 선포중입니다.',
|
||||||
|
|||||||
@@ -149,3 +149,4 @@ export const processDiplomacyMonth = (
|
|||||||
export * from './frontState.js';
|
export * from './frontState.js';
|
||||||
export * from './instantResponse.js';
|
export * from './instantResponse.js';
|
||||||
export * from './messageValidity.js';
|
export * from './messageValidity.js';
|
||||||
|
export * from './treatyTerm.js';
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const NON_AGGRESSION_MIN_TERM_MONTHS = 6;
|
||||||
|
export const NON_AGGRESSION_MAX_END_YEAR_OFFSET = 20;
|
||||||
|
|
||||||
|
export const resolveNonAggressionMaxEndYear = (currentYear: number): number =>
|
||||||
|
currentYear + NON_AGGRESSION_MAX_END_YEAR_OFFSET;
|
||||||
@@ -146,7 +146,7 @@ describe('legacy reservation permission constraints', () => {
|
|||||||
).toEqual({ kind: 'allow' });
|
).toEqual({ kind: 'allow' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks only the six-month minimum when reserving a non-aggression proposal', () => {
|
it('keeps non-aggression proposals between the six-month minimum and Ref twenty-year limit', () => {
|
||||||
const definition = new NonAggressionProposalAction();
|
const definition = new NonAggressionProposalAction();
|
||||||
const shortArgs = { destNationId: 2, year: 190, month: 6 };
|
const shortArgs = { destNationId: 2, year: 190, month: 6 };
|
||||||
expect(
|
expect(
|
||||||
@@ -162,5 +162,23 @@ describe('legacy reservation permission constraints', () => {
|
|||||||
).toEqual({
|
).toEqual({
|
||||||
kind: 'allow',
|
kind: 'allow',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const maxTermArgs = { destNationId: 2, year: 210, month: 12 };
|
||||||
|
expect(
|
||||||
|
evaluatePermission(definition.buildPermissionConstraints(permissionContext, maxTermArgs), maxTermArgs)
|
||||||
|
).toEqual({
|
||||||
|
kind: 'allow',
|
||||||
|
});
|
||||||
|
|
||||||
|
const excessiveTermArgs = { destNationId: 2, year: 211, month: 1 };
|
||||||
|
expect(
|
||||||
|
evaluatePermission(
|
||||||
|
definition.buildPermissionConstraints(permissionContext, excessiveTermArgs),
|
||||||
|
excessiveTermArgs
|
||||||
|
)
|
||||||
|
).toMatchObject({
|
||||||
|
kind: 'deny',
|
||||||
|
reason: '기한은 210년 이하여야 합니다.',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user