feat: restore Runiczny Pancerz with Mana; NBP rates module with latest-rate fallback
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { fetchNbpRate } from '../../src/logic/nbp';
|
||||
|
||||
const ok = (mid: number, effectiveDate: string) => ({ ok: true, json: async () => ({ rates: [{ mid, effectiveDate }] }) });
|
||||
const notFound = { ok: false, json: async () => ({}) };
|
||||
const now = new Date('2026-10-20');
|
||||
|
||||
describe('fetchNbpRate', () => {
|
||||
it('uses the June window for a past year', async () => {
|
||||
const f = vi.fn().mockResolvedValue(ok(4.4, '2024-06-03'));
|
||||
expect(await fetchNbpRate('EUR', 2024, now, f)).toEqual({ rate: 4.4, date: '2024-06-03' });
|
||||
expect(f.mock.calls[0]![0]).toContain('/eur/2024-06-01/2024-06-14/');
|
||||
});
|
||||
|
||||
it('uses the latest rate for a future year without a dated request', async () => {
|
||||
const f = vi.fn().mockResolvedValue(ok(4.3, '2026-10-19'));
|
||||
expect(await fetchNbpRate('eur', 2031, now, f)).toEqual({ rate: 4.3, date: '2026-10-19' });
|
||||
expect(f).toHaveBeenCalledTimes(1);
|
||||
expect(f.mock.calls[0]![0]).not.toMatch(/\d{4}-\d{2}-\d{2}\//);
|
||||
});
|
||||
|
||||
it('falls back to the latest rate when the dated window has no table', async () => {
|
||||
const f = vi.fn().mockResolvedValueOnce(notFound).mockResolvedValueOnce(ok(4.2, '2026-10-19'));
|
||||
expect((await fetchNbpRate('eur', 2026, now, f))?.rate).toBe(4.2);
|
||||
});
|
||||
|
||||
it('returns null when NBP is unreachable', async () => {
|
||||
const f = vi.fn().mockRejectedValue(new Error('offline'));
|
||||
expect(await fetchNbpRate('eur', 2024, now, f)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { rechargeRunicArmor } from '../../src/logic/runic-armor';
|
||||
import { runicArmorManaCost, runicArmorRestorable } from '../../src/logic/rules';
|
||||
|
||||
function makeActor(armor: { value: number; max: number }, mana: number) {
|
||||
const update = vi.fn().mockResolvedValue(undefined);
|
||||
return { actor: { system: { attributes: { magicalArmor: armor, mana: { value: mana, max: 30 } } }, update } as any, update };
|
||||
}
|
||||
|
||||
describe('Runiczny Pancerz restore', () => {
|
||||
it('costs 2 Mana per point and never exceeds max', () => {
|
||||
expect(runicArmorManaCost(3)).toBe(6);
|
||||
expect(runicArmorRestorable(2, 4, 5)).toBe(2);
|
||||
expect(runicArmorRestorable(4, 4, 1)).toBe(0);
|
||||
});
|
||||
|
||||
it('restores points and spends mana', async () => {
|
||||
const { actor, update } = makeActor({ value: 1, max: 4 }, 10);
|
||||
const res = await rechargeRunicArmor(actor, 2);
|
||||
expect(res).toEqual({ ok: true, restored: 2, manaSpent: 4 });
|
||||
expect(update).toHaveBeenCalledWith({
|
||||
'system.attributes.magicalArmor.value': 3,
|
||||
'system.attributes.mana.value': 6,
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses when mana is short, unless partial', async () => {
|
||||
const { actor, update } = makeActor({ value: 0, max: 4 }, 5);
|
||||
expect((await rechargeRunicArmor(actor, 3)).reason).toBe('insufficient-mana');
|
||||
expect(update).not.toHaveBeenCalled();
|
||||
const res = await rechargeRunicArmor(actor, 3, { partial: true });
|
||||
expect(res).toEqual({ ok: true, restored: 2, manaSpent: 4 });
|
||||
});
|
||||
|
||||
it('reports already-full armor', async () => {
|
||||
const { actor } = makeActor({ value: 4, max: 4 }, 10);
|
||||
expect((await rechargeRunicArmor(actor, 1)).reason).toBe('already-full');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user