import { describe, expect, it } from 'vitest'; import { parseAiCalculationAuthoringRequest, validateAiCalculationRevisionDraft } from './calculation-authoring-schema'; const attributes = new Set(['weapon-damage', 'skill-damage']); const draft = { name: 'Combat model 2', notes: 'Drafted from field observations.', stages: [ { stageKey: 'base', label: 'Base damage', operator: 'base_damage', displayOrder: 1, lockedPosition: 'first' }, { stageKey: 'additive', label: 'Additive damage', operator: 'additive_percent', displayOrder: 2, lockedPosition: null }, { stageKey: 'cycle', label: 'Firing cycle', operator: 'firing_cycle', displayOrder: 3, lockedPosition: 'last' } ], profiles: [{ statAttributeId: 'weapon-damage', stageKey: 'additive', weight: 1, softCap: null, hardCap: null, curveType: 'none', curveParameter: null, weaponCategory: '*' }], skillProfiles: [{ statAttributeId: 'skill-damage', stageKey: 'skill_damage', operator: 'add', weight: 1 }] }; describe('AI calculation authoring validation', () => { it('accepts a calculation revision accepted by the manual save constraints', () => { expect(validateAiCalculationRevisionDraft(draft, attributes)).toEqual(draft); }); it('requires base damage first and firing cycle last', () => { expect(() => validateAiCalculationRevisionDraft({ ...draft, stages: draft.stages.map((stage) => ({ ...stage, lockedPosition: null })) }, attributes)).toThrow('Base damage must remain first'); }); it('rejects unavailable Attributes', () => { expect(() => validateAiCalculationRevisionDraft({ ...draft, profiles: [{ ...draft.profiles[0], statAttributeId: 'invented-attribute' }] }, attributes)).toThrow('Weapon Attribute profile 1 is invalid.'); }); it('bounds the stateless clarification transcript', () => { expect(() => parseAiCalculationAuthoringRequest({ instructions: 'Add Weapon Damage to the additive stage.', currentDraft: draft, transcript: Array.from({ length: 9 }, (_, index) => ({ questionId: String(index), question: 'Question', answer: 'Answer' })) })).toThrow('Clarification history may contain at most 8 answers.'); }); });