mirror of https://github.com/vernonkeenan/lib
267 lines
9.8 KiB
Go
267 lines
9.8 KiB
Go
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), "..", "..", ".."))
|
|
}
|