proxy-pool/internal/domain/adminstate/validation_test.go
2026-08-02 15:14:20 +08:00

243 lines
9.1 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 TestValidateDisableRoutingCommand(t *testing.T) {
t.Parallel()
now := time.Date(2026, 8, 2, 12, 0, 0, 0, time.UTC)
valid := DisableRoutingCommand{
RequestID: "req-disable-routing", Actor: Actor{ID: "proxy-controller"}, OccurredAt: now,
Name: "checkout", ExpectedCurrent: "provider-b", Reason: "terminal empty results",
}
if err := valid.Validate(); err != nil {
t.Fatalf("DisableRoutingCommand.Validate(valid): %v", err)
}
for _, test := range []struct {
name string
mutate func(*DisableRoutingCommand)
}{
{name: "missing routing", mutate: func(command *DisableRoutingCommand) { command.Name = "" }},
{name: "missing expected", mutate: func(command *DisableRoutingCommand) { command.ExpectedCurrent = "" }},
{name: "reason too long", mutate: func(command *DisableRoutingCommand) { 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("DisableRoutingCommand.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",
}},
}
}