diff --git a/src/boost/boost.go b/src/boost/boost.go index a198e995..42cab10f 100644 --- a/src/boost/boost.go +++ b/src/boost/boost.go @@ -1472,7 +1472,6 @@ func removeIndex(s []string, index int) []string { func RemoveFarmerByMention(client dc.Client, guildID string, channelID string, operator string, mention string) error { log.Println("RemoveContractBoosterByMention", "GuildID: ", guildID, "ChannelID: ", channelID, "Operator: ", operator, "Mention: ", mention) var contract = FindContract(channelID) - redraw := false redrawSignup := false if contract == nil { return errors.New(errorNoContract) @@ -1558,7 +1557,6 @@ func RemoveFarmerByMention(client dc.Client, guildID string, channelID string, o } } contract.buttonComponents = nil // reset button components - redraw = true } else if booster != nil && len(booster.Alts) > 0 { // If this is a main with alts, clear the alts for _, alt := range booster.Alts { @@ -1569,7 +1567,6 @@ func RemoveFarmerByMention(client dc.Client, guildID string, channelID string, o booster.Alts = nil contract.buttonComponents = nil - redraw = true } contract.Order = removeIndex(contract.Order, removalIndex) contract.OrderRevision++ @@ -1605,10 +1602,14 @@ func RemoveFarmerByMention(client dc.Client, guildID string, channelID string, o changeContractState(contract, ContractStateWaiting) contract.setCurrentBoosterByIndex(len(contract.Order)) sendNextNotification(client, contract, true) + CheckAndPublishAMQPBoosterChange(contract, userID, boosterNick, "booster_remove") + return nil } else if (contract.State == ContractStateFastrun || contract.State == ContractStateBanker) && contract.currentBoosterID() == "" { // set contract to waiting changeContractState(contract, ContractStateWaiting) sendNextNotification(client, contract, true) + CheckAndPublishAMQPBoosterChange(contract, userID, boosterNick, "booster_remove") + return nil } else { nextID := findNextBoosterID(contract) if nextID != "" { @@ -1628,48 +1629,21 @@ func RemoveFarmerByMention(client dc.Client, guildID string, channelID string, o // Remove the first person from the want list firstWaitlistUser := contract.WaitlistBoosters[0] contract.WaitlistBoosters = contract.WaitlistBoosters[1:] - _, _ = AddFarmerToContract(client, contract, guildID, channelID, firstWaitlistUser, contract.BoostOrder, false, false) + _, _ = AddFarmerToContract(client, contract, guildID, channelID, firstWaitlistUser, contract.BoostOrder, true, false) } } } - // Edit the boost List in place - //if contract.BoostPosition != len(contract.Order) { - for _, loc := range contract.Location { - if redraw { - if contract.State == ContractStateSignup && previousBoosters == contract.CoopSize { - redrawSignup = true - } - refreshBoostListMessage(client, contract, redrawSignup) - continue - } - if contract.State == ContractStateSignup && contract.Style&ContractFlagCrt != 0 { - if len(contract.Order) == 0 { - // Need to clear all the contract sinks - contract.Banker.BoostingSinkUserID = "" - contract.Banker.PostSinkUserID = "" - } - } - components := DrawBoostList(contract) - buttonComponents := getContractReactionsComponents(contract) - if len(buttonComponents) > 0 { - components = append(components, buttonComponents...) - } - msg, err := client.EditMessage(loc.ChannelID, loc.ListMsgID, dc.Message{Components: components}) - if err == nil { - loc.ListMsgID = msg.ID - } else { - log.Printf("RemoveFarmerFromContract: failed to edit boost list message %s in channel %s for contract %s: %v", - loc.ListMsgID, loc.ChannelID, contract.ContractHash, err) - } - // Need to disable the speedrun start button if the contract is no longer full - if previousBoosters != len(contract.Boosters) && previousBoosters == contract.CoopSize { - if contract.State == ContractStateSignup { - updateSignupReactionMessage(client, contract, loc) - } + if contract.State == ContractStateSignup && contract.Style&ContractFlagCrt != 0 { + if len(contract.Order) == 0 { + // Need to clear all the contract sinks + contract.Banker.BoostingSinkUserID = "" + contract.Banker.PostSinkUserID = "" } } - //} + + redrawSignup = (contract.State == ContractStateSignup) && (previousBoosters == contract.CoopSize || len(contract.Boosters) == contract.CoopSize || contract.CreatorID[0] == config.DiscordAppID) + refreshBoostListMessage(client, contract, redrawSignup) CheckAndPublishAMQPBoosterChange(contract, userID, boosterNick, "booster_remove") return nil diff --git a/src/boost/contract_test.go b/src/boost/contract_test.go index 5dbe8d3b..8f0d4d71 100644 --- a/src/boost/contract_test.go +++ b/src/boost/contract_test.go @@ -1127,3 +1127,131 @@ func TestAddFarmerToContract_MinimumIHR(t *testing.T) { t.Errorf("expected b.IHRRate >= %f, got %f", DefaultLeggyIHR, b.IHRRate) } } + +func TestRemoveFarmerByMention_CollapsesEditsWithWaitlist(t *testing.T) { + client := dctest.New(). + WithGuild("guild1", "Guild 1"). + WithChannel("channel1", "guild1", "contract-channel"). + WithUser("100000000000000001", "farmer1", "Farmer One"). + WithUser("100000000000000002", "farmer2", "Farmer Two"). + WithUser("100000000000000003", "farmer3", "Farmer Three") + + contract := &Contract{ + ContractHash: "test-hash-remove-waitlist", + ContractID: "test-contract", + CoopID: "test-coop", + CoopSize: 2, + State: ContractStateSignup, + BoostOrder: ContractOrderSignup, + CreatorID: []string{"100000000000000001"}, + Order: []string{"100000000000000001", "100000000000000002"}, + Boosters: map[string]*Booster{ + "100000000000000001": {UserID: "100000000000000001", Name: "Farmer One", Nick: "farmer1"}, + "100000000000000002": {UserID: "100000000000000002", Name: "Farmer Two", Nick: "farmer2"}, + }, + WaitlistBoosters: []string{"100000000000000003"}, + Location: []*LocationData{ + {GuildID: "guild1", ChannelID: "channel1", ListMsgID: "msg-list-1", ReactionID: "msg-rx-1"}, + }, + } + Contracts[contract.ContractHash] = contract + defer delete(Contracts, contract.ContractHash) + + client.Calls = nil // reset tracked calls + + err := RemoveFarmerByMention(client, "guild1", "channel1", "100000000000000002", "<@100000000000000002>") + if err != nil { + t.Fatalf("unexpected error removing farmer: %v", err) + } + + if slices.Contains(contract.Order, "100000000000000002") { + t.Errorf("expected user2 to be removed from Order") + } + if !slices.Contains(contract.Order, "100000000000000003") { + t.Errorf("expected user3 from waitlist to be in Order") + } + if len(contract.WaitlistBoosters) != 0 { + t.Errorf("expected WaitlistBoosters to be empty, got %v", contract.WaitlistBoosters) + } + + listEdits := 0 + rxEdits := 0 + for _, call := range client.Calls { + if call.Method == "EditMessage" { + if len(call.Args) > 1 { + switch call.Args[1] { + case "msg-list-1": + listEdits++ + case "msg-rx-1": + rxEdits++ + } + } + } + } + + if listEdits != 1 { + t.Errorf("expected exactly 1 list message edit, got %d", listEdits) + } + if rxEdits > 1 { + t.Errorf("expected at most 1 reaction message edit, got %d", rxEdits) + } +} + +func TestRemoveFarmerByMention_MultipleLocationsNoN2(t *testing.T) { + client := dctest.New(). + WithGuild("guild1", "Guild 1"). + WithChannel("channel1", "guild1", "contract-channel-1"). + WithChannel("channel2", "guild1", "contract-channel-2"). + WithUser("100000000000000001", "farmer1", "Farmer One"). + WithUser("100000000000000002", "farmer2", "Farmer Two") + + contract := &Contract{ + ContractHash: "test-hash-remove-multiloc", + ContractID: "test-contract", + CoopID: "test-coop", + CoopSize: 5, + State: ContractStateSignup, + BoostOrder: ContractOrderSignup, + CreatorID: []string{"100000000000000001"}, + Order: []string{"100000000000000001", "100000000000000002"}, + Boosters: map[string]*Booster{ + "100000000000000001": {UserID: "100000000000000001", Name: "Farmer One", Nick: "farmer1"}, + "100000000000000002": {UserID: "100000000000000002", Name: "Farmer Two", Nick: "farmer2"}, + }, + Location: []*LocationData{ + {GuildID: "guild1", ChannelID: "channel1", ListMsgID: "msg-list-1", ReactionID: "msg-rx-1"}, + {GuildID: "guild1", ChannelID: "channel2", ListMsgID: "msg-list-2", ReactionID: "msg-rx-2"}, + }, + } + Contracts[contract.ContractHash] = contract + defer delete(Contracts, contract.ContractHash) + + client.Calls = nil + + err := RemoveFarmerByMention(client, "guild1", "channel1", "100000000000000002", "<@100000000000000002>") + if err != nil { + t.Fatalf("unexpected error removing farmer: %v", err) + } + + ch1Edits := 0 + ch2Edits := 0 + for _, call := range client.Calls { + if call.Method == "EditMessage" { + if len(call.Args) > 1 { + switch call.Args[1] { + case "msg-list-1": + ch1Edits++ + case "msg-list-2": + ch2Edits++ + } + } + } + } + + if ch1Edits != 1 { + t.Errorf("expected exactly 1 edit for channel 1, got %d", ch1Edits) + } + if ch2Edits != 1 { + t.Errorf("expected exactly 1 edit for channel 2, got %d", ch2Edits) + } +}