// SPDX-License-Identifier: EUPL-1.2 package api_test import ( "slices" "testing" api "dappco.re/go/core/api" ) type streamGroupStub struct { healthGroup channels []string } func (s *streamGroupStub) Channels() []string { return s.channels } // ── GroupsIter ──────────────────────────────────────────────────────── func TestModernization_GroupsIter_Good(t *testing.T) { engine, _ := api.New() engine.Register(&healthGroup{}) var groups []api.RouteGroup for group := range engine.GroupsIter() { groups = append(groups, group) } if len(groups) != 1 { t.Fatalf("expected 1 group, got %d", len(groups)) } if groups[0].Name() != "health-extra" { t.Errorf("expected group name 'health-extra', got %q", groups[0].Name()) } } func TestModernization_GroupsIter_Bad(t *testing.T) { engine, _ := api.New() // No groups registered — iterator should yield nothing. var groups []api.RouteGroup for group := range engine.GroupsIter() { groups = append(groups, group) } if len(groups) != 0 { t.Fatalf("expected 0 groups with no registration, got %d", len(groups)) } } func TestModernization_GroupsIter_Ugly(t *testing.T) { defer func() { if r := recover(); r != nil { t.Fatalf("GroupsIter on nil groups panicked: %v", r) } }() engine, _ := api.New() // Iterating immediately without any Register call must not panic. for range engine.GroupsIter() { t.Fatal("expected no iterations") } } // ── ChannelsIter ────────────────────────────────────────────────────── func TestModernization_ChannelsIter_Good(t *testing.T) { engine, _ := api.New() engine.Register(&streamGroupStub{channels: []string{"ch1", "ch2"}}) engine.Register(&streamGroupStub{channels: []string{"ch3"}}) var channels []string for channelName := range engine.ChannelsIter() { channels = append(channels, channelName) } expected := []string{"ch1", "ch2", "ch3"} if !slices.Equal(channels, expected) { t.Fatalf("expected channels %v, got %v", expected, channels) } } func TestModernization_ChannelsIter_Bad(t *testing.T) { engine, _ := api.New() // Register a group that has no Channels() — ChannelsIter must skip it. engine.Register(&healthGroup{}) var channels []string for channelName := range engine.ChannelsIter() { channels = append(channels, channelName) } if len(channels) != 0 { t.Fatalf("expected 0 channels for non-StreamGroup, got %v", channels) } } func TestModernization_ChannelsIter_Ugly(t *testing.T) { defer func() { if r := recover(); r != nil { t.Fatalf("ChannelsIter panicked: %v", r) } }() engine, _ := api.New() // Group with empty channel list must not panic during iteration. engine.Register(&streamGroupStub{channels: []string{}}) for range engine.ChannelsIter() { t.Fatal("expected no iterations for empty channel list") } } // ── ToolBridge iterators ────────────────────────────────────────────── func TestModernization_ToolBridgeIterators_Good(t *testing.T) { bridge := api.NewToolBridge("/tools") bridge.Add(api.ToolDescriptor{Name: "test", Group: "g1"}, nil) var tools []api.ToolDescriptor for tool := range bridge.ToolsIter() { tools = append(tools, tool) } if len(tools) != 1 || tools[0].Name != "test" { t.Errorf("ToolsIter failed, got %v", tools) } var descs []api.RouteDescription for desc := range bridge.DescribeIter() { descs = append(descs, desc) } if len(descs) != 1 || descs[0].Path != "/test" { t.Errorf("DescribeIter failed, got %v", descs) } } func TestModernization_ToolBridgeIterators_Bad(t *testing.T) { bridge := api.NewToolBridge("/tools") // Empty bridge — iterators must yield nothing. for range bridge.ToolsIter() { t.Fatal("expected no iterations on empty bridge (ToolsIter)") } for range bridge.DescribeIter() { t.Fatal("expected no iterations on empty bridge (DescribeIter)") } } func TestModernization_ToolBridgeIterators_Ugly(t *testing.T) { defer func() { if r := recover(); r != nil { t.Fatalf("ToolBridge iterator with nil handler panicked: %v", r) } }() bridge := api.NewToolBridge("/tools") bridge.Add(api.ToolDescriptor{Name: "noop"}, nil) var toolCount int for range bridge.ToolsIter() { toolCount++ } if toolCount != 1 { t.Fatalf("expected 1 tool, got %d", toolCount) } } // ── SupportedLanguagesIter ──────────────────────────────────────────── func TestModernization_SupportedLanguagesIter_Good(t *testing.T) { var langs []string for language := range api.SupportedLanguagesIter() { langs = append(langs, language) } if !slices.Contains(langs, "go") { t.Errorf("SupportedLanguagesIter missing 'go'") } if !slices.IsSorted(langs) { t.Errorf("SupportedLanguagesIter should be sorted, got %v", langs) } } func TestModernization_SupportedLanguagesIter_Bad(t *testing.T) { // Iterator and slice function must agree on count. iterCount := 0 for range api.SupportedLanguagesIter() { iterCount++ } sliceCount := len(api.SupportedLanguages()) if iterCount != sliceCount { t.Fatalf("SupportedLanguagesIter count %d != SupportedLanguages count %d", iterCount, sliceCount) } } func TestModernization_SupportedLanguagesIter_Ugly(t *testing.T) { defer func() { if r := recover(); r != nil { t.Fatalf("SupportedLanguagesIter panicked: %v", r) } }() // Calling multiple times concurrently should not panic. done := make(chan struct{}, 5) for goroutineIndex := 0; goroutineIndex < 5; goroutineIndex++ { go func() { for range api.SupportedLanguagesIter() { } done <- struct{}{} }() } for goroutineIndex := 0; goroutineIndex < 5; goroutineIndex++ { <-done } }