import UpgraderHandler from '@/roleHandlers/upgrader.handler'; import * as getById from '@/utils/funcs/getById'; import { PositionSpotStatus, createSourcePositionMatrix, } from '@/utils/positions'; import '~/tests/__mocks__/screeps'; describe('UpgraderHandler', () => { type FixtureOptions = { memory?: Partial; storeUsed?: number; storeCapacity?: number; stateOverrides?: Partial; sourceId?: string; controllerId?: string; controllerInRoom?: StructureController | null; }; const createFixture = ({ memory = {}, storeUsed = 0, storeCapacity = 50, stateOverrides = {}, sourceId = 'src1', controllerId = 'ctrl1', controllerInRoom = null, }: FixtureOptions = {}) => { const state = { sourcesStates: { [sourceId]: { spots: createSourcePositionMatrix( PositionSpotStatus.EMPTY as any ), }, }, ...stateOverrides, } as unknown as GameState; const source = { id: sourceId, pos: new (global as any).RoomPosition(5, 5, 'W1N1'), } as unknown as Source; const controller = { id: controllerId } as StructureController; const creep = { name: 'Creep1', memory: { role: 'upgrader', spawnId: 'spawn1', ...memory, } as CreepMemory, store: { getFreeCapacity: (_res: string) => storeCapacity - storeUsed, getUsedCapacity: (_res: string) => storeUsed, }, pos: new (global as any).RoomPosition(0, 0, 'W1N1'), room: { controller: controllerInRoom, }, harvest: jest.fn().mockReturnValue((global as any).OK), moveTo: jest.fn().mockReturnValue((global as any).OK), upgradeController: jest.fn().mockReturnValue((global as any).OK), } as unknown as Creep; return { state, source, controller, creep, memory: creep.memory, mockSource: (value: Source | null = source) => jest.spyOn(getById, 'getSourceById').mockReturnValue(value), mockController: (value: StructureController | null = controller) => jest .spyOn(getById, 'getControllerById') .mockReturnValue(value), }; }; beforeEach(() => { jest.clearAllMocks(); jest.spyOn(console, 'log').mockImplementation(() => {}); }); afterEach(() => { jest.restoreAllMocks(); }); describe('destroy', () => { it('releases the source spot from destination and clears it', () => { const { state, memory } = createFixture({ memory: { destination: { type: 'source', id: 'src1', sourceSpot: 0 }, }, }); state.sourcesStates['src1'].spots[0] = PositionSpotStatus.OCCUPIED; UpgraderHandler.destroy(memory, state); expect(state.sourcesStates['src1'].spots[0]).toBe( PositionSpotStatus.EMPTY ); expect(memory.destination).toBeUndefined(); }); it('releases the source spot from previousDestination and clears it', () => { const { state, memory } = createFixture({ memory: { previousDestination: { type: 'source', id: 'src1', sourceSpot: 3, }, }, }); state.sourcesStates['src1'].spots[3] = PositionSpotStatus.OCCUPIED; UpgraderHandler.destroy(memory, state); expect(state.sourcesStates['src1'].spots[3]).toBe( PositionSpotStatus.EMPTY ); expect(memory.previousDestination).toBeUndefined(); }); it('releases both destination and previousDestination source spots when both are set', () => { const { state, memory } = createFixture({ memory: { destination: { type: 'source', id: 'src1', sourceSpot: 0 }, previousDestination: { type: 'source', id: 'src1', sourceSpot: 3, }, }, }); state.sourcesStates['src1'].spots[0] = PositionSpotStatus.OCCUPIED; state.sourcesStates['src1'].spots[3] = PositionSpotStatus.OCCUPIED; UpgraderHandler.destroy(memory, state); expect(state.sourcesStates['src1'].spots[0]).toBe( PositionSpotStatus.EMPTY ); expect(state.sourcesStates['src1'].spots[3]).toBe( PositionSpotStatus.EMPTY ); expect(memory.destination).toBeUndefined(); expect(memory.previousDestination).toBeUndefined(); }); it('does nothing when destination is not a source', () => { const { state, memory } = createFixture({ memory: { destination: { type: 'controller', id: 'ctrl1' }, }, }); UpgraderHandler.destroy(memory, state); expect( state.sourcesStates['src1'].spots.every( (s: PositionSpotStatus) => s === PositionSpotStatus.EMPTY || s === PositionSpotStatus.CENTER ) ).toBe(true); }); it('does nothing when memory has no destination', () => { const { state, memory } = createFixture(); expect(() => UpgraderHandler.destroy(memory, state)).not.toThrow(); }); it('does not throw when destination source ID is missing from state', () => { const { state, memory } = createFixture({ memory: { destination: { type: 'source', id: 'nonexistent', sourceSpot: 0, }, }, }); expect(() => UpgraderHandler.destroy(memory, state)).not.toThrow(); expect(memory.destination).toBeUndefined(); }); }); describe('validateCreepMemory (via run)', () => { it('releases previousDestination source spot and deletes it', () => { const { state, creep, mockSource } = createFixture({ memory: { previousDestination: { type: 'source', id: 'src1', sourceSpot: 1, }, }, }); state.sourcesStates['src1'].spots[1] = PositionSpotStatus.OCCUPIED; mockSource(null); UpgraderHandler.run(creep, state); expect(state.sourcesStates['src1'].spots[1]).toBe( PositionSpotStatus.EMPTY ); expect(creep.memory.previousDestination).toBeUndefined(); }); it('transitions from source to controller destination when energy is full', () => { const { state, controller, creep, mockController } = createFixture({ memory: { destination: { type: 'source', id: 'src1', sourceSpot: 0, }, }, storeUsed: 50, storeCapacity: 50, controllerInRoom: { id: 'ctrl1' } as StructureController, }); // getSourceById is not called in this path — only getControllerById is needed mockController(controller); UpgraderHandler.run(creep, state); expect(creep.memory.destination?.type).toBe('controller'); expect(creep.memory.previousDestination?.type).toBe('source'); }); it('clears controller destination when energy is empty', () => { const { state, controller, creep, mockController } = createFixture({ memory: { destination: { type: 'controller', id: 'ctrl1' }, }, storeUsed: 0, storeCapacity: 50, controllerInRoom: { id: 'ctrl1' } as StructureController, }); mockController(controller); UpgraderHandler.run(creep, state); expect(creep.memory.destination).toBeUndefined(); }); it('clears destination and logs when room has no controller during source->controller transition', () => { const { state, creep, mockSource } = createFixture({ memory: { destination: { type: 'source', id: 'src1', sourceSpot: 0 }, }, storeUsed: 50, storeCapacity: 50, controllerInRoom: null, }); state.sourcesStates['src1'].spots = state.sourcesStates[ 'src1' ].spots.map((s: PositionSpotStatus) => s === PositionSpotStatus.CENTER ? s : PositionSpotStatus.OCCUPIED ) as any; mockSource(); UpgraderHandler.run(creep, state); expect(creep.memory.destination).toBeUndefined(); // previousDestination is set to the old source before checking room.controller, // so the source spot can be released on the next tick expect(creep.memory.previousDestination).toEqual({ type: 'source', id: 'src1', sourceSpot: 0, }); }); }); describe('onFindNewSource', () => { it('assigns the first available source spot to the creep', () => { const { state, creep, mockSource } = createFixture(); mockSource(); UpgraderHandler.run(creep, state); expect(creep.memory.destination?.type).toBe('source'); expect((creep.memory.destination as any).id).toBe('src1'); }); it('does not assign a source when all spots are occupied', () => { const { state, creep, mockSource } = createFixture(); state.sourcesStates['src1'].spots = state.sourcesStates[ 'src1' ].spots.map((s: PositionSpotStatus) => s === PositionSpotStatus.CENTER ? s : PositionSpotStatus.OCCUPIED ) as any; mockSource(); UpgraderHandler.run(creep, state); expect(creep.memory.destination).toBeUndefined(); }); it('marks the assigned source spot as OCCUPIED in state', () => { const { state, creep, mockSource } = createFixture(); mockSource(); UpgraderHandler.run(creep, state); const spotIndex = (creep.memory.destination as any)?.sourceSpot; expect(spotIndex).toBeDefined(); expect(state.sourcesStates['src1'].spots[spotIndex]).toBe( PositionSpotStatus.OCCUPIED ); }); }); describe('onSourceDestination', () => { it('harvests when in range', () => { const { state, source, creep, mockSource } = createFixture({ memory: { destination: { type: 'source', id: 'src1', sourceSpot: 0 }, }, }); mockSource(); (creep.harvest as jest.Mock).mockReturnValue((global as any).OK); UpgraderHandler.run(creep, state); expect(creep.harvest).toHaveBeenCalledWith(source); expect(creep.moveTo).not.toHaveBeenCalled(); }); it('moves to source when not in range', () => { const { state, creep, mockSource } = createFixture({ memory: { destination: { type: 'source', id: 'src1', sourceSpot: 0 }, }, }); mockSource(); (creep.harvest as jest.Mock).mockReturnValue( (global as any).ERR_NOT_IN_RANGE ); UpgraderHandler.run(creep, state); expect(creep.moveTo).toHaveBeenCalled(); }); it('moves to the computed spot position (not source pos) when not in range', () => { const { state, creep, mockSource } = createFixture({ memory: { destination: { type: 'source', id: 'src1', sourceSpot: 0 }, }, }); mockSource(); (creep.harvest as jest.Mock).mockReturnValue( (global as any).ERR_NOT_IN_RANGE ); UpgraderHandler.run(creep, state); expect(creep.moveTo).toHaveBeenCalledWith( // spot 0 → delta (-1,-1) from source pos (5,5) → (4,4) expect.objectContaining({ x: 4, y: 4, roomName: 'W1N1' }), expect.objectContaining({ reusePath: 10 }) ); }); it('logs and returns when source is not found', () => { const { state, creep, mockSource } = createFixture({ memory: { destination: { type: 'source', id: 'unknown', sourceSpot: 0, }, }, }); mockSource(null); expect(() => UpgraderHandler.run(creep, state)).not.toThrow(); expect(creep.harvest).not.toHaveBeenCalled(); }); }); describe('onControllerDestination', () => { it('upgrades controller when in range', () => { const { state, controller, creep, mockController } = createFixture({ memory: { destination: { type: 'controller', id: 'ctrl1' }, }, storeUsed: 50, storeCapacity: 50, controllerInRoom: { id: 'ctrl1' } as StructureController, }); mockController(controller); (creep.upgradeController as jest.Mock).mockReturnValue( (global as any).OK ); UpgraderHandler.run(creep, state); expect(creep.upgradeController).toHaveBeenCalledWith(controller); expect(creep.moveTo).not.toHaveBeenCalled(); }); it('moves to controller when not in range', () => { const { state, controller, creep, mockController } = createFixture({ memory: { destination: { type: 'controller', id: 'ctrl1' }, }, storeUsed: 50, storeCapacity: 50, controllerInRoom: { id: 'ctrl1' } as StructureController, }); mockController(controller); (creep.upgradeController as jest.Mock).mockReturnValue( (global as any).ERR_NOT_IN_RANGE ); UpgraderHandler.run(creep, state); expect(creep.moveTo).toHaveBeenCalledWith( controller, expect.any(Object) ); }); it('logs and returns when controller is not found by ID', () => { const { state, creep, mockController } = createFixture({ memory: { destination: { type: 'controller', id: 'ctrl1' }, }, storeUsed: 50, storeCapacity: 50, }); mockController(null); expect(() => UpgraderHandler.run(creep, state)).not.toThrow(); expect(creep.upgradeController).not.toHaveBeenCalled(); }); }); describe('run', () => { it('returns the state object', () => { const { state, creep, mockSource } = createFixture(); mockSource(null); const result = UpgraderHandler.run(creep, state); expect(result).toBe(state); }); }); });