merge: 최신 main을 transport 권한과 durable 검증에 최종 통합한다
This commit is contained in:
@@ -718,6 +718,7 @@ describe('legacy NPC user-chief promotion parity', () => {
|
||||
it('keeps user-ruler duties individually disabled until each setting is enabled', () => {
|
||||
const ruler = makePromotionGeneral({ id: 1, officerLevel: 12, npcState: 0, meta: { killturn: 0 } });
|
||||
expect(canUseAutomatedNationAction(ruler, '선전포고')).toBe(false);
|
||||
expect(canUseAutomatedNationAction(ruler, '불가침제의')).toBe(false);
|
||||
expect(canUseAutomatedNationAction(ruler, '천도')).toBe(false);
|
||||
expect(canUseRulerAutomation(ruler, 'finance')).toBe(false);
|
||||
|
||||
@@ -728,9 +729,17 @@ describe('legacy NPC user-chief promotion parity', () => {
|
||||
use_auto_nation_finance: 1,
|
||||
};
|
||||
expect(canUseAutomatedNationAction(ruler, '불가침제의')).toBe(true);
|
||||
expect(canUseAutomatedNationAction(ruler, '선전포고')).toBe(true);
|
||||
expect(canUseAutomatedNationAction(ruler, '선전포고')).toBe(false);
|
||||
expect(canUseAutomatedNationAction(ruler, '천도')).toBe(true);
|
||||
expect(canUseRulerAutomation(ruler, 'finance')).toBe(true);
|
||||
|
||||
ruler.meta = {
|
||||
...ruler.meta,
|
||||
use_auto_nation_diplomacy: 0,
|
||||
use_auto_nation_war: 1,
|
||||
};
|
||||
expect(canUseAutomatedNationAction(ruler, '불가침제의')).toBe(false);
|
||||
expect(canUseAutomatedNationAction(ruler, '선전포고')).toBe(true);
|
||||
});
|
||||
|
||||
it('honors the existing automatic nation-turn master switch for user chiefs only', () => {
|
||||
|
||||
@@ -373,6 +373,13 @@ describe('core monthly event actions at the real month boundary', () => {
|
||||
await world.advanceMonth(new Date('0191-01-01T00:00:00.000Z'));
|
||||
|
||||
expect(world.getNationById(1)?.meta.prev_income_gold).toBe(157.5);
|
||||
expect(world.peekDirtyState().logs).toContainEqual(
|
||||
expect.objectContaining({
|
||||
generalId: 1,
|
||||
text: '이번 수입은 금 <C>158</>입니다.',
|
||||
})
|
||||
);
|
||||
expect(world.peekDirtyState().logs.map((entry) => entry.text).join('\n')).not.toContain('157.5');
|
||||
});
|
||||
|
||||
it('uses the Ref default nation resource floors when scenario const omits them', async () => {
|
||||
|
||||
@@ -223,6 +223,7 @@ describe('my information world commands', () => {
|
||||
use_treatment: 200,
|
||||
use_auto_nation_turn: 0,
|
||||
use_auto_nation_diplomacy: 1,
|
||||
use_auto_nation_war: 1,
|
||||
use_auto_nation_promotion: 1,
|
||||
use_auto_nation_finance: 1,
|
||||
use_auto_nation_capital: 1,
|
||||
@@ -239,6 +240,7 @@ describe('my information world commands', () => {
|
||||
use_treatment: 100,
|
||||
use_auto_nation_turn: 0,
|
||||
use_auto_nation_diplomacy: 1,
|
||||
use_auto_nation_war: 1,
|
||||
use_auto_nation_promotion: 1,
|
||||
use_auto_nation_finance: 1,
|
||||
use_auto_nation_capital: 1,
|
||||
|
||||
@@ -312,6 +312,39 @@ describe('nation personnel world commands', () => {
|
||||
expect(fixture.world.peekDirtyState().logs).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('rejects self, ruler, head officer, and ambassador targets without partial mutation', async () => {
|
||||
const cases = [
|
||||
{ label: 'self', targetId: 2, reason: '본인은 추방할 수 없습니다.' },
|
||||
{ label: 'ruler', targetId: 1, reason: '군주는 추방할 수 없습니다.' },
|
||||
{ label: 'head officer', targetId: 3, reason: '수뇌는 추방할 수 없습니다.' },
|
||||
{ label: 'ambassador', targetId: 4, reason: '외교권자는 추방할 수 없습니다.' },
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
const fixture = buildWorld({
|
||||
generals: [
|
||||
buildGeneral(1, { officerLevel: 12 }),
|
||||
buildGeneral(2, { officerLevel: 5 }),
|
||||
buildGeneral(3, { officerLevel: 7 }),
|
||||
buildGeneral(4, {
|
||||
meta: { killturn: 12, belong: 5, permission: 'ambassador' },
|
||||
penalty: { noAmbassador: true },
|
||||
}),
|
||||
buildGeneral(5),
|
||||
],
|
||||
});
|
||||
const originalTarget = fixture.world.getGeneralById(testCase.targetId);
|
||||
|
||||
await expect(
|
||||
fixture.handler.handle({ type: 'kick', generalId: 2, destGeneralId: testCase.targetId })
|
||||
).resolves.toMatchObject({ ok: false, reason: testCase.reason });
|
||||
expect(fixture.world.getGeneralById(testCase.targetId), testCase.label).toEqual(originalTarget);
|
||||
expect(fixture.world.getGeneralById(2)?.meta.killturn, testCase.label).toBe(12);
|
||||
expect(fixture.world.peekDirtyState().logs, testCase.label).toEqual([]);
|
||||
expect(fixture.world.peekDirtyState().nations, testCase.label).toEqual([]);
|
||||
}
|
||||
});
|
||||
|
||||
it('preserves the legacy kick year boundaries and deterministic NPC public message', async () => {
|
||||
const early = buildWorld({
|
||||
currentYear: 181,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { GAME_TICKS_PER_TURN } from '@sammo-ts/common';
|
||||
import { parseScenarioGeneralPoolCandidate } from '@sammo-ts/logic';
|
||||
|
||||
import { InMemoryTurnWorld } from '../src/turn/inMemoryWorld.js';
|
||||
import { reserveSelectionPool } from '../src/turn/selectPoolService.js';
|
||||
import { reserveSelectionPool, resolveSelectionPoolUserIcon } from '../src/turn/selectPoolService.js';
|
||||
import type { TurnGeneralPoolEntry, TurnWorldSnapshot, TurnWorldState } from '../src/turn/types.js';
|
||||
|
||||
interface TestPoolRow {
|
||||
@@ -171,6 +171,27 @@ const worldState = {
|
||||
};
|
||||
|
||||
describe('selection-pool reservation command state', () => {
|
||||
it('uses only an explicitly selected owner icon for a human general', () => {
|
||||
expect(resolveSelectionPoolUserIcon({ showImgLevel: 3 })).toEqual({
|
||||
picture: 'default.jpg',
|
||||
imageServer: 0,
|
||||
});
|
||||
expect(
|
||||
resolveSelectionPoolUserIcon({
|
||||
showImgLevel: 3,
|
||||
ownerPicture: 'uploaded/user.png',
|
||||
ownerImageServer: 1,
|
||||
})
|
||||
).toEqual({ picture: 'uploaded/user.png', imageServer: 1 });
|
||||
expect(
|
||||
resolveSelectionPoolUserIcon({
|
||||
showImgLevel: 0,
|
||||
ownerPicture: 'uploaded/user.png',
|
||||
ownerImageServer: 1,
|
||||
})
|
||||
).toEqual({ picture: 'default.jpg', imageServer: 0 });
|
||||
});
|
||||
|
||||
it('excludes current reservations and keeps serialized users disjoint in DB and memory', async () => {
|
||||
const rows = buildRows();
|
||||
const world = buildWorld(rows);
|
||||
|
||||
Reference in New Issue
Block a user