diff --git a/arazzo/core/criterion_test.go b/arazzo/core/criterion_test.go index 03b6f57..30db14c 100644 --- a/arazzo/core/criterion_test.go +++ b/arazzo/core/criterion_test.go @@ -221,3 +221,34 @@ type: }) } } + +func TestCriterionTypeUnion_Unmarshal_NilNode_Error(t *testing.T) { + t.Parallel() + + var union CriterionTypeUnion + _, err := union.Unmarshal(t.Context(), "test", nil) + require.Error(t, err, "should return error for nil node") + require.Contains(t, err.Error(), "node is nil", "error should mention nil node") +} + +func TestCriterionTypeUnion_Unmarshal_InvalidNodeKind_Error(t *testing.T) { + t.Parallel() + + var union CriterionTypeUnion + node := &yaml.Node{Kind: yaml.SequenceNode} + validationErrs, err := union.Unmarshal(t.Context(), "test", node) + require.NoError(t, err, "should not return fatal error") + require.NotEmpty(t, validationErrs, "should have validation errors") + require.Contains(t, validationErrs[0].Error(), "expected string or object", "error should mention expected types") +} + +func TestCriterionTypeUnion_SyncChanges_Int_Error(t *testing.T) { + t.Parallel() + + union := &CriterionTypeUnion{} + union.SetValid(true, true) + + _, err := union.SyncChanges(t.Context(), 42, nil) + require.Error(t, err, "should return error for int model") + require.Contains(t, err.Error(), "expected a struct", "error should mention struct expectation") +} diff --git a/arazzo/core/reusable_test.go b/arazzo/core/reusable_test.go index 86d8c48..dc5c6d6 100644 --- a/arazzo/core/reusable_test.go +++ b/arazzo/core/reusable_test.go @@ -55,3 +55,44 @@ func TestReusable_SyncChanges_NonStruct_Error(t *testing.T) { require.Error(t, err, "SyncChanges should fail") assert.Contains(t, err.Error(), "Reusable.SyncChanges expected a struct, got string", "error message should match") } + +func TestReusable_Unmarshal_NilNode_Error(t *testing.T) { + t.Parallel() + + var reusable Reusable[*Parameter] + _, err := reusable.Unmarshal(t.Context(), "test", nil) + require.Error(t, err, "should return error for nil node") + assert.Contains(t, err.Error(), "node is nil", "error should mention nil node") +} + +func TestReusable_Unmarshal_InlinedObject_Success(t *testing.T) { + t.Parallel() + + yamlContent := `name: petId +in: path +value: "123"` + + var node yaml.Node + err := yaml.Unmarshal([]byte(yamlContent), &node) + require.NoError(t, err, "unmarshal should succeed") + + var reusable Reusable[*Parameter] + validationErrs, err := reusable.Unmarshal(t.Context(), "test", node.Content[0]) + require.NoError(t, err, "unmarshal should succeed") + require.Empty(t, validationErrs, "validation errors should be empty") + assert.True(t, reusable.GetValid(), "reusable should be valid") + require.NotNil(t, reusable.Object, "Object should not be nil") +} + +func TestReusable_SyncChanges_Int_Error(t *testing.T) { + t.Parallel() + + var node yaml.Node + err := yaml.Unmarshal([]byte(`reference: '#/test'`), &node) + require.NoError(t, err, "unmarshal should succeed") + + reusable := Reusable[*Parameter]{} + _, err = reusable.SyncChanges(t.Context(), 42, node.Content[0]) + require.Error(t, err, "SyncChanges should fail") + assert.Contains(t, err.Error(), "expected a struct", "error message should mention struct expectation") +} diff --git a/arazzo/find_test.go b/arazzo/find_test.go new file mode 100644 index 0000000..72e51c1 --- /dev/null +++ b/arazzo/find_test.go @@ -0,0 +1,190 @@ +package arazzo_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/arazzo" + "github.com/speakeasy-api/openapi/pointer" + "github.com/stretchr/testify/assert" +) + +func TestWorkflows_Find_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + workflows arazzo.Workflows + id string + expected *arazzo.Workflow + }{ + { + name: "empty workflows returns nil", + workflows: arazzo.Workflows{}, + id: "test", + expected: nil, + }, + { + name: "finds workflow by id", + workflows: arazzo.Workflows{ + {WorkflowID: "workflow1"}, + {WorkflowID: "workflow2"}, + {WorkflowID: "workflow3"}, + }, + id: "workflow2", + expected: &arazzo.Workflow{WorkflowID: "workflow2"}, + }, + { + name: "returns nil when workflow not found", + workflows: arazzo.Workflows{ + {WorkflowID: "workflow1"}, + }, + id: "nonexistent", + expected: nil, + }, + { + name: "returns first match when multiple workflows with same id", + workflows: arazzo.Workflows{ + {WorkflowID: "duplicate", Summary: pointer.From("first")}, + {WorkflowID: "duplicate", Summary: pointer.From("second")}, + }, + id: "duplicate", + expected: &arazzo.Workflow{WorkflowID: "duplicate", Summary: pointer.From("first")}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.workflows.Find(tt.id) + if tt.expected == nil { + assert.Nil(t, result) + } else { + assert.NotNil(t, result) + assert.Equal(t, tt.expected.WorkflowID, result.WorkflowID) + if tt.expected.Summary != nil { + assert.Equal(t, *tt.expected.Summary, *result.Summary) + } + } + }) + } +} + +func TestSteps_Find_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + steps arazzo.Steps + id string + expected *arazzo.Step + }{ + { + name: "empty steps returns nil", + steps: arazzo.Steps{}, + id: "test", + expected: nil, + }, + { + name: "finds step by id", + steps: arazzo.Steps{ + {StepID: "step1"}, + {StepID: "step2"}, + {StepID: "step3"}, + }, + id: "step2", + expected: &arazzo.Step{StepID: "step2"}, + }, + { + name: "returns nil when step not found", + steps: arazzo.Steps{ + {StepID: "step1"}, + }, + id: "nonexistent", + expected: nil, + }, + { + name: "returns first match when multiple steps with same id", + steps: arazzo.Steps{ + {StepID: "duplicate", Description: pointer.From("first")}, + {StepID: "duplicate", Description: pointer.From("second")}, + }, + id: "duplicate", + expected: &arazzo.Step{StepID: "duplicate", Description: pointer.From("first")}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.steps.Find(tt.id) + if tt.expected == nil { + assert.Nil(t, result) + } else { + assert.NotNil(t, result) + assert.Equal(t, tt.expected.StepID, result.StepID) + if tt.expected.Description != nil { + assert.Equal(t, *tt.expected.Description, *result.Description) + } + } + }) + } +} + +func TestSourceDescriptions_Find_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + sourceDescriptions arazzo.SourceDescriptions + findName string + expected *arazzo.SourceDescription + }{ + { + name: "empty source descriptions returns nil", + sourceDescriptions: arazzo.SourceDescriptions{}, + findName: "test", + expected: nil, + }, + { + name: "finds source description by name", + sourceDescriptions: arazzo.SourceDescriptions{ + {Name: "apiOne", URL: "https://api1.example.com"}, + {Name: "apiTwo", URL: "https://api2.example.com"}, + {Name: "apiThree", URL: "https://api3.example.com"}, + }, + findName: "apiTwo", + expected: &arazzo.SourceDescription{Name: "apiTwo", URL: "https://api2.example.com"}, + }, + { + name: "returns nil when source description not found", + sourceDescriptions: arazzo.SourceDescriptions{ + {Name: "apiOne", URL: "https://api1.example.com"}, + }, + findName: "nonexistent", + expected: nil, + }, + { + name: "returns first match when multiple source descriptions with same name", + sourceDescriptions: arazzo.SourceDescriptions{ + {Name: "duplicate", URL: "https://first.example.com"}, + {Name: "duplicate", URL: "https://second.example.com"}, + }, + findName: "duplicate", + expected: &arazzo.SourceDescription{Name: "duplicate", URL: "https://first.example.com"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.sourceDescriptions.Find(tt.findName) + if tt.expected == nil { + assert.Nil(t, result) + } else { + assert.NotNil(t, result) + assert.Equal(t, tt.expected.Name, result.Name) + assert.Equal(t, tt.expected.URL, result.URL) + } + }) + } +} diff --git a/arazzo/reusable_test.go b/arazzo/reusable_test.go index 4d9fb59..2298e4c 100644 --- a/arazzo/reusable_test.go +++ b/arazzo/reusable_test.go @@ -41,3 +41,127 @@ func TestTypeToComponentType_Success(t *testing.T) { }) } } + +func TestComponentTypeToReusableType_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + expected string + }{ + { + name: "parameters converts to reusableParameter", + input: "parameters", + expected: "reusableParameter", + }, + { + name: "successActions converts to reusableSuccessAction", + input: "successActions", + expected: "reusableSuccessAction", + }, + { + name: "failureActions converts to reusableFailureAction", + input: "failureActions", + expected: "reusableFailureAction", + }, + { + name: "unknown type returns empty", + input: "unknown", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + actual := componentTypeToReusableType(tt.input) + assert.Equal(t, tt.expected, actual, "component type conversion should match expected reusable type") + }) + } +} + +func TestReusable_IsReference_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + reusable ReusableParameter + expected bool + }{ + { + name: "nil reference returns false", + reusable: ReusableParameter{}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, tt.expected, tt.reusable.IsReference()) + }) + } +} + +func TestReusable_Get_Success(t *testing.T) { + t.Parallel() + + param := &Parameter{Name: "testParam"} + + tests := []struct { + name string + reusable ReusableParameter + components *Components + expected *Parameter + }{ + { + name: "inline object returns object", + reusable: ReusableParameter{Object: param}, + components: nil, + expected: param, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := tt.reusable.Get(tt.components) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestReusable_GetReferencedObject_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + reusable ReusableParameter + components *Components + expectNil bool + }{ + { + name: "not a reference returns nil", + reusable: ReusableParameter{}, + components: nil, + expectNil: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := tt.reusable.GetReferencedObject(tt.components) + if tt.expectNil { + assert.Nil(t, result) + } else { + assert.NotNil(t, result) + } + }) + } +} diff --git a/arazzo/walk_test.go b/arazzo/walk_test.go index afe11d0..56bedcc 100644 --- a/arazzo/walk_test.go +++ b/arazzo/walk_test.go @@ -7,6 +7,7 @@ import ( "github.com/speakeasy-api/openapi/arazzo" "github.com/speakeasy-api/openapi/expression" "github.com/speakeasy-api/openapi/pointer" + "github.com/speakeasy-api/openapi/sequencedmap" "github.com/speakeasy-api/openapi/walk" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -304,3 +305,175 @@ func TestWalk_EmptyArazzo_Success(t *testing.T) { assert.Contains(t, visitedTypes, "Arazzo") assert.Contains(t, visitedTypes, "Info") } + +func TestWalk_WithComponents_Success(t *testing.T) { + t.Parallel() + ctx := t.Context() + + // Create an arazzo document with components to exercise component walk functions + arazzoDoc := &arazzo.Arazzo{ + Arazzo: arazzo.Version, + Info: arazzo.Info{ + Title: "Components Test", + Version: "1.0.0", + }, + SourceDescriptions: []*arazzo.SourceDescription{ + { + Name: "api", + URL: "https://api.example.com/openapi.yaml", + Type: "openapi", + }, + }, + Workflows: []*arazzo.Workflow{ + { + WorkflowID: "testWorkflow", + Steps: []*arazzo.Step{ + { + StepID: "step1", + OperationID: (*expression.Expression)(pointer.From("getUser")), + }, + }, + }, + }, + Components: &arazzo.Components{ + Parameters: sequencedmap.New( + sequencedmap.NewElem("userId", &arazzo.Parameter{ + Name: "userId", + }), + ), + SuccessActions: sequencedmap.New( + sequencedmap.NewElem("logSuccess", &arazzo.SuccessAction{ + Name: "logSuccess", + Type: arazzo.SuccessActionTypeEnd, + }), + ), + FailureActions: sequencedmap.New( + sequencedmap.NewElem("logFailure", &arazzo.FailureAction{ + Name: "logFailure", + Type: arazzo.FailureActionTypeEnd, + }), + ), + }, + } + + // Track what we've seen during the walk + var componentsCount, parameterCount, successActionCount, failureActionCount int + + // Walk the document + for item := range arazzo.Walk(ctx, arazzoDoc) { + err := item.Match(arazzo.Matcher{ + Components: func(c *arazzo.Components) error { + componentsCount++ + return nil + }, + Parameter: func(p *arazzo.Parameter) error { + parameterCount++ + assert.Equal(t, "userId", p.Name) + return nil + }, + SuccessAction: func(sa *arazzo.SuccessAction) error { + successActionCount++ + assert.Equal(t, "logSuccess", sa.Name) + return nil + }, + FailureAction: func(fa *arazzo.FailureAction) error { + failureActionCount++ + assert.Equal(t, "logFailure", fa.Name) + return nil + }, + }) + require.NoError(t, err) + } + + // Verify counts + assert.Equal(t, 1, componentsCount, "should visit Components once") + assert.Equal(t, 1, parameterCount, "should visit Parameter once") + assert.Equal(t, 1, successActionCount, "should visit SuccessAction once") + assert.Equal(t, 1, failureActionCount, "should visit FailureAction once") +} + +func TestWalk_WithReusableActions_Success(t *testing.T) { + t.Parallel() + ctx := t.Context() + + // Create an arazzo document with reusable success and failure actions in workflows and steps + arazzoDoc := &arazzo.Arazzo{ + Arazzo: arazzo.Version, + Info: arazzo.Info{ + Title: "Reusable Actions Test", + Version: "1.0.0", + }, + SourceDescriptions: []*arazzo.SourceDescription{ + { + Name: "api", + URL: "https://api.example.com/openapi.yaml", + Type: "openapi", + }, + }, + Workflows: []*arazzo.Workflow{ + { + WorkflowID: "testWorkflow", + SuccessActions: []*arazzo.ReusableSuccessAction{ + { + Object: &arazzo.SuccessAction{ + Name: "workflowSuccess", + Type: arazzo.SuccessActionTypeEnd, + }, + }, + }, + FailureActions: []*arazzo.ReusableFailureAction{ + { + Object: &arazzo.FailureAction{ + Name: "workflowFailure", + Type: arazzo.FailureActionTypeEnd, + }, + }, + }, + Steps: []*arazzo.Step{ + { + StepID: "step1", + OperationID: (*expression.Expression)(pointer.From("getUser")), + OnSuccess: []*arazzo.ReusableSuccessAction{ + { + Object: &arazzo.SuccessAction{ + Name: "stepSuccess", + Type: arazzo.SuccessActionTypeEnd, + }, + }, + }, + OnFailure: []*arazzo.ReusableFailureAction{ + { + Object: &arazzo.FailureAction{ + Name: "stepFailure", + Type: arazzo.FailureActionTypeEnd, + }, + }, + }, + }, + }, + }, + }, + } + + // Track what we've seen during the walk + var reusableSuccessCount, reusableFailureCount int + + // Walk the document + for item := range arazzo.Walk(ctx, arazzoDoc) { + err := item.Match(arazzo.Matcher{ + ReusableSuccessAction: func(rsa *arazzo.ReusableSuccessAction) error { + reusableSuccessCount++ + return nil + }, + ReusableFailureAction: func(rfa *arazzo.ReusableFailureAction) error { + reusableFailureCount++ + return nil + }, + }) + require.NoError(t, err) + } + + // Verify counts - 1 from workflow level + 1 from step level = 2 each + assert.Equal(t, 2, reusableSuccessCount, "should visit ReusableSuccessAction twice") + assert.Equal(t, 2, reusableFailureCount, "should visit ReusableFailureAction twice") +} diff --git a/expression/string_test.go b/expression/string_test.go new file mode 100644 index 0000000..04aef1b --- /dev/null +++ b/expression/string_test.go @@ -0,0 +1,43 @@ +package expression_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/expression" + "github.com/stretchr/testify/assert" +) + +func TestExpression_String_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + expression expression.Expression + expected string + }{ + { + name: "simple expression", + expression: expression.Expression("$url"), + expected: "$url", + }, + { + name: "complex expression", + expression: expression.Expression("$response.body#/data/id"), + expected: "$response.body#/data/id", + }, + { + name: "empty expression", + expression: expression.Expression(""), + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := tt.expression.String() + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/expression/value_test.go b/expression/value_test.go new file mode 100644 index 0000000..bf12534 --- /dev/null +++ b/expression/value_test.go @@ -0,0 +1,99 @@ +package expression_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/expression" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestGetValueOrExpressionValue_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + value *yaml.Node + expectValue bool + expectExpr bool + expectNil bool + expectedString string + }{ + { + name: "nil value returns nil", + value: nil, + expectNil: true, + expectExpr: false, + }, + { + name: "expression string returns expression", + value: &yaml.Node{Kind: yaml.ScalarNode, Value: "$url"}, + expectExpr: true, + expectValue: false, + }, + { + name: "non-expression string returns value", + value: &yaml.Node{Kind: yaml.ScalarNode, Value: "plain string"}, + expectExpr: false, + expectValue: true, + }, + { + name: "integer returns value", + value: &yaml.Node{Kind: yaml.ScalarNode, Value: "42", Tag: "!!int"}, + expectExpr: false, + expectValue: true, + }, + { + name: "boolean returns value", + value: &yaml.Node{Kind: yaml.ScalarNode, Value: "true", Tag: "!!bool"}, + expectExpr: false, + expectValue: true, + }, + { + name: "map node returns value", + value: &yaml.Node{ + Kind: yaml.MappingNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "key"}, + {Kind: yaml.ScalarNode, Value: "val"}, + }, + }, + expectExpr: false, + expectValue: true, + }, + { + name: "sequence node returns value", + value: &yaml.Node{ + Kind: yaml.SequenceNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "item1"}, + }, + }, + expectExpr: false, + expectValue: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + value, expr, err := expression.GetValueOrExpressionValue(tt.value) + + require.NoError(t, err) + + switch { + case tt.expectNil: + assert.Nil(t, value) + assert.Nil(t, expr) + case tt.expectExpr: + assert.Nil(t, value) + assert.NotNil(t, expr) + case tt.expectValue: + assert.NotNil(t, value) + assert.Nil(t, expr) + } + }) + } +} diff --git a/extensions/extensions_test.go b/extensions/extensions_test.go index 7d72312..c3ebc4c 100644 --- a/extensions/extensions_test.go +++ b/extensions/extensions_test.go @@ -10,6 +10,7 @@ import ( coreExtensions "github.com/speakeasy-api/openapi/extensions/core" "github.com/speakeasy-api/openapi/internal/testutils" "github.com/speakeasy-api/openapi/marshaller" + "github.com/speakeasy-api/openapi/sequencedmap" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" @@ -126,3 +127,54 @@ func getTestModelWithExtensions(ctx context.Context, t *testing.T, data string) return m } + +func TestNewElem_Success(t *testing.T) { + t.Parallel() + + node := &yaml.Node{Kind: yaml.ScalarNode, Value: "test-value"} + elem := extensions.NewElem("x-custom", node) + + assert.NotNil(t, elem) + assert.Equal(t, "x-custom", elem.Key) + assert.Equal(t, node, elem.Value) +} + +func TestExtensions_GetCore_Success(t *testing.T) { + t.Parallel() + + ext := extensions.New() + core := ext.GetCore() + + // Core should be nil for newly created extensions + assert.Nil(t, core) +} + +func TestExtensions_Populate_Success(t *testing.T) { + t.Parallel() + + node := &yaml.Node{Kind: yaml.ScalarNode, Value: "custom-value"} + + source := sequencedmap.New( + sequencedmap.NewElem("x-test", marshaller.Node[*yaml.Node]{Value: node}), + ) + + ext := &extensions.Extensions{} + err := ext.Populate(source) + + require.NoError(t, err) + assert.Equal(t, 1, ext.Len()) + + val, ok := ext.Get("x-test") + assert.True(t, ok) + assert.Equal(t, node, val) +} + +func TestExtensions_Populate_Error(t *testing.T) { + t.Parallel() + + ext := &extensions.Extensions{} + err := ext.Populate("invalid source") + + require.Error(t, err) + assert.Contains(t, err.Error(), "expected source to be") +} diff --git a/hashing/hashing_test.go b/hashing/hashing_test.go index e2d7fb5..e18bae7 100644 --- a/hashing/hashing_test.go +++ b/hashing/hashing_test.go @@ -14,6 +14,7 @@ import ( "github.com/speakeasy-api/openapi/yml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) type testEnum string @@ -602,3 +603,220 @@ properties: // They should be equal since they represent the same schema assert.Equal(t, hash1, hash2, "identical schemas should have the same hash regardless of field ordering") } + +func TestHash_YamlNode_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + node *yaml.Node + wantHash string + }{ + { + name: "nil node", + node: nil, + wantHash: "cbf29ce484222325", // empty string hash + }, + { + name: "scalar string node", + node: &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!str", + Value: "hello", + }, + wantHash: "049bf33d4f533213", + }, + { + name: "scalar int node", + node: &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!int", + Value: "42", + }, + wantHash: "f8aa31eb804642d5", + }, + { + name: "scalar without tag", + node: &yaml.Node{ + Kind: yaml.ScalarNode, + Value: "test", + }, + wantHash: "6a1b3c98d6cf2e16", + }, + { + name: "scalar without value", + node: &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!null", + }, + wantHash: "70dd4271b9b4bf98", + }, + { + name: "empty scalar node", + node: &yaml.Node{ + Kind: yaml.ScalarNode, + }, + wantHash: "535a299dfeb2aee1", + }, + { + name: "mapping node with children", + node: &yaml.Node{ + Kind: yaml.MappingNode, + Tag: "!!map", + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Tag: "!!str", Value: "key"}, + {Kind: yaml.ScalarNode, Tag: "!!str", Value: "value"}, + }, + }, + wantHash: "8f158417b793a189", + }, + { + name: "sequence node with children", + node: &yaml.Node{ + Kind: yaml.SequenceNode, + Tag: "!!seq", + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Tag: "!!str", Value: "item1"}, + {Kind: yaml.ScalarNode, Tag: "!!str", Value: "item2"}, + }, + }, + wantHash: "fac33aff0234a67f", + }, + { + name: "document node wrapping scalar", + node: &yaml.Node{ + Kind: yaml.DocumentNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Tag: "!!str", Value: "document content"}, + }, + }, + wantHash: "ae29e55a43581ec6", + }, + { + name: "deeply nested node", + node: &yaml.Node{ + Kind: yaml.MappingNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "outer"}, + { + Kind: yaml.MappingNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "inner"}, + {Kind: yaml.ScalarNode, Value: "value"}, + }, + }, + }, + }, + wantHash: "4999ae359f57db96", + }, + { + name: "node with positional metadata should produce same hash as without", + node: &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!str", + Value: "hello", + Line: 10, + Column: 5, + Style: yaml.DoubleQuotedStyle, + }, + wantHash: "049bf33d4f533213", // Same as basic scalar string "hello" + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + gotHash := Hash(tt.node) + assert.Equal(t, tt.wantHash, gotHash, "hash should match expected value") + }) + } +} + +func TestHash_YamlNodeStruct_Success(t *testing.T) { + t.Parallel() + + // Test passing yaml.Node as value (not pointer) triggers the struct case + tests := []struct { + name string + node yaml.Node + wantHash string + }{ + { + name: "scalar string node as value", + node: yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!str", + Value: "hello", + }, + wantHash: "049bf33d4f533213", + }, + { + name: "empty node as value", + node: yaml.Node{ + Kind: yaml.ScalarNode, + }, + wantHash: "535a299dfeb2aee1", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + gotHash := Hash(tt.node) + assert.Equal(t, tt.wantHash, gotHash, "hash should match expected value") + }) + } +} + +func TestHash_YamlNodeEquivalence_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + left *yaml.Node + right *yaml.Node + }{ + { + name: "positional metadata does not affect hash", + left: &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!str", + Value: "hello", + }, + right: &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!str", + Value: "hello", + Line: 100, + Column: 50, + }, + }, + { + name: "style does not affect hash", + left: &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!str", + Value: "test", + Style: yaml.LiteralStyle, + }, + right: &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!str", + Value: "test", + Style: yaml.DoubleQuotedStyle, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + leftHash := Hash(tt.left) + rightHash := Hash(tt.right) + assert.Equal(t, leftHash, rightHash, "equivalent nodes should have same hash") + }) + } +} diff --git a/internal/utils/slices_test.go b/internal/utils/slices_test.go new file mode 100644 index 0000000..2fd907d --- /dev/null +++ b/internal/utils/slices_test.go @@ -0,0 +1,53 @@ +package utils_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/internal/utils" + "github.com/stretchr/testify/assert" +) + +func TestMapSlice_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input []int + fn func(int) string + expected []string + }{ + { + name: "empty slice", + input: []int{}, + fn: func(i int) string { return "" }, + expected: []string{}, + }, + { + name: "single element", + input: []int{1}, + fn: func(i int) string { + if i == 1 { + return "one" + } + return "" + }, + expected: []string{"one"}, + }, + { + name: "multiple elements", + input: []int{1, 2, 3}, + fn: func(i int) string { + return string(rune('a' + i - 1)) + }, + expected: []string{"a", "b", "c"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := utils.MapSlice(tt.input, tt.fn) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/internal/utils/string_builder_test.go b/internal/utils/string_builder_test.go index b4cb1ce..1a68566 100644 --- a/internal/utils/string_builder_test.go +++ b/internal/utils/string_builder_test.go @@ -129,3 +129,47 @@ func BenchmarkStringConcatenation(b *testing.B) { _ = result } } + +func TestBuildString(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + parts []string + expected string + }{ + { + name: "empty parts", + parts: []string{}, + expected: "", + }, + { + name: "single part", + parts: []string{"hello"}, + expected: "hello", + }, + { + name: "multiple parts", + parts: []string{"hello", " ", "world"}, + expected: "hello world", + }, + { + name: "three parts with empty", + parts: []string{"a", "", "b"}, + expected: "ab", + }, + { + name: "four parts", + parts: []string{"one", "two", "three", "four"}, + expected: "onetwothreefour", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := BuildString(tt.parts...) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/internal/version/version_test.go b/internal/version/version_test.go index 00c306c..dc4eac9 100644 --- a/internal/version/version_test.go +++ b/internal/version/version_test.go @@ -228,3 +228,265 @@ func Test_Version_IsOneOf(t *testing.T) { }) } } + +func Test_Version_String_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + version Version + expected string + }{ + { + name: "standard version", + version: Version{Major: 1, Minor: 2, Patch: 3}, + expected: "1.2.3", + }, + { + name: "zero version", + version: Version{Major: 0, Minor: 0, Patch: 0}, + expected: "0.0.0", + }, + { + name: "high version numbers", + version: Version{Major: 10, Minor: 20, Patch: 30}, + expected: "10.20.30", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.version.String()) + }) + } +} + +func Test_Version_GreaterThan_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + a Version + b Version + expected bool + }{ + { + name: "same version returns false", + a: Version{Major: 1, Minor: 2, Patch: 3}, + b: Version{Major: 1, Minor: 2, Patch: 3}, + expected: false, + }, + { + name: "greater major version returns true", + a: Version{Major: 2, Minor: 0, Patch: 0}, + b: Version{Major: 1, Minor: 9, Patch: 9}, + expected: true, + }, + { + name: "lesser major version returns false", + a: Version{Major: 1, Minor: 9, Patch: 9}, + b: Version{Major: 2, Minor: 0, Patch: 0}, + expected: false, + }, + { + name: "greater minor version returns true", + a: Version{Major: 1, Minor: 3, Patch: 0}, + b: Version{Major: 1, Minor: 2, Patch: 9}, + expected: true, + }, + { + name: "lesser minor version returns false", + a: Version{Major: 1, Minor: 2, Patch: 9}, + b: Version{Major: 1, Minor: 3, Patch: 0}, + expected: false, + }, + { + name: "greater patch version returns true", + a: Version{Major: 1, Minor: 2, Patch: 4}, + b: Version{Major: 1, Minor: 2, Patch: 3}, + expected: true, + }, + { + name: "lesser patch version returns false", + a: Version{Major: 1, Minor: 2, Patch: 3}, + b: Version{Major: 1, Minor: 2, Patch: 4}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.a.GreaterThan(tt.b)) + }) + } +} + +func Test_Version_LessThan_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + a Version + b Version + expected bool + }{ + { + name: "same version returns false", + a: Version{Major: 1, Minor: 2, Patch: 3}, + b: Version{Major: 1, Minor: 2, Patch: 3}, + expected: false, + }, + { + name: "lesser version returns true", + a: Version{Major: 1, Minor: 2, Patch: 3}, + b: Version{Major: 2, Minor: 0, Patch: 0}, + expected: true, + }, + { + name: "greater version returns false", + a: Version{Major: 2, Minor: 0, Patch: 0}, + b: Version{Major: 1, Minor: 2, Patch: 3}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.a.LessThan(tt.b)) + }) + } +} + +func Test_MustParse_Success(t *testing.T) { + t.Parallel() + + v := MustParse("1.2.3") + assert.Equal(t, 1, v.Major) + assert.Equal(t, 2, v.Minor) + assert.Equal(t, 3, v.Patch) +} + +func Test_MustParse_Panic(t *testing.T) { + t.Parallel() + + assert.Panics(t, func() { + MustParse("invalid") + }) +} + +func Test_IsGreaterOrEqual_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + a string + b string + expected bool + }{ + { + name: "equal versions returns true", + a: "1.2.3", + b: "1.2.3", + expected: true, + }, + { + name: "greater version returns true", + a: "2.0.0", + b: "1.2.3", + expected: true, + }, + { + name: "lesser version returns false", + a: "1.2.3", + b: "2.0.0", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result, err := IsGreaterOrEqual(tt.a, tt.b) + require.NoError(t, err) + assert.Equal(t, tt.expected, result) + }) + } +} + +func Test_IsGreaterOrEqual_Error(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + a string + b string + }{ + { + name: "invalid first version", + a: "invalid", + b: "1.2.3", + }, + { + name: "invalid second version", + a: "1.2.3", + b: "invalid", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + _, err := IsGreaterOrEqual(tt.a, tt.b) + require.Error(t, err) + }) + } +} + +func Test_IsLessThan_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + a string + b string + expected bool + }{ + { + name: "equal versions returns false", + a: "1.2.3", + b: "1.2.3", + expected: false, + }, + { + name: "lesser version returns true", + a: "1.2.3", + b: "2.0.0", + expected: true, + }, + { + name: "greater version returns false", + a: "2.0.0", + b: "1.2.3", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result, err := IsLessThan(tt.a, tt.b) + require.NoError(t, err) + assert.Equal(t, tt.expected, result) + }) + } +} + +func Test_IsLessThan_Error(t *testing.T) { + t.Parallel() + + _, err := IsLessThan("invalid", "1.2.3") + require.Error(t, err) +} diff --git a/jsonpointer/jsonpointer_test.go b/jsonpointer/jsonpointer_test.go index 4146919..e49fd04 100644 --- a/jsonpointer/jsonpointer_test.go +++ b/jsonpointer/jsonpointer_test.go @@ -693,3 +693,118 @@ func TestEscapeString_Success(t *testing.T) { }) } } + +func TestJSONPointer_String_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pointer JSONPointer + expected string + }{ + { + name: "root pointer", + pointer: JSONPointer("/"), + expected: "/", + }, + { + name: "simple path", + pointer: JSONPointer("/some/path"), + expected: "/some/path", + }, + { + name: "empty string", + pointer: JSONPointer(""), + expected: "", + }, + { + name: "path with indices", + pointer: JSONPointer("/a/0/b"), + expected: "/a/0/b", + }, + { + name: "escaped characters", + pointer: JSONPointer("/~0/~1"), + expected: "/~0/~1", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := tt.pointer.String() + assert.Equal(t, tt.expected, result, "String() should return the pointer value") + }) + } +} + +func TestPartsToJSONPointer_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + parts []string + expected JSONPointer + }{ + { + name: "empty parts", + parts: []string{}, + expected: JSONPointer(""), + }, + { + name: "single part", + parts: []string{"a"}, + expected: JSONPointer("/a"), + }, + { + name: "multiple parts", + parts: []string{"a", "b", "c"}, + expected: JSONPointer("/a/b/c"), + }, + { + name: "parts with tilde", + parts: []string{"a~b"}, + expected: JSONPointer("/a~0b"), + }, + { + name: "parts with slash", + parts: []string{"a/b"}, + expected: JSONPointer("/a~1b"), + }, + { + name: "parts with both special chars", + parts: []string{"a~/b"}, + expected: JSONPointer("/a~0~1b"), + }, + { + name: "numeric parts", + parts: []string{"0", "1", "2"}, + expected: JSONPointer("/0/1/2"), + }, + { + name: "mixed parts", + parts: []string{"paths", "/users/{id}", "get"}, + expected: JSONPointer("/paths/~1users~1{id}/get"), + }, + { + name: "empty string part", + parts: []string{""}, + expected: JSONPointer("/"), + }, + { + name: "multiple empty parts", + parts: []string{"", ""}, + expected: JSONPointer("//"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := PartsToJSONPointer(tt.parts) + assert.Equal(t, tt.expected, result, "PartsToJSONPointer should produce correct pointer") + }) + } +} diff --git a/jsonschema/oas3/discriminator_validate_test.go b/jsonschema/oas3/discriminator_validate_test.go index 878f3fa..e8dbf2c 100644 --- a/jsonschema/oas3/discriminator_validate_test.go +++ b/jsonschema/oas3/discriminator_validate_test.go @@ -4,8 +4,11 @@ import ( "bytes" "testing" + "github.com/speakeasy-api/openapi/extensions" "github.com/speakeasy-api/openapi/jsonschema/oas3" "github.com/speakeasy-api/openapi/marshaller" + "github.com/speakeasy-api/openapi/pointer" + "github.com/speakeasy-api/openapi/sequencedmap" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -135,3 +138,256 @@ mapping: }) } } + +func TestDiscriminator_GetPropertyName_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + discriminator *oas3.Discriminator + expected string + }{ + { + name: "nil discriminator returns empty", + discriminator: nil, + expected: "", + }, + { + name: "empty discriminator returns empty", + discriminator: &oas3.Discriminator{}, + expected: "", + }, + { + name: "returns property name", + discriminator: &oas3.Discriminator{PropertyName: "petType"}, + expected: "petType", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.discriminator.GetPropertyName()) + }) + } +} + +func TestDiscriminator_GetMapping_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + discriminator *oas3.Discriminator + expectNil bool + }{ + { + name: "nil discriminator returns nil", + discriminator: nil, + expectNil: true, + }, + { + name: "nil mapping returns nil", + discriminator: &oas3.Discriminator{}, + expectNil: true, + }, + { + name: "returns mapping", + discriminator: &oas3.Discriminator{ + Mapping: sequencedmap.New[string, string](), + }, + expectNil: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.discriminator.GetMapping() + if tt.expectNil { + assert.Nil(t, result) + } else { + assert.NotNil(t, result) + } + }) + } +} + +func TestDiscriminator_GetDefaultMapping_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + discriminator *oas3.Discriminator + expected string + }{ + { + name: "nil discriminator returns empty", + discriminator: nil, + expected: "", + }, + { + name: "nil default mapping returns empty", + discriminator: &oas3.Discriminator{}, + expected: "", + }, + { + name: "returns default mapping", + discriminator: &oas3.Discriminator{ + DefaultMapping: pointer.From("#/components/schemas/Default"), + }, + expected: "#/components/schemas/Default", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.discriminator.GetDefaultMapping()) + }) + } +} + +func TestDiscriminator_GetExtensions_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + discriminator *oas3.Discriminator + expectEmpty bool + }{ + { + name: "nil discriminator returns empty extensions", + discriminator: nil, + expectEmpty: true, + }, + { + name: "nil extensions returns empty extensions", + discriminator: &oas3.Discriminator{}, + expectEmpty: true, + }, + { + name: "returns extensions", + discriminator: &oas3.Discriminator{ + Extensions: extensions.New(), + }, + expectEmpty: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.discriminator.GetExtensions() + assert.NotNil(t, result) + if tt.expectEmpty { + assert.Equal(t, 0, result.Len()) + } + }) + } +} + +func TestDiscriminator_IsEqual_Success(t *testing.T) { + t.Parallel() + + mapping := sequencedmap.New[string, string]() + mapping.Set("dog", "#/components/schemas/Dog") + + tests := []struct { + name string + a *oas3.Discriminator + b *oas3.Discriminator + expected bool + }{ + { + name: "both nil returns true", + a: nil, + b: nil, + expected: true, + }, + { + name: "a nil b not nil returns false", + a: nil, + b: &oas3.Discriminator{PropertyName: "type"}, + expected: false, + }, + { + name: "a not nil b nil returns false", + a: &oas3.Discriminator{PropertyName: "type"}, + b: nil, + expected: false, + }, + { + name: "equal property names returns true", + a: &oas3.Discriminator{PropertyName: "type"}, + b: &oas3.Discriminator{PropertyName: "type"}, + expected: true, + }, + { + name: "different property names returns false", + a: &oas3.Discriminator{PropertyName: "type1"}, + b: &oas3.Discriminator{PropertyName: "type2"}, + expected: false, + }, + { + name: "both nil default mapping returns true", + a: &oas3.Discriminator{PropertyName: "type"}, + b: &oas3.Discriminator{PropertyName: "type"}, + expected: true, + }, + { + name: "one nil default mapping returns false", + a: &oas3.Discriminator{PropertyName: "type", DefaultMapping: pointer.From("default")}, + b: &oas3.Discriminator{PropertyName: "type"}, + expected: false, + }, + { + name: "different default mapping returns false", + a: &oas3.Discriminator{PropertyName: "type", DefaultMapping: pointer.From("default1")}, + b: &oas3.Discriminator{PropertyName: "type", DefaultMapping: pointer.From("default2")}, + expected: false, + }, + { + name: "equal default mapping returns true", + a: &oas3.Discriminator{PropertyName: "type", DefaultMapping: pointer.From("default")}, + b: &oas3.Discriminator{PropertyName: "type", DefaultMapping: pointer.From("default")}, + expected: true, + }, + { + name: "both nil mapping returns true", + a: &oas3.Discriminator{PropertyName: "type"}, + b: &oas3.Discriminator{PropertyName: "type"}, + expected: true, + }, + { + name: "one nil mapping returns false", + a: &oas3.Discriminator{PropertyName: "type", Mapping: mapping}, + b: &oas3.Discriminator{PropertyName: "type"}, + expected: false, + }, + { + name: "equal mapping returns true", + a: &oas3.Discriminator{PropertyName: "type", Mapping: mapping}, + b: &oas3.Discriminator{PropertyName: "type", Mapping: mapping}, + expected: true, + }, + { + name: "both nil extensions returns true", + a: &oas3.Discriminator{PropertyName: "type"}, + b: &oas3.Discriminator{PropertyName: "type"}, + expected: true, + }, + { + name: "one nil extensions returns false", + a: &oas3.Discriminator{PropertyName: "type", Extensions: extensions.New()}, + b: &oas3.Discriminator{PropertyName: "type"}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.a.IsEqual(tt.b)) + }) + } +} diff --git a/jsonschema/oas3/discriminator_version_test.go b/jsonschema/oas3/discriminator_version_test.go index 3a160ab..21737e0 100644 --- a/jsonschema/oas3/discriminator_version_test.go +++ b/jsonschema/oas3/discriminator_version_test.go @@ -180,16 +180,17 @@ mapping: dog: "#/components/schemas/Dog" ` - var discriminator oas3.Discriminator - validationErrs, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &discriminator) - require.NoError(t, err) - require.Empty(t, validationErrs) - // Test with both 3.1 and 3.2 versions for _, version := range []string{"3.1.0", "3.2.0"} { t.Run("version_"+version, func(t *testing.T) { t.Parallel() + // Each parallel test needs its own discriminator instance to avoid data races + var discriminator oas3.Discriminator + validationErrs, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &discriminator) + require.NoError(t, err) + require.Empty(t, validationErrs) + opts := []validation.Option{ validation.WithContextObject(&oas3.ParentDocumentVersion{ OpenAPI: pointer.From(version), diff --git a/jsonschema/oas3/externaldoc_validate_test.go b/jsonschema/oas3/externaldoc_validate_test.go index 5993b32..bdecc95 100644 --- a/jsonschema/oas3/externaldoc_validate_test.go +++ b/jsonschema/oas3/externaldoc_validate_test.go @@ -5,8 +5,11 @@ import ( "strings" "testing" + "github.com/speakeasy-api/openapi/extensions" "github.com/speakeasy-api/openapi/jsonschema/oas3" "github.com/speakeasy-api/openapi/marshaller" + "github.com/speakeasy-api/openapi/pointer" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -149,3 +152,180 @@ url: ":invalid url" }) } } + +func TestExternalDocumentation_GetDescription_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + extDoc *oas3.ExternalDocumentation + expected string + }{ + { + name: "nil extDoc returns empty", + extDoc: nil, + expected: "", + }, + { + name: "nil description returns empty", + extDoc: &oas3.ExternalDocumentation{}, + expected: "", + }, + { + name: "returns description", + extDoc: &oas3.ExternalDocumentation{Description: pointer.From("Test docs")}, + expected: "Test docs", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.extDoc.GetDescription()) + }) + } +} + +func TestExternalDocumentation_GetURL_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + extDoc *oas3.ExternalDocumentation + expected string + }{ + { + name: "nil extDoc returns empty", + extDoc: nil, + expected: "", + }, + { + name: "returns URL", + extDoc: &oas3.ExternalDocumentation{URL: "https://example.com"}, + expected: "https://example.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.extDoc.GetURL()) + }) + } +} + +func TestExternalDocumentation_GetExtensions_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + extDoc *oas3.ExternalDocumentation + expectEmpty bool + }{ + { + name: "nil extDoc returns empty extensions", + extDoc: nil, + expectEmpty: true, + }, + { + name: "nil extensions returns empty extensions", + extDoc: &oas3.ExternalDocumentation{}, + expectEmpty: true, + }, + { + name: "returns extensions", + extDoc: &oas3.ExternalDocumentation{Extensions: extensions.New()}, + expectEmpty: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.extDoc.GetExtensions() + assert.NotNil(t, result) + if tt.expectEmpty { + assert.Equal(t, 0, result.Len()) + } + }) + } +} + +func TestExternalDocumentation_IsEqual_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + a *oas3.ExternalDocumentation + b *oas3.ExternalDocumentation + expected bool + }{ + { + name: "both nil returns true", + a: nil, + b: nil, + expected: true, + }, + { + name: "a nil b not nil returns false", + a: nil, + b: &oas3.ExternalDocumentation{URL: "https://example.com"}, + expected: false, + }, + { + name: "a not nil b nil returns false", + a: &oas3.ExternalDocumentation{URL: "https://example.com"}, + b: nil, + expected: false, + }, + { + name: "equal URL returns true", + a: &oas3.ExternalDocumentation{URL: "https://example.com"}, + b: &oas3.ExternalDocumentation{URL: "https://example.com"}, + expected: true, + }, + { + name: "different URL returns false", + a: &oas3.ExternalDocumentation{URL: "https://example.com"}, + b: &oas3.ExternalDocumentation{URL: "https://other.com"}, + expected: false, + }, + { + name: "equal description returns true", + a: &oas3.ExternalDocumentation{URL: "https://example.com", Description: pointer.From("desc")}, + b: &oas3.ExternalDocumentation{URL: "https://example.com", Description: pointer.From("desc")}, + expected: true, + }, + { + name: "different description returns false", + a: &oas3.ExternalDocumentation{URL: "https://example.com", Description: pointer.From("desc1")}, + b: &oas3.ExternalDocumentation{URL: "https://example.com", Description: pointer.From("desc2")}, + expected: false, + }, + { + name: "one nil description returns false", + a: &oas3.ExternalDocumentation{URL: "https://example.com", Description: pointer.From("desc")}, + b: &oas3.ExternalDocumentation{URL: "https://example.com"}, + expected: false, + }, + { + name: "both nil extensions returns true", + a: &oas3.ExternalDocumentation{URL: "https://example.com"}, + b: &oas3.ExternalDocumentation{URL: "https://example.com"}, + expected: true, + }, + { + name: "a nil extensions b not nil returns false", + a: &oas3.ExternalDocumentation{URL: "https://example.com"}, + b: &oas3.ExternalDocumentation{URL: "https://example.com", Extensions: extensions.New()}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.a.IsEqual(tt.b)) + }) + } +} diff --git a/jsonschema/oas3/jsonschema_nil_test.go b/jsonschema/oas3/jsonschema_nil_test.go new file mode 100644 index 0000000..d39a132 --- /dev/null +++ b/jsonschema/oas3/jsonschema_nil_test.go @@ -0,0 +1,193 @@ +package oas3_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/jsonschema/oas3" + "github.com/speakeasy-api/openapi/pointer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestJSONSchema_IsSchema_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + assert.False(t, js.IsSchema(), "nil JSONSchema should return false for IsSchema") +} + +func TestJSONSchema_GetSchema_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + assert.Nil(t, js.GetSchema(), "nil JSONSchema should return nil for GetSchema") +} + +func TestJSONSchema_IsBool_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + assert.False(t, js.IsBool(), "nil JSONSchema should return false for IsBool") +} + +func TestJSONSchema_GetBool_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + assert.Nil(t, js.GetBool(), "nil JSONSchema should return nil for GetBool") +} + +func TestJSONSchema_GetParent_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + assert.Nil(t, js.GetParent(), "nil JSONSchema should return nil for GetParent") +} + +func TestJSONSchema_GetTopLevelParent_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + assert.Nil(t, js.GetTopLevelParent(), "nil JSONSchema should return nil for GetTopLevelParent") +} + +func TestJSONSchema_SetParent_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + // Should not panic + js.SetParent(nil) +} + +func TestJSONSchema_SetTopLevelParent_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + // Should not panic + js.SetTopLevelParent(nil) +} + +func TestJSONSchema_IsEqual_BothNil(t *testing.T) { + t.Parallel() + + var js1 *oas3.JSONSchema[oas3.Referenceable] + var js2 *oas3.JSONSchema[oas3.Referenceable] + assert.True(t, js1.IsEqual(js2), "two nil JSONSchemas should be equal") +} + +func TestJSONSchema_IsEqual_OneNil(t *testing.T) { + t.Parallel() + + js1 := oas3.NewJSONSchemaFromBool(true) + var js2 *oas3.JSONSchema[oas3.Referenceable] + assert.False(t, js1.IsEqual(js2), "nil and non-nil JSONSchemas should not be equal") + assert.False(t, js2.IsEqual(js1), "nil and non-nil JSONSchemas should not be equal") +} + +func TestJSONSchema_Validate_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + errs := js.Validate(t.Context()) + assert.Empty(t, errs, "nil JSONSchema should return empty errors for Validate") +} + +func TestJSONSchema_ShallowCopy_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + schemaCopy := js.ShallowCopy() + assert.Nil(t, schemaCopy, "nil JSONSchema should return nil for ShallowCopy") +} + +func TestJSONSchema_IsResolved_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + assert.False(t, js.IsResolved(), "nil JSONSchema should return false for IsResolved") +} + +func TestJSONSchema_IsReference_Success(t *testing.T) { + t.Parallel() + + // Non-reference schema + js := oas3.NewJSONSchemaFromBool(true) + assert.False(t, js.IsReference(), "boolean JSONSchema should not be a reference") + + // Schema with no ref + schema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{ + Title: pointer.From("test"), + }) + assert.False(t, schema.IsReference(), "schema without ref should not be a reference") +} + +func TestJSONSchema_GetExtensions_NilConcrete(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Concrete] + exts := js.GetExtensions() + require.NotNil(t, exts, "nil JSONSchema should return empty extensions") +} + +func TestJSONSchema_GetReference_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + ref := js.GetReference() + assert.Empty(t, ref, "nil JSONSchema should return empty for GetReference") +} + +func TestJSONSchema_GetRef_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + ref := js.GetRef() + assert.Empty(t, ref, "nil JSONSchema should return empty for GetRef") +} + +func TestJSONSchema_GetResolvedObject_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + resolved := js.GetResolvedObject() + assert.Nil(t, resolved, "nil JSONSchema should return nil for GetResolvedObject") +} + +func TestJSONSchema_MustGetResolvedSchema_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + resolved := js.MustGetResolvedSchema() + assert.Nil(t, resolved, "nil JSONSchema should return nil for MustGetResolvedSchema") +} + +func TestJSONSchema_GetAbsRef_Nil(t *testing.T) { + t.Parallel() + + var js *oas3.JSONSchema[oas3.Referenceable] + ref := js.GetAbsRef() + assert.Empty(t, ref, "nil JSONSchema should return empty for GetAbsRef") +} + +func TestValidate_NilSchema_Success(t *testing.T) { + t.Parallel() + + // Test with nil Referenceable schema + var jsReferenceable *oas3.JSONSchema[oas3.Referenceable] + errs := oas3.Validate(t.Context(), jsReferenceable) + assert.Empty(t, errs, "Validate on nil Referenceable schema should return empty errors") + + // Test with nil Concrete schema + var jsConcrete *oas3.JSONSchema[oas3.Concrete] + errs = oas3.Validate(t.Context(), jsConcrete) + assert.Empty(t, errs, "Validate on nil Concrete schema should return empty errors") +} + +func TestValidate_BoolSchema_Success(t *testing.T) { + t.Parallel() + + // Test with bool schema (not a schema, so returns nil) + js := oas3.NewJSONSchemaFromBool(true) + errs := oas3.Validate(t.Context(), js) + assert.Empty(t, errs, "Validate on bool schema should return empty errors") +} diff --git a/jsonschema/oas3/jsonschema_validate_test.go b/jsonschema/oas3/jsonschema_validate_test.go index 66e1bfe..15b6f97 100644 --- a/jsonschema/oas3/jsonschema_validate_test.go +++ b/jsonschema/oas3/jsonschema_validate_test.go @@ -85,3 +85,36 @@ type: invalid_type }) } } + +func TestJSONSchemaConcrete_GetExtensions_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + schema *oas3.JSONSchema[oas3.Concrete] + expectEmpty bool + }{ + { + name: "nil schema returns empty extensions", + schema: nil, + expectEmpty: true, + }, + { + name: "bool schema returns empty extensions", + schema: oas3.ReferenceableToConcrete(oas3.NewJSONSchemaFromBool(true)), + expectEmpty: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := tt.schema.GetExtensions() + require.NotNil(t, result) + if tt.expectEmpty { + assert.Equal(t, 0, result.Len()) + } + }) + } +} diff --git a/jsonschema/oas3/schema_getters_test.go b/jsonschema/oas3/schema_getters_test.go new file mode 100644 index 0000000..7f0af7a --- /dev/null +++ b/jsonschema/oas3/schema_getters_test.go @@ -0,0 +1,797 @@ +package oas3_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/extensions" + "github.com/speakeasy-api/openapi/jsonschema/oas3" + "github.com/speakeasy-api/openapi/pointer" + "github.com/speakeasy-api/openapi/references" + "github.com/speakeasy-api/openapi/sequencedmap" + "github.com/speakeasy-api/openapi/values" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" +) + +func TestSchema_GetExclusiveMaximum_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + schema *oas3.Schema + expected oas3.ExclusiveMaximum + }{ + { + name: "nil schema returns nil", + schema: nil, + expected: nil, + }, + { + name: "schema with no exclusive maximum returns nil", + schema: &oas3.Schema{}, + expected: nil, + }, + { + name: "schema with exclusive maximum returns value", + schema: &oas3.Schema{ + ExclusiveMaximum: oas3.NewExclusiveMaximumFromFloat64(100.0), + }, + expected: oas3.NewExclusiveMaximumFromFloat64(100.0), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := tt.schema.GetExclusiveMaximum() + if tt.expected == nil { + assert.Nil(t, result) + } else { + assert.NotNil(t, result) + assert.True(t, result.IsEqual(tt.expected)) + } + }) + } +} + +func TestSchema_GetExclusiveMinimum_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + schema *oas3.Schema + expected oas3.ExclusiveMinimum + }{ + { + name: "nil schema returns nil", + schema: nil, + expected: nil, + }, + { + name: "schema with no exclusive minimum returns nil", + schema: &oas3.Schema{}, + expected: nil, + }, + { + name: "schema with exclusive minimum returns value", + schema: &oas3.Schema{ + ExclusiveMinimum: oas3.NewExclusiveMinimumFromFloat64(0.0), + }, + expected: oas3.NewExclusiveMinimumFromFloat64(0.0), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := tt.schema.GetExclusiveMinimum() + if tt.expected == nil { + assert.Nil(t, result) + } else { + assert.NotNil(t, result) + assert.True(t, result.IsEqual(tt.expected)) + } + }) + } +} + +func TestSchema_GetDiscriminator_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + schema *oas3.Schema + expected *oas3.Discriminator + }{ + { + name: "nil schema returns nil", + schema: nil, + expected: nil, + }, + { + name: "schema with no discriminator returns nil", + schema: &oas3.Schema{}, + expected: nil, + }, + { + name: "schema with discriminator returns value", + schema: &oas3.Schema{ + Discriminator: &oas3.Discriminator{PropertyName: "type"}, + }, + expected: &oas3.Discriminator{PropertyName: "type"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := tt.schema.GetDiscriminator() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestSchema_GetExamples_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + schema *oas3.Schema + expected []values.Value + }{ + { + name: "nil schema returns nil", + schema: nil, + expected: nil, + }, + { + name: "schema with no examples returns nil", + schema: &oas3.Schema{}, + expected: nil, + }, + { + name: "schema with examples returns values", + schema: &oas3.Schema{ + Examples: []values.Value{ + &yaml.Node{Kind: yaml.ScalarNode, Value: "example1"}, + }, + }, + expected: []values.Value{ + &yaml.Node{Kind: yaml.ScalarNode, Value: "example1"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := tt.schema.GetExamples() + assert.Len(t, result, len(tt.expected)) + }) + } +} + +func TestSchema_GetPrefixItems_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetPrefixItems()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetPrefixItems()) + + schema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("test")}) + schemaWithPrefixItems := &oas3.Schema{ + PrefixItems: []*oas3.JSONSchema[oas3.Referenceable]{schema}, + } + result := schemaWithPrefixItems.GetPrefixItems() + assert.NotNil(t, result) + assert.Len(t, result, 1) +} + +func TestSchema_GetContains_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetContains()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetContains()) + + childSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("contained")}) + schemaWithContains := &oas3.Schema{Contains: childSchema} + assert.Equal(t, childSchema, schemaWithContains.GetContains()) +} + +func TestSchema_GetMinContains_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetMinContains()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetMinContains()) + + minContains := int64(1) + schemaWithMinContains := &oas3.Schema{MinContains: &minContains} + assert.Equal(t, &minContains, schemaWithMinContains.GetMinContains()) +} + +func TestSchema_GetMaxContains_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetMaxContains()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetMaxContains()) + + maxContains := int64(10) + schemaWithMaxContains := &oas3.Schema{MaxContains: &maxContains} + assert.Equal(t, &maxContains, schemaWithMaxContains.GetMaxContains()) +} + +func TestSchema_GetIf_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetIf()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetIf()) + + ifSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("if")}) + schemaWithIf := &oas3.Schema{If: ifSchema} + assert.Equal(t, ifSchema, schemaWithIf.GetIf()) +} + +func TestSchema_GetElse_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetElse()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetElse()) + + elseSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("else")}) + schemaWithElse := &oas3.Schema{Else: elseSchema} + assert.Equal(t, elseSchema, schemaWithElse.GetElse()) +} + +func TestSchema_GetThen_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetThen()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetThen()) + + thenSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("then")}) + schemaWithThen := &oas3.Schema{Then: thenSchema} + assert.Equal(t, thenSchema, schemaWithThen.GetThen()) +} + +func TestSchema_GetDependentSchemas_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetDependentSchemas()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetDependentSchemas()) + + depSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("dependent")}) + depSchemas := sequencedmap.New(sequencedmap.NewElem("dep1", depSchema)) + schemaWithDeps := &oas3.Schema{DependentSchemas: depSchemas} + assert.Equal(t, depSchemas, schemaWithDeps.GetDependentSchemas()) +} + +func TestSchema_GetPatternProperties_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetPatternProperties()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetPatternProperties()) + + patternSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("pattern")}) + patternProps := sequencedmap.New(sequencedmap.NewElem("^[a-z]+$", patternSchema)) + schemaWithPatterns := &oas3.Schema{PatternProperties: patternProps} + assert.Equal(t, patternProps, schemaWithPatterns.GetPatternProperties()) +} + +func TestSchema_GetPropertyNames_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetPropertyNames()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetPropertyNames()) + + propNamesSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Pattern: pointer.From("^[a-z]+$")}) + schemaWithPropNames := &oas3.Schema{PropertyNames: propNamesSchema} + assert.Equal(t, propNamesSchema, schemaWithPropNames.GetPropertyNames()) +} + +func TestSchema_GetUnevaluatedItems_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetUnevaluatedItems()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetUnevaluatedItems()) + + unevalItemsSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("unevaluated")}) + schemaWithUnevalItems := &oas3.Schema{UnevaluatedItems: unevalItemsSchema} + assert.Equal(t, unevalItemsSchema, schemaWithUnevalItems.GetUnevaluatedItems()) +} + +func TestSchema_GetUnevaluatedProperties_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetUnevaluatedProperties()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetUnevaluatedProperties()) + + unevalPropsSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("unevaluated")}) + schemaWithUnevalProps := &oas3.Schema{UnevaluatedProperties: unevalPropsSchema} + assert.Equal(t, unevalPropsSchema, schemaWithUnevalProps.GetUnevaluatedProperties()) +} + +func TestSchema_GetItems_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetItems()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetItems()) + + itemsSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Type: oas3.NewTypeFromString("string")}) + schemaWithItems := &oas3.Schema{Items: itemsSchema} + assert.Equal(t, itemsSchema, schemaWithItems.GetItems()) +} + +func TestSchema_GetAnchor_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Empty(t, nilSchema.GetAnchor()) + + emptySchema := &oas3.Schema{} + assert.Empty(t, emptySchema.GetAnchor()) + + anchor := "myAnchor" + schemaWithAnchor := &oas3.Schema{Anchor: &anchor} + assert.Equal(t, anchor, schemaWithAnchor.GetAnchor()) +} + +func TestSchema_GetMultipleOf_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetMultipleOf()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetMultipleOf()) + + multipleOf := 5.0 + schemaWithMultipleOf := &oas3.Schema{MultipleOf: &multipleOf} + assert.Equal(t, &multipleOf, schemaWithMultipleOf.GetMultipleOf()) +} + +func TestSchema_GetAdditionalProperties_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetAdditionalProperties()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetAdditionalProperties()) + + additionalPropsSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Type: oas3.NewTypeFromString("string")}) + schemaWithAdditionalProps := &oas3.Schema{AdditionalProperties: additionalPropsSchema} + assert.Equal(t, additionalPropsSchema, schemaWithAdditionalProps.GetAdditionalProperties()) +} + +func TestSchema_GetDefault_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetDefault()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetDefault()) + + defaultValue := &yaml.Node{Kind: yaml.ScalarNode, Value: "default"} + schemaWithDefault := &oas3.Schema{Default: defaultValue} + assert.Equal(t, defaultValue, schemaWithDefault.GetDefault()) +} + +func TestSchema_GetConst_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetConst()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetConst()) + + constValue := &yaml.Node{Kind: yaml.ScalarNode, Value: "constant"} + schemaWithConst := &oas3.Schema{Const: constValue} + assert.Equal(t, constValue, schemaWithConst.GetConst()) +} + +func TestSchema_GetExternalDocs_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetExternalDocs()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetExternalDocs()) + + externalDocs := &oas3.ExternalDocumentation{URL: "https://example.com"} + schemaWithExternalDocs := &oas3.Schema{ExternalDocs: externalDocs} + assert.Equal(t, externalDocs, schemaWithExternalDocs.GetExternalDocs()) +} + +func TestSchema_GetExample_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetExample()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetExample()) + + exampleValue := &yaml.Node{Kind: yaml.ScalarNode, Value: "example"} + schemaWithExample := &oas3.Schema{Example: exampleValue} + assert.Equal(t, exampleValue, schemaWithExample.GetExample()) +} + +func TestSchema_GetSchema_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Empty(t, nilSchema.GetSchema()) + + emptySchema := &oas3.Schema{} + assert.Empty(t, emptySchema.GetSchema()) + + schemaURI := "https://json-schema.org/draft/2020-12/schema" + schemaWithSchemaURI := &oas3.Schema{Schema: &schemaURI} + assert.Equal(t, schemaURI, schemaWithSchemaURI.GetSchema()) +} + +func TestSchema_GetXML_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetXML()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetXML()) + + xmlMetadata := &oas3.XML{Name: pointer.From("MyElement")} + schemaWithXML := &oas3.Schema{XML: xmlMetadata} + assert.Equal(t, xmlMetadata, schemaWithXML.GetXML()) +} + +func TestJSONSchema_NewJSONSchemaFromReference_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/components/schemas/User") + schema := oas3.NewJSONSchemaFromReference(ref) + + assert.NotNil(t, schema) + assert.True(t, schema.IsReference()) + concreteSchema := schema.GetSchema() + assert.NotNil(t, concreteSchema) + assert.Equal(t, ref, concreteSchema.GetRef()) +} + +func TestJSONSchema_NewJSONSchemaFromBool_Success(t *testing.T) { + t.Parallel() + + trueSchema := oas3.NewJSONSchemaFromBool(true) + assert.NotNil(t, trueSchema) + assert.True(t, trueSchema.IsBool()) + assert.True(t, *trueSchema.GetBool()) + + falseSchema := oas3.NewJSONSchemaFromBool(false) + assert.NotNil(t, falseSchema) + assert.True(t, falseSchema.IsBool()) + assert.False(t, *falseSchema.GetBool()) +} + +func TestJSONSchema_GetExtensions_Success(t *testing.T) { + t.Parallel() + + schema := &oas3.Schema{} + // Should return empty extensions if not set + exts := schema.GetExtensions() + assert.NotNil(t, exts) + assert.Equal(t, 0, exts.Len()) + + // With extensions set + extensions := extensions.New() + extensions.Set("x-custom", &yaml.Node{Kind: yaml.ScalarNode, Value: "value"}) + schemaWithExts := &oas3.Schema{Extensions: extensions} + result := schemaWithExts.GetExtensions() + assert.Equal(t, extensions, result) +} + +func TestJSONSchema_GetReference_Success(t *testing.T) { + t.Parallel() + + // Schema without reference + schema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("Test")}) + ref := schema.GetReference() + assert.Equal(t, references.Reference(""), ref) + + // Schema with reference + refSchema := oas3.NewJSONSchemaFromReference("#/components/schemas/User") + ref = refSchema.GetReference() + assert.Equal(t, references.Reference("#/components/schemas/User"), ref) +} + +func TestJSONSchema_GetResolvedObject_Success(t *testing.T) { + t.Parallel() + + // Non-reference schema - GetResolvedObject converts to Concrete type + schema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("Test")}) + resolved := schema.GetResolvedObject() + assert.NotNil(t, resolved) + assert.Equal(t, "Test", resolved.GetSchema().GetTitle()) +} + +func TestJSONSchema_GetReferenceResolutionInfo_Success(t *testing.T) { + t.Parallel() + + // Non-reference schema returns nil info (it's not a reference) + schema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("Test")}) + info := schema.GetReferenceResolutionInfo() + // For non-reference schemas, this returns nil + assert.Nil(t, info) +} + +func TestSchema_GetAllOf_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetAllOf()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetAllOf()) + + childSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("child")}) + schemaWithAllOf := &oas3.Schema{AllOf: []*oas3.JSONSchema[oas3.Referenceable]{childSchema}} + result := schemaWithAllOf.GetAllOf() + assert.NotNil(t, result) + assert.Len(t, result, 1) +} + +func TestSchema_GetOneOf_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetOneOf()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetOneOf()) + + childSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("child")}) + schemaWithOneOf := &oas3.Schema{OneOf: []*oas3.JSONSchema[oas3.Referenceable]{childSchema}} + result := schemaWithOneOf.GetOneOf() + assert.NotNil(t, result) + assert.Len(t, result, 1) +} + +func TestSchema_GetAnyOf_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetAnyOf()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetAnyOf()) + + childSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("child")}) + schemaWithAnyOf := &oas3.Schema{AnyOf: []*oas3.JSONSchema[oas3.Referenceable]{childSchema}} + result := schemaWithAnyOf.GetAnyOf() + assert.NotNil(t, result) + assert.Len(t, result, 1) +} + +func TestSchema_GetNot_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetNot()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetNot()) + + notSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("not")}) + schemaWithNot := &oas3.Schema{Not: notSchema} + assert.Equal(t, notSchema, schemaWithNot.GetNot()) +} + +func TestSchema_GetProperties_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetProperties()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetProperties()) + + propSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("prop")}) + props := sequencedmap.New(sequencedmap.NewElem("name", propSchema)) + schemaWithProps := &oas3.Schema{Properties: props} + assert.Equal(t, props, schemaWithProps.GetProperties()) +} + +func TestSchema_GetDefs_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetDefs()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetDefs()) + + defSchema := oas3.NewJSONSchemaFromSchema[oas3.Referenceable](&oas3.Schema{Title: pointer.From("def")}) + defs := sequencedmap.New(sequencedmap.NewElem("MyDef", defSchema)) + schemaWithDefs := &oas3.Schema{Defs: defs} + assert.Equal(t, defs, schemaWithDefs.GetDefs()) +} + +func TestSchema_GetTitle_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Empty(t, nilSchema.GetTitle()) + + emptySchema := &oas3.Schema{} + assert.Empty(t, emptySchema.GetTitle()) + + schemaWithTitle := &oas3.Schema{Title: pointer.From("My Title")} + assert.Equal(t, "My Title", schemaWithTitle.GetTitle()) +} + +func TestSchema_GetMaximum_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetMaximum()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetMaximum()) + + maximum := 100.0 + schemaWithMaximum := &oas3.Schema{Maximum: &maximum} + assert.Equal(t, &maximum, schemaWithMaximum.GetMaximum()) +} + +func TestSchema_GetMinimum_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetMinimum()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetMinimum()) + + minimum := 0.0 + schemaWithMinimum := &oas3.Schema{Minimum: &minimum} + assert.Equal(t, &minimum, schemaWithMinimum.GetMinimum()) +} + +func TestSchema_GetMaxLength_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetMaxLength()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetMaxLength()) + + maxLength := int64(100) + schemaWithMaxLength := &oas3.Schema{MaxLength: &maxLength} + assert.Equal(t, &maxLength, schemaWithMaxLength.GetMaxLength()) +} + +func TestSchema_GetMinLength_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetMinLength()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetMinLength()) + + minLength := int64(1) + schemaWithMinLength := &oas3.Schema{MinLength: &minLength} + assert.Equal(t, &minLength, schemaWithMinLength.GetMinLength()) +} + +func TestSchema_GetPattern_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Empty(t, nilSchema.GetPattern()) + + emptySchema := &oas3.Schema{} + assert.Empty(t, emptySchema.GetPattern()) + + schemaWithPattern := &oas3.Schema{Pattern: pointer.From("^[a-z]+$")} + assert.Equal(t, "^[a-z]+$", schemaWithPattern.GetPattern()) +} + +func TestSchema_GetFormat_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Empty(t, nilSchema.GetFormat()) + + emptySchema := &oas3.Schema{} + assert.Empty(t, emptySchema.GetFormat()) + + schemaWithFormat := &oas3.Schema{Format: pointer.From("date-time")} + assert.Equal(t, "date-time", schemaWithFormat.GetFormat()) +} + +func TestSchema_GetMaxItems_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetMaxItems()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetMaxItems()) + + maxItems := int64(10) + schemaWithMaxItems := &oas3.Schema{MaxItems: &maxItems} + assert.Equal(t, &maxItems, schemaWithMaxItems.GetMaxItems()) +} + +func TestSchema_GetUniqueItems_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.False(t, nilSchema.GetUniqueItems()) + + emptySchema := &oas3.Schema{} + assert.False(t, emptySchema.GetUniqueItems()) + + uniqueItems := true + schemaWithUniqueItems := &oas3.Schema{UniqueItems: &uniqueItems} + assert.True(t, schemaWithUniqueItems.GetUniqueItems()) +} + +func TestSchema_GetMaxProperties_Success(t *testing.T) { + t.Parallel() + + nilSchema := (*oas3.Schema)(nil) + assert.Nil(t, nilSchema.GetMaxProperties()) + + emptySchema := &oas3.Schema{} + assert.Nil(t, emptySchema.GetMaxProperties()) + + maxProps := int64(5) + schemaWithMaxProps := &oas3.Schema{MaxProperties: &maxProps} + assert.Equal(t, &maxProps, schemaWithMaxProps.GetMaxProperties()) +} diff --git a/jsonschema/oas3/value_test.go b/jsonschema/oas3/value_test.go new file mode 100644 index 0000000..46d2663 --- /dev/null +++ b/jsonschema/oas3/value_test.go @@ -0,0 +1,245 @@ +package oas3 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewExclusiveMaximumFromBool_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + value bool + expected bool + }{ + { + name: "true value", + value: true, + expected: true, + }, + { + name: "false value", + value: false, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := NewExclusiveMaximumFromBool(tt.value) + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsLeft(), "should be left value (bool)") + assert.Equal(t, tt.expected, *result.Left, "left value should match input") + }) + } +} + +func TestNewExclusiveMaximumFromFloat64_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + value float64 + expected float64 + }{ + { + name: "zero value", + value: 0.0, + expected: 0.0, + }, + { + name: "positive value", + value: 100.5, + expected: 100.5, + }, + { + name: "negative value", + value: -50.25, + expected: -50.25, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := NewExclusiveMaximumFromFloat64(tt.value) + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsRight(), "should be right value (float64)") + assert.InDelta(t, tt.expected, *result.Right, 0.0001, "right value should match input") + }) + } +} + +func TestNewExclusiveMinimumFromBool_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + value bool + expected bool + }{ + { + name: "true value", + value: true, + expected: true, + }, + { + name: "false value", + value: false, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := NewExclusiveMinimumFromBool(tt.value) + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsLeft(), "should be left value (bool)") + assert.Equal(t, tt.expected, *result.Left, "left value should match input") + }) + } +} + +func TestNewExclusiveMinimumFromFloat64_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + value float64 + expected float64 + }{ + { + name: "zero value", + value: 0.0, + expected: 0.0, + }, + { + name: "positive value", + value: 100.5, + expected: 100.5, + }, + { + name: "negative value", + value: -50.25, + expected: -50.25, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := NewExclusiveMinimumFromFloat64(tt.value) + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsRight(), "should be right value (float64)") + assert.InDelta(t, tt.expected, *result.Right, 0.0001, "right value should match input") + }) + } +} + +func TestNewTypeFromArray_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + value []SchemaType + expected []SchemaType + }{ + { + name: "empty array", + value: []SchemaType{}, + expected: []SchemaType{}, + }, + { + name: "single type", + value: []SchemaType{SchemaTypeString}, + expected: []SchemaType{SchemaTypeString}, + }, + { + name: "multiple types", + value: []SchemaType{SchemaTypeString, SchemaTypeNumber}, + expected: []SchemaType{SchemaTypeString, SchemaTypeNumber}, + }, + { + name: "all types", + value: []SchemaType{SchemaTypeObject, SchemaTypeArray, SchemaTypeString, SchemaTypeNumber, SchemaTypeInteger, SchemaTypeBoolean, SchemaTypeNull}, + expected: []SchemaType{SchemaTypeObject, SchemaTypeArray, SchemaTypeString, SchemaTypeNumber, SchemaTypeInteger, SchemaTypeBoolean, SchemaTypeNull}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := NewTypeFromArray(tt.value) + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsLeft(), "should be left value (array)") + assert.Equal(t, tt.expected, *result.Left, "left value should match input") + }) + } +} + +func TestNewTypeFromString_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + value SchemaType + expected SchemaType + }{ + { + name: "string type", + value: SchemaTypeString, + expected: SchemaTypeString, + }, + { + name: "number type", + value: SchemaTypeNumber, + expected: SchemaTypeNumber, + }, + { + name: "integer type", + value: SchemaTypeInteger, + expected: SchemaTypeInteger, + }, + { + name: "boolean type", + value: SchemaTypeBoolean, + expected: SchemaTypeBoolean, + }, + { + name: "object type", + value: SchemaTypeObject, + expected: SchemaTypeObject, + }, + { + name: "array type", + value: SchemaTypeArray, + expected: SchemaTypeArray, + }, + { + name: "null type", + value: SchemaTypeNull, + expected: SchemaTypeNull, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := NewTypeFromString(tt.value) + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsRight(), "should be right value (string)") + assert.Equal(t, tt.expected, *result.Right, "right value should match input") + }) + } +} diff --git a/jsonschema/oas3/xml_validate_test.go b/jsonschema/oas3/xml_validate_test.go index 27392bd..8e10088 100644 --- a/jsonschema/oas3/xml_validate_test.go +++ b/jsonschema/oas3/xml_validate_test.go @@ -5,8 +5,11 @@ import ( "strings" "testing" + "github.com/speakeasy-api/openapi/extensions" "github.com/speakeasy-api/openapi/jsonschema/oas3" "github.com/speakeasy-api/openapi/marshaller" + "github.com/speakeasy-api/openapi/pointer" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -160,3 +163,299 @@ namespace: ":invalid namespace" }) } } + +func TestXML_GetName_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + xml *oas3.XML + expected string + }{ + { + name: "nil xml returns empty", + xml: nil, + expected: "", + }, + { + name: "returns name", + xml: &oas3.XML{Name: pointer.From("element")}, + expected: "element", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.xml.GetName()) + }) + } +} + +func TestXML_GetNamespace_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + xml *oas3.XML + expected string + }{ + { + name: "nil xml returns empty", + xml: nil, + expected: "", + }, + { + name: "returns namespace", + xml: &oas3.XML{Namespace: pointer.From("https://example.com")}, + expected: "https://example.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.xml.GetNamespace()) + }) + } +} + +func TestXML_GetPrefix_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + xml *oas3.XML + expected string + }{ + { + name: "nil xml returns empty", + xml: nil, + expected: "", + }, + { + name: "returns prefix", + xml: &oas3.XML{Prefix: pointer.From("ex")}, + expected: "ex", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.xml.GetPrefix()) + }) + } +} + +func TestXML_GetAttribute_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + xml *oas3.XML + expected bool + }{ + { + name: "nil xml returns false", + xml: nil, + expected: false, + }, + { + name: "returns true", + xml: &oas3.XML{Attribute: pointer.From(true)}, + expected: true, + }, + { + name: "returns false", + xml: &oas3.XML{Attribute: pointer.From(false)}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.xml.GetAttribute()) + }) + } +} + +func TestXML_GetWrapped_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + xml *oas3.XML + expected bool + }{ + { + name: "nil xml returns false", + xml: nil, + expected: false, + }, + { + name: "returns true", + xml: &oas3.XML{Wrapped: pointer.From(true)}, + expected: true, + }, + { + name: "returns false", + xml: &oas3.XML{Wrapped: pointer.From(false)}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.xml.GetWrapped()) + }) + } +} + +func TestXML_GetExtensions_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + xml *oas3.XML + expectEmpty bool + }{ + { + name: "nil xml returns empty extensions", + xml: nil, + expectEmpty: true, + }, + { + name: "nil extensions returns empty extensions", + xml: &oas3.XML{}, + expectEmpty: true, + }, + { + name: "returns extensions", + xml: &oas3.XML{Extensions: extensions.New()}, + expectEmpty: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.xml.GetExtensions() + assert.NotNil(t, result) + if tt.expectEmpty { + assert.Equal(t, 0, result.Len()) + } + }) + } +} + +func TestXML_IsEqual_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + a *oas3.XML + b *oas3.XML + expected bool + }{ + { + name: "both nil returns true", + a: nil, + b: nil, + expected: true, + }, + { + name: "a nil b not nil returns false", + a: nil, + b: &oas3.XML{Name: pointer.From("element")}, + expected: false, + }, + { + name: "a not nil b nil returns false", + a: &oas3.XML{Name: pointer.From("element")}, + b: nil, + expected: false, + }, + { + name: "equal name returns true", + a: &oas3.XML{Name: pointer.From("element")}, + b: &oas3.XML{Name: pointer.From("element")}, + expected: true, + }, + { + name: "different name returns false", + a: &oas3.XML{Name: pointer.From("element1")}, + b: &oas3.XML{Name: pointer.From("element2")}, + expected: false, + }, + { + name: "equal namespace returns true", + a: &oas3.XML{Namespace: pointer.From("https://example.com")}, + b: &oas3.XML{Namespace: pointer.From("https://example.com")}, + expected: true, + }, + { + name: "different namespace returns false", + a: &oas3.XML{Namespace: pointer.From("https://example1.com")}, + b: &oas3.XML{Namespace: pointer.From("https://example2.com")}, + expected: false, + }, + { + name: "equal prefix returns true", + a: &oas3.XML{Prefix: pointer.From("ex")}, + b: &oas3.XML{Prefix: pointer.From("ex")}, + expected: true, + }, + { + name: "different prefix returns false", + a: &oas3.XML{Prefix: pointer.From("ex1")}, + b: &oas3.XML{Prefix: pointer.From("ex2")}, + expected: false, + }, + { + name: "equal attribute returns true", + a: &oas3.XML{Attribute: pointer.From(true)}, + b: &oas3.XML{Attribute: pointer.From(true)}, + expected: true, + }, + { + name: "different attribute returns false", + a: &oas3.XML{Attribute: pointer.From(true)}, + b: &oas3.XML{Attribute: pointer.From(false)}, + expected: false, + }, + { + name: "equal wrapped returns true", + a: &oas3.XML{Wrapped: pointer.From(true)}, + b: &oas3.XML{Wrapped: pointer.From(true)}, + expected: true, + }, + { + name: "different wrapped returns false", + a: &oas3.XML{Wrapped: pointer.From(true)}, + b: &oas3.XML{Wrapped: pointer.From(false)}, + expected: false, + }, + { + name: "both nil extensions returns true", + a: &oas3.XML{Name: pointer.From("element")}, + b: &oas3.XML{Name: pointer.From("element")}, + expected: true, + }, + { + name: "one nil extensions returns false", + a: &oas3.XML{Name: pointer.From("element"), Extensions: extensions.New()}, + b: &oas3.XML{Name: pointer.From("element")}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.a.IsEqual(tt.b)) + }) + } +} diff --git a/marshaller/coremodel_getters_test.go b/marshaller/coremodel_getters_test.go new file mode 100644 index 0000000..db1c035 --- /dev/null +++ b/marshaller/coremodel_getters_test.go @@ -0,0 +1,121 @@ +package marshaller_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/marshaller" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" +) + +func TestCoreModel_GetRootNodeLine_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + model marshaller.CoreModel + expected int + }{ + { + name: "nil root node returns -1", + model: marshaller.CoreModel{}, + expected: -1, + }, + { + name: "returns line number", + model: marshaller.CoreModel{RootNode: &yaml.Node{Line: 42}}, + expected: 42, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.model.GetRootNodeLine()) + }) + } +} + +func TestCoreModel_GetValid_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + model marshaller.CoreModel + expected bool + }{ + { + name: "default is false", + model: marshaller.CoreModel{}, + expected: false, + }, + { + name: "returns true when set", + model: marshaller.CoreModel{Valid: true}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.model.GetValid()) + }) + } +} + +func TestCoreModel_GetValidYaml_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + model marshaller.CoreModel + expected bool + }{ + { + name: "default is false", + model: marshaller.CoreModel{}, + expected: false, + }, + { + name: "returns true when set", + model: marshaller.CoreModel{ValidYaml: true}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.model.GetValidYaml()) + }) + } +} + +func TestCoreModel_GetUnknownProperties_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + model *marshaller.CoreModel + expected []string + }{ + { + name: "nil unknown properties returns empty slice", + model: &marshaller.CoreModel{}, + expected: []string{}, + }, + { + name: "returns unknown properties", + model: &marshaller.CoreModel{UnknownProperties: []string{"prop1", "prop2"}}, + expected: []string{"prop1", "prop2"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.model.GetUnknownProperties()) + }) + } +} diff --git a/marshaller/model_test.go b/marshaller/model_test.go index 724f0e2..c96e4de 100644 --- a/marshaller/model_test.go +++ b/marshaller/model_test.go @@ -170,3 +170,155 @@ func TestModel_GetPropertyLine_ComplexModel_Success(t *testing.T) { actual := model.GetPropertyLine("ArrayField") assert.Equal(t, 25, actual, "should return line number for array field") } + +func TestModel_GetCoreAny_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func() *marshaller.Model[core.TestPrimitiveModel] + isNil bool + }{ + { + name: "non-nil model returns core", + setup: func() *marshaller.Model[core.TestPrimitiveModel] { + return &marshaller.Model[core.TestPrimitiveModel]{} + }, + isNil: false, + }, + { + name: "nil model returns nil", + setup: func() *marshaller.Model[core.TestPrimitiveModel] { + return nil + }, + isNil: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + model := tt.setup() + result := model.GetCoreAny() + if tt.isNil { + assert.Nil(t, result) + } else { + assert.NotNil(t, result) + } + }) + } +} + +func TestModel_GetRootNodeLine_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func() *marshaller.Model[core.TestPrimitiveModel] + expected int + }{ + { + name: "nil model returns -1", + setup: func() *marshaller.Model[core.TestPrimitiveModel] { + return nil + }, + expected: -1, + }, + { + name: "model with no root node returns -1", + setup: func() *marshaller.Model[core.TestPrimitiveModel] { + return &marshaller.Model[core.TestPrimitiveModel]{} + }, + expected: -1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + model := tt.setup() + result := model.GetRootNodeLine() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestModel_GetRootNodeColumn_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func() *marshaller.Model[core.TestPrimitiveModel] + expected int + }{ + { + name: "nil model returns -1", + setup: func() *marshaller.Model[core.TestPrimitiveModel] { + return nil + }, + expected: -1, + }, + { + name: "model with no root node returns -1", + setup: func() *marshaller.Model[core.TestPrimitiveModel] { + return &marshaller.Model[core.TestPrimitiveModel]{} + }, + expected: -1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + model := tt.setup() + result := model.GetRootNodeColumn() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestModel_Cache_Success(t *testing.T) { + t.Parallel() + + t.Run("nil model GetCachedReferencedObject returns nil false", func(t *testing.T) { + t.Parallel() + var model *marshaller.Model[core.TestPrimitiveModel] + result, ok := model.GetCachedReferencedObject("key") + assert.Nil(t, result) + assert.False(t, ok) + }) + + t.Run("nil model GetCachedReferenceDocument returns nil false", func(t *testing.T) { + t.Parallel() + var model *marshaller.Model[core.TestPrimitiveModel] + result, ok := model.GetCachedReferenceDocument("key") + assert.Nil(t, result) + assert.False(t, ok) + }) + + t.Run("model with uninitialized cache returns nil false", func(t *testing.T) { + t.Parallel() + model := &marshaller.Model[core.TestPrimitiveModel]{} + result, ok := model.GetCachedReferencedObject("key") + assert.Nil(t, result) + assert.False(t, ok) + }) + + t.Run("InitCache creates caches that work correctly", func(t *testing.T) { + t.Parallel() + model := &marshaller.Model[core.TestPrimitiveModel]{} + model.InitCache() + + // Store and retrieve object + model.StoreReferencedObjectInCache("objKey", "testObject") + result, ok := model.GetCachedReferencedObject("objKey") + assert.True(t, ok) + assert.Equal(t, "testObject", result) + + // Store and retrieve document + model.StoreReferenceDocumentInCache("docKey", []byte("testDoc")) + doc, ok := model.GetCachedReferenceDocument("docKey") + assert.True(t, ok) + assert.Equal(t, []byte("testDoc"), doc) + }) +} diff --git a/marshaller/node_test.go b/marshaller/node_test.go index 9a1f966..d0085b0 100644 --- a/marshaller/node_test.go +++ b/marshaller/node_test.go @@ -511,3 +511,337 @@ func TestNode_Unmarshal_Int_Success(t *testing.T) { }) } } + +func TestNode_GetKeyNodeOrRoot_Success(t *testing.T) { + t.Parallel() + + rootNode := &yaml.Node{Kind: yaml.DocumentNode, Line: 1} + + tests := []struct { + name string + node Node[string] + expected *yaml.Node + }{ + { + name: "not present returns root node", + node: Node[string]{Present: false, KeyNode: &yaml.Node{Line: 5}}, + expected: rootNode, + }, + { + name: "present but nil key node returns root node", + node: Node[string]{Present: true, KeyNode: nil}, + expected: rootNode, + }, + { + name: "present with key node returns key node", + node: Node[string]{Present: true, KeyNode: &yaml.Node{Line: 10}}, + expected: &yaml.Node{Line: 10}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.node.GetKeyNodeOrRoot(rootNode) + assert.Equal(t, tt.expected.Line, result.Line) + }) + } +} + +func TestNode_GetKeyNodeOrRootLine_Success(t *testing.T) { + t.Parallel() + + rootNode := &yaml.Node{Kind: yaml.DocumentNode, Line: 1} + + tests := []struct { + name string + node Node[string] + expected int + }{ + { + name: "not present returns root line", + node: Node[string]{Present: false, KeyNode: &yaml.Node{Line: 5}}, + expected: 1, + }, + { + name: "present with key node returns key line", + node: Node[string]{Present: true, KeyNode: &yaml.Node{Line: 10}}, + expected: 10, + }, + { + name: "nil root node returns -1", + node: Node[string]{Present: false}, + expected: -1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var rn *yaml.Node + if tt.name != "nil root node returns -1" { + rn = rootNode + } + result := tt.node.GetKeyNodeOrRootLine(rn) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestNode_GetValueNode_Success(t *testing.T) { + t.Parallel() + + valueNode := &yaml.Node{Kind: yaml.ScalarNode, Value: "test"} + node := Node[string]{ValueNode: valueNode} + + assert.Equal(t, valueNode, node.GetValueNode()) +} + +func TestNode_GetValueNodeOrRoot_Success(t *testing.T) { + t.Parallel() + + rootNode := &yaml.Node{Kind: yaml.DocumentNode, Line: 1} + valueNode := &yaml.Node{Kind: yaml.ScalarNode, Line: 5} + + tests := []struct { + name string + node Node[string] + expectedLine int + }{ + { + name: "not present returns root node", + node: Node[string]{Present: false, ValueNode: valueNode}, + expectedLine: 1, + }, + { + name: "present but nil value node returns root node", + node: Node[string]{Present: true, ValueNode: nil}, + expectedLine: 1, + }, + { + name: "present with value node returns value node", + node: Node[string]{Present: true, ValueNode: valueNode}, + expectedLine: 5, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.node.GetValueNodeOrRoot(rootNode) + assert.Equal(t, tt.expectedLine, result.Line) + }) + } +} + +func TestNode_GetValueNodeOrRootLine_Success(t *testing.T) { + t.Parallel() + + rootNode := &yaml.Node{Kind: yaml.DocumentNode, Line: 1} + valueNode := &yaml.Node{Kind: yaml.ScalarNode, Line: 5} + + tests := []struct { + name string + node Node[string] + expected int + }{ + { + name: "not present returns root line", + node: Node[string]{Present: false, ValueNode: valueNode}, + expected: 1, + }, + { + name: "present with value node returns value line", + node: Node[string]{Present: true, ValueNode: valueNode}, + expected: 5, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.node.GetValueNodeOrRootLine(rootNode) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestNode_GetSliceValueNodeOrRoot_Success(t *testing.T) { + t.Parallel() + + rootNode := &yaml.Node{Kind: yaml.DocumentNode, Line: 1} + contentNode0 := &yaml.Node{Kind: yaml.ScalarNode, Line: 10, Value: "item0"} + contentNode1 := &yaml.Node{Kind: yaml.ScalarNode, Line: 11, Value: "item1"} + seqNode := &yaml.Node{ + Kind: yaml.SequenceNode, + Line: 5, + Content: []*yaml.Node{contentNode0, contentNode1}, + } + + tests := []struct { + name string + node Node[[]string] + idx int + expectedLine int + }{ + { + name: "not present returns root node", + node: Node[[]string]{Present: false, ValueNode: seqNode}, + idx: 0, + expectedLine: 1, + }, + { + name: "valid index returns content node", + node: Node[[]string]{Present: true, ValueNode: seqNode}, + idx: 0, + expectedLine: 10, + }, + { + name: "valid index 1 returns content node", + node: Node[[]string]{Present: true, ValueNode: seqNode}, + idx: 1, + expectedLine: 11, + }, + { + name: "negative index returns value node", + node: Node[[]string]{Present: true, ValueNode: seqNode}, + idx: -1, + expectedLine: 5, + }, + { + name: "out of bounds index returns value node", + node: Node[[]string]{Present: true, ValueNode: seqNode}, + idx: 10, + expectedLine: 5, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.node.GetSliceValueNodeOrRoot(tt.idx, rootNode) + assert.Equal(t, tt.expectedLine, result.Line) + }) + } +} + +func TestNode_GetMapKeyNodeOrRoot_Success(t *testing.T) { + t.Parallel() + + rootNode := &yaml.Node{Kind: yaml.DocumentNode, Line: 1} + keyNode := &yaml.Node{Kind: yaml.ScalarNode, Line: 10, Value: "key1"} + valNode := &yaml.Node{Kind: yaml.ScalarNode, Line: 10, Value: "val1"} + mapNode := &yaml.Node{ + Kind: yaml.MappingNode, + Line: 5, + Content: []*yaml.Node{keyNode, valNode}, + } + + tests := []struct { + name string + node Node[map[string]string] + key string + expectedLine int + }{ + { + name: "not present returns root node", + node: Node[map[string]string]{Present: false, ValueNode: mapNode}, + key: "key1", + expectedLine: 1, + }, + { + name: "key found returns key node", + node: Node[map[string]string]{Present: true, ValueNode: mapNode}, + key: "key1", + expectedLine: 10, + }, + { + name: "key not found returns value node", + node: Node[map[string]string]{Present: true, ValueNode: mapNode}, + key: "nonexistent", + expectedLine: 5, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.node.GetMapKeyNodeOrRoot(tt.key, rootNode) + assert.Equal(t, tt.expectedLine, result.Line) + }) + } +} + +func TestNode_GetMapKeyNodeOrRootLine_Success(t *testing.T) { + t.Parallel() + + rootNode := &yaml.Node{Kind: yaml.DocumentNode, Line: 1} + keyNode := &yaml.Node{Kind: yaml.ScalarNode, Line: 10, Value: "key1"} + valNode := &yaml.Node{Kind: yaml.ScalarNode, Line: 10, Value: "val1"} + mapNode := &yaml.Node{ + Kind: yaml.MappingNode, + Line: 5, + Content: []*yaml.Node{keyNode, valNode}, + } + + node := Node[map[string]string]{Present: true, ValueNode: mapNode} + + assert.Equal(t, 10, node.GetMapKeyNodeOrRootLine("key1", rootNode)) + assert.Equal(t, 5, node.GetMapKeyNodeOrRootLine("nonexistent", rootNode)) +} + +func TestNode_GetMapValueNodeOrRoot_Success(t *testing.T) { + t.Parallel() + + rootNode := &yaml.Node{Kind: yaml.DocumentNode, Line: 1} + keyNode := &yaml.Node{Kind: yaml.ScalarNode, Line: 10, Value: "key1"} + valNode := &yaml.Node{Kind: yaml.ScalarNode, Line: 11, Value: "val1"} + mapNode := &yaml.Node{ + Kind: yaml.MappingNode, + Line: 5, + Content: []*yaml.Node{keyNode, valNode}, + } + + tests := []struct { + name string + node Node[map[string]string] + key string + expectedLine int + }{ + { + name: "not present returns root node", + node: Node[map[string]string]{Present: false, ValueNode: mapNode}, + key: "key1", + expectedLine: 1, + }, + { + name: "key found returns value node", + node: Node[map[string]string]{Present: true, ValueNode: mapNode}, + key: "key1", + expectedLine: 11, + }, + { + name: "key not found returns value node", + node: Node[map[string]string]{Present: true, ValueNode: mapNode}, + key: "nonexistent", + expectedLine: 5, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.node.GetMapValueNodeOrRoot(tt.key, rootNode) + assert.Equal(t, tt.expectedLine, result.Line) + }) + } +} + +func TestNode_GetNavigableNode_Success(t *testing.T) { + t.Parallel() + + node := Node[string]{Value: "test value"} + result, err := node.GetNavigableNode() + require.NoError(t, err) + assert.Equal(t, "test value", result) +} diff --git a/marshaller/unmarshalling_test.go b/marshaller/unmarshalling_test.go index 78bbdff..aa0ab3d 100644 --- a/marshaller/unmarshalling_test.go +++ b/marshaller/unmarshalling_test.go @@ -948,6 +948,42 @@ float64Field: 3.14 require.Contains(t, err.Error(), "out parameter cannot be nil", "error should indicate nil out parameter") } +func TestDecodeNode_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yml string + expected string + }{ + { + name: "decode string value", + yml: `"test string"`, + expected: "test string", + }, + { + name: "decode unquoted string value", + yml: `test value`, + expected: "test value", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + node := parseYAML(t, tt.yml) + // Navigate to scalar node (first content of document) + scalarNode := node.Content[0] + + var result string + validationErrs, err := marshaller.DecodeNode(t.Context(), "test", scalarNode, &result) + require.NoError(t, err) + require.Empty(t, validationErrs) + assert.Equal(t, tt.expected, result) + }) + } +} + // Helper functions func parseYAML(t *testing.T, yml string) *yaml.Node { t.Helper() diff --git a/mise-tasks/test-coverage b/mise-tasks/test-coverage index ed079fd..1f14c42 100755 --- a/mise-tasks/test-coverage +++ b/mise-tasks/test-coverage @@ -40,13 +40,13 @@ go tool cover -func=coverage.out | awk ' split($1, parts, ":") file_func = parts[1] ":" parts[2] - # Extract coverage percentage - gsub(/%/, "", $3) - coverage = $3 + # Extract coverage percentage from last field + gsub(/%/, "", $NF) + coverage = $NF + 0 # Show functions with 0% coverage - if (coverage == "0.0") { - printf "| %-70s | %7s%% |\n", file_func, coverage + if (coverage == 0) { + printf "| %-70s | %7.1f%% |\n", file_func, coverage } }' | head -30 @@ -56,20 +56,20 @@ echo "" echo "| File:Function | Coverage |" echo "|------------------------------------------------------------------------|----------|" -# Show functions with low coverage +# Show functions with low coverage (>0% and <50%) go tool cover -func=coverage.out | awk ' /^github\.com/ && !/total/ { # Extract file and function name split($1, parts, ":") file_func = parts[1] ":" parts[2] - # Extract coverage percentage - gsub(/%/, "", $3) - coverage = $3 + # Extract coverage percentage from last field + gsub(/%/, "", $NF) + coverage = $NF + 0 # Show functions with less than 50% coverage (but not 0%) - if (coverage != "" && coverage > 0 && coverage < 50) { - printf "| %-70s | %7s%% |\n", file_func, coverage + if (coverage > 0 && coverage < 50) { + printf "| %-70s | %7.1f%% |\n", file_func, coverage } }' | head -30 @@ -81,7 +81,7 @@ echo "|---------------------------------------------------------------|--------- # Show packages that need improvement go tool cover -func=coverage.out | awk ' -/^github\.com/ { +/^github\.com/ && !/total/ { # Extract package name split($1, parts, "/") pkg_path = "" @@ -91,12 +91,9 @@ go tool cover -func=coverage.out | awk ' if (parts[i+1] ~ /\.go:/) break } - # Extract coverage percentage - gsub(/%/, "", $3) - coverage = $3 - - # Skip if not a valid coverage line - if (coverage == "" || coverage == "total") next + # Extract coverage percentage from last field + gsub(/%/, "", $NF) + coverage = $NF + 0 # Accumulate coverage for each package if (pkg_path in pkg_total) { @@ -115,7 +112,7 @@ END { if (avg_coverage > 60) priority = "🟡 Medium" if (avg_coverage > 40) priority = "🟠 Medium-High" if (avg_coverage == 0) priority = "🚨 Critical" - printf "| %-61s | %7s%% | %-14s |\n", pkg, sprintf("%.1f", avg_coverage), priority + printf "| %-61s | %7.1f%% | %-14s |\n", pkg, avg_coverage, priority } } }' | sort -k4,4n | head -15 @@ -125,7 +122,7 @@ echo "---" echo "" # Extract and display total coverage at the bottom for visibility -CURRENT_COV=$(go tool cover -func=coverage.out | tail -1 | awk '{print $3}') +CURRENT_COV=$(go tool cover -func=coverage.out | tail -1 | awk '{print $NF}') echo "## 🎯 **TOTAL COVERAGE: \`$CURRENT_COV\`**" echo "" echo "- 🧪 All tests completed" diff --git a/openapi/core/callbacks_test.go b/openapi/core/callbacks_test.go index 02301ad..0a3795a 100644 --- a/openapi/core/callbacks_test.go +++ b/openapi/core/callbacks_test.go @@ -6,8 +6,18 @@ import ( "github.com/speakeasy-api/openapi/marshaller" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) +func TestNewCallback_Success(t *testing.T) { + t.Parallel() + + callback := NewCallback() + require.NotNil(t, callback, "NewCallback should return a non-nil callback") + require.NotNil(t, callback.Map, "callback.Map should be initialized") + assert.Equal(t, 0, callback.Len(), "newly created callback should be empty") +} + func TestCallback_GetMapKeyNodeOrRoot_Success(t *testing.T) { t.Parallel() tests := []struct { @@ -176,3 +186,37 @@ func TestCallback_GetMapKeyNodeOrRootLine_ReturnsRootLine(t *testing.T) { }) } } + +func TestCallback_GetMapKeyNodeOrRoot_Uninitialized(t *testing.T) { + t.Parallel() + + t.Run("returns root when callback is not initialized", func(t *testing.T) { + t.Parallel() + var callback Callback + // Don't unmarshal - leave uninitialized + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := callback.GetMapKeyNodeOrRoot("anykey", rootNode) + assert.Equal(t, rootNode, result, "should return root node when not initialized") + }) + + t.Run("returns root when RootNode is nil", func(t *testing.T) { + t.Parallel() + callback := &Callback{} + callback.SetValid(true, true) // Mark as initialized but no RootNode + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := callback.GetMapKeyNodeOrRoot("anykey", rootNode) + assert.Equal(t, rootNode, result, "should return root node when RootNode is nil") + }) +} + +func TestCallback_GetMapKeyNodeOrRootLine_NilNode(t *testing.T) { + t.Parallel() + + t.Run("returns -1 when GetMapKeyNodeOrRoot returns nil", func(t *testing.T) { + t.Parallel() + var callback Callback + // Pass nil as rootNode - when not initialized, nil is returned + line := callback.GetMapKeyNodeOrRootLine("anykey", nil) + assert.Equal(t, -1, line, "should return -1 when node is nil") + }) +} diff --git a/openapi/core/paths_test.go b/openapi/core/paths_test.go index d72ea0b..fca396b 100644 --- a/openapi/core/paths_test.go +++ b/openapi/core/paths_test.go @@ -6,8 +6,25 @@ import ( "github.com/speakeasy-api/openapi/marshaller" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) +func TestNewPaths_Success(t *testing.T) { + t.Parallel() + + paths := NewPaths() + require.NotNil(t, paths, "NewPaths should return a non-nil paths") + require.NotNil(t, paths.Map, "paths.Map should be initialized") + assert.Equal(t, 0, paths.Len(), "newly created paths should be empty") +} + +func TestNewPathItem_Success(t *testing.T) { + t.Parallel() + + pathItem := NewPathItem() + require.NotNil(t, pathItem, "NewPathItem should return a non-nil path item") +} + func TestPaths_GetMapKeyNodeOrRoot_Success(t *testing.T) { t.Parallel() tests := []struct { @@ -336,3 +353,67 @@ get: }) } } + +func TestPaths_GetMapKeyNodeOrRoot_Uninitialized(t *testing.T) { + t.Parallel() + + t.Run("returns root when paths is not initialized", func(t *testing.T) { + t.Parallel() + var paths Paths + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := paths.GetMapKeyNodeOrRoot("/pets", rootNode) + assert.Equal(t, rootNode, result, "should return root node when not initialized") + }) + + t.Run("returns root when RootNode is nil", func(t *testing.T) { + t.Parallel() + paths := &Paths{} + paths.SetValid(true, true) + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := paths.GetMapKeyNodeOrRoot("/pets", rootNode) + assert.Equal(t, rootNode, result, "should return root node when RootNode is nil") + }) +} + +func TestPaths_GetMapKeyNodeOrRootLine_NilNode(t *testing.T) { + t.Parallel() + + t.Run("returns -1 when GetMapKeyNodeOrRoot returns nil", func(t *testing.T) { + t.Parallel() + var paths Paths + line := paths.GetMapKeyNodeOrRootLine("/pets", nil) + assert.Equal(t, -1, line, "should return -1 when node is nil") + }) +} + +func TestPathItem_GetMapKeyNodeOrRoot_Uninitialized(t *testing.T) { + t.Parallel() + + t.Run("returns root when pathItem is not initialized", func(t *testing.T) { + t.Parallel() + var pathItem PathItem + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := pathItem.GetMapKeyNodeOrRoot("get", rootNode) + assert.Equal(t, rootNode, result, "should return root node when not initialized") + }) + + t.Run("returns root when RootNode is nil", func(t *testing.T) { + t.Parallel() + pathItem := &PathItem{} + pathItem.SetValid(true, true) + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := pathItem.GetMapKeyNodeOrRoot("get", rootNode) + assert.Equal(t, rootNode, result, "should return root node when RootNode is nil") + }) +} + +func TestPathItem_GetMapKeyNodeOrRootLine_NilNode(t *testing.T) { + t.Parallel() + + t.Run("returns -1 when GetMapKeyNodeOrRoot returns nil", func(t *testing.T) { + t.Parallel() + var pathItem PathItem + line := pathItem.GetMapKeyNodeOrRootLine("get", nil) + assert.Equal(t, -1, line, "should return -1 when node is nil") + }) +} diff --git a/openapi/core/reference_test.go b/openapi/core/reference_test.go new file mode 100644 index 0000000..99db694 --- /dev/null +++ b/openapi/core/reference_test.go @@ -0,0 +1,144 @@ +package core + +import ( + "testing" + + "github.com/speakeasy-api/openapi/marshaller" + "github.com/speakeasy-api/openapi/pointer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestReference_Unmarshal_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yaml string + isRef bool + expected string + }{ + { + name: "unmarshals reference with $ref", + yaml: ` +$ref: '#/components/schemas/Pet' +summary: A pet reference +description: Reference to pet schema +`, + isRef: true, + expected: "#/components/schemas/Pet", + }, + { + name: "unmarshals inlined object", + yaml: ` +description: A new pet +`, + isRef: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var ref Reference[*Response] + _, err := marshaller.UnmarshalCore(ctx, "", parseYAML(t, tt.yaml), &ref) + require.NoError(t, err, "unmarshal should succeed") + + if tt.isRef { + require.NotNil(t, ref.Reference.Value, "should have reference value") + assert.Equal(t, tt.expected, *ref.Reference.Value, "should have correct reference") + } else { + require.NotNil(t, ref.Object, "should have inlined object") + } + }) + } +} + +func TestReference_Unmarshal_Error(t *testing.T) { + t.Parallel() + + t.Run("returns error when node is nil", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var ref Reference[*Response] + _, err := ref.Unmarshal(ctx, "", nil) + require.Error(t, err, "should return error for nil node") + assert.Contains(t, err.Error(), "node is nil", "error should mention nil node") + }) + + t.Run("returns validation error for non-object node", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var ref Reference[*Response] + node := &yaml.Node{Kind: yaml.ScalarNode, Value: "just a string"} + validationErrs, err := ref.Unmarshal(ctx, "test", node) + require.NoError(t, err, "should not return fatal error") + require.NotEmpty(t, validationErrs, "should have validation errors") + assert.False(t, ref.GetValid(), "should not be valid") + }) +} + +func TestReference_SyncChanges_ReferenceCase(t *testing.T) { + t.Parallel() + + t.Run("syncs reference with $ref field", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + + // Create a reference with $ref + type TestModel struct { + Reference *string + Object *Response + } + + coreRef := &Reference[*Response]{} + coreRef.Reference = marshaller.Node[*string]{Value: pointer.From("#/components/responses/NotFound")} + coreRef.SetValid(true, true) + + model := &TestModel{ + Reference: pointer.From("#/components/responses/NotFound"), + } + + valueNode := &yaml.Node{ + Kind: yaml.MappingNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "$ref"}, + {Kind: yaml.ScalarNode, Value: "#/components/responses/NotFound"}, + }, + } + + result, err := coreRef.SyncChanges(ctx, model, valueNode) + require.NoError(t, err, "SyncChanges should succeed") + require.NotNil(t, result, "should return a node") + }) + + t.Run("errors on non-struct model", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + + coreRef := &Reference[*Response]{} + coreRef.SetValid(true, true) + + _, err := coreRef.SyncChanges(ctx, "not a struct", nil) + require.Error(t, err, "should return error for non-struct model") + assert.Contains(t, err.Error(), "expected a struct", "error should mention struct expectation") + }) +} + +func TestReference_SyncChanges_ErrorOnInt(t *testing.T) { + t.Parallel() + + t.Run("errors on non-pointer non-struct model", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + + coreRef := &Reference[*Response]{} + coreRef.SetValid(true, true) + + _, err := coreRef.SyncChanges(ctx, 42, nil) + require.Error(t, err, "should return error for int model") + assert.Contains(t, err.Error(), "expected a struct", "error should mention struct expectation") + }) +} diff --git a/openapi/core/responses_test.go b/openapi/core/responses_test.go index 4ddc16e..292753b 100644 --- a/openapi/core/responses_test.go +++ b/openapi/core/responses_test.go @@ -6,8 +6,18 @@ import ( "github.com/speakeasy-api/openapi/marshaller" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) +func TestNewResponses_Success(t *testing.T) { + t.Parallel() + + responses := NewResponses() + require.NotNil(t, responses, "NewResponses should return a non-nil responses") + require.NotNil(t, responses.Map, "responses.Map should be initialized") + assert.Equal(t, 0, responses.Len(), "newly created responses should be empty") +} + func TestResponses_GetMapKeyNodeOrRoot_Success(t *testing.T) { t.Parallel() tests := []struct { @@ -134,6 +144,38 @@ func TestResponses_GetMapKeyNodeOrRootLine_Success(t *testing.T) { } } +func TestResponses_GetMapKeyNodeOrRoot_Uninitialized(t *testing.T) { + t.Parallel() + + t.Run("returns root when responses is not initialized", func(t *testing.T) { + t.Parallel() + var responses Responses + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := responses.GetMapKeyNodeOrRoot("200", rootNode) + assert.Equal(t, rootNode, result, "should return root node when not initialized") + }) + + t.Run("returns root when RootNode is nil", func(t *testing.T) { + t.Parallel() + responses := &Responses{} + responses.SetValid(true, true) + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := responses.GetMapKeyNodeOrRoot("200", rootNode) + assert.Equal(t, rootNode, result, "should return root node when RootNode is nil") + }) +} + +func TestResponses_GetMapKeyNodeOrRootLine_NilNode(t *testing.T) { + t.Parallel() + + t.Run("returns -1 when GetMapKeyNodeOrRoot returns nil", func(t *testing.T) { + t.Parallel() + var responses Responses + line := responses.GetMapKeyNodeOrRootLine("200", nil) + assert.Equal(t, -1, line, "should return -1 when node is nil") + }) +} + func TestResponses_GetMapKeyNodeOrRootLine_ReturnsRootLine(t *testing.T) { t.Parallel() diff --git a/openapi/core/security_test.go b/openapi/core/security_test.go index 5fea91d..6e96293 100644 --- a/openapi/core/security_test.go +++ b/openapi/core/security_test.go @@ -9,6 +9,15 @@ import ( "gopkg.in/yaml.v3" ) +func TestNewSecurityRequirement_Success(t *testing.T) { + t.Parallel() + + secReq := NewSecurityRequirement() + require.NotNil(t, secReq, "NewSecurityRequirement should return a non-nil security requirement") + require.NotNil(t, secReq.Map, "secReq.Map should be initialized") + assert.Equal(t, 0, secReq.Len(), "newly created security requirement should be empty") +} + func TestSecurityRequirement_GetMapKeyNodeOrRoot_Success(t *testing.T) { t.Parallel() @@ -167,6 +176,38 @@ oauth2: } } +func TestSecurityRequirement_GetMapKeyNodeOrRoot_Uninitialized(t *testing.T) { + t.Parallel() + + t.Run("returns root when security requirement is not initialized", func(t *testing.T) { + t.Parallel() + var secReq SecurityRequirement + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := secReq.GetMapKeyNodeOrRoot("oauth2", rootNode) + assert.Equal(t, rootNode, result, "should return root node when not initialized") + }) + + t.Run("returns root when RootNode is nil", func(t *testing.T) { + t.Parallel() + secReq := &SecurityRequirement{} + secReq.SetValid(true, true) + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := secReq.GetMapKeyNodeOrRoot("oauth2", rootNode) + assert.Equal(t, rootNode, result, "should return root node when RootNode is nil") + }) +} + +func TestSecurityRequirement_GetMapKeyNodeOrRootLine_NilNode(t *testing.T) { + t.Parallel() + + t.Run("returns -1 when GetMapKeyNodeOrRoot returns nil", func(t *testing.T) { + t.Parallel() + var secReq SecurityRequirement + line := secReq.GetMapKeyNodeOrRootLine("oauth2", nil) + assert.Equal(t, -1, line, "should return -1 when node is nil") + }) +} + // Helper function func parseYAML(t *testing.T, yml string) *yaml.Node { t.Helper() diff --git a/openapi/encoding_validate_test.go b/openapi/encoding_validate_test.go index 913ed38..ea99cad 100644 --- a/openapi/encoding_validate_test.go +++ b/openapi/encoding_validate_test.go @@ -6,6 +6,7 @@ import ( "github.com/speakeasy-api/openapi/marshaller" "github.com/speakeasy-api/openapi/openapi" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -165,3 +166,101 @@ style: invalidStyle }) } } + +func TestEncoding_Getters_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yml string + wantContentType string + wantStyle openapi.SerializationStyle + wantExplode bool + wantAllowReserved bool + }{ + { + name: "all fields set", + yml: ` +contentType: application/json +style: form +explode: true +allowReserved: true +`, + wantContentType: "application/json", + wantStyle: openapi.SerializationStyleForm, + wantExplode: true, + wantAllowReserved: true, + }, + { + name: "defaults - no style", + yml: `{}`, + wantContentType: "application/octet-stream", + wantStyle: openapi.SerializationStyleForm, + wantExplode: true, // form style defaults to explode=true + wantAllowReserved: false, + }, + { + name: "non-form style defaults explode to false", + yml: ` +style: pipeDelimited +`, + wantContentType: "application/octet-stream", + wantStyle: openapi.SerializationStylePipeDelimited, + wantExplode: false, + wantAllowReserved: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var encoding openapi.Encoding + _, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(tt.yml), &encoding) + require.NoError(t, err) + + assert.Equal(t, tt.wantContentType, encoding.GetContentType(nil), "GetContentType mismatch") + assert.Equal(t, tt.wantStyle, encoding.GetStyle(), "GetStyle mismatch") + assert.Equal(t, tt.wantExplode, encoding.GetExplode(), "GetExplode mismatch") + assert.Equal(t, tt.wantAllowReserved, encoding.GetAllowReserved(), "GetAllowReserved mismatch") + assert.NotNil(t, encoding.GetExtensions(), "GetExtensions should never be nil") + }) + } +} + +func TestEncoding_Getters_Nil(t *testing.T) { + t.Parallel() + + var encoding *openapi.Encoding = nil + + assert.Equal(t, "application/octet-stream", encoding.GetContentType(nil), "nil encoding GetContentType should return default") + assert.Empty(t, encoding.GetContentTypeValue(), "nil encoding GetContentTypeValue should return empty") + assert.Equal(t, openapi.SerializationStyleForm, encoding.GetStyle(), "nil encoding GetStyle should return form") + assert.True(t, encoding.GetExplode(), "nil encoding GetExplode should return true (form default)") + assert.False(t, encoding.GetAllowReserved(), "nil encoding GetAllowReserved should return false") + assert.Nil(t, encoding.GetHeaders(), "nil encoding GetHeaders should return nil") + assert.NotNil(t, encoding.GetExtensions(), "nil encoding GetExtensions should return empty") +} + +func TestEncoding_GetHeaders_Success(t *testing.T) { + t.Parallel() + + yml := ` +contentType: application/json +headers: + X-Rate-Limit: + schema: + type: integer + X-Custom: + schema: + type: string +` + + var encoding openapi.Encoding + _, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &encoding) + require.NoError(t, err) + + headers := encoding.GetHeaders() + require.NotNil(t, headers, "GetHeaders should not be nil") + assert.Equal(t, 2, headers.Len(), "headers should have two entries") +} diff --git a/openapi/examples_test.go b/openapi/examples_test.go new file mode 100644 index 0000000..93e7d0c --- /dev/null +++ b/openapi/examples_test.go @@ -0,0 +1,83 @@ +package openapi + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestExample_ResolveExternalValue_Success(t *testing.T) { + t.Parallel() + + ctx := t.Context() + e := &Example{} + + val, err := e.ResolveExternalValue(ctx) + + require.NoError(t, err, "ResolveExternalValue should not return error") + assert.Nil(t, val, "ResolveExternalValue should return nil (TODO implementation)") +} + +func TestExample_GetSummary_Nil_Success(t *testing.T) { + t.Parallel() + + var e *Example + result := e.GetSummary() + + assert.Empty(t, result, "GetSummary on nil should return empty string") +} + +func TestExample_GetDescription_Nil_Success(t *testing.T) { + t.Parallel() + + var e *Example + result := e.GetDescription() + + assert.Empty(t, result, "GetDescription on nil should return empty string") +} + +func TestExample_GetValue_Nil_Success(t *testing.T) { + t.Parallel() + + var e *Example + result := e.GetValue() + + assert.Nil(t, result, "GetValue on nil should return nil") +} + +func TestExample_GetExternalValue_Nil_Success(t *testing.T) { + t.Parallel() + + var e *Example + result := e.GetExternalValue() + + assert.Empty(t, result, "GetExternalValue on nil should return empty string") +} + +func TestExample_GetDataValue_Nil_Success(t *testing.T) { + t.Parallel() + + var e *Example + result := e.GetDataValue() + + assert.Nil(t, result, "GetDataValue on nil should return nil") +} + +func TestExample_GetSerializedValue_Nil_Success(t *testing.T) { + t.Parallel() + + var e *Example + result := e.GetSerializedValue() + + assert.Empty(t, result, "GetSerializedValue on nil should return empty string") +} + +func TestExample_GetExtensions_Nil_Success(t *testing.T) { + t.Parallel() + + var e *Example + result := e.GetExtensions() + + assert.NotNil(t, result, "GetExtensions on nil should return empty extensions") +} diff --git a/openapi/header_validate_test.go b/openapi/header_validate_test.go index 9385416..4e229c6 100644 --- a/openapi/header_validate_test.go +++ b/openapi/header_validate_test.go @@ -157,3 +157,150 @@ description: Header with invalid schema }) } } + +func TestHeader_Getters_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yml string + wantRequired bool + wantDeprec bool + wantStyle openapi.SerializationStyle + wantExplode bool + wantDescr string + }{ + { + name: "all fields set", + yml: ` +required: true +deprecated: true +style: simple +explode: true +description: Test header +schema: + type: string +`, + wantRequired: true, + wantDeprec: true, + wantStyle: openapi.SerializationStyleSimple, + wantExplode: true, + wantDescr: "Test header", + }, + { + name: "default values", + yml: ` +schema: + type: string +`, + wantRequired: false, + wantDeprec: false, + wantStyle: openapi.SerializationStyleSimple, + wantExplode: false, + wantDescr: "", + }, + { + name: "only required set", + yml: ` +required: true +schema: + type: string +`, + wantRequired: true, + wantDeprec: false, + wantStyle: openapi.SerializationStyleSimple, + wantExplode: false, + wantDescr: "", + }, + { + name: "only deprecated set", + yml: ` +deprecated: true +schema: + type: string +`, + wantRequired: false, + wantDeprec: true, + wantStyle: openapi.SerializationStyleSimple, + wantExplode: false, + wantDescr: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var header openapi.Header + _, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(tt.yml), &header) + require.NoError(t, err) + + assert.Equal(t, tt.wantRequired, header.GetRequired(), "GetRequired mismatch") + assert.Equal(t, tt.wantDeprec, header.GetDeprecated(), "GetDeprecated mismatch") + assert.Equal(t, tt.wantStyle, header.GetStyle(), "GetStyle mismatch") + assert.Equal(t, tt.wantExplode, header.GetExplode(), "GetExplode mismatch") + assert.Equal(t, tt.wantDescr, header.GetDescription(), "GetDescription mismatch") + assert.NotNil(t, header.GetSchema(), "GetSchema should not be nil") + assert.NotNil(t, header.GetExtensions(), "GetExtensions should never be nil") + }) + } +} + +func TestHeader_Getters_Nil(t *testing.T) { + t.Parallel() + + var header *openapi.Header = nil + + assert.False(t, header.GetRequired(), "nil header GetRequired should return false") + assert.False(t, header.GetDeprecated(), "nil header GetDeprecated should return false") + assert.Equal(t, openapi.SerializationStyleSimple, header.GetStyle(), "nil header GetStyle should return simple") + assert.False(t, header.GetExplode(), "nil header GetExplode should return false") + assert.Empty(t, header.GetDescription(), "nil header GetDescription should return empty") + assert.Nil(t, header.GetSchema(), "nil header GetSchema should return nil") + assert.Nil(t, header.GetContent(), "nil header GetContent should return nil") + assert.Nil(t, header.GetExample(), "nil header GetExample should return nil") + assert.Nil(t, header.GetExamples(), "nil header GetExamples should return nil") + assert.NotNil(t, header.GetExtensions(), "nil header GetExtensions should return empty") +} + +func TestHeader_GetContent_Success(t *testing.T) { + t.Parallel() + + yml := ` +content: + application/json: + schema: + type: object +description: Header with content +` + + var header openapi.Header + _, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &header) + require.NoError(t, err) + + content := header.GetContent() + require.NotNil(t, content, "GetContent should not be nil") + assert.Equal(t, 1, content.Len(), "content should have one entry") +} + +func TestHeader_GetExamples_Success(t *testing.T) { + t.Parallel() + + yml := ` +schema: + type: string +examples: + example1: + value: "test1" + example2: + value: "test2" +` + + var header openapi.Header + _, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &header) + require.NoError(t, err) + + examples := header.GetExamples() + require.NotNil(t, examples, "GetExamples should not be nil") + assert.Equal(t, 2, examples.Len(), "examples should have two entries") +} diff --git a/openapi/info_getters_test.go b/openapi/info_getters_test.go new file mode 100644 index 0000000..94d1de9 --- /dev/null +++ b/openapi/info_getters_test.go @@ -0,0 +1,1094 @@ +package openapi + +import ( + "testing" + + "github.com/speakeasy-api/openapi/extensions" + "github.com/speakeasy-api/openapi/jsonschema/oas3" + "github.com/speakeasy-api/openapi/pointer" + "github.com/speakeasy-api/openapi/sequencedmap" + "github.com/stretchr/testify/assert" +) + +// Info getter tests + +func TestInfo_GetTitle_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + info *Info + expected string + }{ + { + name: "nil info returns empty", + info: nil, + expected: "", + }, + { + name: "returns title", + info: &Info{Title: "Test API"}, + expected: "Test API", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.info.GetTitle()) + }) + } +} + +func TestInfo_GetVersion_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + info *Info + expected string + }{ + { + name: "nil info returns empty", + info: nil, + expected: "", + }, + { + name: "returns version", + info: &Info{Version: "1.0.0"}, + expected: "1.0.0", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.info.GetVersion()) + }) + } +} + +func TestInfo_GetSummary_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + info *Info + expected string + }{ + { + name: "nil info returns empty", + info: nil, + expected: "", + }, + { + name: "nil summary returns empty", + info: &Info{}, + expected: "", + }, + { + name: "returns summary", + info: &Info{Summary: pointer.From("A test API")}, + expected: "A test API", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.info.GetSummary()) + }) + } +} + +func TestInfo_GetDescription_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + info *Info + expected string + }{ + { + name: "nil info returns empty", + info: nil, + expected: "", + }, + { + name: "nil description returns empty", + info: &Info{}, + expected: "", + }, + { + name: "returns description", + info: &Info{Description: pointer.From("API description")}, + expected: "API description", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.info.GetDescription()) + }) + } +} + +func TestInfo_GetTermsOfService_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + info *Info + expected string + }{ + { + name: "nil info returns empty", + info: nil, + expected: "", + }, + { + name: "nil tos returns empty", + info: &Info{}, + expected: "", + }, + { + name: "returns tos", + info: &Info{TermsOfService: pointer.From("https://example.com/tos")}, + expected: "https://example.com/tos", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.info.GetTermsOfService()) + }) + } +} + +func TestInfo_GetContact_Success(t *testing.T) { + t.Parallel() + + contact := &Contact{Name: pointer.From("Test")} + tests := []struct { + name string + info *Info + expected *Contact + }{ + { + name: "nil info returns nil", + info: nil, + expected: nil, + }, + { + name: "nil contact returns nil", + info: &Info{}, + expected: nil, + }, + { + name: "returns contact", + info: &Info{Contact: contact}, + expected: contact, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.info.GetContact()) + }) + } +} + +func TestInfo_GetLicense_Success(t *testing.T) { + t.Parallel() + + license := &License{Name: "MIT"} + tests := []struct { + name string + info *Info + expected *License + }{ + { + name: "nil info returns nil", + info: nil, + expected: nil, + }, + { + name: "nil license returns nil", + info: &Info{}, + expected: nil, + }, + { + name: "returns license", + info: &Info{License: license}, + expected: license, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.info.GetLicense()) + }) + } +} + +func TestInfo_GetExtensions_Success(t *testing.T) { + t.Parallel() + + ext := extensions.New() + tests := []struct { + name string + info *Info + expectEmpty bool + }{ + { + name: "nil info returns empty", + info: nil, + expectEmpty: true, + }, + { + name: "nil extensions returns empty", + info: &Info{}, + expectEmpty: true, + }, + { + name: "returns extensions", + info: &Info{Extensions: ext}, + expectEmpty: true, // ext is empty + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.info.GetExtensions() + assert.NotNil(t, result) + if tt.expectEmpty { + assert.Equal(t, 0, result.Len()) + } + }) + } +} + +// Contact getter tests + +func TestContact_GetName_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + contact *Contact + expected string + }{ + { + name: "nil contact returns empty", + contact: nil, + expected: "", + }, + { + name: "nil name returns empty", + contact: &Contact{}, + expected: "", + }, + { + name: "returns name", + contact: &Contact{Name: pointer.From("John Doe")}, + expected: "John Doe", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.contact.GetName()) + }) + } +} + +func TestContact_GetURL_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + contact *Contact + expected string + }{ + { + name: "nil contact returns empty", + contact: nil, + expected: "", + }, + { + name: "nil url returns empty", + contact: &Contact{}, + expected: "", + }, + { + name: "returns url", + contact: &Contact{URL: pointer.From("https://example.com")}, + expected: "https://example.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.contact.GetURL()) + }) + } +} + +func TestContact_GetEmail_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + contact *Contact + expected string + }{ + { + name: "nil contact returns empty", + contact: nil, + expected: "", + }, + { + name: "nil email returns empty", + contact: &Contact{}, + expected: "", + }, + { + name: "returns email", + contact: &Contact{Email: pointer.From("test@example.com")}, + expected: "test@example.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.contact.GetEmail()) + }) + } +} + +func TestContact_GetExtensions_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + contact *Contact + expectEmpty bool + }{ + { + name: "nil contact returns empty", + contact: nil, + expectEmpty: true, + }, + { + name: "nil extensions returns empty", + contact: &Contact{}, + expectEmpty: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.contact.GetExtensions() + assert.NotNil(t, result) + assert.Equal(t, 0, result.Len()) + }) + } +} + +// License getter tests + +func TestLicense_GetName_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + license *License + expected string + }{ + { + name: "nil license returns empty", + license: nil, + expected: "", + }, + { + name: "returns name", + license: &License{Name: "MIT"}, + expected: "MIT", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.license.GetName()) + }) + } +} + +func TestLicense_GetIdentifier_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + license *License + expected string + }{ + { + name: "nil license returns empty", + license: nil, + expected: "", + }, + { + name: "nil identifier returns empty", + license: &License{Name: "MIT"}, + expected: "", + }, + { + name: "returns identifier", + license: &License{Name: "MIT", Identifier: pointer.From("MIT")}, + expected: "MIT", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.license.GetIdentifier()) + }) + } +} + +func TestLicense_GetURL_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + license *License + expected string + }{ + { + name: "nil license returns empty", + license: nil, + expected: "", + }, + { + name: "nil url returns empty", + license: &License{}, + expected: "", + }, + { + name: "returns url", + license: &License{URL: pointer.From("https://opensource.org/licenses/MIT")}, + expected: "https://opensource.org/licenses/MIT", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.license.GetURL()) + }) + } +} + +func TestLicense_GetExtensions_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + license *License + expectEmpty bool + }{ + { + name: "nil license returns empty", + license: nil, + expectEmpty: true, + }, + { + name: "nil extensions returns empty", + license: &License{}, + expectEmpty: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.license.GetExtensions() + assert.NotNil(t, result) + assert.Equal(t, 0, result.Len()) + }) + } +} + +// Operation getter tests + +func TestOperation_GetOperationID_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *Operation + expected string + }{ + { + name: "nil operation returns empty", + op: nil, + expected: "", + }, + { + name: "nil operationID returns empty", + op: &Operation{}, + expected: "", + }, + { + name: "returns operationID", + op: &Operation{OperationID: pointer.From("getUser")}, + expected: "getUser", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetOperationID()) + }) + } +} + +func TestOperation_GetSummary_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *Operation + expected string + }{ + { + name: "nil operation returns empty", + op: nil, + expected: "", + }, + { + name: "nil summary returns empty", + op: &Operation{}, + expected: "", + }, + { + name: "returns summary", + op: &Operation{Summary: pointer.From("Get a user")}, + expected: "Get a user", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetSummary()) + }) + } +} + +func TestOperation_GetDescription_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *Operation + expected string + }{ + { + name: "nil operation returns empty", + op: nil, + expected: "", + }, + { + name: "nil description returns empty", + op: &Operation{}, + expected: "", + }, + { + name: "returns description", + op: &Operation{Description: pointer.From("Get user by ID")}, + expected: "Get user by ID", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetDescription()) + }) + } +} + +func TestOperation_GetDeprecated_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *Operation + expected bool + }{ + { + name: "nil operation returns false", + op: nil, + expected: false, + }, + { + name: "nil deprecated returns false", + op: &Operation{}, + expected: false, + }, + { + name: "returns deprecated true", + op: &Operation{Deprecated: pointer.From(true)}, + expected: true, + }, + { + name: "returns deprecated false", + op: &Operation{Deprecated: pointer.From(false)}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetDeprecated()) + }) + } +} + +func TestOperation_GetTags_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *Operation + expected []string + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "empty tags returns nil", + op: &Operation{}, + expected: nil, + }, + { + name: "returns tags", + op: &Operation{Tags: []string{"users", "admin"}}, + expected: []string{"users", "admin"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetTags()) + }) + } +} + +func TestOperation_GetServers_Success(t *testing.T) { + t.Parallel() + + servers := []*Server{{URL: "https://api.example.com"}} + tests := []struct { + name string + op *Operation + expected []*Server + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "empty servers returns nil", + op: &Operation{}, + expected: nil, + }, + { + name: "returns servers", + op: &Operation{Servers: servers}, + expected: servers, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetServers()) + }) + } +} + +func TestOperation_GetSecurity_Success(t *testing.T) { + t.Parallel() + + security := []*SecurityRequirement{NewSecurityRequirement()} + tests := []struct { + name string + op *Operation + expected []*SecurityRequirement + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "empty security returns nil", + op: &Operation{}, + expected: nil, + }, + { + name: "returns security", + op: &Operation{Security: security}, + expected: security, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetSecurity()) + }) + } +} + +func TestOperation_GetParameters_Success(t *testing.T) { + t.Parallel() + + params := []*ReferencedParameter{{}} + tests := []struct { + name string + op *Operation + expected []*ReferencedParameter + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "empty params returns nil", + op: &Operation{}, + expected: nil, + }, + { + name: "returns parameters", + op: &Operation{Parameters: params}, + expected: params, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetParameters()) + }) + } +} + +func TestOperation_GetRequestBody_Success(t *testing.T) { + t.Parallel() + + body := &ReferencedRequestBody{} + tests := []struct { + name string + op *Operation + expected *ReferencedRequestBody + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "nil request body returns nil", + op: &Operation{}, + expected: nil, + }, + { + name: "returns request body", + op: &Operation{RequestBody: body}, + expected: body, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetRequestBody()) + }) + } +} + +func TestOperation_GetCallbacks_Success(t *testing.T) { + t.Parallel() + + callbacks := sequencedmap.New[string, *ReferencedCallback]() + tests := []struct { + name string + op *Operation + expected *sequencedmap.Map[string, *ReferencedCallback] + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "nil callbacks returns nil", + op: &Operation{}, + expected: nil, + }, + { + name: "returns callbacks", + op: &Operation{Callbacks: callbacks}, + expected: callbacks, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetCallbacks()) + }) + } +} + +func TestOperation_GetExternalDocs_Success(t *testing.T) { + t.Parallel() + + docs := &oas3.ExternalDocumentation{} + tests := []struct { + name string + op *Operation + expected *oas3.ExternalDocumentation + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "nil docs returns nil", + op: &Operation{}, + expected: nil, + }, + { + name: "returns external docs", + op: &Operation{ExternalDocs: docs}, + expected: docs, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetExternalDocs()) + }) + } +} + +func TestOperation_GetExtensions_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *Operation + expectEmpty bool + }{ + { + name: "nil operation returns empty", + op: nil, + expectEmpty: true, + }, + { + name: "nil extensions returns empty", + op: &Operation{}, + expectEmpty: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.op.GetExtensions() + assert.NotNil(t, result) + assert.Equal(t, 0, result.Len()) + }) + } +} + +func TestOperation_IsDeprecated_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *Operation + expected bool + }{ + { + name: "returns false when nil deprecated", + op: &Operation{}, + expected: false, + }, + { + name: "returns true when deprecated", + op: &Operation{Deprecated: pointer.From(true)}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.IsDeprecated()) + }) + } +} + +// Callback tests + +func TestNewCallback_Success(t *testing.T) { + t.Parallel() + + callback := NewCallback() + assert.NotNil(t, callback) + assert.NotNil(t, callback.Map) + assert.Equal(t, 0, callback.Len()) +} + +func TestCallback_Len_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + callback *Callback + expected int + }{ + { + name: "nil callback returns 0", + callback: nil, + expected: 0, + }, + { + name: "nil map returns 0", + callback: &Callback{}, + expected: 0, + }, + { + name: "empty map returns 0", + callback: NewCallback(), + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.callback.Len()) + }) + } +} + +func TestCallback_GetExtensions_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + callback *Callback + expectEmpty bool + }{ + { + name: "nil callback returns empty", + callback: nil, + expectEmpty: true, + }, + { + name: "nil extensions returns empty", + callback: &Callback{}, + expectEmpty: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.callback.GetExtensions() + assert.NotNil(t, result) + assert.Equal(t, 0, result.Len()) + }) + } +} + +// SerializationStyle tests + +func TestSerializationStyle_String_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + style SerializationStyle + expected string + }{ + { + name: "simple", + style: SerializationStyleSimple, + expected: "simple", + }, + { + name: "form", + style: SerializationStyleForm, + expected: "form", + }, + { + name: "label", + style: SerializationStyleLabel, + expected: "label", + }, + { + name: "matrix", + style: SerializationStyleMatrix, + expected: "matrix", + }, + { + name: "spaceDelimited", + style: SerializationStyleSpaceDelimited, + expected: "spaceDelimited", + }, + { + name: "pipeDelimited", + style: SerializationStylePipeDelimited, + expected: "pipeDelimited", + }, + { + name: "deepObject", + style: SerializationStyleDeepObject, + expected: "deepObject", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.style.String()) + }) + } +} diff --git a/openapi/links_validate_test.go b/openapi/links_validate_test.go index 8c8ffb2..ce2e2fe 100644 --- a/openapi/links_validate_test.go +++ b/openapi/links_validate_test.go @@ -437,3 +437,58 @@ description: Empty parameters map errs := link.Validate(t.Context(), validation.WithContextObject(openAPIDoc)) require.Empty(t, errs, "Expected no validation errors for empty parameters") } + +func TestLink_Getters_Success(t *testing.T) { + t.Parallel() + + yml := ` +operationId: getUserById +operationRef: '#/paths/~1users~1{id}/get' +description: Get user by ID +parameters: + id: '$response.body#/id' +requestBody: '$response.body#/user' +server: + url: https://api.example.com +x-custom: value +` + var link openapi.Link + + _, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &link) + require.NoError(t, err) + + require.Equal(t, "getUserById", link.GetOperationID(), "GetOperationID should return correct value") + require.Equal(t, "#/paths/~1users~1{id}/get", link.GetOperationRef(), "GetOperationRef should return correct value") + require.Equal(t, "Get user by ID", link.GetDescription(), "GetDescription should return correct value") + require.NotNil(t, link.GetParameters(), "GetParameters should return non-nil") + require.NotNil(t, link.GetRequestBody(), "GetRequestBody should return non-nil") + require.NotNil(t, link.GetServer(), "GetServer should return non-nil") + require.NotNil(t, link.GetExtensions(), "GetExtensions should return non-nil") + require.Equal(t, "https://api.example.com", link.GetServer().GetURL(), "GetServer should return correct URL") +} + +func TestLink_Getters_NilLink(t *testing.T) { + t.Parallel() + + var link *openapi.Link + + require.Empty(t, link.GetOperationID(), "GetOperationID should return empty string for nil") + require.Empty(t, link.GetOperationRef(), "GetOperationRef should return empty string for nil") + require.Empty(t, link.GetDescription(), "GetDescription should return empty string for nil") + require.Nil(t, link.GetParameters(), "GetParameters should return nil for nil link") + require.Nil(t, link.GetRequestBody(), "GetRequestBody should return nil for nil link") + require.Nil(t, link.GetServer(), "GetServer should return nil for nil link") + require.NotNil(t, link.GetExtensions(), "GetExtensions should return empty extensions for nil link") +} + +func TestLink_ResolveOperation(t *testing.T) { + t.Parallel() + + link := &openapi.Link{ + OperationID: pointer.From("getUserById"), + } + + op, err := link.ResolveOperation(t.Context()) + require.NoError(t, err, "ResolveOperation should not error") + require.Nil(t, op, "ResolveOperation returns nil for now (TODO)") +} diff --git a/openapi/mediatype_getters_test.go b/openapi/mediatype_getters_test.go new file mode 100644 index 0000000..55e3f7f --- /dev/null +++ b/openapi/mediatype_getters_test.go @@ -0,0 +1,116 @@ +package openapi + +import ( + "testing" + + "github.com/speakeasy-api/openapi/jsonschema/oas3" + "github.com/stretchr/testify/assert" +) + +func TestMediaType_GetItemSchema_Nil_Success(t *testing.T) { + t.Parallel() + + var m *MediaType + result := m.GetItemSchema() + + assert.Nil(t, result, "GetItemSchema on nil should return nil") +} + +func TestMediaType_GetItemSchema_Set_Success(t *testing.T) { + t.Parallel() + + schema := &oas3.JSONSchema[oas3.Referenceable]{} + m := &MediaType{ + ItemSchema: schema, + } + result := m.GetItemSchema() + + assert.Equal(t, schema, result, "GetItemSchema should return ItemSchema") +} + +func TestMediaType_GetPrefixEncoding_Nil_Success(t *testing.T) { + t.Parallel() + + var m *MediaType + result := m.GetPrefixEncoding() + + assert.Nil(t, result, "GetPrefixEncoding on nil should return nil") +} + +func TestMediaType_GetPrefixEncoding_Set_Success(t *testing.T) { + t.Parallel() + + enc := []*Encoding{{}, {}} + m := &MediaType{ + PrefixEncoding: enc, + } + result := m.GetPrefixEncoding() + + assert.Equal(t, enc, result, "GetPrefixEncoding should return PrefixEncoding") +} + +func TestMediaType_GetItemEncoding_Nil_Success(t *testing.T) { + t.Parallel() + + var m *MediaType + result := m.GetItemEncoding() + + assert.Nil(t, result, "GetItemEncoding on nil should return nil") +} + +func TestMediaType_GetItemEncoding_Set_Success(t *testing.T) { + t.Parallel() + + enc := &Encoding{} + m := &MediaType{ + ItemEncoding: enc, + } + result := m.GetItemEncoding() + + assert.Equal(t, enc, result, "GetItemEncoding should return ItemEncoding") +} + +func TestMediaType_GetSchema_Nil_Success(t *testing.T) { + t.Parallel() + + var m *MediaType + result := m.GetSchema() + + assert.Nil(t, result, "GetSchema on nil should return nil") +} + +func TestMediaType_GetEncoding_Nil_Success(t *testing.T) { + t.Parallel() + + var m *MediaType + result := m.GetEncoding() + + assert.Nil(t, result, "GetEncoding on nil should return nil") +} + +func TestMediaType_GetExamples_Nil_Success(t *testing.T) { + t.Parallel() + + var m *MediaType + result := m.GetExamples() + + assert.Nil(t, result, "GetExamples on nil should return nil") +} + +func TestMediaType_GetExtensions_Nil_Success(t *testing.T) { + t.Parallel() + + var m *MediaType + result := m.GetExtensions() + + assert.NotNil(t, result, "GetExtensions on nil should return empty extensions") +} + +func TestMediaType_GetExample_Nil_Success(t *testing.T) { + t.Parallel() + + var m *MediaType + result := m.GetExample() + + assert.Nil(t, result, "GetExample on nil should return nil") +} diff --git a/openapi/nil_getters_test.go b/openapi/nil_getters_test.go new file mode 100644 index 0000000..1f8ffd5 --- /dev/null +++ b/openapi/nil_getters_test.go @@ -0,0 +1,211 @@ +package openapi_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/openapi" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestComponents_Nil(t *testing.T) { + t.Parallel() + + var c *openapi.Components + + assert.Nil(t, c.GetSchemas(), "nil Components should return nil for GetSchemas") + assert.Nil(t, c.GetResponses(), "nil Components should return nil for GetResponses") + assert.Nil(t, c.GetParameters(), "nil Components should return nil for GetParameters") + assert.Nil(t, c.GetExamples(), "nil Components should return nil for GetExamples") + assert.Nil(t, c.GetRequestBodies(), "nil Components should return nil for GetRequestBodies") + assert.Nil(t, c.GetHeaders(), "nil Components should return nil for GetHeaders") + assert.Nil(t, c.GetSecuritySchemes(), "nil Components should return nil for GetSecuritySchemes") + assert.Nil(t, c.GetLinks(), "nil Components should return nil for GetLinks") + assert.Nil(t, c.GetCallbacks(), "nil Components should return nil for GetCallbacks") + assert.Nil(t, c.GetPathItems(), "nil Components should return nil for GetPathItems") + require.NotNil(t, c.GetExtensions(), "nil Components should return empty extensions") +} + +func TestEncoding_GetExtensions_Nil(t *testing.T) { + t.Parallel() + + var e *openapi.Encoding + exts := e.GetExtensions() + require.NotNil(t, exts, "nil Encoding should return empty extensions") +} + +func TestExample_Nil(t *testing.T) { + t.Parallel() + + var e *openapi.Example + + assert.Empty(t, e.GetSummary(), "nil Example should return empty string for GetSummary") + assert.Empty(t, e.GetDescription(), "nil Example should return empty string for GetDescription") + assert.Nil(t, e.GetValue(), "nil Example should return nil for GetValue") + assert.Empty(t, e.GetExternalValue(), "nil Example should return empty string for GetExternalValue") + assert.Nil(t, e.GetDataValue(), "nil Example should return nil for GetDataValue") + require.NotNil(t, e.GetExtensions(), "nil Example should return empty extensions") +} + +func TestHeader_Nil(t *testing.T) { + t.Parallel() + + var h *openapi.Header + + assert.Empty(t, h.GetDescription(), "nil Header should return empty string for GetDescription") + assert.False(t, h.GetRequired(), "nil Header should return false for GetRequired") + assert.False(t, h.GetDeprecated(), "nil Header should return false for GetDeprecated") + require.NotNil(t, h.GetExtensions(), "nil Header should return empty extensions") +} + +func TestMediaType_Nil(t *testing.T) { + t.Parallel() + + var m *openapi.MediaType + + assert.Nil(t, m.GetSchema(), "nil MediaType should return nil for GetSchema") + assert.Nil(t, m.GetExample(), "nil MediaType should return nil for GetExample") + assert.Nil(t, m.GetExamples(), "nil MediaType should return nil for GetExamples") + assert.Nil(t, m.GetEncoding(), "nil MediaType should return nil for GetEncoding") + require.NotNil(t, m.GetExtensions(), "nil MediaType should return empty extensions") +} + +func TestOperation_Nil(t *testing.T) { + t.Parallel() + + var o *openapi.Operation + + assert.Nil(t, o.GetTags(), "nil Operation should return nil for GetTags") + assert.Empty(t, o.GetSummary(), "nil Operation should return empty string for GetSummary") + assert.Empty(t, o.GetDescription(), "nil Operation should return empty string for GetDescription") + assert.Nil(t, o.GetExternalDocs(), "nil Operation should return nil for GetExternalDocs") + assert.Empty(t, o.GetOperationID(), "nil Operation should return empty string for GetOperationID") + assert.Nil(t, o.GetParameters(), "nil Operation should return nil for GetParameters") + assert.Nil(t, o.GetRequestBody(), "nil Operation should return nil for GetRequestBody") + assert.Nil(t, o.GetResponses(), "nil Operation should return nil for GetResponses") + assert.Nil(t, o.GetCallbacks(), "nil Operation should return nil for GetCallbacks") + assert.False(t, o.GetDeprecated(), "nil Operation should return false for GetDeprecated") + assert.Nil(t, o.GetSecurity(), "nil Operation should return nil for GetSecurity") + assert.Nil(t, o.GetServers(), "nil Operation should return nil for GetServers") + require.NotNil(t, o.GetExtensions(), "nil Operation should return empty extensions") +} + +func TestParameter_Nil(t *testing.T) { + t.Parallel() + + var p *openapi.Parameter + + assert.Empty(t, p.GetName(), "nil Parameter should return empty string for GetName") + assert.Empty(t, p.GetIn(), "nil Parameter should return empty for GetIn") + assert.Empty(t, p.GetDescription(), "nil Parameter should return empty string for GetDescription") + assert.False(t, p.GetRequired(), "nil Parameter should return false for GetRequired") + assert.False(t, p.GetDeprecated(), "nil Parameter should return false for GetDeprecated") + assert.False(t, p.GetAllowEmptyValue(), "nil Parameter should return false for GetAllowEmptyValue") + require.NotNil(t, p.GetExtensions(), "nil Parameter should return empty extensions") +} + +func TestRequestBody_Nil(t *testing.T) { + t.Parallel() + + var r *openapi.RequestBody + + assert.Empty(t, r.GetDescription(), "nil RequestBody should return empty string for GetDescription") + assert.Nil(t, r.GetContent(), "nil RequestBody should return nil for GetContent") + assert.False(t, r.GetRequired(), "nil RequestBody should return false for GetRequired") +} + +func TestResponse_Nil(t *testing.T) { + t.Parallel() + + var r *openapi.Response + + assert.Empty(t, r.GetDescription(), "nil Response should return empty string for GetDescription") + assert.Nil(t, r.GetHeaders(), "nil Response should return nil for GetHeaders") + assert.Nil(t, r.GetContent(), "nil Response should return nil for GetContent") + assert.Nil(t, r.GetLinks(), "nil Response should return nil for GetLinks") + require.NotNil(t, r.GetExtensions(), "nil Response should return empty extensions") +} + +func TestResponses_GetExtensions_Nil(t *testing.T) { + t.Parallel() + + var r *openapi.Responses + exts := r.GetExtensions() + require.NotNil(t, exts, "nil Responses should return empty extensions") +} + +func TestServer_Nil(t *testing.T) { + t.Parallel() + + var s *openapi.Server + + assert.Empty(t, s.GetURL(), "nil Server should return empty string for GetURL") + assert.Empty(t, s.GetDescription(), "nil Server should return empty string for GetDescription") + assert.Nil(t, s.GetVariables(), "nil Server should return nil for GetVariables") + require.NotNil(t, s.GetExtensions(), "nil Server should return empty extensions") +} + +func TestServerVariable_Nil(t *testing.T) { + t.Parallel() + + var s *openapi.ServerVariable + + assert.Nil(t, s.GetEnum(), "nil ServerVariable should return nil for GetEnum") + assert.Empty(t, s.GetDefault(), "nil ServerVariable should return empty string for GetDefault") + assert.Empty(t, s.GetDescription(), "nil ServerVariable should return empty string for GetDescription") +} + +func TestTag_Nil(t *testing.T) { + t.Parallel() + + var tag *openapi.Tag + + assert.Empty(t, tag.GetName(), "nil Tag should return empty string for GetName") + assert.Empty(t, tag.GetDescription(), "nil Tag should return empty string for GetDescription") + assert.Nil(t, tag.GetExternalDocs(), "nil Tag should return nil for GetExternalDocs") + require.NotNil(t, tag.GetExtensions(), "nil Tag should return empty extensions") +} + +func TestSecurityScheme_Nil(t *testing.T) { + t.Parallel() + + var s *openapi.SecurityScheme + + assert.Empty(t, s.GetType(), "nil SecurityScheme should return empty for GetType") + assert.Empty(t, s.GetDescription(), "nil SecurityScheme should return empty string for GetDescription") + assert.Empty(t, s.GetName(), "nil SecurityScheme should return empty string for GetName") + assert.Empty(t, s.GetIn(), "nil SecurityScheme should return empty for GetIn") + assert.Empty(t, s.GetScheme(), "nil SecurityScheme should return empty string for GetScheme") + assert.Empty(t, s.GetBearerFormat(), "nil SecurityScheme should return empty string for GetBearerFormat") + assert.Nil(t, s.GetFlows(), "nil SecurityScheme should return nil for GetFlows") + assert.Empty(t, s.GetOpenIdConnectUrl(), "nil SecurityScheme should return empty string for GetOpenIdConnectUrl") + assert.Empty(t, s.GetOAuth2MetadataUrl(), "nil SecurityScheme should return empty string for GetOAuth2MetadataUrl") + assert.False(t, s.GetDeprecated(), "nil SecurityScheme should return false for GetDeprecated") + require.NotNil(t, s.GetExtensions(), "nil SecurityScheme should return empty extensions") +} + +func TestOAuthFlows_Nil(t *testing.T) { + t.Parallel() + + var o *openapi.OAuthFlows + + assert.Nil(t, o.GetImplicit(), "nil OAuthFlows should return nil for GetImplicit") + assert.Nil(t, o.GetPassword(), "nil OAuthFlows should return nil for GetPassword") + assert.Nil(t, o.GetClientCredentials(), "nil OAuthFlows should return nil for GetClientCredentials") + assert.Nil(t, o.GetAuthorizationCode(), "nil OAuthFlows should return nil for GetAuthorizationCode") + assert.Nil(t, o.GetDeviceAuthorization(), "nil OAuthFlows should return nil for GetDeviceAuthorization") + require.NotNil(t, o.GetExtensions(), "nil OAuthFlows should return empty extensions") +} + +func TestOAuthFlow_Nil(t *testing.T) { + t.Parallel() + + var o *openapi.OAuthFlow + + assert.Empty(t, o.GetAuthorizationURL(), "nil OAuthFlow should return empty string for GetAuthorizationURL") + assert.Empty(t, o.GetDeviceAuthorizationURL(), "nil OAuthFlow should return empty string for GetDeviceAuthorizationURL") + assert.Empty(t, o.GetTokenURL(), "nil OAuthFlow should return empty string for GetTokenURL") + assert.Empty(t, o.GetRefreshURL(), "nil OAuthFlow should return empty string for GetRefreshURL") + assert.Nil(t, o.GetScopes(), "nil OAuthFlow should return nil for GetScopes") + require.NotNil(t, o.GetExtensions(), "nil OAuthFlow should return empty extensions") +} diff --git a/openapi/operation.go b/openapi/operation.go index da02ea9..179f22a 100644 --- a/openapi/operation.go +++ b/openapi/operation.go @@ -123,6 +123,9 @@ func (o *Operation) GetRequestBody() *ReferencedRequestBody { // GetResponses returns the value of the Responses field. Returns nil if not set. func (o *Operation) GetResponses() *Responses { + if o == nil { + return nil + } return &o.Responses } diff --git a/openapi/paths_getters_test.go b/openapi/paths_getters_test.go new file mode 100644 index 0000000..142ec5b --- /dev/null +++ b/openapi/paths_getters_test.go @@ -0,0 +1,376 @@ +package openapi_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/extensions" + "github.com/speakeasy-api/openapi/openapi" + "github.com/speakeasy-api/openapi/sequencedmap" + "github.com/stretchr/testify/assert" +) + +func TestHTTPMethod_Is_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + method openapi.HTTPMethod + input string + expected bool + }{ + { + name: "GET matches get lowercase", + method: openapi.HTTPMethodGet, + input: "get", + expected: true, + }, + { + name: "GET matches GET uppercase", + method: openapi.HTTPMethodGet, + input: "GET", + expected: true, + }, + { + name: "GET matches Get mixed case", + method: openapi.HTTPMethodGet, + input: "Get", + expected: true, + }, + { + name: "POST does not match GET", + method: openapi.HTTPMethodPost, + input: "GET", + expected: false, + }, + { + name: "POST matches POST", + method: openapi.HTTPMethodPost, + input: "POST", + expected: true, + }, + { + name: "QUERY matches QUERY", + method: openapi.HTTPMethodQuery, + input: "query", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.method.Is(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestPaths_GetExtensions_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + paths *openapi.Paths + hasContent bool + }{ + { + name: "nil paths returns empty extensions", + paths: nil, + hasContent: false, + }, + { + name: "paths without extensions returns empty extensions", + paths: &openapi.Paths{ + Map: sequencedmap.New[string, *openapi.ReferencedPathItem](), + Extensions: nil, + }, + hasContent: false, + }, + { + name: "paths with extensions returns extensions", + paths: &openapi.Paths{ + Map: sequencedmap.New[string, *openapi.ReferencedPathItem](), + Extensions: extensions.New(), + }, + hasContent: false, // Empty extensions + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.paths.GetExtensions() + assert.NotNil(t, result, "GetExtensions should never return nil") + }) + } +} + +func TestPathItem_Query_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pathItem *openapi.PathItem + expected *openapi.Operation + }{ + { + name: "nil path item returns nil", + pathItem: nil, + expected: nil, + }, + { + name: "path item without query returns nil", + pathItem: &openapi.PathItem{ + Map: sequencedmap.New[openapi.HTTPMethod, *openapi.Operation](), + }, + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.pathItem.Query() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestPathItem_GetAdditionalOperations_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pathItem *openapi.PathItem + expected *sequencedmap.Map[string, *openapi.Operation] + }{ + { + name: "nil path item returns nil", + pathItem: nil, + expected: nil, + }, + { + name: "path item without additional operations returns nil", + pathItem: &openapi.PathItem{ + Map: sequencedmap.New[openapi.HTTPMethod, *openapi.Operation](), + }, + expected: nil, + }, + { + name: "path item with additional operations returns them", + pathItem: &openapi.PathItem{ + Map: sequencedmap.New[openapi.HTTPMethod, *openapi.Operation](), + AdditionalOperations: sequencedmap.New[string, *openapi.Operation](), + }, + expected: sequencedmap.New[string, *openapi.Operation](), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.pathItem.GetAdditionalOperations() + if tt.expected == nil { + assert.Nil(t, result) + } else { + assert.NotNil(t, result) + } + }) + } +} + +func TestPathItem_GetServers_Success(t *testing.T) { + t.Parallel() + + server := &openapi.Server{URL: "https://api.example.com"} + + tests := []struct { + name string + pathItem *openapi.PathItem + expected []*openapi.Server + }{ + { + name: "nil path item returns nil", + pathItem: nil, + expected: nil, + }, + { + name: "path item without servers returns nil", + pathItem: &openapi.PathItem{ + Map: sequencedmap.New[openapi.HTTPMethod, *openapi.Operation](), + }, + expected: nil, + }, + { + name: "path item with servers returns them", + pathItem: &openapi.PathItem{ + Map: sequencedmap.New[openapi.HTTPMethod, *openapi.Operation](), + Servers: []*openapi.Server{server}, + }, + expected: []*openapi.Server{server}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.pathItem.GetServers() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestPathItem_GetParameters_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pathItem *openapi.PathItem + expected []*openapi.ReferencedParameter + }{ + { + name: "nil path item returns nil", + pathItem: nil, + expected: nil, + }, + { + name: "path item without parameters returns nil", + pathItem: &openapi.PathItem{ + Map: sequencedmap.New[openapi.HTTPMethod, *openapi.Operation](), + }, + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.pathItem.GetParameters() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestPathItem_GetExtensions_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pathItem *openapi.PathItem + hasContent bool + }{ + { + name: "nil path item returns empty extensions", + pathItem: nil, + hasContent: false, + }, + { + name: "path item without extensions returns empty extensions", + pathItem: &openapi.PathItem{ + Map: sequencedmap.New[openapi.HTTPMethod, *openapi.Operation](), + Extensions: nil, + }, + hasContent: false, + }, + { + name: "path item with extensions returns extensions", + pathItem: &openapi.PathItem{ + Map: sequencedmap.New[openapi.HTTPMethod, *openapi.Operation](), + Extensions: extensions.New(), + }, + hasContent: false, // Empty extensions + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.pathItem.GetExtensions() + assert.NotNil(t, result, "GetExtensions should never return nil") + }) + } +} + +func TestParameterIn_String_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + param openapi.ParameterIn + expected string + }{ + { + name: "query parameter", + param: openapi.ParameterInQuery, + expected: "query", + }, + { + name: "header parameter", + param: openapi.ParameterInHeader, + expected: "header", + }, + { + name: "path parameter", + param: openapi.ParameterInPath, + expected: "path", + }, + { + name: "cookie parameter", + param: openapi.ParameterInCookie, + expected: "cookie", + }, + { + name: "querystring parameter", + param: openapi.ParameterInQueryString, + expected: "querystring", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.param.String() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestParameter_GetContent_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + parameter *openapi.Parameter + expected *sequencedmap.Map[string, *openapi.MediaType] + }{ + { + name: "nil parameter returns nil", + parameter: nil, + expected: nil, + }, + { + name: "parameter without content returns nil", + parameter: &openapi.Parameter{}, + expected: nil, + }, + { + name: "parameter with content returns it", + parameter: &openapi.Parameter{ + Content: sequencedmap.New[string, *openapi.MediaType](), + }, + expected: sequencedmap.New[string, *openapi.MediaType](), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.parameter.GetContent() + if tt.expected == nil { + assert.Nil(t, result) + } else { + assert.NotNil(t, result) + } + }) + } +} diff --git a/openapi/reference_factory_test.go b/openapi/reference_factory_test.go new file mode 100644 index 0000000..e84f994 --- /dev/null +++ b/openapi/reference_factory_test.go @@ -0,0 +1,227 @@ +package openapi_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/openapi" + "github.com/speakeasy-api/openapi/pointer" + "github.com/speakeasy-api/openapi/references" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewReferencedPathItemFromRef_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/components/pathItems/MyPath") + result := openapi.NewReferencedPathItemFromRef(ref) + + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsReference(), "should be a reference") + assert.Equal(t, ref, result.GetReference(), "reference should match") + assert.Nil(t, result.GetObject(), "object should be nil for unresolved reference") +} + +func TestNewReferencedPathItemFromPathItem_Success(t *testing.T) { + t.Parallel() + + pathItem := &openapi.PathItem{ + Summary: pointer.From("Test path item"), + } + result := openapi.NewReferencedPathItemFromPathItem(pathItem) + + require.NotNil(t, result, "result should not be nil") + assert.False(t, result.IsReference(), "should not be a reference") + assert.Equal(t, pathItem, result.GetObject(), "object should match") +} + +func TestNewReferencedExampleFromRef_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/components/examples/MyExample") + result := openapi.NewReferencedExampleFromRef(ref) + + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsReference(), "should be a reference") + assert.Equal(t, ref, result.GetReference(), "reference should match") +} + +func TestNewReferencedExampleFromExample_Success(t *testing.T) { + t.Parallel() + + example := &openapi.Example{ + Summary: pointer.From("Test example"), + } + result := openapi.NewReferencedExampleFromExample(example) + + require.NotNil(t, result, "result should not be nil") + assert.False(t, result.IsReference(), "should not be a reference") + assert.Equal(t, example, result.GetObject(), "object should match") +} + +func TestNewReferencedParameterFromRef_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/components/parameters/MyParam") + result := openapi.NewReferencedParameterFromRef(ref) + + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsReference(), "should be a reference") + assert.Equal(t, ref, result.GetReference(), "reference should match") +} + +func TestNewReferencedParameterFromParameter_Success(t *testing.T) { + t.Parallel() + + param := &openapi.Parameter{ + Name: "testParam", + In: openapi.ParameterInQuery, + } + result := openapi.NewReferencedParameterFromParameter(param) + + require.NotNil(t, result, "result should not be nil") + assert.False(t, result.IsReference(), "should not be a reference") + assert.Equal(t, param, result.GetObject(), "object should match") +} + +func TestNewReferencedHeaderFromRef_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/components/headers/MyHeader") + result := openapi.NewReferencedHeaderFromRef(ref) + + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsReference(), "should be a reference") + assert.Equal(t, ref, result.GetReference(), "reference should match") +} + +func TestNewReferencedHeaderFromHeader_Success(t *testing.T) { + t.Parallel() + + header := &openapi.Header{ + Description: pointer.From("Test header"), + } + result := openapi.NewReferencedHeaderFromHeader(header) + + require.NotNil(t, result, "result should not be nil") + assert.False(t, result.IsReference(), "should not be a reference") + assert.Equal(t, header, result.GetObject(), "object should match") +} + +func TestNewReferencedRequestBodyFromRef_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/components/requestBodies/MyBody") + result := openapi.NewReferencedRequestBodyFromRef(ref) + + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsReference(), "should be a reference") + assert.Equal(t, ref, result.GetReference(), "reference should match") +} + +func TestNewReferencedRequestBodyFromRequestBody_Success(t *testing.T) { + t.Parallel() + + body := &openapi.RequestBody{ + Description: pointer.From("Test request body"), + } + result := openapi.NewReferencedRequestBodyFromRequestBody(body) + + require.NotNil(t, result, "result should not be nil") + assert.False(t, result.IsReference(), "should not be a reference") + assert.Equal(t, body, result.GetObject(), "object should match") +} + +func TestNewReferencedResponseFromRef_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/components/responses/MyResponse") + result := openapi.NewReferencedResponseFromRef(ref) + + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsReference(), "should be a reference") + assert.Equal(t, ref, result.GetReference(), "reference should match") +} + +func TestNewReferencedResponseFromResponse_Success(t *testing.T) { + t.Parallel() + + response := &openapi.Response{ + Description: "Test response", + } + result := openapi.NewReferencedResponseFromResponse(response) + + require.NotNil(t, result, "result should not be nil") + assert.False(t, result.IsReference(), "should not be a reference") + assert.Equal(t, response, result.GetObject(), "object should match") +} + +func TestNewReferencedCallbackFromRef_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/components/callbacks/MyCallback") + result := openapi.NewReferencedCallbackFromRef(ref) + + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsReference(), "should be a reference") + assert.Equal(t, ref, result.GetReference(), "reference should match") +} + +func TestNewReferencedCallbackFromCallback_Success(t *testing.T) { + t.Parallel() + + callback := &openapi.Callback{} + result := openapi.NewReferencedCallbackFromCallback(callback) + + require.NotNil(t, result, "result should not be nil") + assert.False(t, result.IsReference(), "should not be a reference") + assert.Equal(t, callback, result.GetObject(), "object should match") +} + +func TestNewReferencedLinkFromRef_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/components/links/MyLink") + result := openapi.NewReferencedLinkFromRef(ref) + + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsReference(), "should be a reference") + assert.Equal(t, ref, result.GetReference(), "reference should match") +} + +func TestNewReferencedLinkFromLink_Success(t *testing.T) { + t.Parallel() + + link := &openapi.Link{ + Description: pointer.From("Test link"), + } + result := openapi.NewReferencedLinkFromLink(link) + + require.NotNil(t, result, "result should not be nil") + assert.False(t, result.IsReference(), "should not be a reference") + assert.Equal(t, link, result.GetObject(), "object should match") +} + +func TestNewReferencedSecuritySchemeFromRef_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/components/securitySchemes/MyScheme") + result := openapi.NewReferencedSecuritySchemeFromRef(ref) + + require.NotNil(t, result, "result should not be nil") + assert.True(t, result.IsReference(), "should be a reference") + assert.Equal(t, ref, result.GetReference(), "reference should match") +} + +func TestNewReferencedSecuritySchemeFromSecurityScheme_Success(t *testing.T) { + t.Parallel() + + scheme := &openapi.SecurityScheme{ + Type: openapi.SecuritySchemeTypeAPIKey, + } + result := openapi.NewReferencedSecuritySchemeFromSecurityScheme(scheme) + + require.NotNil(t, result, "result should not be nil") + assert.False(t, result.IsReference(), "should not be a reference") + assert.Equal(t, scheme, result.GetObject(), "object should match") +} diff --git a/openapi/reference_getters_test.go b/openapi/reference_getters_test.go new file mode 100644 index 0000000..e1bcc0f --- /dev/null +++ b/openapi/reference_getters_test.go @@ -0,0 +1,41 @@ +package openapi_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/openapi" + "github.com/speakeasy-api/openapi/pointer" + "github.com/speakeasy-api/openapi/references" + "github.com/stretchr/testify/assert" +) + +func TestReference_GetResolvedObject_Nil_Success(t *testing.T) { + t.Parallel() + + var ref *openapi.ReferencedPathItem + result := ref.GetResolvedObject() + + assert.Nil(t, result, "GetResolvedObject on nil should return nil") +} + +func TestReference_GetResolvedObject_InlineObject_Success(t *testing.T) { + t.Parallel() + + pathItem := &openapi.PathItem{ + Summary: pointer.From("Test path item"), + } + ref := openapi.NewReferencedPathItemFromPathItem(pathItem) + result := ref.GetResolvedObject() + + assert.Equal(t, pathItem, result, "GetResolvedObject should return the inline object") +} + +func TestReference_GetResolvedObject_UnresolvedRef_Success(t *testing.T) { + t.Parallel() + + refStr := references.Reference("#/components/pathItems/UnresolvedPath") + ref := openapi.NewReferencedPathItemFromRef(refStr) + result := ref.GetResolvedObject() + + assert.Nil(t, result, "GetResolvedObject on unresolved ref should return nil") +} diff --git a/openapi/reference_validate_test.go b/openapi/reference_validate_test.go index cf6392f..4468ff5 100644 --- a/openapi/reference_validate_test.go +++ b/openapi/reference_validate_test.go @@ -132,6 +132,24 @@ description: A reference to the user example expectValid: false, errorMsg: "invalid reference JSON pointer", }, + { + name: "invalid reference - empty component name in schemas", + yaml: `$ref: '#/components/schemas/'`, + expectValid: false, + errorMsg: "component name cannot be empty", + }, + { + name: "invalid reference - empty component name in parameters", + yaml: `$ref: '#/components/parameters/'`, + expectValid: false, + errorMsg: "component name cannot be empty", + }, + { + name: "invalid reference - empty component name in responses", + yaml: `$ref: '#/components/responses/'`, + expectValid: false, + errorMsg: "component name cannot be empty", + }, } for _, tt := range tests { diff --git a/openapi/security_validate_test.go b/openapi/security_validate_test.go index 1e460b3..306ae1f 100644 --- a/openapi/security_validate_test.go +++ b/openapi/security_validate_test.go @@ -758,3 +758,96 @@ scopes: }) } } + +func TestSecurityScheme_Getters_Success(t *testing.T) { + t.Parallel() + + yml := ` +type: oauth2 +description: OAuth2 authentication +flows: + authorizationCode: + authorizationUrl: https://example.com/auth + tokenUrl: https://example.com/token + scopes: + read: Read access +openIdConnectUrl: https://example.com/.well-known/openid +oauth2MetadataUrl: https://example.com/.well-known/oauth +deprecated: true +x-custom: value +` + var securityScheme openapi.SecurityScheme + + validationErrs, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &securityScheme) + require.NoError(t, err) + require.Empty(t, validationErrs) + + require.Equal(t, openapi.SecuritySchemeTypeOAuth2, securityScheme.GetType(), "GetType should return correct value") + require.Equal(t, "OAuth2 authentication", securityScheme.GetDescription(), "GetDescription should return correct value") + require.NotNil(t, securityScheme.GetFlows(), "GetFlows should return non-nil") + require.Equal(t, "https://example.com/.well-known/openid", securityScheme.GetOpenIdConnectUrl(), "GetOpenIdConnectUrl should return correct value") + require.Equal(t, "https://example.com/.well-known/oauth", securityScheme.GetOAuth2MetadataUrl(), "GetOAuth2MetadataUrl should return correct value") + require.True(t, securityScheme.GetDeprecated(), "GetDeprecated should return true") + require.NotNil(t, securityScheme.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestSecurityScheme_Getters_HTTPScheme(t *testing.T) { + t.Parallel() + + yml := ` +type: http +scheme: bearer +bearerFormat: JWT +` + var securityScheme openapi.SecurityScheme + + validationErrs, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &securityScheme) + require.NoError(t, err) + require.Empty(t, validationErrs) + + require.Equal(t, "bearer", securityScheme.GetScheme(), "GetScheme should return correct value") + require.Equal(t, "JWT", securityScheme.GetBearerFormat(), "GetBearerFormat should return correct value") +} + +func TestSecurityScheme_Getters_Nil(t *testing.T) { + t.Parallel() + + var securityScheme *openapi.SecurityScheme + + require.Empty(t, securityScheme.GetType(), "GetType should return empty for nil") + require.Empty(t, securityScheme.GetDescription(), "GetDescription should return empty string for nil") + require.Empty(t, securityScheme.GetScheme(), "GetScheme should return empty string for nil") + require.Empty(t, securityScheme.GetBearerFormat(), "GetBearerFormat should return empty string for nil") + require.Nil(t, securityScheme.GetFlows(), "GetFlows should return nil for nil scheme") + require.Empty(t, securityScheme.GetOpenIdConnectUrl(), "GetOpenIdConnectUrl should return empty string for nil") + require.Empty(t, securityScheme.GetOAuth2MetadataUrl(), "GetOAuth2MetadataUrl should return empty string for nil") + require.False(t, securityScheme.GetDeprecated(), "GetDeprecated should return false for nil") + require.NotNil(t, securityScheme.GetExtensions(), "GetExtensions should return empty extensions for nil scheme") +} + +func TestOAuthFlows_Getters_Success(t *testing.T) { + t.Parallel() + + yml := ` +implicit: + authorizationUrl: https://example.com/auth + scopes: + read: Read access +x-custom: value +` + var flows openapi.OAuthFlows + + validationErrs, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &flows) + require.NoError(t, err) + require.Empty(t, validationErrs) + + require.NotNil(t, flows.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestOAuthFlows_Getters_Nil(t *testing.T) { + t.Parallel() + + var flows *openapi.OAuthFlows + + require.NotNil(t, flows.GetExtensions(), "GetExtensions should return empty extensions for nil flows") +} diff --git a/openapi/tag_kind_registry_test.go b/openapi/tag_kind_registry_test.go new file mode 100644 index 0000000..d30b570 --- /dev/null +++ b/openapi/tag_kind_registry_test.go @@ -0,0 +1,52 @@ +package openapi_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/openapi" + "github.com/stretchr/testify/assert" +) + +func TestTagKind_String_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + kind openapi.TagKind + expected string + }{ + { + name: "nav tag kind", + kind: openapi.TagKindNav, + expected: "nav", + }, + { + name: "badge tag kind", + kind: openapi.TagKindBadge, + expected: "badge", + }, + { + name: "audience tag kind", + kind: openapi.TagKindAudience, + expected: "audience", + }, + { + name: "custom tag kind", + kind: openapi.TagKind("custom"), + expected: "custom", + }, + { + name: "empty tag kind", + kind: openapi.TagKind(""), + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.kind.String() + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/overlay/format_test.go b/overlay/format_test.go new file mode 100644 index 0000000..0701b7f --- /dev/null +++ b/overlay/format_test.go @@ -0,0 +1,81 @@ +package overlay_test + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/speakeasy-api/openapi/overlay" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestOverlay_Format_Success(t *testing.T) { + t.Parallel() + + // Create a test overlay + o := &overlay.Overlay{ + Version: "1.0.0", + Info: overlay.Info{ + Title: "Test Overlay", + Version: "1.0.0", + }, + Actions: []overlay.Action{ + { + Target: "$.info.title", + Update: yaml.Node{ + Kind: yaml.ScalarNode, + Value: "New Title", + Tag: "!!str", + }, + }, + }, + } + + var buf bytes.Buffer + err := o.Format(&buf) + + require.NoError(t, err, "Format should not return error") + assert.Contains(t, buf.String(), "overlay:", "output should contain overlay key") + assert.Contains(t, buf.String(), "info:", "output should contain info key") + assert.Contains(t, buf.String(), "actions:", "output should contain actions key") +} + +func TestFormat_FileFormat_Success(t *testing.T) { + t.Parallel() + + // Create a temp directory for the test + tempDir := t.TempDir() + testFile := filepath.Join(tempDir, "test-overlay.yaml") + + // Create a simple overlay file + overlayContent := `overlay: "1.0.0" +info: + title: Test + version: "1.0" +actions: + - target: $.info + update: + title: NewTitle +` + err := os.WriteFile(testFile, []byte(overlayContent), 0o644) + require.NoError(t, err, "setup: should create test file") + + // Test Format function + err = overlay.Format(testFile) + require.NoError(t, err, "Format should succeed") + + // Verify file was reformatted (exists and is valid) + _, err = os.Stat(testFile) + require.NoError(t, err, "file should still exist after formatting") +} + +func TestFormat_InvalidPath_Error(t *testing.T) { + t.Parallel() + + err := overlay.Format("/nonexistent/path/overlay.yaml") + + require.Error(t, err, "Format should return error for non-existent file") +} diff --git a/overlay/utils_test.go b/overlay/utils_test.go new file mode 100644 index 0000000..8c96a0a --- /dev/null +++ b/overlay/utils_test.go @@ -0,0 +1,89 @@ +package overlay_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/overlay" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" +) + +func TestNewTargetSelector_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + path string + method string + expected string + }{ + { + name: "simple path and method", + path: "/users", + method: "get", + expected: `$["paths"]["/users"]["get"]`, + }, + { + name: "path with parameter", + path: "/users/{id}", + method: "patch", + expected: `$["paths"]["/users/{id}"]["patch"]`, + }, + { + name: "root path", + path: "/", + method: "post", + expected: `$["paths"]["/"]["post"]`, + }, + { + name: "nested path", + path: "/users/{userId}/orders/{orderId}", + method: "delete", + expected: `$["paths"]["/users/{userId}/orders/{orderId}"]["delete"]`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := overlay.NewTargetSelector(tt.path, tt.method) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestNewUpdateAction_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + path string + method string + update yaml.Node + expectedTarget string + }{ + { + name: "simple update action", + path: "/users", + method: "get", + update: yaml.Node{Kind: yaml.ScalarNode, Value: "test"}, + expectedTarget: `$["paths"]["/users"]["get"]`, + }, + { + name: "update action with path parameter", + path: "/users/{id}", + method: "put", + update: yaml.Node{Kind: yaml.MappingNode}, + expectedTarget: `$["paths"]["/users/{id}"]["put"]`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := overlay.NewUpdateAction(tt.path, tt.method, tt.update) + assert.Equal(t, tt.expectedTarget, result.Target) + assert.Equal(t, tt.update.Kind, result.Update.Kind) + }) + } +} diff --git a/references/reference_test.go b/references/reference_test.go index 1a9a4f4..980f359 100644 --- a/references/reference_test.go +++ b/references/reference_test.go @@ -206,7 +206,8 @@ func TestReference_String(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - result := string(tt.ref) + // Test String() method + result := tt.ref.String() assert.Equal(t, tt.expected, result) }) } diff --git a/swagger/core/paths_test.go b/swagger/core/paths_test.go new file mode 100644 index 0000000..0a4a370 --- /dev/null +++ b/swagger/core/paths_test.go @@ -0,0 +1,333 @@ +package core + +import ( + "testing" + + "github.com/speakeasy-api/openapi/marshaller" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func parseYAML(t *testing.T, yml string) *yaml.Node { + t.Helper() + var node yaml.Node + err := yaml.Unmarshal([]byte(yml), &node) + require.NoError(t, err) + return &node +} + +func TestNewPaths_Success(t *testing.T) { + t.Parallel() + + paths := NewPaths() + require.NotNil(t, paths, "NewPaths should return a non-nil paths") + require.NotNil(t, paths.Map, "paths.Map should be initialized") + assert.Equal(t, 0, paths.Len(), "newly created paths should be empty") +} + +func TestNewPathItem_Success(t *testing.T) { + t.Parallel() + + pathItem := NewPathItem() + require.NotNil(t, pathItem, "NewPathItem should return a non-nil path item") + require.NotNil(t, pathItem.Map, "pathItem.Map should be initialized") + assert.Equal(t, 0, pathItem.Len(), "newly created path item should be empty") +} + +func TestPaths_GetMapKeyNodeOrRoot_Success(t *testing.T) { + t.Parallel() + tests := []struct { + name string + yaml string + key string + }{ + { + name: "returns key node when path exists - first path", + yaml: ` +/pets: + get: + summary: List all pets +/users: + get: + summary: List all users +`, + key: "/pets", + }, + { + name: "returns key node when path exists - second path", + yaml: ` +/pets: + get: + summary: List all pets +/users: + get: + summary: List all users +`, + key: "/users", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var paths Paths + _, err := marshaller.UnmarshalCore(ctx, "", parseYAML(t, tt.yaml), &paths) + require.NoError(t, err, "unmarshal should succeed") + + rootNode := paths.GetRootNode() + result := paths.GetMapKeyNodeOrRoot(tt.key, rootNode) + require.NotNil(t, result, "result should not be nil") + assert.Equal(t, tt.key, result.Value, "should return correct key node") + }) + } +} + +func TestPaths_GetMapKeyNodeOrRoot_ReturnsRoot(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yaml string + key string + }{ + { + name: "returns root when path not found", + yaml: ` +/pets: + get: + summary: List all pets +`, + key: "/nonexistent", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var paths Paths + _, err := marshaller.UnmarshalCore(ctx, "", parseYAML(t, tt.yaml), &paths) + require.NoError(t, err, "unmarshal should succeed") + + rootNode := paths.GetRootNode() + result := paths.GetMapKeyNodeOrRoot(tt.key, rootNode) + assert.Equal(t, rootNode, result, "should return root node when key not found") + }) + } +} + +func TestPaths_GetMapKeyNodeOrRoot_Uninitialized(t *testing.T) { + t.Parallel() + + t.Run("returns root when paths is not initialized", func(t *testing.T) { + t.Parallel() + var paths Paths + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := paths.GetMapKeyNodeOrRoot("/pets", rootNode) + assert.Equal(t, rootNode, result, "should return root node when not initialized") + }) + + t.Run("returns root when RootNode is nil", func(t *testing.T) { + t.Parallel() + paths := &Paths{} + paths.SetValid(true, true) + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := paths.GetMapKeyNodeOrRoot("/pets", rootNode) + assert.Equal(t, rootNode, result, "should return root node when RootNode is nil") + }) +} + +func TestPaths_GetMapKeyNodeOrRootLine_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yaml string + key string + expectedLine int + }{ + { + name: "returns line number when path exists", + yaml: ` +/pets: + get: + summary: List all pets +`, + key: "/pets", + expectedLine: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var paths Paths + _, err := marshaller.UnmarshalCore(ctx, "", parseYAML(t, tt.yaml), &paths) + require.NoError(t, err, "unmarshal should succeed") + + rootNode := paths.GetRootNode() + line := paths.GetMapKeyNodeOrRootLine(tt.key, rootNode) + assert.Equal(t, tt.expectedLine, line, "should return correct line number") + }) + } +} + +func TestPaths_GetMapKeyNodeOrRootLine_NilNode(t *testing.T) { + t.Parallel() + + t.Run("returns -1 when GetMapKeyNodeOrRoot returns nil", func(t *testing.T) { + t.Parallel() + var paths Paths + line := paths.GetMapKeyNodeOrRootLine("/pets", nil) + assert.Equal(t, -1, line, "should return -1 when node is nil") + }) +} + +func TestPathItem_GetMapKeyNodeOrRoot_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yaml string + key string + }{ + { + name: "returns key node when method exists - first method", + yaml: ` +get: + summary: Get operation +post: + summary: Post operation +`, + key: "get", + }, + { + name: "returns key node when method exists - second method", + yaml: ` +get: + summary: Get operation +post: + summary: Post operation +`, + key: "post", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var pathItem PathItem + _, err := marshaller.UnmarshalCore(ctx, "", parseYAML(t, tt.yaml), &pathItem) + require.NoError(t, err, "unmarshal should succeed") + + rootNode := pathItem.GetRootNode() + result := pathItem.GetMapKeyNodeOrRoot(tt.key, rootNode) + require.NotNil(t, result, "result should not be nil") + assert.Equal(t, tt.key, result.Value, "should return correct key node") + }) + } +} + +func TestPathItem_GetMapKeyNodeOrRoot_ReturnsRoot(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yaml string + key string + }{ + { + name: "returns root when method not found", + yaml: ` +get: + summary: Get operation +`, + key: "delete", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var pathItem PathItem + _, err := marshaller.UnmarshalCore(ctx, "", parseYAML(t, tt.yaml), &pathItem) + require.NoError(t, err, "unmarshal should succeed") + + rootNode := pathItem.GetRootNode() + result := pathItem.GetMapKeyNodeOrRoot(tt.key, rootNode) + assert.Equal(t, rootNode, result, "should return root node when key not found") + }) + } +} + +func TestPathItem_GetMapKeyNodeOrRoot_Uninitialized(t *testing.T) { + t.Parallel() + + t.Run("returns root when pathItem is not initialized", func(t *testing.T) { + t.Parallel() + var pathItem PathItem + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := pathItem.GetMapKeyNodeOrRoot("get", rootNode) + assert.Equal(t, rootNode, result, "should return root node when not initialized") + }) + + t.Run("returns root when RootNode is nil", func(t *testing.T) { + t.Parallel() + pathItem := &PathItem{} + pathItem.SetValid(true, true) + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := pathItem.GetMapKeyNodeOrRoot("get", rootNode) + assert.Equal(t, rootNode, result, "should return root node when RootNode is nil") + }) +} + +func TestPathItem_GetMapKeyNodeOrRootLine_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yaml string + key string + expectedLine int + }{ + { + name: "returns line number when method exists", + yaml: ` +get: + summary: Get operation +`, + key: "get", + expectedLine: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var pathItem PathItem + _, err := marshaller.UnmarshalCore(ctx, "", parseYAML(t, tt.yaml), &pathItem) + require.NoError(t, err, "unmarshal should succeed") + + rootNode := pathItem.GetRootNode() + line := pathItem.GetMapKeyNodeOrRootLine(tt.key, rootNode) + assert.Equal(t, tt.expectedLine, line, "should return correct line number") + }) + } +} + +func TestPathItem_GetMapKeyNodeOrRootLine_NilNode(t *testing.T) { + t.Parallel() + + t.Run("returns -1 when GetMapKeyNodeOrRoot returns nil", func(t *testing.T) { + t.Parallel() + var pathItem PathItem + line := pathItem.GetMapKeyNodeOrRootLine("get", nil) + assert.Equal(t, -1, line, "should return -1 when node is nil") + }) +} diff --git a/swagger/core/reference_test.go b/swagger/core/reference_test.go new file mode 100644 index 0000000..58865ef --- /dev/null +++ b/swagger/core/reference_test.go @@ -0,0 +1,109 @@ +package core + +import ( + "testing" + + "github.com/speakeasy-api/openapi/marshaller" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestReference_Unmarshal_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yaml string + isRef bool + expected string + }{ + { + name: "unmarshals reference with $ref", + yaml: ` +$ref: '#/definitions/Pet' +`, + isRef: true, + expected: "#/definitions/Pet", + }, + { + name: "unmarshals inlined parameter object", + yaml: ` +name: petId +in: path +type: string +required: true +`, + isRef: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var ref Reference[*Parameter] + _, err := marshaller.UnmarshalCore(ctx, "", parseYAML(t, tt.yaml), &ref) + require.NoError(t, err, "unmarshal should succeed") + + if tt.isRef { + require.NotNil(t, ref.Reference.Value, "should have reference value") + assert.Equal(t, tt.expected, *ref.Reference.Value, "should have correct reference") + } else { + require.NotNil(t, ref.Object, "should have inlined object") + } + }) + } +} + +func TestReference_Unmarshal_Error(t *testing.T) { + t.Parallel() + + t.Run("returns error when node is nil", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var ref Reference[*Parameter] + _, err := ref.Unmarshal(ctx, "", nil) + require.Error(t, err, "should return error for nil node") + assert.Contains(t, err.Error(), "node is nil", "error should mention nil node") + }) + + t.Run("returns validation error for non-object node", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var ref Reference[*Parameter] + node := &yaml.Node{Kind: yaml.ScalarNode, Value: "just a string"} + validationErrs, err := ref.Unmarshal(ctx, "test", node) + require.NoError(t, err, "should not return fatal error") + require.NotEmpty(t, validationErrs, "should have validation errors") + assert.False(t, ref.GetValid(), "should not be valid") + }) +} + +func TestReference_SyncChanges_ErrorCases(t *testing.T) { + t.Parallel() + + t.Run("errors on non-struct model", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + + coreRef := &Reference[*Parameter]{} + coreRef.SetValid(true, true) + + _, err := coreRef.SyncChanges(ctx, "not a struct", nil) + require.Error(t, err, "should return error for non-struct model") + assert.Contains(t, err.Error(), "expected a struct", "error should mention struct expectation") + }) + + t.Run("errors on non-pointer non-struct model", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + + coreRef := &Reference[*Parameter]{} + coreRef.SetValid(true, true) + + _, err := coreRef.SyncChanges(ctx, 42, nil) + require.Error(t, err, "should return error for int model") + assert.Contains(t, err.Error(), "expected a struct", "error should mention struct expectation") + }) +} diff --git a/swagger/core/response_test.go b/swagger/core/response_test.go new file mode 100644 index 0000000..5b3cb88 --- /dev/null +++ b/swagger/core/response_test.go @@ -0,0 +1,164 @@ +package core + +import ( + "testing" + + "github.com/speakeasy-api/openapi/marshaller" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestNewResponses_Success(t *testing.T) { + t.Parallel() + + responses := NewResponses() + require.NotNil(t, responses, "NewResponses should return a non-nil responses") + require.NotNil(t, responses.Map, "responses.Map should be initialized") + assert.Equal(t, 0, responses.Len(), "newly created responses should be empty") +} + +func TestResponses_GetMapKeyNodeOrRoot_Success(t *testing.T) { + t.Parallel() + tests := []struct { + name string + yaml string + key string + }{ + { + name: "returns key node when status code exists - first status", + yaml: ` +'200': + description: Success response +'404': + description: Not found +`, + key: "200", + }, + { + name: "returns key node when status code exists - second status", + yaml: ` +'200': + description: Success response +'404': + description: Not found +`, + key: "404", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var responses Responses + _, err := marshaller.UnmarshalCore(ctx, "", parseYAML(t, tt.yaml), &responses) + require.NoError(t, err, "unmarshal should succeed") + + rootNode := responses.GetRootNode() + result := responses.GetMapKeyNodeOrRoot(tt.key, rootNode) + require.NotNil(t, result, "result should not be nil") + assert.Equal(t, tt.key, result.Value, "should return correct key node") + }) + } +} + +func TestResponses_GetMapKeyNodeOrRoot_ReturnsRoot(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yaml string + key string + }{ + { + name: "returns root when status code not found", + yaml: ` +'200': + description: Success response +`, + key: "500", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var responses Responses + _, err := marshaller.UnmarshalCore(ctx, "", parseYAML(t, tt.yaml), &responses) + require.NoError(t, err, "unmarshal should succeed") + + rootNode := responses.GetRootNode() + result := responses.GetMapKeyNodeOrRoot(tt.key, rootNode) + assert.Equal(t, rootNode, result, "should return root node when key not found") + }) + } +} + +func TestResponses_GetMapKeyNodeOrRoot_Uninitialized(t *testing.T) { + t.Parallel() + + t.Run("returns root when responses is not initialized", func(t *testing.T) { + t.Parallel() + var responses Responses + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := responses.GetMapKeyNodeOrRoot("200", rootNode) + assert.Equal(t, rootNode, result, "should return root node when not initialized") + }) + + t.Run("returns root when RootNode is nil", func(t *testing.T) { + t.Parallel() + responses := &Responses{} + responses.SetValid(true, true) + rootNode := &yaml.Node{Kind: yaml.MappingNode, Line: 1} + result := responses.GetMapKeyNodeOrRoot("200", rootNode) + assert.Equal(t, rootNode, result, "should return root node when RootNode is nil") + }) +} + +func TestResponses_GetMapKeyNodeOrRootLine_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + yaml string + key string + expectedLine int + }{ + { + name: "returns line number when status code exists", + yaml: ` +'200': + description: Success response +`, + key: "200", + expectedLine: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := t.Context() + var responses Responses + _, err := marshaller.UnmarshalCore(ctx, "", parseYAML(t, tt.yaml), &responses) + require.NoError(t, err, "unmarshal should succeed") + + rootNode := responses.GetRootNode() + line := responses.GetMapKeyNodeOrRootLine(tt.key, rootNode) + assert.Equal(t, tt.expectedLine, line, "should return correct line number") + }) + } +} + +func TestResponses_GetMapKeyNodeOrRootLine_NilNode(t *testing.T) { + t.Parallel() + + t.Run("returns -1 when GetMapKeyNodeOrRoot returns nil", func(t *testing.T) { + t.Parallel() + var responses Responses + line := responses.GetMapKeyNodeOrRootLine("200", nil) + assert.Equal(t, -1, line, "should return -1 when node is nil") + }) +} diff --git a/swagger/core/security_test.go b/swagger/core/security_test.go new file mode 100644 index 0000000..210ba77 --- /dev/null +++ b/swagger/core/security_test.go @@ -0,0 +1,19 @@ +package core_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/swagger/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewSecurityRequirement_Success(t *testing.T) { + t.Parallel() + + secReq := core.NewSecurityRequirement() + + require.NotNil(t, secReq, "NewSecurityRequirement should not return nil") + require.NotNil(t, secReq.Map, "Map should be initialized") + assert.Equal(t, 0, secReq.Len(), "new security requirement should be empty") +} diff --git a/swagger/externaldocs_nil_test.go b/swagger/externaldocs_nil_test.go new file mode 100644 index 0000000..50c6905 --- /dev/null +++ b/swagger/externaldocs_nil_test.go @@ -0,0 +1,45 @@ +package swagger_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/swagger" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestExternalDocumentation_GetDescription_Nil(t *testing.T) { + t.Parallel() + + var extDoc *swagger.ExternalDocumentation + assert.Empty(t, extDoc.GetDescription(), "nil ExternalDocumentation should return empty string for GetDescription") +} + +func TestExternalDocumentation_GetURL_Nil(t *testing.T) { + t.Parallel() + + var extDoc *swagger.ExternalDocumentation + assert.Empty(t, extDoc.GetURL(), "nil ExternalDocumentation should return empty string for GetURL") +} + +func TestExternalDocumentation_GetExtensions_Nil(t *testing.T) { + t.Parallel() + + var extDoc *swagger.ExternalDocumentation + exts := extDoc.GetExtensions() + require.NotNil(t, exts, "nil ExternalDocumentation should return empty extensions for GetExtensions") +} + +func TestInfo_GetTitle_Nil(t *testing.T) { + t.Parallel() + + var info *swagger.Info + assert.Empty(t, info.GetTitle(), "nil Info should return empty string for GetTitle") +} + +func TestInfo_GetDescription_Nil(t *testing.T) { + t.Parallel() + + var info *swagger.Info + assert.Empty(t, info.GetDescription(), "nil Info should return empty string for GetDescription") +} diff --git a/swagger/info_getters_test.go b/swagger/info_getters_test.go new file mode 100644 index 0000000..eb6b950 --- /dev/null +++ b/swagger/info_getters_test.go @@ -0,0 +1,271 @@ +package swagger_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/extensions" + "github.com/speakeasy-api/openapi/pointer" + "github.com/speakeasy-api/openapi/swagger" + "github.com/stretchr/testify/assert" +) + +func TestInfo_GetTermsOfService_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + info *swagger.Info + expected string + }{ + { + name: "nil info returns empty string", + info: nil, + expected: "", + }, + { + name: "nil TermsOfService returns empty string", + info: &swagger.Info{}, + expected: "", + }, + { + name: "returns TermsOfService value", + info: &swagger.Info{TermsOfService: pointer.From("https://example.com/terms")}, + expected: "https://example.com/terms", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.info.GetTermsOfService()) + }) + } +} + +func TestInfo_GetContact_Success(t *testing.T) { + t.Parallel() + + contact := &swagger.Contact{Name: pointer.From("Test Contact")} + tests := []struct { + name string + info *swagger.Info + expected *swagger.Contact + }{ + { + name: "nil info returns nil", + info: nil, + expected: nil, + }, + { + name: "nil Contact returns nil", + info: &swagger.Info{}, + expected: nil, + }, + { + name: "returns Contact value", + info: &swagger.Info{Contact: contact}, + expected: contact, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.info.GetContact()) + }) + } +} + +func TestInfo_GetLicense_Success(t *testing.T) { + t.Parallel() + + license := &swagger.License{Name: "MIT"} + tests := []struct { + name string + info *swagger.Info + expected *swagger.License + }{ + { + name: "nil info returns nil", + info: nil, + expected: nil, + }, + { + name: "nil License returns nil", + info: &swagger.Info{}, + expected: nil, + }, + { + name: "returns License value", + info: &swagger.Info{License: license}, + expected: license, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.info.GetLicense()) + }) + } +} + +func TestInfo_GetExtensions_Success(t *testing.T) { + t.Parallel() + + ext := extensions.New() + tests := []struct { + name string + info *swagger.Info + expectEmpty bool + expectedExts *extensions.Extensions + }{ + { + name: "nil info returns empty extensions", + info: nil, + expectEmpty: true, + }, + { + name: "nil Extensions returns empty extensions", + info: &swagger.Info{}, + expectEmpty: true, + }, + { + name: "returns Extensions value", + info: &swagger.Info{Extensions: ext}, + expectedExts: ext, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.info.GetExtensions() + if tt.expectEmpty { + assert.Equal(t, 0, result.Len()) + } else { + assert.Equal(t, tt.expectedExts, result) + } + }) + } +} + +func TestContact_GetExtensions_Success(t *testing.T) { + t.Parallel() + + ext := extensions.New() + tests := []struct { + name string + contact *swagger.Contact + expectEmpty bool + expectedExts *extensions.Extensions + }{ + { + name: "nil contact returns empty extensions", + contact: nil, + expectEmpty: true, + }, + { + name: "nil Extensions returns empty extensions", + contact: &swagger.Contact{}, + expectEmpty: true, + }, + { + name: "returns Extensions value", + contact: &swagger.Contact{Extensions: ext}, + expectedExts: ext, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.contact.GetExtensions() + if tt.expectEmpty { + assert.Equal(t, 0, result.Len()) + } else { + assert.Equal(t, tt.expectedExts, result) + } + }) + } +} + +func TestLicense_GetExtensions_Success(t *testing.T) { + t.Parallel() + + ext := extensions.New() + tests := []struct { + name string + license *swagger.License + expectEmpty bool + expectedExts *extensions.Extensions + }{ + { + name: "nil license returns empty extensions", + license: nil, + expectEmpty: true, + }, + { + name: "nil Extensions returns empty extensions", + license: &swagger.License{}, + expectEmpty: true, + }, + { + name: "returns Extensions value", + license: &swagger.License{Extensions: ext}, + expectedExts: ext, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.license.GetExtensions() + if tt.expectEmpty { + assert.Equal(t, 0, result.Len()) + } else { + assert.Equal(t, tt.expectedExts, result) + } + }) + } +} + +func TestExternalDocumentation_GetExtensions_Success(t *testing.T) { + t.Parallel() + + ext := extensions.New() + tests := []struct { + name string + extDoc *swagger.ExternalDocumentation + expectEmpty bool + expectedExts *extensions.Extensions + }{ + { + name: "nil extDoc returns empty extensions", + extDoc: nil, + expectEmpty: true, + }, + { + name: "nil Extensions returns empty extensions", + extDoc: &swagger.ExternalDocumentation{}, + expectEmpty: true, + }, + { + name: "returns Extensions value", + extDoc: &swagger.ExternalDocumentation{Extensions: ext}, + expectedExts: ext, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.extDoc.GetExtensions() + if tt.expectEmpty { + assert.Equal(t, 0, result.Len()) + } else { + assert.Equal(t, tt.expectedExts, result) + } + }) + } +} diff --git a/swagger/operation_getters_test.go b/swagger/operation_getters_test.go new file mode 100644 index 0000000..9f60c70 --- /dev/null +++ b/swagger/operation_getters_test.go @@ -0,0 +1,455 @@ +package swagger_test + +import ( + "testing" + + "github.com/speakeasy-api/openapi/extensions" + "github.com/speakeasy-api/openapi/pointer" + "github.com/speakeasy-api/openapi/swagger" + "github.com/stretchr/testify/assert" +) + +func TestOperation_GetTags_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *swagger.Operation + expected []string + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "empty tags returns nil", + op: &swagger.Operation{}, + expected: nil, + }, + { + name: "returns tags", + op: &swagger.Operation{Tags: []string{"tag1", "tag2"}}, + expected: []string{"tag1", "tag2"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetTags()) + }) + } +} + +func TestOperation_GetSummary_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *swagger.Operation + expected string + }{ + { + name: "nil operation returns empty string", + op: nil, + expected: "", + }, + { + name: "nil Summary returns empty string", + op: &swagger.Operation{}, + expected: "", + }, + { + name: "returns Summary value", + op: &swagger.Operation{Summary: pointer.From("Test summary")}, + expected: "Test summary", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetSummary()) + }) + } +} + +func TestOperation_GetDescription_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *swagger.Operation + expected string + }{ + { + name: "nil operation returns empty string", + op: nil, + expected: "", + }, + { + name: "nil Description returns empty string", + op: &swagger.Operation{}, + expected: "", + }, + { + name: "returns Description value", + op: &swagger.Operation{Description: pointer.From("Test description")}, + expected: "Test description", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetDescription()) + }) + } +} + +func TestOperation_GetExternalDocs_Success(t *testing.T) { + t.Parallel() + + docs := &swagger.ExternalDocumentation{URL: "https://docs.example.com"} + tests := []struct { + name string + op *swagger.Operation + expected *swagger.ExternalDocumentation + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "nil ExternalDocs returns nil", + op: &swagger.Operation{}, + expected: nil, + }, + { + name: "returns ExternalDocs value", + op: &swagger.Operation{ExternalDocs: docs}, + expected: docs, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetExternalDocs()) + }) + } +} + +func TestOperation_GetOperationID_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *swagger.Operation + expected string + }{ + { + name: "nil operation returns empty string", + op: nil, + expected: "", + }, + { + name: "nil OperationID returns empty string", + op: &swagger.Operation{}, + expected: "", + }, + { + name: "returns OperationID value", + op: &swagger.Operation{OperationID: pointer.From("getUser")}, + expected: "getUser", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetOperationID()) + }) + } +} + +func TestOperation_GetConsumes_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *swagger.Operation + expected []string + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "empty Consumes returns nil", + op: &swagger.Operation{}, + expected: nil, + }, + { + name: "returns Consumes value", + op: &swagger.Operation{Consumes: []string{"application/json", "application/xml"}}, + expected: []string{"application/json", "application/xml"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetConsumes()) + }) + } +} + +func TestOperation_GetProduces_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *swagger.Operation + expected []string + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "empty Produces returns nil", + op: &swagger.Operation{}, + expected: nil, + }, + { + name: "returns Produces value", + op: &swagger.Operation{Produces: []string{"application/json"}}, + expected: []string{"application/json"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetProduces()) + }) + } +} + +func TestOperation_GetParameters_Success(t *testing.T) { + t.Parallel() + + params := []*swagger.ReferencedParameter{{}} + tests := []struct { + name string + op *swagger.Operation + expected []*swagger.ReferencedParameter + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "empty Parameters returns nil", + op: &swagger.Operation{}, + expected: nil, + }, + { + name: "returns Parameters value", + op: &swagger.Operation{Parameters: params}, + expected: params, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetParameters()) + }) + } +} + +func TestOperation_GetResponses_Success(t *testing.T) { + t.Parallel() + + responses := &swagger.Responses{} + tests := []struct { + name string + op *swagger.Operation + expected *swagger.Responses + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "nil Responses returns nil", + op: &swagger.Operation{}, + expected: nil, + }, + { + name: "returns Responses value", + op: &swagger.Operation{Responses: responses}, + expected: responses, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetResponses()) + }) + } +} + +func TestOperation_GetSchemes_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *swagger.Operation + expected []string + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "empty Schemes returns nil", + op: &swagger.Operation{}, + expected: nil, + }, + { + name: "returns Schemes value", + op: &swagger.Operation{Schemes: []string{"https", "wss"}}, + expected: []string{"https", "wss"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetSchemes()) + }) + } +} + +func TestOperation_GetDeprecated_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + op *swagger.Operation + expected bool + }{ + { + name: "nil operation returns false", + op: nil, + expected: false, + }, + { + name: "nil Deprecated returns false", + op: &swagger.Operation{}, + expected: false, + }, + { + name: "returns Deprecated true", + op: &swagger.Operation{Deprecated: pointer.From(true)}, + expected: true, + }, + { + name: "returns Deprecated false", + op: &swagger.Operation{Deprecated: pointer.From(false)}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetDeprecated()) + }) + } +} + +func TestOperation_GetSecurity_Success(t *testing.T) { + t.Parallel() + + security := []*swagger.SecurityRequirement{{}} + tests := []struct { + name string + op *swagger.Operation + expected []*swagger.SecurityRequirement + }{ + { + name: "nil operation returns nil", + op: nil, + expected: nil, + }, + { + name: "empty Security returns nil", + op: &swagger.Operation{}, + expected: nil, + }, + { + name: "returns Security value", + op: &swagger.Operation{Security: security}, + expected: security, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, tt.op.GetSecurity()) + }) + } +} + +func TestOperation_GetExtensions_Success(t *testing.T) { + t.Parallel() + + ext := extensions.New() + tests := []struct { + name string + op *swagger.Operation + expectEmpty bool + expectedExts *extensions.Extensions + }{ + { + name: "nil operation returns empty extensions", + op: nil, + expectEmpty: true, + }, + { + name: "nil Extensions returns empty extensions", + op: &swagger.Operation{}, + expectEmpty: true, + }, + { + name: "returns Extensions value", + op: &swagger.Operation{Extensions: ext}, + expectedExts: ext, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.op.GetExtensions() + if tt.expectEmpty { + assert.Equal(t, 0, result.Len()) + } else { + assert.Equal(t, tt.expectedExts, result) + } + }) + } +} diff --git a/swagger/parameter_test.go b/swagger/parameter_test.go index e18f980..ef50b23 100644 --- a/swagger/parameter_test.go +++ b/swagger/parameter_test.go @@ -426,3 +426,71 @@ type: object }) } } + +func TestParameter_Getters_Success(t *testing.T) { + t.Parallel() + + yml := ` +name: userId +in: path +description: User identifier +required: true +type: string +schema: + type: object +x-custom: value +` + var param swagger.Parameter + + validationErrs, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), ¶m) + require.NoError(t, err) + require.Empty(t, validationErrs) + + require.Equal(t, "userId", param.GetName(), "GetName should return correct value") + require.Equal(t, swagger.ParameterInPath, param.GetIn(), "GetIn should return correct value") + require.Equal(t, "User identifier", param.GetDescription(), "GetDescription should return correct value") + require.True(t, param.GetRequired(), "GetRequired should return true") + require.Equal(t, "string", param.GetType(), "GetType should return correct value") + require.NotNil(t, param.GetSchema(), "GetSchema should return non-nil") + require.NotNil(t, param.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestParameter_Getters_Nil(t *testing.T) { + t.Parallel() + + var param *swagger.Parameter + + require.Empty(t, param.GetName(), "GetName should return empty string for nil") + require.Empty(t, param.GetIn(), "GetIn should return empty for nil") + require.Empty(t, param.GetDescription(), "GetDescription should return empty string for nil") + require.False(t, param.GetRequired(), "GetRequired should return false for nil") + require.Empty(t, param.GetType(), "GetType should return empty string for nil") + require.Nil(t, param.GetSchema(), "GetSchema should return nil for nil param") + require.NotNil(t, param.GetExtensions(), "GetExtensions should return empty extensions for nil param") +} + +func TestItems_Getters_Success(t *testing.T) { + t.Parallel() + + yml := ` +type: string +x-custom: value +` + var items swagger.Items + + validationErrs, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &items) + require.NoError(t, err) + require.Empty(t, validationErrs) + + require.Equal(t, "string", items.GetType(), "GetType should return correct value") + require.NotNil(t, items.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestItems_Getters_Nil(t *testing.T) { + t.Parallel() + + var items *swagger.Items + + require.Empty(t, items.GetType(), "GetType should return empty string for nil") + require.NotNil(t, items.GetExtensions(), "GetExtensions should return empty extensions for nil items") +} diff --git a/swagger/paths_test.go b/swagger/paths_test.go new file mode 100644 index 0000000..1172d4f --- /dev/null +++ b/swagger/paths_test.go @@ -0,0 +1,220 @@ +package swagger_test + +import ( + "strings" + "testing" + + "github.com/speakeasy-api/openapi/pointer" + "github.com/speakeasy-api/openapi/swagger" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPaths_GetExtensions_Success(t *testing.T) { + t.Parallel() + ctx := t.Context() + + yml := `swagger: "2.0" +info: + title: Test API + version: 1.0.0 +paths: + /users: + x-path-ext: path-value + get: + responses: + "200": + description: Success + x-custom: value +` + doc, validationErrs, err := swagger.Unmarshal(ctx, strings.NewReader(yml)) + require.NoError(t, err, "unmarshal should succeed") + require.Empty(t, validationErrs, "should have no validation errors") + + assert.NotNil(t, doc.Paths.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestPaths_GetExtensions_Nil(t *testing.T) { + t.Parallel() + + var paths *swagger.Paths + ext := paths.GetExtensions() + assert.NotNil(t, ext, "GetExtensions should return empty extensions for nil paths") +} + +func TestPathItem_NewPathItem(t *testing.T) { + t.Parallel() + + pi := swagger.NewPathItem() + assert.NotNil(t, pi, "NewPathItem should return non-nil") +} + +func TestPathItem_GetRef_Success(t *testing.T) { + t.Parallel() + + pi := &swagger.PathItem{ + Ref: pointer.From("#/paths/~1other"), + } + + assert.Equal(t, "#/paths/~1other", pi.GetRef(), "GetRef should return correct value") +} + +func TestPathItem_GetRef_Nil(t *testing.T) { + t.Parallel() + + var pi *swagger.PathItem + assert.Empty(t, pi.GetRef(), "GetRef should return empty string for nil") + + pi2 := &swagger.PathItem{} + assert.Empty(t, pi2.GetRef(), "GetRef should return empty string when Ref is nil") +} + +func TestPathItem_GetParameters_Success(t *testing.T) { + t.Parallel() + ctx := t.Context() + + yml := `swagger: "2.0" +info: + title: Test API + version: 1.0.0 +paths: + /users/{id}: + parameters: + - name: id + in: path + required: true + type: string + get: + responses: + "200": + description: Success +` + doc, _, err := swagger.Unmarshal(ctx, strings.NewReader(yml)) + require.NoError(t, err, "unmarshal should succeed") + + pathItem, ok := doc.Paths.Get("/users/{id}") + require.True(t, ok, "path should exist") + assert.NotNil(t, pathItem.GetParameters(), "GetParameters should return non-nil") + assert.Len(t, pathItem.GetParameters(), 1, "should have one parameter") +} + +func TestPathItem_GetParameters_Nil(t *testing.T) { + t.Parallel() + + var pi *swagger.PathItem + assert.Nil(t, pi.GetParameters(), "GetParameters should return nil for nil pathitem") +} + +func TestPathItem_GetExtensions_Success(t *testing.T) { + t.Parallel() + ctx := t.Context() + + yml := `swagger: "2.0" +info: + title: Test API + version: 1.0.0 +paths: + /users: + x-custom: value + get: + responses: + "200": + description: Success +` + doc, _, err := swagger.Unmarshal(ctx, strings.NewReader(yml)) + require.NoError(t, err, "unmarshal should succeed") + + pathItem, ok := doc.Paths.Get("/users") + require.True(t, ok, "path should exist") + assert.NotNil(t, pathItem.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestPathItem_GetExtensions_Nil(t *testing.T) { + t.Parallel() + + var pi *swagger.PathItem + ext := pi.GetExtensions() + assert.NotNil(t, ext, "GetExtensions should return empty extensions for nil pathitem") +} + +func TestPathItem_Operations_Success(t *testing.T) { + t.Parallel() + ctx := t.Context() + + yml := `swagger: "2.0" +info: + title: Test API + version: 1.0.0 +paths: + /users: + get: + operationId: getUsers + responses: + "200": + description: Success + put: + operationId: updateUsers + responses: + "200": + description: Success + post: + operationId: createUsers + responses: + "200": + description: Success + delete: + operationId: deleteUsers + responses: + "200": + description: Success + options: + operationId: optionsUsers + responses: + "200": + description: Success + head: + operationId: headUsers + responses: + "200": + description: Success + patch: + operationId: patchUsers + responses: + "200": + description: Success +` + doc, validationErrs, err := swagger.Unmarshal(ctx, strings.NewReader(yml)) + require.NoError(t, err, "unmarshal should succeed") + require.Empty(t, validationErrs, "should have no validation errors") + + pathItem, ok := doc.Paths.Get("/users") + require.True(t, ok, "path should exist") + + assert.NotNil(t, pathItem.Get(), "Get should return non-nil") + assert.Equal(t, "getUsers", pathItem.Get().GetOperationID()) + assert.NotNil(t, pathItem.Put(), "Put should return non-nil") + assert.Equal(t, "updateUsers", pathItem.Put().GetOperationID()) + assert.NotNil(t, pathItem.Post(), "Post should return non-nil") + assert.Equal(t, "createUsers", pathItem.Post().GetOperationID()) + assert.NotNil(t, pathItem.Delete(), "Delete should return non-nil") + assert.Equal(t, "deleteUsers", pathItem.Delete().GetOperationID()) + assert.NotNil(t, pathItem.Options(), "Options should return non-nil") + assert.Equal(t, "optionsUsers", pathItem.Options().GetOperationID()) + assert.NotNil(t, pathItem.Head(), "Head should return non-nil") + assert.Equal(t, "headUsers", pathItem.Head().GetOperationID()) + assert.NotNil(t, pathItem.Patch(), "Patch should return non-nil") + assert.Equal(t, "patchUsers", pathItem.Patch().GetOperationID()) +} + +func TestPathItem_Operations_Nil(t *testing.T) { + t.Parallel() + + var pi *swagger.PathItem + assert.Nil(t, pi.Get(), "Get should return nil for nil pathitem") + assert.Nil(t, pi.Put(), "Put should return nil for nil pathitem") + assert.Nil(t, pi.Post(), "Post should return nil for nil pathitem") + assert.Nil(t, pi.Delete(), "Delete should return nil for nil pathitem") + assert.Nil(t, pi.Options(), "Options should return nil for nil pathitem") + assert.Nil(t, pi.Head(), "Head should return nil for nil pathitem") + assert.Nil(t, pi.Patch(), "Patch should return nil for nil pathitem") +} diff --git a/swagger/reference_factory_test.go b/swagger/reference_factory_test.go new file mode 100644 index 0000000..69c329f --- /dev/null +++ b/swagger/reference_factory_test.go @@ -0,0 +1,54 @@ +package swagger + +import ( + "testing" + + "github.com/speakeasy-api/openapi/references" + "github.com/stretchr/testify/assert" +) + +func TestNewReferencedParameterFromRef_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/parameters/MyParam") + result := NewReferencedParameterFromRef(ref) + + assert.NotNil(t, result, "NewReferencedParameterFromRef should return non-nil") + assert.NotNil(t, result.Reference, "Reference field should be set") + assert.Equal(t, ref, *result.Reference, "Reference should match") + assert.Nil(t, result.Object, "Object should be nil for reference") +} + +func TestNewReferencedParameterFromParameter_Success(t *testing.T) { + t.Parallel() + + param := &Parameter{} + result := NewReferencedParameterFromParameter(param) + + assert.NotNil(t, result, "NewReferencedParameterFromParameter should return non-nil") + assert.Nil(t, result.Reference, "Reference should be nil for inline object") + assert.Equal(t, param, result.Object, "Object should match") +} + +func TestNewReferencedResponseFromRef_Success(t *testing.T) { + t.Parallel() + + ref := references.Reference("#/responses/MyResponse") + result := NewReferencedResponseFromRef(ref) + + assert.NotNil(t, result, "NewReferencedResponseFromRef should return non-nil") + assert.NotNil(t, result.Reference, "Reference field should be set") + assert.Equal(t, ref, *result.Reference, "Reference should match") + assert.Nil(t, result.Object, "Object should be nil for reference") +} + +func TestNewReferencedResponseFromResponse_Success(t *testing.T) { + t.Parallel() + + resp := &Response{} + result := NewReferencedResponseFromResponse(resp) + + assert.NotNil(t, result, "NewReferencedResponseFromResponse should return non-nil") + assert.Nil(t, result.Reference, "Reference should be nil for inline object") + assert.Equal(t, resp, result.Object, "Object should match") +} diff --git a/swagger/response_validate_test.go b/swagger/response_validate_test.go index 5832d92..4c8292f 100644 --- a/swagger/response_validate_test.go +++ b/swagger/response_validate_test.go @@ -209,3 +209,114 @@ description: Array header`, }) } } + +func TestResponses_Getters_Success(t *testing.T) { + t.Parallel() + + yml := `swagger: "2.0" +info: + title: Test API + version: 1.0.0 +paths: + /users: + get: + responses: + default: + description: Default response + "200": + description: Success response + x-custom: value +` + doc, validationErrs, err := swagger.Unmarshal(t.Context(), strings.NewReader(yml)) + require.NoError(t, err, "unmarshal should succeed") + require.Empty(t, validationErrs, "should have no validation errors") + + pathItem, ok := doc.Paths.Get("/users") + require.True(t, ok, "path should exist") + resp := pathItem.Get().Responses + + require.NotNil(t, resp.GetDefault(), "GetDefault should return non-nil") + require.NotNil(t, resp.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestResponses_Getters_Nil(t *testing.T) { + t.Parallel() + + var resp *swagger.Responses + + require.Nil(t, resp.GetDefault(), "GetDefault should return nil for nil responses") + require.NotNil(t, resp.GetExtensions(), "GetExtensions should return empty extensions for nil responses") +} + +func TestResponses_NewResponses(t *testing.T) { + t.Parallel() + + resp := swagger.NewResponses() + require.NotNil(t, resp, "NewResponses should return non-nil") +} + +func TestResponse_Getters_Success(t *testing.T) { + t.Parallel() + + yml := `description: Success response +schema: + type: object +headers: + X-Rate-Limit: + type: integer +examples: + application/json: {"key": "value"} +x-custom: value +` + var resp swagger.Response + + validationErrs, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &resp) + require.NoError(t, err) + require.Empty(t, validationErrs) + + require.Equal(t, "Success response", resp.GetDescription(), "GetDescription should return correct value") + require.NotNil(t, resp.GetSchema(), "GetSchema should return non-nil") + require.NotNil(t, resp.GetHeaders(), "GetHeaders should return non-nil") + require.NotNil(t, resp.GetExamples(), "GetExamples should return non-nil") + require.NotNil(t, resp.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestResponse_Getters_Nil(t *testing.T) { + t.Parallel() + + var resp *swagger.Response + + require.Empty(t, resp.GetDescription(), "GetDescription should return empty string for nil") + require.Nil(t, resp.GetSchema(), "GetSchema should return nil for nil response") + require.Nil(t, resp.GetHeaders(), "GetHeaders should return nil for nil response") + require.Nil(t, resp.GetExamples(), "GetExamples should return nil for nil response") + require.NotNil(t, resp.GetExtensions(), "GetExtensions should return empty extensions for nil response") +} + +func TestHeader_Getters_Success(t *testing.T) { + t.Parallel() + + yml := `type: integer +description: Rate limit header +x-custom: value +` + var header swagger.Header + + validationErrs, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &header) + require.NoError(t, err) + require.Empty(t, validationErrs) + + require.Equal(t, "Rate limit header", header.GetDescription(), "GetDescription should return correct value") + require.Equal(t, "integer", header.GetType(), "GetType should return correct value") + require.NotNil(t, header.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestHeader_Getters_Nil(t *testing.T) { + t.Parallel() + + var header *swagger.Header + + require.Empty(t, header.GetDescription(), "GetDescription should return empty string for nil") + require.Empty(t, header.GetType(), "GetType should return empty string for nil") + require.NotNil(t, header.GetExtensions(), "GetExtensions should return empty extensions for nil header") +} diff --git a/swagger/security_validate_test.go b/swagger/security_validate_test.go index 45d2405..4da59fd 100644 --- a/swagger/security_validate_test.go +++ b/swagger/security_validate_test.go @@ -213,3 +213,58 @@ tokenUrl: https://example.com/token`, }) } } + +func TestSecurityScheme_Getters_Success(t *testing.T) { + t.Parallel() + + yml := `type: oauth2 +description: OAuth2 authentication +name: test_name +in: header +flow: accessCode +authorizationUrl: https://example.com/authorize +tokenUrl: https://example.com/token +scopes: + read: Read access + write: Write access +x-custom: value +` + var securityScheme swagger.SecurityScheme + + validationErrs, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &securityScheme) + require.NoError(t, err) + require.Empty(t, validationErrs) + + require.Equal(t, swagger.SecuritySchemeTypeOAuth2, securityScheme.GetType(), "GetType should return correct value") + require.Equal(t, "OAuth2 authentication", securityScheme.GetDescription(), "GetDescription should return correct value") + require.Equal(t, "test_name", securityScheme.GetName(), "GetName should return correct value") + require.Equal(t, swagger.SecuritySchemeInHeader, securityScheme.GetIn(), "GetIn should return correct value") + require.Equal(t, swagger.OAuth2FlowAccessCode, securityScheme.GetFlow(), "GetFlow should return correct value") + require.Equal(t, "https://example.com/authorize", securityScheme.GetAuthorizationURL(), "GetAuthorizationURL should return correct value") + require.Equal(t, "https://example.com/token", securityScheme.GetTokenURL(), "GetTokenURL should return correct value") + require.NotNil(t, securityScheme.GetScopes(), "GetScopes should return non-nil") + require.NotNil(t, securityScheme.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestSecurityScheme_Getters_Nil(t *testing.T) { + t.Parallel() + + var securityScheme *swagger.SecurityScheme + + require.Empty(t, securityScheme.GetType(), "GetType should return empty for nil") + require.Empty(t, securityScheme.GetDescription(), "GetDescription should return empty string for nil") + require.Empty(t, securityScheme.GetName(), "GetName should return empty string for nil") + require.Empty(t, securityScheme.GetIn(), "GetIn should return empty for nil") + require.Empty(t, securityScheme.GetFlow(), "GetFlow should return empty for nil") + require.Empty(t, securityScheme.GetAuthorizationURL(), "GetAuthorizationURL should return empty string for nil") + require.Empty(t, securityScheme.GetTokenURL(), "GetTokenURL should return empty string for nil") + require.Nil(t, securityScheme.GetScopes(), "GetScopes should return nil for nil scheme") + require.NotNil(t, securityScheme.GetExtensions(), "GetExtensions should return empty extensions for nil scheme") +} + +func TestSecurityRequirement_NewSecurityRequirement(t *testing.T) { + t.Parallel() + + sr := swagger.NewSecurityRequirement() + require.NotNil(t, sr, "NewSecurityRequirement should return non-nil") +} diff --git a/swagger/swagger_test.go b/swagger/swagger_test.go index 59e8965..e63c8d3 100644 --- a/swagger/swagger_test.go +++ b/swagger/swagger_test.go @@ -171,3 +171,97 @@ paths: {} ` assert.Equal(t, expected, buf.String(), "marshaled output should match expected YAML") } + +func TestSwagger_Getters_Success(t *testing.T) { + t.Parallel() + ctx := t.Context() + + yml := `swagger: "2.0" +info: + title: Test API + version: 1.0.0 +host: api.example.com +basePath: /v1 +schemes: + - https +consumes: + - application/json +produces: + - application/xml +paths: + /users: + get: + responses: + "200": + description: Success +definitions: + User: + type: object +parameters: + limitParam: + name: limit + in: query + type: integer +responses: + NotFound: + description: Not found +securityDefinitions: + api_key: + type: apiKey + name: X-API-Key + in: header +security: + - api_key: [] +tags: + - name: users + description: User operations +externalDocs: + description: External docs + url: https://example.com/docs +x-custom: value +` + doc, validationErrs, err := swagger.Unmarshal(ctx, strings.NewReader(yml)) + require.NoError(t, err, "unmarshal should succeed") + require.Empty(t, validationErrs, "should have no validation errors") + + assert.Equal(t, "2.0", doc.GetSwagger(), "GetSwagger should return 2.0") + assert.NotNil(t, doc.GetInfo(), "GetInfo should return non-nil Info") + assert.Equal(t, "Test API", doc.GetInfo().Title, "GetInfo should return correct Info") + assert.Equal(t, "api.example.com", doc.GetHost(), "GetHost should return correct value") + assert.Equal(t, "/v1", doc.GetBasePath(), "GetBasePath should return correct value") + assert.Equal(t, []string{"https"}, doc.GetSchemes(), "GetSchemes should return correct value") + assert.Equal(t, []string{"application/json"}, doc.GetConsumes(), "GetConsumes should return correct value") + assert.Equal(t, []string{"application/xml"}, doc.GetProduces(), "GetProduces should return correct value") + assert.NotNil(t, doc.GetPaths(), "GetPaths should return non-nil") + assert.NotNil(t, doc.GetDefinitions(), "GetDefinitions should return non-nil") + assert.NotNil(t, doc.GetParameters(), "GetParameters should return non-nil") + assert.NotNil(t, doc.GetResponses(), "GetResponses should return non-nil") + assert.NotNil(t, doc.GetSecurityDefinitions(), "GetSecurityDefinitions should return non-nil") + assert.NotNil(t, doc.GetSecurity(), "GetSecurity should return non-nil") + assert.NotNil(t, doc.GetTags(), "GetTags should return non-nil") + assert.NotNil(t, doc.GetExternalDocs(), "GetExternalDocs should return non-nil") + assert.NotNil(t, doc.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestSwagger_Getters_Nil(t *testing.T) { + t.Parallel() + + var doc *swagger.Swagger + + assert.Empty(t, doc.GetSwagger(), "GetSwagger should return empty string for nil") + assert.Nil(t, doc.GetInfo(), "GetInfo should return nil for nil doc") + assert.Empty(t, doc.GetHost(), "GetHost should return empty string for nil") + assert.Empty(t, doc.GetBasePath(), "GetBasePath should return empty string for nil") + assert.Nil(t, doc.GetSchemes(), "GetSchemes should return nil for nil doc") + assert.Nil(t, doc.GetConsumes(), "GetConsumes should return nil for nil doc") + assert.Nil(t, doc.GetProduces(), "GetProduces should return nil for nil doc") + assert.Nil(t, doc.GetPaths(), "GetPaths should return nil for nil doc") + assert.Nil(t, doc.GetDefinitions(), "GetDefinitions should return nil for nil doc") + assert.Nil(t, doc.GetParameters(), "GetParameters should return nil for nil doc") + assert.Nil(t, doc.GetResponses(), "GetResponses should return nil for nil doc") + assert.Nil(t, doc.GetSecurityDefinitions(), "GetSecurityDefinitions should return nil for nil doc") + assert.Nil(t, doc.GetSecurity(), "GetSecurity should return nil for nil doc") + assert.Nil(t, doc.GetTags(), "GetTags should return nil for nil doc") + assert.Nil(t, doc.GetExternalDocs(), "GetExternalDocs should return nil for nil doc") + assert.NotNil(t, doc.GetExtensions(), "GetExtensions should return empty extensions for nil doc") +} diff --git a/swagger/tag_validate_test.go b/swagger/tag_validate_test.go index 9a85a3f..e3bf4a7 100644 --- a/swagger/tag_validate_test.go +++ b/swagger/tag_validate_test.go @@ -175,3 +175,35 @@ func TestExternalDocumentation_Validate_Error(t *testing.T) { }) } } + +func TestTag_Getters_Success(t *testing.T) { + t.Parallel() + + yml := `name: users +description: User operations +externalDocs: + url: https://example.com/docs +x-custom: value +` + var tag swagger.Tag + + validationErrs, err := marshaller.Unmarshal(t.Context(), bytes.NewBufferString(yml), &tag) + require.NoError(t, err) + require.Empty(t, validationErrs) + + require.Equal(t, "users", tag.GetName(), "GetName should return correct value") + require.Equal(t, "User operations", tag.GetDescription(), "GetDescription should return correct value") + require.NotNil(t, tag.GetExternalDocs(), "GetExternalDocs should return non-nil") + require.NotNil(t, tag.GetExtensions(), "GetExtensions should return non-nil") +} + +func TestTag_Getters_Nil(t *testing.T) { + t.Parallel() + + var tag *swagger.Tag + + require.Empty(t, tag.GetName(), "GetName should return empty string for nil") + require.Empty(t, tag.GetDescription(), "GetDescription should return empty string for nil") + require.Nil(t, tag.GetExternalDocs(), "GetExternalDocs should return nil for nil tag") + require.NotNil(t, tag.GetExtensions(), "GetExtensions should return empty extensions for nil tag") +} diff --git a/yml/config_test.go b/yml/config_test.go index 43978fb..92d9ea2 100644 --- a/yml/config_test.go +++ b/yml/config_test.go @@ -762,3 +762,55 @@ func TestGetConfigFromDoc_WithNumericStrings_Success(t *testing.T) { }) } } + +func TestIndentationStyle_ToIndent_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + style yml.IndentationStyle + expected string + }{ + { + name: "space style returns space character", + style: yml.IndentationStyleSpace, + expected: " ", + }, + { + name: "tab style returns tab character", + style: yml.IndentationStyleTab, + expected: "\t", + }, + { + name: "unknown style returns empty string", + style: yml.IndentationStyle("unknown"), + expected: "", + }, + { + name: "empty style returns empty string", + style: yml.IndentationStyle(""), + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := tt.style.ToIndent() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestGetDefaultConfig_Success(t *testing.T) { + t.Parallel() + + config := yml.GetDefaultConfig() + + require.NotNil(t, config, "GetDefaultConfig should not return nil") + assert.Equal(t, 2, config.Indentation, "default indentation should be 2") + assert.Equal(t, yml.IndentationStyleSpace, config.IndentationStyle, "default indentation style should be space") + assert.Equal(t, yml.OutputFormatYAML, config.OutputFormat, "default output format should be YAML") + assert.Equal(t, yaml.Style(0), config.KeyStringStyle, "default key string style should be 0") + assert.Equal(t, yaml.Style(0), config.ValueStringStyle, "default value string style should be 0") +} diff --git a/yml/yml_test.go b/yml/yml_test.go index 6113829..67782d9 100644 --- a/yml/yml_test.go +++ b/yml/yml_test.go @@ -1,6 +1,7 @@ package yml_test import ( + "reflect" "testing" "github.com/speakeasy-api/openapi/yml" @@ -679,3 +680,166 @@ func TestEqualNodes_Success(t *testing.T) { }) } } + +func TestTypeToYamlTags_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + typ reflect.Type + expected []string + }{ + { + name: "nil type returns nil", + typ: nil, + expected: nil, + }, + { + name: "string type returns str and numeric tags", + typ: reflect.TypeOf(""), + expected: []string{"!!bool", "!!float", "!!str", "!!int"}, + }, + { + name: "int type returns numeric tags", + typ: reflect.TypeOf(0), + expected: []string{"!!float", "!!str", "!!int"}, + }, + { + name: "int64 type returns numeric tags", + typ: reflect.TypeOf(int64(0)), + expected: []string{"!!float", "!!str", "!!int"}, + }, + { + name: "float64 type returns numeric tags", + typ: reflect.TypeOf(float64(0)), + expected: []string{"!!float", "!!str", "!!int"}, + }, + { + name: "bool type returns bool and str tags", + typ: reflect.TypeOf(true), + expected: []string{"!!bool", "!!str"}, + }, + { + name: "struct type returns map tag", + typ: reflect.TypeOf(struct{}{}), + expected: []string{"!!map"}, + }, + { + name: "map type returns map tag", + typ: reflect.TypeOf(map[string]string{}), + expected: []string{"!!map"}, + }, + { + name: "slice type returns seq tag", + typ: reflect.TypeOf([]string{}), + expected: []string{"!!seq"}, + }, + { + name: "array type returns seq tag", + typ: reflect.TypeOf([3]string{}), + expected: []string{"!!seq"}, + }, + { + name: "pointer to string includes null tag", + typ: reflect.TypeOf((*string)(nil)), + expected: []string{"!!bool", "!!float", "!!str", "!!int", "!!null"}, + }, + { + name: "pointer to int includes null tag", + typ: reflect.TypeOf((*int)(nil)), + expected: []string{"!!float", "!!str", "!!int", "!!null"}, + }, + { + name: "pointer to bool includes null tag", + typ: reflect.TypeOf((*bool)(nil)), + expected: []string{"!!bool", "!!str", "!!null"}, + }, + { + name: "pointer to struct includes null tag", + typ: reflect.TypeOf((*struct{})(nil)), + expected: []string{"!!map", "!!null"}, + }, + { + name: "channel type returns nil", + typ: reflect.TypeOf(make(chan int)), + expected: nil, + }, + { + name: "func type returns nil", + typ: reflect.TypeOf(func() {}), + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := yml.TypeToYamlTags(tt.typ) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestNodeTagToString_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + tag string + expected string + }{ + { + name: "str tag", + tag: "!!str", + expected: "string", + }, + { + name: "int tag", + tag: "!!int", + expected: "int", + }, + { + name: "float tag", + tag: "!!float", + expected: "float", + }, + { + name: "bool tag", + tag: "!!bool", + expected: "bool", + }, + { + name: "map tag", + tag: "!!map", + expected: "object", + }, + { + name: "seq tag", + tag: "!!seq", + expected: "sequence", + }, + { + name: "null tag", + tag: "!!null", + expected: "null", + }, + { + name: "unknown tag returns as-is", + tag: "!!custom", + expected: "!!custom", + }, + { + name: "empty tag returns as-is", + tag: "", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := yml.NodeTagToString(tt.tag) + assert.Equal(t, tt.expected, result) + }) + } +}