package research_client_test import ( "context" "encoding/json" "path/filepath" "reflect" "runtime" "strings" "testing" "code.tnxs.net/vernonkeenan/lib/api/research/research_client/portfolio_enrichment_runs" "code.tnxs.net/vernonkeenan/lib/api/research/research_models" "github.com/go-openapi/loads" openapiruntime "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" ) type reconciliationCaptureTransport struct { operation *openapiruntime.ClientOperation } func (transport *reconciliationCaptureTransport) Submit( operation *openapiruntime.ClientOperation, ) (any, error) { return transport.SubmitContext(context.Background(), operation) } func (transport *reconciliationCaptureTransport) SubmitContext( _ context.Context, operation *openapiruntime.ClientOperation, ) (any, error) { transport.operation = operation return portfolio_enrichment_runs.NewPostPortfolioReconciliationOK(), nil } func TestGeneratedPortfolioReconciliationOperationContract(t *testing.T) { transport := &reconciliationCaptureTransport{} runID := strfmt.UUID("11111111-1111-4111-8111-111111111111") request := validServiceUpdateReconciliationRequest() _, err := portfolio_enrichment_runs.New(transport, strfmt.Default). PostPortfolioReconciliation( portfolio_enrichment_runs.NewPostPortfolioReconciliationParams(). WithPortfolioEnrichmentRunID(runID). WithPortfolioReconciliationRequest(request), nil, ) if err != nil { t.Fatalf("generated reconciliation client failed: %v", err) } if transport.operation == nil { t.Fatal("generated reconciliation client did not submit an operation") } if got := transport.operation.ID; got != "postPortfolioReconciliation" { t.Errorf("operation ID = %q, want postPortfolioReconciliation", got) } if got := transport.operation.Method; got != "POST" { t.Errorf("method = %q, want POST", got) } if got := transport.operation.PathPattern; got != "/portfolioenrichmentruns/{portfolioEnrichmentRunId}/reconciliations" { t.Errorf("path = %q, want the portfolio reconciliation path", got) } } func TestGeneratedServiceUpdateReconciliationRequestShape(t *testing.T) { request := validServiceUpdateReconciliationRequest() if err := request.Validate(strfmt.Default); err != nil { t.Fatalf("valid Service update request failed generated validation: %v", err) } if got := *request.Action; got != research_models.PortfolioReconciliationRequestActionUpdate { t.Errorf("Action = %q, want update", got) } if got := *request.EntityType; got != research_models.PortfolioReconciliationRequestEntityTypeCompanyService { t.Errorf("EntityType = %q, want company_service", got) } encoded, err := json.Marshal(request) if err != nil { t.Fatalf("marshal generated Service update request: %v", err) } for _, serverOwned := range []string{ "TargetID", "TargetBaseVersion", "TargetBeforeVersion", "TargetResultVersion", } { if strings.Contains(string(encoded), `"`+serverOwned+`"`) { t.Errorf("caller request serialized server-owned %s", serverOwned) } } } func TestGeneratedReconciliationRequestStrictlyExcludesCallerOwnedTargetAndVersions( t *testing.T, ) { requestType := reflect.TypeOf(research_models.PortfolioReconciliationRequest{}) for _, serverOwned := range []string{ "TargetID", "TargetBaseVersion", "TargetBeforeVersion", "TargetResultVersion", } { if _, exists := requestType.FieldByName(serverOwned); exists { t.Errorf("generated request exposes server-owned field %s", serverOwned) } } document, err := loads.Spec(filepath.Join( portfolioReconciliationRepoRoot(t), "swagger", "research-vernonkeenan.yaml", )) if err != nil { t.Fatalf("load generated Research provider projection: %v", err) } schema := document.Spec().Definitions["PortfolioReconciliationRequest"] if schema.AdditionalProperties == nil || schema.AdditionalProperties.Allows { t.Fatal("PortfolioReconciliationRequest does not preserve additionalProperties: false") } for _, serverOwned := range []string{ "TargetID", "TargetBaseVersion", "TargetBeforeVersion", "TargetResultVersion", } { if _, exists := schema.Properties[serverOwned]; exists { t.Errorf("provider request schema exposes server-owned property %s", serverOwned) } } } func TestGeneratedServiceUpdateReceiptCarriesBeforeAndResultVersions(t *testing.T) { before, err := strfmt.ParseDateTime("2026-07-27T12:00:00Z") if err != nil { t.Fatalf("parse before version: %v", err) } result, err := strfmt.ParseDateTime("2026-07-27T12:00:01Z") if err != nil { t.Fatalf("parse result version: %v", err) } created, err := strfmt.ParseDateTime("2026-07-27T12:00:02Z") if err != nil { t.Fatalf("parse receipt created date: %v", err) } action := research_models.PortfolioReconciliationReceiptActionUpdate entityType := research_models.PortfolioReconciliationReceiptEntityTypeCompanyService mode := research_models.PortfolioReconciliationReceiptModeDiscovery principalKey := "studio" policyRevision := int64(2) receiptID := strfmt.UUID("11111111-1111-4111-8111-111111111111") runID := strfmt.UUID("22222222-2222-4222-8222-222222222222") candidateID := strfmt.UUID("33333333-3333-4333-8333-333333333333") targetID := strfmt.UUID("44444444-4444-4444-8444-444444444444") actorID := strfmt.UUID("55555555-5555-4555-8555-555555555555") principalID := strfmt.UUID("66666666-6666-4666-8666-666666666666") receipt := &research_models.PortfolioReconciliationReceipt{ ID: &receiptID, RunID: &runID, CandidateID: &candidateID, Mode: &mode, EntityType: &entityType, Action: &action, TargetID: &targetID, TargetBeforeVersion: before, TargetResultVersion: &result, PolicyRevision: &policyRevision, ActorUserID: &actorID, ClientPrincipalID: &principalID, ClientPrincipalKey: &principalKey, CreatedDate: &created, } if err := receipt.Validate(strfmt.Default); err != nil { t.Fatalf("valid Service update receipt failed generated validation: %v", err) } encoded, err := json.Marshal(receipt) if err != nil { t.Fatalf("marshal generated Service update receipt: %v", err) } var decoded research_models.PortfolioReconciliationReceipt if err := json.Unmarshal(encoded, &decoded); err != nil { t.Fatalf("unmarshal generated Service update receipt: %v", err) } if !reflect.DeepEqual(decoded.TargetBeforeVersion, before) { t.Errorf("TargetBeforeVersion = %s, want %s", decoded.TargetBeforeVersion, before) } if decoded.TargetResultVersion == nil || !reflect.DeepEqual(*decoded.TargetResultVersion, result) { t.Errorf("TargetResultVersion = %v, want %s", decoded.TargetResultVersion, result) } } func TestPortfolioReconciliationContractKeepsProductUpdatesGoverned(t *testing.T) { document, err := loads.Spec(filepath.Join( portfolioReconciliationRepoRoot(t), "swagger", "research-vernonkeenan.yaml", )) if err != nil { t.Fatalf("load generated Research provider projection: %v", err) } reconciliation := document.Spec().Paths.Paths["/portfolioenrichmentruns/{portfolioEnrichmentRunId}/reconciliations"] if reconciliation.Post == nil { t.Fatal("portfolio reconciliation POST is absent") } if !strings.Contains( reconciliation.Post.Description, "Existing Product changes remain governed proposals", ) { t.Fatal("reconciliation contract does not preserve Product update rejection") } if document.Spec().Paths.Paths["/companyproducts"].Put != nil { t.Fatal("provider projection exposes a direct CompanyProduct PUT") } if _, ok := reconciliation.Post.Responses.StatusCodeResponses[422]; !ok { t.Fatal("reconciliation contract does not expose invalid Product update rejection") } if !portfolio_enrichment_runs. NewPostPortfolioReconciliationUnprocessableEntity(). IsCode(422) { t.Fatal("generated client does not preserve Product update rejection status 422") } } func TestPortfolioReconciliationRequiresCompoundAuthentication(t *testing.T) { document, err := loads.Spec(filepath.Join( portfolioReconciliationRepoRoot(t), "swagger", "research-vernonkeenan.yaml", )) if err != nil { t.Fatalf("load generated Research provider projection: %v", err) } operation := document.Spec().Paths.Paths["/portfolioenrichmentruns/{portfolioEnrichmentRunId}/reconciliations"].Post if len(operation.Security) != 1 { t.Fatalf("security alternatives = %d, want one compound alternative", len(operation.Security)) } if _, ok := operation.Security[0]["ApiKeyAuth"]; !ok { t.Fatal("reconciliation operation is missing ApiKeyAuth") } if _, ok := operation.Security[0]["kvSessionCookie"]; !ok { t.Fatal("reconciliation operation is missing kvSessionCookie") } } func validServiceUpdateReconciliationRequest() *research_models.PortfolioReconciliationRequest { acknowledged := true action := research_models.PortfolioReconciliationRequestActionUpdate entityType := research_models.PortfolioReconciliationRequestEntityTypeCompanyService mode := research_models.PortfolioReconciliationRequestModeDiscovery idempotencyKey := "studio-service-update-0001" candidateID := strfmt.UUID("11111111-1111-4111-8111-111111111111") fieldID := strfmt.UUID("22222222-2222-4222-8222-222222222222") fieldName := research_models.PortfolioReconciliationFieldSelectionFieldNameTagline value := "Bounded service update" return &research_models.PortfolioReconciliationRequest{ Acknowledged: &acknowledged, Action: &action, CandidateID: &candidateID, EntityType: &entityType, Fields: []*research_models.PortfolioReconciliationFieldSelection{{ CandidateFieldID: &fieldID, FieldName: &fieldName, Value: &value, }}, IdempotencyKey: &idempotencyKey, Mode: &mode, } } func portfolioReconciliationRepoRoot(t *testing.T) string { t.Helper() _, testFile, _, ok := runtime.Caller(0) if !ok { t.Fatal("resolve test source path") } return filepath.Clean(filepath.Join(filepath.Dir(testFile), "..", "..", "..")) }