116 lines
6.3 KiB
Go
116 lines
6.3 KiB
Go
package companion
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type dispatchFixture struct {
|
|
commands []ClaimedCommand
|
|
acks []string
|
|
completed []CommandResult
|
|
}
|
|
|
|
func (fixture *dispatchFixture) ClaimCommands(_ context.Context, _ int) ([]ClaimedCommand, error) {
|
|
return append([]ClaimedCommand(nil), fixture.commands...), nil
|
|
}
|
|
func (fixture *dispatchFixture) AckCommand(_ context.Context, id string, _ uint64) (CommandAck, error) {
|
|
fixture.acks = append(fixture.acks, id)
|
|
return CommandAck{CommandID: id, State: "claimed", FencingToken: 7}, nil
|
|
}
|
|
func (fixture *dispatchFixture) CompleteCommand(_ context.Context, _ string, _ uint64, result CommandResult) (CompletedCommand, error) {
|
|
fixture.completed = append(fixture.completed, result)
|
|
return CompletedCommand{State: "succeeded"}, nil
|
|
}
|
|
|
|
type adapterFixture struct{ reads int }
|
|
|
|
func (adapter *adapterFixture) ReadConfiguration(context.Context) (map[string]any, error) {
|
|
adapter.reads++
|
|
return map[string]any{"version": "0.9.700.90357", "hostPath": "C:/must-redact"}, nil
|
|
}
|
|
func (*adapterFixture) PatchConfiguration(context.Context, map[string]any) (map[string]any, error) {
|
|
return nil, nil
|
|
}
|
|
func (*adapterFixture) Diagnostics(context.Context) (map[string]any, error) {
|
|
return map[string]any{"status": "healthy"}, nil
|
|
}
|
|
func (*adapterFixture) PatchGameState(context.Context, map[string]any) (map[string]any, error) {
|
|
return nil, nil
|
|
}
|
|
func (*adapterFixture) DeliverReward(context.Context, map[string]any) (map[string]any, error) {
|
|
return nil, nil
|
|
}
|
|
func (*adapterFixture) StartEvent(context.Context, map[string]any) (map[string]any, error) {
|
|
return nil, nil
|
|
}
|
|
func (*adapterFixture) NotifyPlayer(context.Context, map[string]any) (map[string]any, error) {
|
|
return nil, nil
|
|
}
|
|
func (*adapterFixture) SpawnVehicle(context.Context, map[string]any) (map[string]any, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func TestDispatcherAcknowledgesOnlyLiveValidatedTypedCommands(t *testing.T) {
|
|
stamp := time.Now().UTC()
|
|
adapter := &adapterFixture{}
|
|
registry := NewHandlerRegistry(HandlerAvailability{BoundServerID: "server-1", Approved: true, Capabilities: map[string]bool{"config.read": true}}, adapter)
|
|
fixture := &dispatchFixture{commands: []ClaimedCommand{{ID: "read-1", ProfileKey: ProfileKey, CommandType: "config.read", Payload: map[string]any{}, FencingToken: 7, LeaseExpiresAt: stamp.Add(time.Minute), ExpiresAt: stamp.Add(time.Minute)}, {ID: "expired-1", ProfileKey: ProfileKey, CommandType: "config.read", Payload: map[string]any{}, FencingToken: 8, LeaseExpiresAt: stamp.Add(-time.Second), ExpiresAt: stamp.Add(-time.Second)}}}
|
|
dispatcher := Dispatcher{Client: fixture, Registry: registry, Now: func() time.Time { return stamp }}
|
|
if err := dispatcher.DispatchOnce(context.Background()); err != nil {
|
|
t.Fatalf("dispatch: %v", err)
|
|
}
|
|
if len(fixture.acks) != 1 || fixture.acks[0] != "read-1" || len(fixture.completed) != 2 || adapter.reads != 1 {
|
|
t.Fatalf("unexpected bounded dispatch: acks=%v completed=%+v reads=%d", fixture.acks, fixture.completed, adapter.reads)
|
|
}
|
|
if fixture.completed[0].Payload["hostPath"] != nil || fixture.completed[1].Payload["result"] != "validation-failed" {
|
|
t.Fatalf("unsafe or malformed result: %+v", fixture.completed)
|
|
}
|
|
}
|
|
|
|
func TestRegistryReturnsCachedResultForDuplicateDelivery(t *testing.T) {
|
|
stamp := time.Now().UTC()
|
|
adapter := &adapterFixture{}
|
|
registry := NewHandlerRegistry(HandlerAvailability{BoundServerID: "server-1", Approved: true, Capabilities: map[string]bool{"config.read": true}}, adapter)
|
|
command := ClaimedCommand{ID: "duplicate-1", ProfileKey: ProfileKey, CommandType: "config.read", Payload: map[string]any{}, FencingToken: 7, LeaseExpiresAt: stamp.Add(time.Minute), ExpiresAt: stamp.Add(time.Minute)}
|
|
if _, err := registry.Execute(context.Background(), command); err != nil {
|
|
t.Fatalf("first execute: %v", err)
|
|
}
|
|
if _, err := registry.Execute(context.Background(), command); err != nil {
|
|
t.Fatalf("second execute: %v", err)
|
|
}
|
|
if adapter.reads != 1 {
|
|
t.Fatalf("duplicate delivery invoked adapter %d times", adapter.reads)
|
|
}
|
|
}
|
|
|
|
func TestRegistryRejectsUndeclaredAndMalformedPayloads(t *testing.T) {
|
|
stamp := time.Now().UTC()
|
|
registry := NewHandlerRegistry(HandlerAvailability{BoundServerID: "server-1", Approved: true, Capabilities: map[string]bool{"config.patch": true}}, &adapterFixture{})
|
|
for _, command := range []ClaimedCommand{{ID: "bad-type", ProfileKey: ProfileKey, CommandType: "raw.rcon", Payload: map[string]any{}, FencingToken: 1, LeaseExpiresAt: stamp.Add(time.Minute), ExpiresAt: stamp.Add(time.Minute)}, {ID: "bad-payload", ProfileKey: ProfileKey, CommandType: "config.patch", Payload: map[string]any{"revision": "r1"}, FencingToken: 1, LeaseExpiresAt: stamp.Add(time.Minute), ExpiresAt: stamp.Add(time.Minute)}, {ID: "unsafe-vehicle", ProfileKey: ProfileKey, CommandType: "vehicle.spawn", Payload: map[string]any{"vehicleCode": "BPC_Laika_C", "command": "#spawnvehicle BPC_Laika_C"}, FencingToken: 1, LeaseExpiresAt: stamp.Add(time.Minute), ExpiresAt: stamp.Add(time.Minute)}} {
|
|
result, err := registry.Execute(context.Background(), command)
|
|
if err != nil || result.Payload["result"] != "validation-failed" {
|
|
t.Fatalf("unsafe command was not rejected: result=%+v err=%v", result, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRewardPayloadValidationRequiresItemsOrOperations(t *testing.T) {
|
|
validBase := map[string]any{"grantId": "grant-1", "playerId": "76561198000000001"}
|
|
for name, payload := range map[string]map[string]any{
|
|
"items": {"grantId": validBase["grantId"], "playerId": validBase["playerId"], "items": []any{map[string]any{"catalogCode": "BPC_Apple", "quantity": float64(1)}}, "operations": []any{}},
|
|
"operations": {"grantId": validBase["grantId"], "playerId": validBase["playerId"], "items": []any{}, "operations": []any{"#SetFamePoints 250"}},
|
|
"both": {"grantId": validBase["grantId"], "playerId": validBase["playerId"], "items": []any{map[string]any{"catalogCode": "BPC_Apple", "quantity": float64(1)}}, "operations": []any{"#SetFamePoints 250"}},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
if err := validateCommandPayload("reward.deliver", payload); err != nil {
|
|
t.Fatalf("valid reward payload was rejected: %v", err)
|
|
}
|
|
})
|
|
}
|
|
if err := validateCommandPayload("reward.deliver", map[string]any{"grantId": "grant-empty", "playerId": "76561198000000001", "items": []any{}, "operations": []any{}}); err == nil {
|
|
t.Fatal("reward payload with no items or operations was accepted")
|
|
}
|
|
}
|