fix(multiplayer): bind allocation to accepted proposal

This commit is contained in:
Josh Creek
2026-09-01 21:41:44 +01:00
parent d3a457d8d0
commit 950e879861
3 changed files with 33 additions and 1 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ type Service struct {
// from allocating capacity for an OPEN/DECLINED proposal or for a request
// whose playlist does not match the proposal that produced it.
func (s Service) AllocateAcceptedProposal(ctx context.Context, proposal domain.Proposal, request domain.AllocationRequest, playlist domain.Playlist, labels map[string]string) (agones.AllocatedServer, error) {
if proposal.State != domain.Accepted || proposal.Playlist != playlist || len(proposal.Participants) == 0 {
if proposal.State != domain.Accepted || proposal.Playlist != playlist || len(proposal.Participants) == 0 || (request.Playlist != "" && request.Playlist != playlist) || (proposal.Region != "" && request.Region != proposal.Region) || (proposal.Protocol > 0 && request.Protocol != proposal.Protocol) || request.ArenaPath != proposal.ArenaPath {
return agones.AllocatedServer{}, domain.ErrAllocationInput
}
if proposal.Playlist == domain.Ranked && len(proposal.Participants) != 6 {
+30
View File
@@ -166,6 +166,36 @@ func TestServiceAllocatesOnlyUnanimouslyAcceptedMatchingProposal(t *testing.T) {
}
}
func TestServiceRejectsAllocationRequestThatDoesNotMatchAcceptedProposal(t *testing.T) {
proposal := domain.Proposal{
ProposalID: "proposal-ranked", Playlist: domain.Ranked, State: domain.Accepted,
Region: "EU", Protocol: 1, ArenaPath: "res://scenes/arena_01.tscn",
Participants: []domain.ProposalParticipant{
{PlayerID: "player-a", Response: domain.AcceptedResponse}, {PlayerID: "player-b", Response: domain.AcceptedResponse},
{PlayerID: "player-c", Response: domain.AcceptedResponse}, {PlayerID: "player-d", Response: domain.AcceptedResponse},
{PlayerID: "player-e", Response: domain.AcceptedResponse}, {PlayerID: "player-f", Response: domain.AcceptedResponse},
},
}
provider := &providerSpy{}
service := Service{Provider: provider, Durable: &durableSpy{}, Now: func() time.Time { return time.Unix(1000, 0) }}
request := domain.AllocationRequest{AllocationID: "a", MatchID: "m", Playlist: domain.Ranked, Region: "EU", Build: "b", Protocol: 1, ArenaPath: proposal.ArenaPath, Transport: "enet"}
for name, mutate := range map[string]func(*domain.AllocationRequest){
"playlist": func(r *domain.AllocationRequest) { r.Playlist = domain.Casual },
"region": func(r *domain.AllocationRequest) { r.Region = "NA" },
"protocol": func(r *domain.AllocationRequest) { r.Protocol = 2 },
"arena": func(r *domain.AllocationRequest) { r.ArenaPath = "res://scenes/arena_02.tscn" },
} {
candidate := request
mutate(&candidate)
if _, err := service.AllocateAcceptedProposal(context.Background(), proposal, candidate, domain.Ranked, map[string]string{"region": "EU"}); err == nil {
t.Fatalf("%s mismatch was accepted", name)
}
}
if provider.calls != 0 {
t.Fatalf("provider calls=%d, want 0", provider.calls)
}
}
func TestServicePublishesRosterOnlyForAllocatedAssignment(t *testing.T) {
roster := &rosterSpy{}
service := Service{Roster: roster}