package model import ( "strings" "testing" ) func TestNormalizeScopeNilAndEmptyMeanFullSync(t *testing.T) { for name, scope := range map[string]*SyncScope{ "nil": nil, "no channels": {}, "empty slice": {Channels: []string{}}, } { got, err := NormalizeScope(scope) if err != nil { t.Fatalf("%s: unexpected error: %v", name, err) } if got != nil { t.Fatalf("%s: expected nil scope for full sync, got %+v", name, got) } if got.ChannelCount() != 0 || !got.Allows("anything") { t.Fatalf("%s: nil scope must allow every channel", name) } } } func TestNormalizeScopeDeduplicatesAndSorts(t *testing.T) { got, err := NormalizeScope(&SyncScope{Channels: []string{"217", "173", "217", "189"}}) if err != nil { t.Fatalf("unexpected error: %v", err) } want := []string{"173", "189", "217"} if len(got.Channels) != len(want) { t.Fatalf("channels = %v, want %v", got.Channels, want) } for i := range want { if got.Channels[i] != want[i] { t.Fatalf("channels = %v, want %v (sorted)", got.Channels, want) } } // Sorting keeps ScopeDigest stable for requests listing the same channels // in a different order. reordered, err := NormalizeScope(&SyncScope{Channels: []string{"189", "217", "173"}}) if err != nil { t.Fatalf("unexpected error: %v", err) } if strings.Join(reordered.Channels, ",") != strings.Join(got.Channels, ",") { t.Fatalf("order-dependent normalization: %v vs %v", reordered.Channels, got.Channels) } } func TestNormalizeScopeTrimsWhitespace(t *testing.T) { got, err := NormalizeScope(&SyncScope{Channels: []string{" 217 ", "173"}}) if err != nil { t.Fatalf("unexpected error: %v", err) } if got.Allows("217") != true { t.Fatalf("whitespace-padded chanKey was not trimmed: %+v", got) } } func TestNormalizeScopeRejectsInvalidInput(t *testing.T) { cases := map[string]*SyncScope{ "too many channels": {Channels: make([]string, MaxScopeChannels+1)}, "chanKey too long": {Channels: []string{strings.Repeat("a", MaxChanKeyLength+1)}}, "chanKey with slash": {Channels: []string{"a/b"}}, "chanKey with space": {Channels: []string{"a b"}}, "chanKey with unicode": {Channels: []string{"通道"}}, "chanKey with brace": {Channels: []string{"a{b}"}}, "empty chanKey": {Channels: []string{""}}, "blank chanKey": {Channels: []string{" "}}, "unknown triggerSource": {Channels: []string{"217"}, TriggerSource: "cron"}, } for name, scope := range cases { if _, err := NormalizeScope(scope); err == nil { t.Fatalf("%s: expected an error", name) } } } func TestNormalizeScopeAcceptsBoundaries(t *testing.T) { // Exactly the maximum count must pass. channels := make([]string, MaxScopeChannels) for i := range channels { channels[i] = "chan-" + strings.Repeat("0", i%10) + string(rune('a'+i%26)) } if _, err := NormalizeScope(&SyncScope{Channels: channels}); err != nil { t.Fatalf("exactly %d channels must be accepted: %v", MaxScopeChannels, err) } // A chanKey of exactly the maximum length must pass. if err := ValidateChanKey(strings.Repeat("a", MaxChanKeyLength)); err != nil { t.Fatalf("chanKey of exactly %d characters must be accepted: %v", MaxChanKeyLength, err) } } func TestNormalizeScopeDefaultsTriggerSource(t *testing.T) { got, err := NormalizeScope(&SyncScope{Channels: []string{"217"}}) if err != nil { t.Fatalf("unexpected error: %v", err) } if got.TriggerSource != TriggerSourceManual { t.Fatalf("triggerSource = %q, want %q", got.TriggerSource, TriggerSourceManual) } } func TestNormalizeScopeKeepsExplicitTriggerSource(t *testing.T) { got, err := NormalizeScope(&SyncScope{Channels: []string{"217"}, TriggerSource: TriggerSourceAPI}) if err != nil { t.Fatalf("unexpected error: %v", err) } if got.TriggerSource != TriggerSourceAPI { t.Fatalf("triggerSource = %q, want %q", got.TriggerSource, TriggerSourceAPI) } } func TestScopeChannelSetAndSample(t *testing.T) { scope, err := NormalizeScope(&SyncScope{Channels: []string{"217", "173"}}) if err != nil { t.Fatalf("unexpected error: %v", err) } set := scope.ChannelSet() if _, ok := set["173"]; !ok { t.Fatalf("channel set missing 173: %v", set) } if len(set) != 2 { t.Fatalf("channel set = %v, want 2 entries", set) } if sample := scope.ChannelSample(1); len(sample) != 1 || sample[0] != "173" { t.Fatalf("sample = %v, want [173]", sample) } if sample := scope.ChannelSample(10); len(sample) != 2 { t.Fatalf("sample = %v, want both channels", sample) } var nilScope *SyncScope if nilScope.ChannelSet() != nil { t.Fatalf("nil scope must return a nil channel set") } if nilScope.ChannelSample(10) != nil { t.Fatalf("nil scope must return a nil sample") } }