import 'package:flutter_test/flutter_test.dart'; import 'package:uto_manager/domain/entities/authorized_rank.dart'; import 'package:uto_manager/domain/entities/doctrinal_branch.dart'; import 'package:uto_manager/domain/entities/echelon.dart'; import 'package:uto_manager/domain/entities/personnel_record.dart'; import 'package:uto_manager/domain/entities/position_slot.dart'; import 'package:uto_manager/domain/entities/unit_node.dart'; import 'package:uto_manager/domain/services/assignment_service.dart'; import 'package:uto_manager/domain/services/unit_tree_service.dart'; import 'package:uto_manager/domain/value_objects/duty_mos.dart'; void main() { const treeService = UnitTreeService(); const service = AssignmentService(treeService: treeService); PositionSlot makeSlot({ String slotId = 's1', String unitId = 'u1', String mosCode = '11B', AuthorizedRank rank = AuthorizedRank.e5, bool isKeyLeader = false, String? assignedPersonnelId, }) { return PositionSlot( slotId: slotId, unitId: unitId, positionTitle: 'Test Position', dutyMos: DutyMos(mosCode), authorizedRank: rank, isKeyLeader: isKeyLeader, assignedPersonnelId: assignedPersonnelId, ); } PersonnelRecord makeSoldier({ String id = 'p1', String mosCode = '11B', AuthorizedRank rank = AuthorizedRank.e5, String? assignedSlotId, ReadinessState readiness = ReadinessState.fullyMissionCapable, }) { return PersonnelRecord( soldierId: id, name: 'SGT Test', primaryMos: DutyMos(mosCode), payGrade: rank, readinessState: readiness, assignedSlotId: assignedSlotId, ); } // --------------------------------------------------------------------------- // assign // --------------------------------------------------------------------------- group('assign', () { test('successful assignment returns success outcome', () { final slot = makeSlot(); final soldier = makeSoldier(); final outcome = service.assign(slot, soldier); expect(outcome.isSuccess, isTrue); expect(outcome.updatedSlot?.assignedPersonnelId, 'p1'); expect(outcome.updatedSoldier?.assignedSlotId, 's1'); }); test('assignment to filled slot returns failure', () { final slot = makeSlot(assignedPersonnelId: 'existing'); final soldier = makeSoldier(); final outcome = service.assign(slot, soldier); expect(outcome.isSuccess, isFalse); expect(outcome.failureReason, contains('already filled')); }); test('assignment of already-assigned soldier returns failure', () { final slot = makeSlot(); final soldier = makeSoldier(assignedSlotId: 'other-slot'); final outcome = service.assign(slot, soldier); expect(outcome.isSuccess, isFalse); expect(outcome.failureReason, contains('already assigned')); }); test('MOS mismatch is flagged but does not block assignment', () { final slot = makeSlot(mosCode: '68W'); final soldier = makeSoldier(); final outcome = service.assign(slot, soldier); expect(outcome.isSuccess, isTrue); expect(outcome.hasMosMismatch, isTrue); }); test('rank mismatch is flagged when soldier rank is below minimum', () { final slot = makeSlot(rank: AuthorizedRank.e7); final soldier = makeSoldier(rank: AuthorizedRank.e4); final outcome = service.assign(slot, soldier); expect(outcome.isSuccess, isTrue); expect(outcome.hasRankMismatch, isTrue); }); test('no rank mismatch when soldier meets minimum rank', () { final slot = makeSlot(); final soldier = makeSoldier(rank: AuthorizedRank.e6); final outcome = service.assign(slot, soldier); expect(outcome.isSuccess, isTrue); expect(outcome.hasRankMismatch, isFalse); }); test('updated slot has correct personnel ID', () { final slot = makeSlot(slotId: 'slot-abc'); final soldier = makeSoldier(id: 'soldier-xyz'); final outcome = service.assign(slot, soldier); expect(outcome.updatedSlot?.assignedPersonnelId, 'soldier-xyz'); expect(outcome.updatedSoldier?.assignedSlotId, 'slot-abc'); }); }); // --------------------------------------------------------------------------- // unassign // --------------------------------------------------------------------------- group('unassign', () { test('unassigning from vacant slot returns unchanged slot', () { final slot = makeSlot(); final outcome = service.unassign(slot, null); expect(outcome.updatedSlot.isVacant, isTrue); expect(outcome.updatedSoldier, isNull); }); test('unassigning from filled slot clears assignment', () { final slot = makeSlot(assignedPersonnelId: 'p1'); final soldier = makeSoldier(assignedSlotId: 's1'); final outcome = service.unassign(slot, soldier); expect(outcome.updatedSlot.isVacant, isTrue); expect(outcome.updatedSoldier?.isUnassigned, isTrue); }); test('unassigning with null soldier still clears slot', () { final slot = makeSlot(assignedPersonnelId: 'p1'); final outcome = service.unassign(slot, null); expect(outcome.updatedSlot.isVacant, isTrue); expect(outcome.updatedSoldier, isNull); }); }); // --------------------------------------------------------------------------- // aggregateReadiness // --------------------------------------------------------------------------- group('aggregateReadiness', () { test('empty list returns grey', () { expect(service.aggregateReadiness([]), ReadinessColor.grey); }); test('all FMC returns green', () { final soldiers = [ makeSoldier(), makeSoldier(id: 'p2'), ]; expect(service.aggregateReadiness(soldiers), ReadinessColor.green); }); test('any NMC returns red', () { final soldiers = [ makeSoldier(), makeSoldier(id: 'p2', readiness: ReadinessState.notMissionCapable), ]; expect(service.aggregateReadiness(soldiers), ReadinessColor.red); }); test('mixed FMC and PMC returns amber', () { final soldiers = [ makeSoldier(), makeSoldier(id: 'p2', readiness: ReadinessState.partiallyMissionCapable), ]; expect(service.aggregateReadiness(soldiers), ReadinessColor.amber); }); test('single PMC soldier returns amber', () { final soldiers = [makeSoldier(readiness: ReadinessState.partiallyMissionCapable)]; expect(service.aggregateReadiness(soldiers), ReadinessColor.amber); }); }); // --------------------------------------------------------------------------- // UnitNode fixture (used to verify UnitTreeService integration) // --------------------------------------------------------------------------- group('UnitTreeService integration via AssignmentService', () { test('validateReparent is accessible through treeService', () { const bde = UnitNode( unitId: 'bde', echelon: Echelon.brigade, branch: DoctrinalBranch.infantry, name: 'Brigade', isKeyLeader: false, ); const bn = UnitNode( unitId: 'bn', echelon: Echelon.battalion, branch: DoctrinalBranch.infantry, name: 'Battalion', isKeyLeader: false, parentUnitId: 'bde', ); final result = service.treeService.validateReparent(bn, bde, [bde, bn]); // bn (battalion) under bde (brigade) is valid expect(result.isValid, isTrue); }); }); }