package openai import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" edgeservice "iop/apps/edge/internal/service" "iop/packages/go/config" iop "iop/proto/gen/iop" ) // TestResponsesProtocolProfileOperationPassthrough verifies that an explicit // openai_responses profile admits the responses operation through the // provider-pool tunnel and that the tunnel request carries the canonical // operation identifier. func TestResponsesProtocolProfileOperationPassthrough(t *testing.T) { profile, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfileCatalog()) if err != nil { t.Fatalf("ResolveProtocolProfile: %v", err) } fake := &providerFakeRunService{ tunnelFrames: staticProviderTunnelFrames(`{"id":"resp-1","object":"response","output":[]}`), poolSelectedCandidate: edgeservice.ProviderPoolCandidate{ ProviderID: "prov-openai", ProfileID: profile.ID, ProfileDriver: string(profile.Driver), ProfileCapabilities: append([]string(nil), profile.Capabilities...), ProtocolProfile: &profile, }, } catalog := []config.ModelCatalogEntry{{ ID: "responses-model", Providers: map[string]string{"prov-openai": "gpt-4-responses"}, }} srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog(catalog) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"responses-model", "input":"say hello via responses" }`)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if len(fake.reqsSnapshot()) != 0 { t.Fatalf("profile-backed responses must not fall back to normalized SubmitRun, got %d calls", len(fake.reqsSnapshot())) } reqs := fake.tunnelReqsSnapshot() if len(reqs) != 1 { t.Fatalf("expected 1 tunnel dispatch, got %d", len(reqs)) } got := reqs[0] if got.Operation != string(config.OperationResponses) { t.Fatalf("tunnel operation: got %q want %q", got.Operation, config.OperationResponses) } if got.Path != "/v1/responses" { t.Fatalf("tunnel path: got %q want /v1/responses", got.Path) } if got.Method != http.MethodPost { t.Fatalf("tunnel method: got %q want POST", got.Method) } if got.ProviderID != "prov-openai" { t.Fatalf("selected provider: got %q want prov-openai", got.ProviderID) } } // TestResponsesProtocolProfileOperationPassthroughNonStream verifies that the // responses profile operation admits non-streaming passthrough and that the // provider body preserves the caller's Responses-shaped fields verbatim. func TestResponsesProtocolProfileOperationPassthroughNonStream(t *testing.T) { profile, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfileCatalog()) if err != nil { t.Fatalf("ResolveProtocolProfile: %v", err) } fake := &providerFakeRunService{ tunnelFrames: staticProviderTunnelFrames(`{"id":"resp-ns","object":"response","output":[]}`), poolSelectedCandidate: edgeservice.ProviderPoolCandidate{ ProviderID: "prov-openai-ns", ProfileID: profile.ID, ProfileDriver: string(profile.Driver), ProfileCapabilities: append([]string(nil), profile.Capabilities...), ProtocolProfile: &profile, }, } catalog := []config.ModelCatalogEntry{{ ID: "responses-ns", Providers: map[string]string{"prov-openai-ns": "gpt-4-ns"}, }} srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog(catalog) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"responses-ns", "input":"non-stream input", "max_output_tokens":128 }`)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } bodies := fake.tunnelBodiesSnapshot() if len(bodies) != 1 { t.Fatalf("expected 1 tunnel body, got %d", len(bodies)) } var providerReq map[string]any if err := json.Unmarshal(bodies[0], &providerReq); err != nil { t.Fatalf("provider body JSON: %v", err) } if providerReq["max_output_tokens"].(float64) != 128 { t.Fatalf("max_output_tokens must be preserved: %+v", providerReq) } if _, ok := providerReq["stream"]; ok { t.Fatalf("stream must not be injected on passthrough: %+v", providerReq) } } // TestResponsesProtocolProfileOperationPassthroughStream verifies that the // responses profile operation admits stream=true passthrough and that the // provider tunnel carries the stream flag to the upstream provider. func TestResponsesProtocolProfileOperationPassthroughStream(t *testing.T) { profile, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfileCatalog()) if err != nil { t.Fatalf("ResolveProtocolProfile: %v", err) } frames := make(chan *iop.ProviderTunnelFrame, 3) frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte(`{"id":"resp-stream","object":"response","output":[],"delta":"chunk-1"}`)} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true} close(frames) fake := &providerFakeRunService{ tunnelFrames: frames, poolSelectedCandidate: edgeservice.ProviderPoolCandidate{ ProviderID: "prov-openai-stream", ProfileID: profile.ID, ProfileDriver: string(profile.Driver), ProfileCapabilities: append([]string(nil), profile.Capabilities...), ProtocolProfile: &profile, }, } catalog := []config.ModelCatalogEntry{{ ID: "responses-stream", Providers: map[string]string{"prov-openai-stream": "gpt-4-stream"}, }} srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog(catalog) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"responses-stream", "input":"stream this", "stream":true }`)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } reqs := fake.tunnelReqsSnapshot() if len(reqs) != 1 { t.Fatalf("expected 1 tunnel dispatch, got %d", len(reqs)) } if !reqs[0].Stream { t.Fatal("stream tunnel request must carry stream=true") } } // TestResponsesProtocolProfileUnsupportedRejectsBeforeDispatch verifies that an // explicit profile which does not declare the responses operation is rejected // before any candidate selection or dispatch occurs. The handler maps the typed // operation admission failure to HTTP 400 with invalid_request_error and the // sanitized message "no provider profile supports the requested Responses operation". func TestResponsesProtocolProfileUnsupportedRejectsBeforeDispatch(t *testing.T) { // Anthropic profile does not declare the responses operation. anthropicProfile, err := config.ResolveProtocolProfile("anthropic", "", config.BuiltInProtocolProfileCatalog()) if err != nil { t.Fatalf("ResolveProtocolProfile anthropic: %v", err) } fake := &providerFakeRunService{ poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel), poolSelectedCandidate: edgeservice.ProviderPoolCandidate{ ProviderID: "prov-anthropic", ProfileID: anthropicProfile.ID, ProfileDriver: string(anthropicProfile.Driver), ProfileCapabilities: append([]string(nil), anthropicProfile.Capabilities...), ProtocolProfile: &anthropicProfile, }, } catalog := []config.ModelCatalogEntry{{ ID: "responses-model", Providers: map[string]string{"prov-anthropic": "claude-3"}, }} srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog(catalog) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"responses-model", "input":"should be rejected" }`)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected HTTP 400 for unsupported operation, got %d body=%s", w.Code, w.Body.String()) } var resp map[string]any if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("unmarshal error response: %v", err) } errObj, ok := resp["error"].(map[string]any) if !ok { t.Fatalf("error object missing: %v", resp) } if errObj["type"] != "invalid_request_error" { t.Fatalf("error.type = %v, want invalid_request_error", errObj["type"]) } if errObj["message"] != "no provider profile supports the requested Responses operation" { t.Fatalf("error.message = %v, want 'no provider profile supports the requested Responses operation'", errObj["message"]) } if len(fake.tunnelReqsSnapshot()) != 0 { t.Fatalf("unsupported profile must not dispatch any tunnel request, got %d", len(fake.tunnelReqsSnapshot())) } if len(fake.reqsSnapshot()) != 0 { t.Fatalf("unsupported profile must not dispatch any normalized run, got %d", len(fake.reqsSnapshot())) } } // TestResponsesLegacyProfileFallback verifies that a legacy candidate without a // profile still falls back to the path-based dispatch and carries the // /v1/responses path for tunnel passthrough. func TestResponsesLegacyProfileFallback(t *testing.T) { fake := &providerFakeRunService{ tunnelFrames: staticProviderTunnelFrames(`{"id":"resp-legacy","object":"response","output":[]}`), tunnelServedTarget: "legacy-served", } catalog := []config.ModelCatalogEntry{{ ID: "legacy-model", Providers: map[string]string{"prov-legacy": "legacy-served"}, }} srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog(catalog) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"legacy-model", "input":"legacy input" }`)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if len(fake.reqsSnapshot()) != 0 { t.Fatalf("legacy fallback must not fall back to normalized SubmitRun, got %d calls", len(fake.reqsSnapshot())) } reqs := fake.tunnelReqsSnapshot() if len(reqs) != 1 { t.Fatalf("expected 1 tunnel dispatch, got %d", len(reqs)) } got := reqs[0] if got.Path != "/v1/responses" { t.Fatalf("legacy tunnel path: got %q want /v1/responses", got.Path) } if got.Operation != string(config.OperationResponses) { t.Fatalf("legacy tunnel operation: got %q want %q", got.Operation, config.OperationResponses) } } // TestResponsesProtocolProfileProviderAuthInjection verifies that provider auth // headers configured via the request header are injected into the tunnel // request when the selected candidate carries an explicit profile. func TestResponsesProtocolProfileProviderAuthInjection(t *testing.T) { profile, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfileCatalog()) if err != nil { t.Fatalf("ResolveProtocolProfile: %v", err) } fake := &providerFakeRunService{ tunnelFrames: staticProviderTunnelFrames(`{"id":"resp-auth","object":"response","output":[]}`), poolSelectedCandidate: edgeservice.ProviderPoolCandidate{ ProviderID: "prov-openai-auth", ProfileID: profile.ID, ProfileDriver: string(profile.Driver), ProfileCapabilities: append([]string(nil), profile.Capabilities...), ProtocolProfile: &profile, }, } catalog := []config.ModelCatalogEntry{{ ID: "responses-auth", Providers: map[string]string{"prov-openai-auth": "gpt-4-auth"}, }} srv := NewServer(config.EdgeOpenAIConf{ProviderAuth: config.EdgeOpenAIProviderAuthConf{ Enabled: true, FromHeader: "X-IOP-Provider-Authorization", TargetHeader: "Authorization", Scheme: "Bearer", Required: true, }}, fake, nil) srv.SetModelCatalog(catalog) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"responses-auth", "input":"auth input" }`)) req.Header.Set("X-IOP-Provider-Authorization", "request-provider-secret") w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } reqs := fake.tunnelReqsSnapshot() if len(reqs) != 1 { t.Fatalf("expected 1 tunnel dispatch, got %d", len(reqs)) } got := reqs[0] if got.Headers["Authorization"] != "Bearer request-provider-secret" { t.Fatalf("provider auth header: got %q, want %q", got.Headers["Authorization"], "Bearer request-provider-secret") } } // TestResponsesProtocolProfileProviderAuthRequiredMissing verifies that a // required provider auth header missing from the request is rejected with a // 400 before any tunnel dispatch is sent. func TestResponsesProtocolProfileProviderAuthRequiredMissing(t *testing.T) { profile, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfileCatalog()) if err != nil { t.Fatalf("ResolveProtocolProfile: %v", err) } fake := &providerFakeRunService{ poolSelectedCandidate: edgeservice.ProviderPoolCandidate{ ProviderID: "prov-openai-auth-req", ProfileID: profile.ID, ProfileDriver: string(profile.Driver), ProfileCapabilities: append([]string(nil), profile.Capabilities...), ProtocolProfile: &profile, }, } catalog := []config.ModelCatalogEntry{{ ID: "responses-auth-req", Providers: map[string]string{"prov-openai-auth-req": "gpt-4-auth-req"}, }} srv := NewServer(config.EdgeOpenAIConf{ProviderAuth: config.EdgeOpenAIProviderAuthConf{ Enabled: true, FromHeader: "X-IOP-Provider-Authorization", TargetHeader: "Authorization", Scheme: "Bearer", Required: true, }}, fake, nil) srv.SetModelCatalog(catalog) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"responses-auth-req", "input":"missing auth" }`)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code == http.StatusOK { t.Fatalf("expected 400 for missing required provider auth, got %d", w.Code) } if len(fake.tunnelReqsSnapshot()) != 0 { t.Fatalf("missing auth must not dispatch any tunnel request, got %d", len(fake.tunnelReqsSnapshot())) } } // TestResponsesProtocolProfileOperationAdmissionTable verifies the operation // admission behavior across supported/unsupported/nil-profile scenarios by // driving each case through the handler and asserting the dispatch outcome. func TestResponsesProtocolProfileOperationAdmissionTable(t *testing.T) { openaiProfile, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfileCatalog()) if err != nil { t.Fatalf("ResolveProtocolProfile openai: %v", err) } anthropicProfile, err := config.ResolveProtocolProfile("anthropic", "", config.BuiltInProtocolProfileCatalog()) if err != nil { t.Fatalf("ResolveProtocolProfile anthropic: %v", err) } cases := []struct { name string profile *config.ConcreteProtocolProfile profileID string expectedStatus int expectedErrType string expectedErrMsg string expectTunnelCall bool }{ { name: "openai profile admits responses operation", profile: &openaiProfile, profileID: "openai", expectedStatus: http.StatusOK, expectTunnelCall: true, }, { name: "anthropic profile rejects responses operation", profile: &anthropicProfile, profileID: "anthropic", expectedStatus: http.StatusBadRequest, expectedErrType: "invalid_request_error", expectedErrMsg: "no provider profile supports the requested Responses operation", expectTunnelCall: false, }, { name: "nil profile (legacy) admits responses via path fallback", profile: nil, profileID: "", expectedStatus: http.StatusOK, expectTunnelCall: true, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { fake := &providerFakeRunService{ poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel), tunnelFrames: staticProviderTunnelFrames(`{"id":"resp-table","object":"response","output":[]}`), poolSelectedCandidate: edgeservice.ProviderPoolCandidate{ ProviderID: "prov-table", ProfileID: tc.profileID, ProfileDriver: "", ProtocolProfile: tc.profile, }, } catalog := []config.ModelCatalogEntry{{ ID: "responses-table", Providers: map[string]string{"prov-table": "served-table"}, }} srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog(catalog) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"responses-table", "input":"table input" }`)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != tc.expectedStatus { t.Fatalf("status: got %d, want %d (body=%s)", w.Code, tc.expectedStatus, w.Body.String()) } if tc.expectedErrType != "" { var resp map[string]any if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("unmarshal error response: %v", err) } errObj, ok := resp["error"].(map[string]any) if !ok { t.Fatalf("error object missing: %v", resp) } if errObj["type"] != tc.expectedErrType { t.Fatalf("error.type = %v, want %s", errObj["type"], tc.expectedErrType) } if errObj["message"] != tc.expectedErrMsg { t.Fatalf("error.message = %v, want %s", errObj["message"], tc.expectedErrMsg) } } tunnelReqs := fake.tunnelReqsSnapshot() if tc.expectTunnelCall && len(tunnelReqs) != 1 { t.Fatalf("expected 1 tunnel call, got %d", len(tunnelReqs)) } if !tc.expectTunnelCall && len(tunnelReqs) != 0 { t.Fatalf("expected 0 tunnel calls, got %d", len(tunnelReqs)) } }) } }