package node import ( "reflect" "strings" "testing" "google.golang.org/protobuf/types/known/structpb" "iop/packages/go/config" ) // TestConcreteProfileToProtoDeepCopy verifies that concreteProfileToProto // deep-copies maps so the wire payload is independent of the source config // snapshot. func TestProtocolProfileConcreteToProtoDeepCopy(t *testing.T) { profile := config.ConcreteProtocolProfile{ ID: "openai", ProtocolProfileConf: config.ProtocolProfileConf{ Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://api.openai.com/v1", Operations: map[string]string{ "models": "/models", "chat_completions": "/chat/completions", }, Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"chat", "streaming"}, ModelMapping: map[string]string{ "gpt-4": "gpt-4", }, }, } proto := concreteProfileToProto(&profile) if proto == nil { t.Fatal("expected non-nil proto") } if proto.GetId() != "openai" { t.Errorf("Id = %q, want %q", proto.GetId(), "openai") } if proto.GetDriver() != string(config.ProtocolDriverOpenAIChat) { t.Errorf("Driver = %q, want %q", proto.GetDriver(), config.ProtocolDriverOpenAIChat) } if proto.GetBaseUrl() != "https://api.openai.com/v1" { t.Errorf("BaseUrl = %q, want %q", proto.GetBaseUrl(), "https://api.openai.com/v1") } if proto.GetAuth().GetHeader() != "Authorization" { t.Errorf("Auth.Header = %q, want %q", proto.GetAuth().GetHeader(), "Authorization") } if len(proto.GetOperations()) != 2 { t.Errorf("Operations len = %d, want 2", len(proto.GetOperations())) } if len(proto.GetCapabilities()) != 2 { t.Errorf("Capabilities len = %d, want 2", len(proto.GetCapabilities())) } // Mutate the proto maps and verify the source profile is unaffected. proto.Operations["injected"] = "/injected" if _, ok := profile.Operations["injected"]; ok { t.Error("mutating proto operations affected source profile") } proto.Capabilities = append(proto.Capabilities, "injected") if len(profile.Capabilities) != 2 { t.Error("mutating proto capabilities affected source profile") } } // TestConcreteProfileToProtoNil verifies that a nil profile produces a nil // proto, preserving legacy payloads without a profile. func TestProtocolProfileConcreteToProtoNil(t *testing.T) { if proto := concreteProfileToProto(nil); proto != nil { t.Errorf("expected nil proto for nil profile, got %v", proto) } } // TestConcreteProfileToProtoNoBase verifies that the concrete proto snapshot // never carries a Base field across the wire. func TestProtocolProfileConcreteToProtoNoBase(t *testing.T) { profile := config.ConcreteProtocolProfile{ ID: "custom", ProtocolProfileConf: config.ProtocolProfileConf{ Base: "openai", Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://proxy.example.invalid/v1", Operations: map[string]string{ "models": "/models", }, Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"chat"}, }, } proto := concreteProfileToProto(&profile) if proto == nil { t.Fatal("expected non-nil proto") } // The ConcreteProtocolProfile proto message has no Base field, so there // is nothing to assert beyond confirming the snapshot resolves correctly. if proto.GetId() != "custom" { t.Errorf("Id = %q, want %q", proto.GetId(), "custom") } if proto.GetBaseUrl() != "https://proxy.example.invalid/v1" { t.Errorf("BaseUrl = %q, want overridden value", proto.GetBaseUrl()) } } // TestConcreteProfileToProtoExtensions verifies that extensions are carried // without panicking even when the struct conversion is partial. func TestProtocolProfileConcreteToProtoExtensions(t *testing.T) { profile := config.ConcreteProtocolProfile{ ID: "ext", ProtocolProfileConf: config.ProtocolProfileConf{ Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://api.example.com/v1", Operations: map[string]string{ "models": "/models", }, Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"chat"}, Extensions: map[string]any{ "key": "value", "nested": map[string]any{ "enabled": true, "items": []any{"one", map[string]any{"leaf": "two"}}, }, }, }, } proto := concreteProfileToProto(&profile) if proto == nil { t.Fatal("expected non-nil proto") } // Extensions are converted to a structpb.Struct; verify it is non-nil // when the source has extensions. if proto.GetExtensions() == nil { t.Error("expected non-nil extensions struct") } want := map[string]any{ "key": "value", "nested": map[string]any{ "enabled": true, "items": []any{"one", map[string]any{"leaf": "two"}}, }, } if got := proto.GetExtensions().AsMap(); !reflect.DeepEqual(got, want) { t.Fatalf("extensions = %#v, want %#v", got, want) } profile.Extensions["nested"].(map[string]any)["enabled"] = false if got := proto.GetExtensions().AsMap()["nested"].(map[string]any)["enabled"]; got != true { t.Fatalf("source mutation crossed mapper boundary: %v", got) } proto.Extensions.Fields["key"] = structpb.NewStringValue("wire-mutated") if got := profile.Extensions["key"]; got != "value" { t.Fatalf("wire mutation crossed mapper boundary: %v", got) } } // TestProviderToAdapterConfigProfile verifies that providerToAdapterConfig // carries the concrete profile into the OpenAICompatAdapterConfig payload. func TestProtocolProfileProviderToAdapterConfig(t *testing.T) { profile := config.ConcreteProtocolProfile{ ID: "openai", ProtocolProfileConf: config.ProtocolProfileConf{ Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://api.openai.com/v1", Operations: map[string]string{ "models": "/models", }, Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"chat"}, }, } p := config.NodeProviderConf{ ID: "my-provider", Type: "openai_api", Profile: "openai", RuntimeProfile: &profile, Endpoint: "https://api.openai.com/v1", Models: []string{"gpt-4"}, Capacity: 4, } ac, err := providerToAdapterConfig(p) if err != nil { t.Fatalf("providerToAdapterConfig: %v", err) } if ac.GetType() != "openai_compat" { t.Errorf("Type = %q, want %q", ac.GetType(), "openai_compat") } if ac.GetName() != "my-provider" { t.Errorf("Name = %q, want %q", ac.GetName(), "my-provider") } openaiCfg := ac.GetOpenaiCompat() if openaiCfg == nil { t.Fatal("expected non-nil OpenAICompatAdapterConfig") } if openaiCfg.GetProtocolProfile() == nil { t.Error("expected non-nil protocol_profile in payload") } if openaiCfg.GetProtocolProfile().GetId() != "openai" { t.Errorf("ProtocolProfile.Id = %q, want %q", openaiCfg.GetProtocolProfile().GetId(), "openai") } } // TestBuildConfigPayloadNoBaseCrossesWire is a guard that the mapper does not // leak a Base reference into the wire payload for any adapter type. func TestProtocolProfileBuildConfigPayloadNoBase(t *testing.T) { profile := config.ConcreteProtocolProfile{ ID: "openai", ProtocolProfileConf: config.ProtocolProfileConf{ Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://api.openai.com/v1", Operations: map[string]string{ "models": "/models", }, Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"chat"}, }, } rec := &NodeRecord{ ID: "node-1", Providers: []config.NodeProviderConf{ { ID: "my-provider", Type: "openai_api", Profile: "openai", RuntimeProfile: &profile, Endpoint: "https://api.openai.com/v1", Models: []string{"gpt-4"}, Capacity: 4, }, }, } payload, err := BuildConfigPayload(rec) if err != nil { t.Fatalf("BuildConfigPayload: %v", err) } if len(payload.GetAdapters()) != 1 { t.Fatalf("expected 1 adapter, got %d", len(payload.GetAdapters())) } openaiCfg := payload.GetAdapters()[0].GetOpenaiCompat() if openaiCfg == nil { t.Fatal("expected non-nil OpenAICompatAdapterConfig") } // ConcreteProtocolProfile proto has no Base field, so this is always nil. _ = openaiCfg.GetProtocolProfile() } func TestProtocolProfileAdapterBackingCompile(t *testing.T) { profile := config.ConcreteProtocolProfile{ ID: "custom", ProtocolProfileConf: config.ProtocolProfileConf{ Driver: config.ProtocolDriverOpenAIChat, BaseURL: "http://backing.invalid/v1", Operations: map[string]string{"chat_completions": "/chat/completions"}, Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"chat"}, }, } t.Run("vllm_backing_with_provider_overlay", func(t *testing.T) { rec := &NodeRecord{ ID: "node-vllm", Adapters: config.AdaptersConf{VllmInstances: []config.VllmInstanceConf{{ Name: "gpu", Enabled: true, Endpoint: "http://vllm:8000/v1", Capacity: 2, MaxQueue: 3, QueueTimeoutMS: 4, RequestTimeoutMS: 5, }}}, Providers: []config.NodeProviderConf{{ ID: "profile-provider", Type: "vllm", Adapter: "gpu", RuntimeProfile: &profile, Endpoint: "http://overlay:9000/v1", Capacity: 7, }}, } payload, err := BuildConfigPayload(rec) if err != nil { t.Fatalf("BuildConfigPayload: %v", err) } if len(payload.GetAdapters()) != 2 { t.Fatalf("adapters = %d, want profile adapter plus retained backing", len(payload.GetAdapters())) } compiled := payload.GetAdapters()[0] got := compiled.GetOpenaiCompat() if compiled.GetName() != "profile-provider" || got == nil { t.Fatalf("compiled adapter = %#v", compiled) } if got.GetProvider() != "vllm" || got.GetEndpoint() != "http://overlay:9000/v1" || got.GetCapacity() != 7 || got.GetMaxQueue() != 3 { t.Fatalf("compiled backing overlay = %#v", got) } if got.GetProtocolProfile().GetId() != "custom" || payload.GetAdapters()[1].GetVllm() == nil || payload.GetAdapters()[1].GetName() != "gpu" { t.Fatalf("profile/backing payload = %#v", payload.GetAdapters()) } }) t.Run("openai_compat_backing_fields", func(t *testing.T) { rec := &NodeRecord{ ID: "node-openai", Adapters: config.AdaptersConf{OpenAICompatInstances: []config.OpenAICompatInstanceConf{{ Name: "api", Enabled: true, Provider: "lemonade", Endpoint: "http://api:8000/v1", Headers: map[string]string{"Authorization": "Bearer secret"}, Capacity: 2, RequestTimeoutMS: 90, }}}, Providers: []config.NodeProviderConf{{ID: "profile-provider", Type: "openai_compat", Adapter: "api", RuntimeProfile: &profile}}, } payload, err := BuildConfigPayload(rec) if err != nil { t.Fatalf("BuildConfigPayload: %v", err) } got := payload.GetAdapters()[0].GetOpenaiCompat() if got.GetProvider() != "lemonade" || got.GetEndpoint() != "http://api:8000/v1" || got.GetHeaders()["Authorization"] != "Bearer secret" || got.GetRequestTimeoutMs() != 90 { t.Fatalf("compiled adapter did not inherit backing fields: %#v", got) } got.Headers["Authorization"] = "mutated" if rec.Adapters.OpenAICompatInstances[0].Headers["Authorization"] != "Bearer secret" { t.Fatal("wire headers alias backing config") } }) } func TestProtocolProfileBackingAdapterRejected(t *testing.T) { profile := config.ConcreteProtocolProfile{ID: "openai", ProtocolProfileConf: config.ProtocolProfileConf{Driver: config.ProtocolDriverOpenAIChat}} for _, tc := range []struct { name, ref, want string adapters config.AdaptersConf }{ {name: "missing", ref: "missing", want: "missing"}, {name: "disabled", ref: "api", want: "disabled", adapters: config.AdaptersConf{OpenAICompatInstances: []config.OpenAICompatInstanceConf{{Name: "api", Enabled: false}}}}, {name: "ambiguous_exact", ref: "shared", want: "ambiguous", adapters: config.AdaptersConf{ VllmInstances: []config.VllmInstanceConf{{Name: "shared", Enabled: true}}, OpenAICompatInstances: []config.OpenAICompatInstanceConf{{Name: "shared", Enabled: true}}, }}, {name: "ambiguous_type", ref: "vllm", want: "ambiguous", adapters: config.AdaptersConf{VllmInstances: []config.VllmInstanceConf{ {Name: "one", Enabled: true}, {Name: "two", Enabled: true}, }}}, } { t.Run(tc.name, func(t *testing.T) { rec := &NodeRecord{ID: "node", Adapters: tc.adapters, Providers: []config.NodeProviderConf{{ ID: "profile-provider", Type: "openai_compat", Adapter: tc.ref, RuntimeProfile: &profile, }}} _, err := BuildConfigPayload(rec) if err == nil || !strings.Contains(err.Error(), tc.want) || !strings.Contains(err.Error(), "profile-provider") || !strings.Contains(err.Error(), tc.ref) { t.Fatalf("expected contextual %q error, got %v", tc.want, err) } }) } }