// Copyright 2022-2025 Princess B33f Heavy Industries / Dave Shanley // SPDX-License-Identifier: MIT package libopenapi import ( "testing" "github.com/pb33f/libopenapi/datamodel" v2 "github.com/pb33f/libopenapi/datamodel/high/v2" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/overlay" "github.com/pb33f/testify/assert" "github.com/pb33f/testify/require" ) func TestNewOverlayDocument(t *testing.T) { overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.info update: title: Updated Title` ov, err := NewOverlayDocument([]byte(overlayYAML)) require.NoError(t, err) assert.NotNil(t, ov) assert.Equal(t, "1.0.0", ov.Overlay) assert.Equal(t, "Test Overlay", ov.Info.Title) assert.Len(t, ov.Actions, 1) } func TestNewOverlayDocument_InvalidYAML(t *testing.T) { ov, err := NewOverlayDocument([]byte(`invalid: yaml: content:`)) assert.Error(t, err) assert.Nil(t, ov) } func TestNewOverlayDocument_EmptyDocument(t *testing.T) { ov, err := NewOverlayDocument([]byte(``)) assert.ErrorIs(t, err, overlay.ErrInvalidOverlay) assert.Nil(t, ov) } func TestNewOverlayDocument_InvalidOverlay(t *testing.T) { // Missing required fields overlayYAML := `foo: bar` ov, err := NewOverlayDocument([]byte(overlayYAML)) // BuildModel should succeed but Build should return an error // Actually, lowoverlay.Build returns nil for missing fields, // and validation happens in overlay.Apply require.NoError(t, err) assert.NotNil(t, ov) } func TestNewOverlayDocument_InvalidInfoObject(t *testing.T) { overlayYAML := `overlay: 1.0.0 info: $ref: "" actions: []` ov, err := NewOverlayDocument([]byte(overlayYAML)) assert.Error(t, err) assert.Nil(t, ov) } func TestNewOverlayDocument_SequenceRoot(t *testing.T) { // Sequence at root - BuildModel is lenient and returns empty overlay overlayYAML := `- item1 - item2` ov, err := NewOverlayDocument([]byte(overlayYAML)) // BuildModel is lenient and doesn't fail, but the overlay will be empty require.NoError(t, err) assert.NotNil(t, ov) assert.Empty(t, ov.Overlay) } func TestNewOverlayDocument_WithExtensions(t *testing.T) { overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 x-custom: custom value actions: - target: $.info update: title: Updated` ov, err := NewOverlayDocument([]byte(overlayYAML)) require.NoError(t, err) assert.NotNil(t, ov) assert.Equal(t, "1.0.0", ov.Overlay) assert.NotNil(t, ov.Extensions) } // Tests for ApplyOverlay (Document, Overlay) func TestApplyOverlay(t *testing.T) { targetYAML := `openapi: 3.0.0 info: title: Original Title version: 1.0.0 paths: {}` overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.info update: title: Updated Title` doc, err := NewDocument([]byte(targetYAML)) require.NoError(t, err) ov, err := NewOverlayDocument([]byte(overlayYAML)) require.NoError(t, err) result, err := ApplyOverlay(doc, ov) require.NoError(t, err) assert.NotNil(t, result) assert.Contains(t, string(result.Bytes), "Updated Title") assert.Len(t, result.Warnings, 0) // Verify OverlayDocument is populated and ready to use assert.NotNil(t, result.OverlayDocument) assert.Equal(t, "3.0.0", result.OverlayDocument.GetVersion()) } func TestApplyOverlay_PreservesConfiguration(t *testing.T) { targetYAML := `openapi: 3.0.0 info: title: Original Title version: 1.0.0 paths: {}` overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.info update: title: Updated Title` // Create document with custom configuration config := &datamodel.DocumentConfiguration{ AllowFileReferences: true, AllowRemoteReferences: false, } doc, err := NewDocumentWithConfiguration([]byte(targetYAML), config) require.NoError(t, err) ov, err := NewOverlayDocument([]byte(overlayYAML)) require.NoError(t, err) result, err := ApplyOverlay(doc, ov) require.NoError(t, err) // Verify configuration is preserved in the resulting document resultConfig := result.OverlayDocument.GetConfiguration() assert.NotNil(t, resultConfig) assert.True(t, resultConfig.AllowFileReferences) assert.False(t, resultConfig.AllowRemoteReferences) } func TestApplyOverlay_WithWarnings(t *testing.T) { targetYAML := `openapi: 3.0.0 info: title: Test version: 1.0.0` overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.nonexistent update: value: test` doc, err := NewDocument([]byte(targetYAML)) require.NoError(t, err) ov, err := NewOverlayDocument([]byte(overlayYAML)) require.NoError(t, err) result, err := ApplyOverlay(doc, ov) require.NoError(t, err) assert.Len(t, result.Warnings, 1) assert.Contains(t, result.Warnings[0].Message, "zero nodes") // OverlayDocument should still be populated assert.NotNil(t, result.OverlayDocument) } func TestApplyOverlay_NilOverlay(t *testing.T) { targetYAML := `openapi: 3.0.0 info: title: Test version: 1.0.0` doc, err := NewDocument([]byte(targetYAML)) require.NoError(t, err) result, err := ApplyOverlay(doc, nil) assert.ErrorIs(t, err, overlay.ErrInvalidOverlay) assert.Nil(t, result) } // Tests for ApplyOverlayFromBytes (Document, overlayBytes) func TestApplyOverlayFromBytes(t *testing.T) { targetYAML := `openapi: 3.0.0 info: title: Original Title version: 1.0.0 paths: {}` overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.info update: title: Updated Title` doc, err := NewDocument([]byte(targetYAML)) require.NoError(t, err) result, err := ApplyOverlayFromBytes(doc, []byte(overlayYAML)) require.NoError(t, err) assert.NotNil(t, result) assert.Contains(t, string(result.Bytes), "Updated Title") // Verify OverlayDocument is populated assert.NotNil(t, result.OverlayDocument) assert.Equal(t, "3.0.0", result.OverlayDocument.GetVersion()) } func TestApplyOverlayFromBytes_InvalidOverlay(t *testing.T) { targetYAML := `openapi: 3.0.0 info: title: Test version: 1.0.0` doc, err := NewDocument([]byte(targetYAML)) require.NoError(t, err) result, err := ApplyOverlayFromBytes(doc, []byte(`invalid: yaml: content:`)) assert.Error(t, err) assert.Nil(t, result) } // Tests for ApplyOverlayToSpecBytes (docBytes, Overlay) func TestApplyOverlayToSpecBytes(t *testing.T) { targetYAML := `openapi: 3.0.0 info: title: Original Title version: 1.0.0 paths: {}` overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.info update: title: Updated Title` ov, err := NewOverlayDocument([]byte(overlayYAML)) require.NoError(t, err) result, err := ApplyOverlayToSpecBytes([]byte(targetYAML), ov) require.NoError(t, err) assert.NotNil(t, result) assert.Contains(t, string(result.Bytes), "Updated Title") // Verify OverlayDocument is populated (with default config) assert.NotNil(t, result.OverlayDocument) assert.Equal(t, "3.0.0", result.OverlayDocument.GetVersion()) } func TestApplyOverlayToSpecBytes_NilOverlay(t *testing.T) { result, err := ApplyOverlayToSpecBytes([]byte(`openapi: 3.0.0`), nil) assert.ErrorIs(t, err, overlay.ErrInvalidOverlay) assert.Nil(t, result) } func TestApplyOverlayToSpecBytes_InvalidTarget(t *testing.T) { targetYAML := `invalid: yaml: content:` overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.info update: title: Updated` ov, err := NewOverlayDocument([]byte(overlayYAML)) require.NoError(t, err) result, err := ApplyOverlayToSpecBytes([]byte(targetYAML), ov) assert.Error(t, err) assert.Nil(t, result) } // Tests for ApplyOverlayFromBytesToSpecBytes (docBytes, overlayBytes) func TestApplyOverlayFromBytesToSpecBytes(t *testing.T) { targetYAML := `openapi: 3.0.0 info: title: Original Title version: 1.0.0 paths: {}` overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.info update: title: Updated Title` result, err := ApplyOverlayFromBytesToSpecBytes([]byte(targetYAML), []byte(overlayYAML)) require.NoError(t, err) assert.NotNil(t, result) assert.Contains(t, string(result.Bytes), "Updated Title") // Verify OverlayDocument is populated (with default config) assert.NotNil(t, result.OverlayDocument) assert.Equal(t, "3.0.0", result.OverlayDocument.GetVersion()) } func TestApplyOverlayFromBytesToSpecBytes_InvalidOverlay(t *testing.T) { targetYAML := `openapi: 3.0.0` overlayYAML := `invalid: yaml: content:` result, err := ApplyOverlayFromBytesToSpecBytes([]byte(targetYAML), []byte(overlayYAML)) assert.Error(t, err) assert.Nil(t, result) } func TestApplyOverlayFromBytesToSpecBytes_InvalidTarget(t *testing.T) { targetYAML := `invalid: yaml: content:` overlayYAML := `overlay: 1.0.0 info: title: Test version: 1.0.0 actions: - target: $.info update: title: Updated` result, err := ApplyOverlayFromBytesToSpecBytes([]byte(targetYAML), []byte(overlayYAML)) assert.Error(t, err) assert.Nil(t, result) } func TestApplyOverlayFromBytesToSpecBytes_ComplexOverlay(t *testing.T) { targetYAML := `openapi: 3.0.0 info: title: Original version: 1.0.0 description: Remove me tags: - name: existing paths: {}` overlayYAML := `overlay: 1.0.0 info: title: Complex Overlay version: 1.0.0 actions: - target: $.info update: title: Updated - target: $.info.description remove: true - target: $.tags update: - name: new-tag` result, err := ApplyOverlayFromBytesToSpecBytes([]byte(targetYAML), []byte(overlayYAML)) require.NoError(t, err) assert.Contains(t, string(result.Bytes), "Updated") assert.NotContains(t, string(result.Bytes), "Remove me") assert.Contains(t, string(result.Bytes), "existing") assert.Contains(t, string(result.Bytes), "new-tag") // Verify OverlayDocument is populated assert.NotNil(t, result.OverlayDocument) } func TestApplyOverlay_CanBuildModel(t *testing.T) { targetYAML := `openapi: 3.0.0 info: title: Original Title version: 1.0.0 paths: /test: get: summary: Test endpoint` overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.info update: title: Updated Title` doc, err := NewDocument([]byte(targetYAML)) require.NoError(t, err) ov, err := NewOverlayDocument([]byte(overlayYAML)) require.NoError(t, err) result, err := ApplyOverlay(doc, ov) require.NoError(t, err) // Verify we can build a model from the OverlayDocument model, errs := result.OverlayDocument.BuildV3Model() require.Empty(t, errs) assert.NotNil(t, model) assert.Equal(t, "Updated Title", model.Model.Info.Title) } // mockDocument is a minimal Document implementation for testing edge cases type mockDocument struct { specBytes *[]byte config *datamodel.DocumentConfiguration version string } func (m *mockDocument) GetSpecInfo() *datamodel.SpecInfo { return &datamodel.SpecInfo{SpecBytes: m.specBytes} } func (m *mockDocument) GetConfiguration() *datamodel.DocumentConfiguration { return m.config } func (m *mockDocument) GetVersion() string { return m.version } func (m *mockDocument) GetRolodex() *index.Rolodex { return nil } func (m *mockDocument) SetConfiguration(*datamodel.DocumentConfiguration) {} func (m *mockDocument) Render() ([]byte, error) { return nil, nil } func (m *mockDocument) BuildV2Model() (*DocumentModel[v2.Swagger], error) { return nil, nil } func (m *mockDocument) BuildV3Model() (*DocumentModel[v3.Document], error) { return nil, nil } func (m *mockDocument) Serialize() ([]byte, error) { return nil, nil } func (m *mockDocument) RenderAndReload() ([]byte, Document, *DocumentModel[v3.Document], error) { return nil, nil, nil, nil } func (m *mockDocument) Release() {} func TestApplyOverlay_NilSpecBytes(t *testing.T) { // Test line 63: specBytes == nil doc := &mockDocument{ specBytes: nil, config: nil, } overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.info update: title: Updated` ov, err := NewOverlayDocument([]byte(overlayYAML)) require.NoError(t, err) result, err := ApplyOverlay(doc, ov) assert.ErrorIs(t, err, overlay.ErrNoTargetDocument) assert.Nil(t, result) } func TestApplyOverlay_InvalidResultDocument(t *testing.T) { // Test line 73: NewDocumentWithConfiguration fails // Create an overlay that removes the openapi version, making the result invalid targetYAML := `openapi: 3.0.0 info: title: Test version: 1.0.0` overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.openapi remove: true` doc, err := NewDocument([]byte(targetYAML)) require.NoError(t, err) ov, err := NewOverlayDocument([]byte(overlayYAML)) require.NoError(t, err) result, err := ApplyOverlay(doc, ov) // Should fail because resulting document has no openapi version assert.Error(t, err) assert.Nil(t, result) } func TestApplyOverlayToSpecBytes_InvalidResultDocument(t *testing.T) { // Test line 126: NewDocumentWithConfiguration fails in applyOverlayToBytesWithConfig targetYAML := `openapi: 3.0.0 info: title: Test version: 1.0.0` overlayYAML := `overlay: 1.0.0 info: title: Test Overlay version: 1.0.0 actions: - target: $.openapi remove: true` ov, err := NewOverlayDocument([]byte(overlayYAML)) require.NoError(t, err) result, err := ApplyOverlayToSpecBytes([]byte(targetYAML), ov) // Should fail because resulting document has no openapi version assert.Error(t, err) assert.Nil(t, result) }