uto_manager/test/infrastructure/codecs/unit_tree_codec_test.dart

242 lines
7.9 KiB
Dart

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/value_objects/duty_mos.dart';
import 'package:uto_manager/infrastructure/codecs/unit_tree_codec.dart';
void main() {
const codec = UnitTreeCodec();
// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------
final brigade = UnitNode(
unitId: 'bde-1',
echelon: Echelon.brigade,
branch: DoctrinalBranch.infantry,
name: 'Test Brigade',
isKeyLeader: false,
);
final battalion = UnitNode(
unitId: 'bn-1',
echelon: Echelon.battalion,
branch: DoctrinalBranch.infantry,
parentUnitId: 'bde-1',
name: 'Test Battalion',
isKeyLeader: false,
);
final slot = PositionSlot(
slotId: 'slot-1',
unitId: 'bde-1',
positionTitle: 'Brigade Commander',
dutyMos: DutyMos.unvalidated('11A'),
authorizedRank: AuthorizedRank.o6,
isKeyLeader: true,
);
final soldier = PersonnelRecord(
soldierId: 'p-1',
name: 'COL Test',
primaryMos: DutyMos.unvalidated('11A'),
payGrade: AuthorizedRank.o6,
readinessState: ReadinessState.fullyMissionCapable,
assignedSlotId: 'slot-1',
);
// ---------------------------------------------------------------------------
// encode / decode round-trip
// ---------------------------------------------------------------------------
group('encode / decode round-trip', () {
test('empty snapshot round-trips correctly', () {
final json = codec.encode(units: [], slots: [], personnel: []);
final snapshot = codec.decode(json);
expect(snapshot.units, isEmpty);
expect(snapshot.slots, isEmpty);
expect(snapshot.personnel, isEmpty);
});
test('units round-trip with all fields preserved', () {
final json = codec.encode(
units: [brigade, battalion],
slots: [],
personnel: [],
);
final snapshot = codec.decode(json);
expect(snapshot.units.length, 2);
final decodedBde = snapshot.units.firstWhere((u) => u.unitId == 'bde-1');
expect(decodedBde.name, brigade.name);
expect(decodedBde.echelon, brigade.echelon);
expect(decodedBde.branch, brigade.branch);
expect(decodedBde.parentUnitId, isNull);
expect(decodedBde.isKeyLeader, brigade.isKeyLeader);
final decodedBn = snapshot.units.firstWhere((u) => u.unitId == 'bn-1');
expect(decodedBn.parentUnitId, 'bde-1');
});
test('slots round-trip with all fields preserved', () {
final json = codec.encode(
units: [brigade],
slots: [slot],
personnel: [],
);
final snapshot = codec.decode(json);
expect(snapshot.slots.length, 1);
final decodedSlot = snapshot.slots.first;
expect(decodedSlot.slotId, slot.slotId);
expect(decodedSlot.unitId, slot.unitId);
expect(decodedSlot.positionTitle, slot.positionTitle);
expect(decodedSlot.dutyMos.code, slot.dutyMos.code);
expect(decodedSlot.authorizedRank, slot.authorizedRank);
expect(decodedSlot.isKeyLeader, slot.isKeyLeader);
expect(decodedSlot.assignedPersonnelId, isNull);
});
test('personnel round-trip with all fields preserved', () {
final json = codec.encode(
units: [],
slots: [],
personnel: [soldier],
);
final snapshot = codec.decode(json);
expect(snapshot.personnel.length, 1);
final decoded = snapshot.personnel.first;
expect(decoded.soldierId, soldier.soldierId);
expect(decoded.name, soldier.name);
expect(decoded.primaryMos.code, soldier.primaryMos.code);
expect(decoded.payGrade, soldier.payGrade);
expect(decoded.readinessState, soldier.readinessState);
expect(decoded.assignedSlotId, soldier.assignedSlotId);
});
test('full snapshot round-trips correctly', () {
final json = codec.encode(
units: [brigade, battalion],
slots: [slot],
personnel: [soldier],
);
final snapshot = codec.decode(json);
expect(snapshot.units.length, 2);
expect(snapshot.slots.length, 1);
expect(snapshot.personnel.length, 1);
});
test('encodeToMap / decodeFromMap round-trip', () {
final map = codec.encodeToMap(
units: [brigade],
slots: [slot],
personnel: [],
);
final snapshot = codec.decodeFromMap(map);
expect(snapshot.units.length, 1);
expect(snapshot.slots.length, 1);
});
});
// ---------------------------------------------------------------------------
// Schema version
// ---------------------------------------------------------------------------
group('schema version', () {
test('encoded JSON contains version field', () {
final json = codec.encode(units: [], slots: [], personnel: []);
expect(json, contains('"version"'));
expect(json, contains('"exportedAt"'));
});
test('throws FormatException for wrong schema version', () {
final map = codec.encodeToMap(units: [], slots: [], personnel: []);
map['version'] = 99;
expect(
() => codec.decodeFromMap(map),
throwsA(isA<FormatException>()),
);
});
test('throws FormatException for missing version', () {
final map = codec.encodeToMap(units: [], slots: [], personnel: []);
map.remove('version');
expect(
() => codec.decodeFromMap(map),
throwsA(isA<FormatException>()),
);
});
test('throws FormatException for non-object JSON root', () {
expect(
() => codec.decode('[1, 2, 3]'),
throwsA(isA<FormatException>()),
);
});
});
// ---------------------------------------------------------------------------
// Null / optional fields
// ---------------------------------------------------------------------------
group('optional fields', () {
test('null parentUnitId is preserved through round-trip', () {
final json = codec.encode(units: [brigade], slots: [], personnel: []);
final snapshot = codec.decode(json);
expect(snapshot.units.first.parentUnitId, isNull);
});
test('null assignedPersonnelId is preserved through round-trip', () {
final json = codec.encode(units: [], slots: [slot], personnel: []);
final snapshot = codec.decode(json);
expect(snapshot.slots.first.assignedPersonnelId, isNull);
});
test('null assignedSlotId is preserved through round-trip', () {
final unassigned = PersonnelRecord(
soldierId: 'p-2',
name: 'SGT Free',
primaryMos: DutyMos.unvalidated('11B'),
payGrade: AuthorizedRank.e5,
readinessState: ReadinessState.unknown,
);
final json = codec.encode(units: [], slots: [], personnel: [unassigned]);
final snapshot = codec.decode(json);
expect(snapshot.personnel.first.assignedSlotId, isNull);
});
test('qualifications list round-trips correctly', () {
final soldierWithQuals = PersonnelRecord(
soldierId: 'p-3',
name: 'SGT Qualified',
primaryMos: DutyMos.unvalidated('11B'),
payGrade: AuthorizedRank.e5,
readinessState: ReadinessState.fullyMissionCapable,
qualifications: ['Airborne', 'Ranger', 'SERE'],
);
final json = codec.encode(
units: [],
slots: [],
personnel: [soldierWithQuals],
);
final snapshot = codec.decode(json);
expect(
snapshot.personnel.first.qualifications,
containsAll(['Airborne', 'Ranger', 'SERE']),
);
});
});
}