package companion import ( "reflect" "testing" ) func TestMatchTeamKeepsManualSelectionAndFillsByLevelAndJob(t *testing.T) { candidates := []TeamCandidate{ {CharacterID: "1", Level: 90, Job: "0", Slot: 0}, {CharacterID: "2", Level: 90, Job: "12", Slot: 1}, {CharacterID: "3", Level: 90, Job: "3", Slot: 2}, {CharacterID: "4", Level: 90, Job: "0", Slot: 3}, {CharacterID: "5", Level: 21, Job: "2", Slot: 4}, } match := MatchTeam("1", []string{"4"}, candidates, 3) if want := []string{"4", "2", "3"}; !reflect.DeepEqual(match.Selected, want) { t.Fatalf("selected=%v want=%v", match.Selected, want) } } func TestMatchTeamSkipsOwnerOnlineDeletedLowLevelAndInvalid(t *testing.T) { candidates := []TeamCandidate{ {CharacterID: "1", Level: 90, Job: "0", Slot: 0}, // owner {CharacterID: "2", Level: 90, Job: "0", Slot: 1, Online: true}, // online {CharacterID: "3", Level: 90, Job: "0", Slot: 2, Deleted: true}, {CharacterID: "4", Level: 0, Job: "0", Slot: 3}, {CharacterID: "5", Level: 90, Job: "x", Slot: 4}, {CharacterID: "0", Level: 90, Job: "0", Slot: 5}, {CharacterID: "6", Level: 90, Job: "1", Slot: 6}, } match := MatchTeam("1", []string{"2", "3", "4", "5", "0", "99"}, candidates, 3) if want := []string{"6"}; !reflect.DeepEqual(match.Selected, want) { t.Fatalf("selected=%v want=%v", match.Selected, want) } assertSkip(t, match, "2") // online assertSkip(t, match, "3") // deleted assertSkip(t, match, "4") // low level assertSkip(t, match, "5") // unsupported job assertSkip(t, match, "99") // missing } func TestMatchTeamCapsAtThreeAndIsDeterministic(t *testing.T) { candidates := []TeamCandidate{ {CharacterID: "9", Level: 90, Job: "0", Slot: 9}, {CharacterID: "7", Level: 90, Job: "2", Slot: 7}, {CharacterID: "8", Level: 90, Job: "4", Slot: 8}, {CharacterID: "6", Level: 90, Job: "1", Slot: 6}, } first := MatchTeam("1", nil, candidates, 99) if len(first.Selected) != TeamMaxCompanions { t.Fatalf("selected=%v want %d entries", first.Selected, TeamMaxCompanions) } // Slot order wins inside the same-levelled, different-job set. if want := []string{"6", "7", "8"}; !reflect.DeepEqual(first.Selected, want) { t.Fatalf("selected=%v want=%v", first.Selected, want) } for i := 0; i < 20; i++ { again := MatchTeam("1", nil, candidates, 99) if !reflect.DeepEqual(again.Selected, first.Selected) { t.Fatalf("iteration %d selected=%v want=%v", i, again.Selected, first.Selected) } } } func TestMatchTeamEmptyRosterReturnsNoSelection(t *testing.T) { match := MatchTeam("1", []string{"2"}, nil, 3) if len(match.Selected) != 0 { t.Fatalf("selected=%v want empty", match.Selected) } assertSkip(t, match, "2") } func TestDefaultTeamAIProfileIsAggressiveAndJobScoped(t *testing.T) { profile := DefaultTeamAIProfile(12) if profile.Behavior != "aggressive" { t.Fatalf("behavior=%q want aggressive", profile.Behavior) } if profile.ClassID != 12 { t.Fatalf("class id=%d want 12", profile.ClassID) } if err := profile.Validate(); err != nil { t.Fatalf("default team profile is invalid: %v", err) } } func assertSkip(t *testing.T, match TeamMatch, id string) { t.Helper() for _, skipped := range match.Skipped { if skipped.CharacterID == id { return } } t.Fatalf("candidate %s was not recorded as skipped: %+v", id, match.Skipped) }