212 lines
8.0 KiB
Go
212 lines
8.0 KiB
Go
package adminstate
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestValidateSetUpstreamCommand(t *testing.T) {
|
|
t.Parallel()
|
|
now := time.Date(2026, 7, 29, 10, 0, 0, 0, time.UTC)
|
|
valid := SetUpstreamCommand{
|
|
RequestID: "req-upstream", Actor: Actor{ID: "admin-a", SourceIP: "192.0.2.10"},
|
|
OccurredAt: now, Name: "provider-a", Enabled: true,
|
|
}
|
|
if err := valid.Validate(); err != nil {
|
|
t.Fatalf("SetUpstreamCommand.Validate(valid): %v", err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
mutate func(*SetUpstreamCommand)
|
|
}{
|
|
{name: "missing request", mutate: func(command *SetUpstreamCommand) { command.RequestID = "" }},
|
|
{name: "missing actor", mutate: func(command *SetUpstreamCommand) { command.Actor.ID = "" }},
|
|
{name: "invalid source", mutate: func(command *SetUpstreamCommand) { command.Actor.SourceIP = "not-an-ip" }},
|
|
{name: "zero time", mutate: func(command *SetUpstreamCommand) { command.OccurredAt = time.Time{} }},
|
|
{name: "invalid name", mutate: func(command *SetUpstreamCommand) { command.Name = "provider/a" }},
|
|
}
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
command := valid
|
|
test.mutate(&command)
|
|
if err := command.Validate(); !errors.Is(err, ErrInvalidCommand) {
|
|
t.Fatalf("SetUpstreamCommand.Validate() error = %v, want ErrInvalidCommand", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateSwitchRoutingCommand(t *testing.T) {
|
|
t.Parallel()
|
|
now := time.Date(2026, 7, 29, 10, 0, 0, 0, time.UTC)
|
|
valid := SwitchRoutingCommand{
|
|
RequestID: "req-switch", Actor: Actor{ID: "admin-a"}, OccurredAt: now,
|
|
Name: "checkout", ExpectedCurrent: "provider-a", Target: "provider-b", Reason: "capacity",
|
|
}
|
|
if err := valid.Validate(); err != nil {
|
|
t.Fatalf("SwitchRoutingCommand.Validate(valid): %v", err)
|
|
}
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
mutate func(*SwitchRoutingCommand)
|
|
}{
|
|
{name: "missing routing", mutate: func(command *SwitchRoutingCommand) { command.Name = "" }},
|
|
{name: "missing expected", mutate: func(command *SwitchRoutingCommand) { command.ExpectedCurrent = "" }},
|
|
{name: "missing target", mutate: func(command *SwitchRoutingCommand) { command.Target = "" }},
|
|
{name: "reason too long", mutate: func(command *SwitchRoutingCommand) { command.Reason = strings.Repeat("r", MaxReasonBytes+1) }},
|
|
} {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
command := valid
|
|
test.mutate(&command)
|
|
if err := command.Validate(); !errors.Is(err, ErrInvalidCommand) {
|
|
t.Fatalf("SwitchRoutingCommand.Validate() error = %v, want ErrInvalidCommand", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateCommitConfigCommandAndReferences(t *testing.T) {
|
|
t.Parallel()
|
|
valid := validCommitConfigCommand()
|
|
if err := valid.Validate(); err != nil {
|
|
t.Fatalf("CommitConfigCommand.Validate(valid): %v", err)
|
|
}
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
mutate func(*CommitConfigCommand)
|
|
}{
|
|
{name: "invalid checksum", mutate: func(command *CommitConfigCommand) { command.Checksum = "sha256:bad" }},
|
|
{name: "duplicate upstream", mutate: func(command *CommitConfigCommand) {
|
|
command.Upstreams = append(command.Upstreams, command.Upstreams[0])
|
|
}},
|
|
{name: "duplicate routing", mutate: func(command *CommitConfigCommand) { command.Routings = append(command.Routings, command.Routings[0]) }},
|
|
{name: "duplicate candidate", mutate: func(command *CommitConfigCommand) {
|
|
command.Routings[0].Upstreams = append(command.Routings[0].Upstreams, "provider-a")
|
|
}},
|
|
{name: "unknown candidate", mutate: func(command *CommitConfigCommand) { command.Routings[0].Upstreams[0] = "provider-missing" }},
|
|
{name: "current not candidate", mutate: func(command *CommitConfigCommand) { command.Routings[0].CurrentUpstream = "provider-c" }},
|
|
{name: "empty candidate list", mutate: func(command *CommitConfigCommand) { command.Routings[0].Upstreams = nil }},
|
|
} {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
command := cloneCommitConfigCommand(valid)
|
|
test.mutate(&command)
|
|
if err := command.Validate(); !errors.Is(err, ErrInvalidCommand) {
|
|
t.Fatalf("CommitConfigCommand.Validate() error = %v, want ErrInvalidCommand", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateOutboxCommands(t *testing.T) {
|
|
t.Parallel()
|
|
now := time.Date(2026, 7, 29, 10, 0, 0, 0, time.UTC)
|
|
if err := (ClaimCommand{
|
|
ConsumerID: "publisher-a", Now: now, Limit: 100, Lease: time.Minute,
|
|
}).Validate(); err != nil {
|
|
t.Fatalf("ClaimCommand.Validate(valid): %v", err)
|
|
}
|
|
if err := (AcknowledgeCommand{
|
|
ConsumerID: "publisher-a", Now: now, EventIDs: []uint64{1, 2},
|
|
}).Validate(); err != nil {
|
|
t.Fatalf("AcknowledgeCommand.Validate(valid): %v", err)
|
|
}
|
|
|
|
invalidClaims := []ClaimCommand{
|
|
{Now: now, Limit: 1, Lease: time.Second},
|
|
{ConsumerID: "publisher-a", Limit: 1, Lease: time.Second},
|
|
{ConsumerID: "publisher-a", Now: now, Lease: time.Second},
|
|
{ConsumerID: "publisher-a", Now: now, Limit: MaxPageSize + 1, Lease: time.Second},
|
|
{ConsumerID: "publisher-a", Now: now, Limit: 1},
|
|
}
|
|
for _, command := range invalidClaims {
|
|
if err := command.Validate(); !errors.Is(err, ErrInvalidCommand) {
|
|
t.Fatalf("ClaimCommand.Validate(%+v) error = %v", command, err)
|
|
}
|
|
}
|
|
|
|
invalidAcks := []AcknowledgeCommand{
|
|
{Now: now, EventIDs: []uint64{1}},
|
|
{ConsumerID: "publisher-a", EventIDs: []uint64{1}},
|
|
{ConsumerID: "publisher-a", Now: now},
|
|
{ConsumerID: "publisher-a", Now: now, EventIDs: []uint64{0}},
|
|
{ConsumerID: "publisher-a", Now: now, EventIDs: []uint64{1, 1}},
|
|
}
|
|
for _, command := range invalidAcks {
|
|
if err := command.Validate(); !errors.Is(err, ErrInvalidCommand) {
|
|
t.Fatalf("AcknowledgeCommand.Validate(%+v) error = %v", command, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAuditQueryValidate(t *testing.T) {
|
|
t.Parallel()
|
|
if err := (AuditQuery{Limit: 10}).Validate(); err != nil {
|
|
t.Fatalf("AuditQuery.Validate(valid): %v", err)
|
|
}
|
|
for _, query := range []AuditQuery{{}, {Limit: -1}, {Limit: MaxPageSize + 1}} {
|
|
if err := query.Validate(); !errors.Is(err, ErrInvalidCommand) {
|
|
t.Fatalf("AuditQuery.Validate(%+v) error = %v, want ErrInvalidCommand", query, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCloneValuesDoNotShareMutableState(t *testing.T) {
|
|
t.Parallel()
|
|
command := validCommitConfigCommand()
|
|
clonedCommand := cloneCommitConfigCommand(command)
|
|
clonedCommand.Upstreams[0].Name = "changed"
|
|
clonedCommand.Routings[0].Upstreams[0] = "changed"
|
|
if command.Upstreams[0].Name != "provider-a" || command.Routings[0].Upstreams[0] != "provider-a" {
|
|
t.Fatalf("cloneCommitConfigCommand shared input storage: %+v", command)
|
|
}
|
|
|
|
snapshot := Snapshot{
|
|
Revision: 3,
|
|
Config: &ConfigRevision{Revision: 3, ConfigVersion: "cfg-3"},
|
|
Upstreams: []UpstreamState{{Name: "provider-a", Enabled: true}},
|
|
Routings: []RoutingState{{Name: "checkout", Upstreams: []string{"provider-a"}, CurrentUpstream: "provider-a"}},
|
|
}
|
|
clonedSnapshot := cloneSnapshot(snapshot)
|
|
clonedSnapshot.Config.ConfigVersion = "changed"
|
|
clonedSnapshot.Upstreams[0].Name = "changed"
|
|
clonedSnapshot.Routings[0].Upstreams[0] = "changed"
|
|
if snapshot.Config.ConfigVersion != "cfg-3" || snapshot.Upstreams[0].Name != "provider-a" ||
|
|
snapshot.Routings[0].Upstreams[0] != "provider-a" {
|
|
t.Fatalf("cloneSnapshot shared input storage: %+v", snapshot)
|
|
}
|
|
|
|
event := Event{Payload: json.RawMessage(`{"enabled":true}`)}
|
|
clonedEvent := cloneEvent(event)
|
|
clonedEvent.Payload[2] = 'X'
|
|
if string(event.Payload) != `{"enabled":true}` {
|
|
t.Fatalf("cloneEvent shared payload storage: %s", event.Payload)
|
|
}
|
|
}
|
|
|
|
func validCommitConfigCommand() CommitConfigCommand {
|
|
return CommitConfigCommand{
|
|
RequestID: "req-config", Actor: Actor{ID: "admin-a", SourceIP: "192.0.2.10"},
|
|
OccurredAt: time.Date(2026, 7, 29, 10, 0, 0, 0, time.UTC),
|
|
ConfigVersion: "cfg-1", Checksum: strings.Repeat("a", SHA256HexBytes), Source: "configs/proxy-pool.yaml",
|
|
Upstreams: []UpstreamDefinition{
|
|
{Name: "provider-a", Enabled: true},
|
|
{Name: "provider-b", Enabled: true},
|
|
},
|
|
Routings: []RoutingDefinition{{
|
|
Name: "checkout", Enabled: true, Upstreams: []string{"provider-a", "provider-b"}, CurrentUpstream: "provider-a",
|
|
}},
|
|
}
|
|
}
|