package xmlparser import ( "bytes" "fmt" "io" "strings" "sync" "testing" ) // Mock XML data for testing const ( validMockXML = ` Test Mock Name Test mock value ` validMockXMLWithNamespace = ` Namespaced Mock Namespaced value ` invalidMockXML = ` Missing name ` malformedXML = ` Unclosed tag This will fail ` emptyXML = `` largeMockXML = ` Large Mock This is a large XML document with nested elements Deep nesting test ` deeplyNestedXML = ` Deep Nesting Test Testing depth limits Deep ` ) // errorReader is a mock reader that always returns an error type errorReader struct{} func (er errorReader) Read(p []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } func TestNewParser(t *testing.T) { tests := []struct { name string options *ParserOptions }{ { name: "Default options", options: nil, }, { name: "Custom options", options: &ParserOptions{ StrictValidation: false, AllowEmptyElements: false, PreserveWhitespace: true, IgnoreUnknownElements: true, MaxDepth: 50, MaxSize: 1024 * 1024, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { parser := NewParser[MockParsable](tt.options) if parser == nil { t.Error("Expected parser to not be nil") return } // Verify parser type if _, ok := parser.(*GenericParser[MockParsable]); !ok { t.Error("Expected parser to be of type *GenericParser[MockParsable]") } }) } } func TestParseString(t *testing.T) { tests := []struct { name string xmlData string expectError bool expectedID string options *ParserOptions }{ { name: "Valid XML", xmlData: validMockXML, expectError: false, expectedID: "test-123", options: nil, }, { name: "Valid XML with namespace", xmlData: validMockXMLWithNamespace, expectError: false, expectedID: "test-456", options: nil, }, { name: "Invalid XML (validation failure)", xmlData: invalidMockXML, expectError: true, expectedID: "", options: nil, }, { name: "Malformed XML", xmlData: malformedXML, expectError: true, expectedID: "", options: nil, }, { name: "Empty XML", xmlData: emptyXML, expectError: true, expectedID: "", options: nil, }, { name: "Large XML", xmlData: largeMockXML, expectError: false, expectedID: "large-test", options: nil, }, { name: "XML exceeds size limit", xmlData: largeMockXML, expectError: true, expectedID: "", options: &ParserOptions{ MaxSize: 100, // Very small limit }, }, { name: "Deeply nested XML exceeds depth", xmlData: deeplyNestedXML, expectError: true, expectedID: "", options: &ParserOptions{ MaxDepth: 10, // Shallow depth limit }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { parser := NewParser[MockParsable](tt.options) result, err := parser.ParseString(tt.xmlData) if tt.expectError { if err == nil { t.Error("Expected parsing error but got none") } if result != nil { t.Error("Expected result to be nil when error occurs") } return } if err != nil { t.Errorf("Unexpected parsing error: %v", err) return } if result == nil { t.Error("Expected result to not be nil") return } if result.GetID() != tt.expectedID { t.Errorf("Expected ID '%s', got '%s'", tt.expectedID, result.GetID()) } // Verify the mock is valid (should pass validation) if err := result.Validate(); err != nil { t.Errorf("Parsed result failed validation: %v", err) } }) } } func TestParseBytes(t *testing.T) { xmlData := []byte(validMockXML) parser := NewParser[MockParsable](nil) result, err := parser.ParseBytes(xmlData) if err != nil { t.Errorf("Unexpected parsing error: %v", err) return } if result == nil { t.Error("Expected result to not be nil") return } if result.GetID() != "test-123" { t.Errorf("Expected ID 'test-123', got '%s'", result.GetID()) } } func TestParse(t *testing.T) { tests := []struct { name string reader io.Reader expectError bool expectedID string }{ { name: "Valid reader", reader: strings.NewReader(validMockXML), expectError: false, expectedID: "test-123", }, { name: "Error reader", reader: errorReader{}, expectError: true, expectedID: "", }, { name: "Empty reader", reader: strings.NewReader(""), expectError: true, expectedID: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { parser := NewParser[MockParsable](nil) result, err := parser.Parse(tt.reader) if tt.expectError { if err == nil { t.Error("Expected parsing error but got none") } if result != nil { t.Error("Expected result to be nil when error occurs") } return } if err != nil { t.Errorf("Unexpected parsing error: %v", err) return } if result == nil { t.Error("Expected result to not be nil") return } if result.GetID() != tt.expectedID { t.Errorf("Expected ID '%s', got '%s'", tt.expectedID, result.GetID()) } }) } } func TestValidate(t *testing.T) { tests := []struct { name string reader io.Reader expectError bool options *ParserOptions }{ { name: "Valid XML", reader: strings.NewReader(validMockXML), expectError: false, options: nil, }, { name: "Malformed XML", reader: strings.NewReader(malformedXML), expectError: true, options: nil, }, { name: "Empty XML", reader: strings.NewReader(""), expectError: true, options: nil, }, { name: "Error reader", reader: errorReader{}, expectError: true, options: nil, }, { name: "Deeply nested XML exceeds depth", reader: strings.NewReader(deeplyNestedXML), expectError: true, options: &ParserOptions{ MaxDepth: 10, }, }, { name: "Valid deeply nested XML within limits", reader: strings.NewReader(deeplyNestedXML), expectError: false, options: &ParserOptions{ MaxDepth: 100, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { parser := NewParser[MockParsable](tt.options) err := parser.Validate(tt.reader) if tt.expectError { if err == nil { t.Error("Expected validation error but got none") } } else { if err != nil { t.Errorf("Unexpected validation error: %v", err) } } }) } } func TestParseWithResult(t *testing.T) { tests := []struct { name string reader io.Reader expectError bool expectedID string expectMetadata bool expectedVersion string expectedEncoding string }{ { name: "Valid XML with metadata", reader: strings.NewReader(validMockXML), expectError: false, expectedID: "test-123", expectMetadata: true, expectedVersion: "1.0", expectedEncoding: "UTF-8", }, { name: "Valid XML with namespace", reader: strings.NewReader(validMockXMLWithNamespace), expectError: false, expectedID: "test-456", expectMetadata: true, expectedVersion: "1.0", expectedEncoding: "UTF-8", }, { name: "Error reader", reader: errorReader{}, expectError: true, expectedID: "", expectMetadata: false, }, { name: "Invalid XML (validation failure)", reader: strings.NewReader(invalidMockXML), expectError: true, expectedID: "", expectMetadata: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { parser := NewParser[MockParsable](nil) // Cast to GenericParser to access ParseWithResult genericParser, ok := parser.(*GenericParser[MockParsable]) if !ok { t.Error("Expected parser to be of type *GenericParser[MockParsable]") return } result, err := genericParser.ParseWithResult(tt.reader) if result == nil { t.Error("Expected result to not be nil") return } if tt.expectError { if err == nil { t.Error("Expected parsing error but got none") } if len(result.Errors) == 0 { t.Error("Expected errors in result") } } else { if err != nil { t.Errorf("Unexpected parsing error: %v", err) } if len(result.Errors) > 0 { t.Errorf("Unexpected errors in result: %v", result.Errors) } } if tt.expectMetadata { if result.Metadata == nil { t.Error("Expected metadata to not be nil") } else { if tt.expectedVersion != "" && result.Metadata.Version != tt.expectedVersion { t.Errorf("Expected version '%s', got '%s'", tt.expectedVersion, result.Metadata.Version) } if tt.expectedEncoding != "" && result.Metadata.Encoding != tt.expectedEncoding { t.Errorf("Expected encoding '%s', got '%s'", tt.expectedEncoding, result.Metadata.Encoding) } } } if !tt.expectError && result.Data != nil { if result.Data.GetID() != tt.expectedID { t.Errorf("Expected ID '%s', got '%s'", tt.expectedID, result.Data.GetID()) } } }) } } func TestParseWithInvalidMockData(t *testing.T) { // Create invalid mock data that will pass XML parsing but fail validation invalidMockData := MockParsable{ ID: "test-invalid", Name: "Test Name", Value: "test-validation-failure", // This will cause validation to fail Valid: false, // This will cause validation to fail } // This test ensures that even if XML parsing succeeds, validation errors are caught xmlData := ` Test Name test-validation-failure ` parser := NewParser[MockParsable](nil) result, err := parser.ParseString(xmlData) // This should fail during validation, not XML parsing if err == nil { t.Error("Expected validation error but got none") } if result != nil { t.Error("Expected result to be nil when validation fails") } // Verify the error message contains validation information if !strings.Contains(err.Error(), "validation failed") { t.Errorf("Expected validation error message, got: %v", err) } // Test the mock data validation directly validationErr := invalidMockData.Validate() if validationErr == nil { t.Error("Expected direct validation to fail") } } func TestConcurrentParsing(t *testing.T) { parser := NewParser[MockParsable](nil) numGoroutines := 10 numOperations := 5 var wg sync.WaitGroup errors := make(chan error, numGoroutines*numOperations) results := make(chan *MockParsable, numGoroutines*numOperations) for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(goroutineID int) { defer wg.Done() for j := 0; j < numOperations; j++ { // Use different XML data for each operation xmlData := strings.Replace(validMockXML, "test-123", fmt.Sprintf("test-%d-%d", goroutineID, j), 1) result, err := parser.ParseString(xmlData) if err != nil { errors <- err } else { results <- result } } }(i) } wg.Wait() close(errors) close(results) // Check for errors errorCount := 0 for err := range errors { t.Errorf("Concurrent parsing error: %v", err) errorCount++ } // Check results resultCount := 0 for range results { resultCount++ } expectedResults := numGoroutines * numOperations if resultCount != expectedResults { t.Errorf("Expected %d results, got %d (errors: %d)", expectedResults, resultCount, errorCount) } } func TestParserOptionsEffects(t *testing.T) { tests := []struct { name string options *ParserOptions xmlData string expectError bool }{ { name: "Strict validation enabled", options: &ParserOptions{ StrictValidation: true, }, xmlData: validMockXML, expectError: false, }, { name: "Strict validation disabled", options: &ParserOptions{ StrictValidation: false, }, xmlData: validMockXML, expectError: false, }, { name: "Very small size limit", options: &ParserOptions{ MaxSize: 10, // Very small }, xmlData: validMockXML, expectError: true, }, { name: "Very small depth limit", options: &ParserOptions{ MaxDepth: 1, // Very shallow }, xmlData: largeMockXML, // Contains nested elements expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { parser := NewParser[MockParsable](tt.options) _, err := parser.ParseString(tt.xmlData) if tt.expectError { if err == nil { t.Error("Expected parsing error but got none") } } else { if err != nil { t.Errorf("Unexpected parsing error: %v", err) } } }) } } func TestExtractMetadata(t *testing.T) { parser := NewParser[MockParsable](nil) genericParser, ok := parser.(*GenericParser[MockParsable]) if !ok { t.Error("Expected parser to be of type *GenericParser[MockParsable]") return } tests := []struct { name string xmlData string expectedVersion string expectedEncoding string expectedNamespace string }{ { name: "Standard XML with version and encoding", xmlData: validMockXML, expectedVersion: "1.0", expectedEncoding: "UTF-8", expectedNamespace: "", }, { name: "XML with namespace", xmlData: validMockXMLWithNamespace, expectedVersion: "1.0", expectedEncoding: "UTF-8", expectedNamespace: "http://test.example.com", }, { name: "XML with single quotes", xmlData: ` Test Value `, expectedVersion: "1.1", expectedEncoding: "ISO-8859-1", expectedNamespace: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { metadata := &XMLMetadata{} err := genericParser.extractMetadata([]byte(tt.xmlData), metadata) if err != nil { t.Errorf("Unexpected metadata extraction error: %v", err) return } if metadata.Version != tt.expectedVersion { t.Errorf("Expected version '%s', got '%s'", tt.expectedVersion, metadata.Version) } if metadata.Encoding != tt.expectedEncoding { t.Errorf("Expected encoding '%s', got '%s'", tt.expectedEncoding, metadata.Encoding) } if metadata.Namespace != tt.expectedNamespace { t.Errorf("Expected namespace '%s', got '%s'", tt.expectedNamespace, metadata.Namespace) } }) } } func TestParseEdgeCases(t *testing.T) { parser := NewParser[MockParsable](nil) tests := []struct { name string xmlData string expectError bool description string }{ { name: "XML with comments", xmlData: ` Test with comments Comments should be ignored `, expectError: false, description: "XML with comments should parse successfully", }, { name: "XML with CDATA", xmlData: ` CDATA Test characters]]> `, expectError: false, description: "XML with CDATA should parse successfully", }, { name: "XML with special characters", xmlData: ` Special & Characters <tag> "quoted" 'text' `, expectError: false, description: "XML with escaped special characters should parse successfully", }, { name: "Minimal valid XML", xmlData: ` Minimal Test `, expectError: false, description: "Minimal XML without declaration should parse successfully", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := parser.ParseString(tt.xmlData) if tt.expectError { if err == nil { t.Errorf("%s: Expected error but got none", tt.description) } } else { if err != nil { t.Errorf("%s: Unexpected error: %v", tt.description, err) return } if result == nil { t.Errorf("%s: Expected result to not be nil", tt.description) return } // Verify the result is valid if err := result.Validate(); err != nil { t.Errorf("%s: Result validation failed: %v", tt.description, err) } } }) } } func TestParserMemoryUsage(t *testing.T) { // Test parsing multiple documents to ensure no memory leaks parser := NewParser[MockParsable](nil) for i := 0; i < 100; i++ { xmlData := strings.Replace(validMockXML, "test-123", fmt.Sprintf("test-%d", i), 1) result, err := parser.ParseString(xmlData) if err != nil { t.Errorf("Parse error on iteration %d: %v", i, err) break } if result == nil { t.Errorf("Nil result on iteration %d", i) break } // Explicitly trigger validation to ensure full processing if err := result.Validate(); err != nil { t.Errorf("Validation error on iteration %d: %v", i, err) break } } } func TestParseFromBuffer(t *testing.T) { // Test parsing from a bytes.Buffer buffer := bytes.NewBufferString(validMockXML) parser := NewParser[MockParsable](nil) result, err := parser.Parse(buffer) if err != nil { t.Errorf("Unexpected error parsing from buffer: %v", err) return } if result == nil { t.Error("Expected result to not be nil") return } if result.GetID() != "test-123" { t.Errorf("Expected ID 'test-123', got '%s'", result.GetID()) } }