Files
tm_back/pkg/xmlparser/parser_test.go
T
n.nakhostin 5322d3cd8e Add comprehensive tests for XML parser functionality
- Introduced benchmark tests for various XML parsing methods, including parsing from strings, bytes, and readers, to evaluate performance.
- Added unit tests for the parser interface and validation mechanisms, ensuring robust error handling and validation logic.
- Implemented tests for utility functions related to XML processing, enhancing overall test coverage and reliability.
- Created a test suite for the TED domain, validating the structure and functionality of contract notices and related entities.
- Developed a comprehensive test runner script to facilitate automated testing, coverage analysis, and performance benchmarking for the XML parser package.
2025-08-12 12:51:24 +03:30

815 lines
19 KiB
Go

package xmlparser
import (
"bytes"
"fmt"
"io"
"strings"
"sync"
"testing"
)
// Mock XML data for testing
const (
validMockXML = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="test-123">
<name>Test Mock Name</name>
<value>Test mock value</value>
</MockElement>`
validMockXMLWithNamespace = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement xmlns="http://test.example.com" id="test-456">
<name>Namespaced Mock</name>
<value>Namespaced value</value>
</MockElement>`
invalidMockXML = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="test-invalid">
<name></name>
<value>Missing name</value>
</MockElement>`
malformedXML = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="malformed">
<name>Unclosed tag</name>
<value>This will fail</value>
</MockElemen>`
emptyXML = ``
largeMockXML = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="large-test">
<name>Large Mock</name>
<value>This is a large XML document with nested elements</value>
<nested>
<level1>
<level2>
<level3>
<level4>
<level5>Deep nesting test</level5>
</level4>
</level3>
</level2>
</level1>
</nested>
</MockElement>`
deeplyNestedXML = `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="deep-test">
<name>Deep Nesting Test</name>
<value>Testing depth limits</value>
<level1><level2><level3><level4><level5>
<level6><level7><level8><level9><level10>
<level11><level12><level13><level14><level15>
<level16><level17><level18><level19><level20>Deep</level20></level19></level18></level17></level16>
</level15></level14></level13></level12></level11>
</level10></level9></level8></level7></level6>
</level5></level4></level3></level2></level1>
</MockElement>`
)
// 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 := `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="test-invalid">
<name>Test Name</name>
<value>test-validation-failure</value>
</MockElement>`
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: `<?xml version='1.1' encoding='ISO-8859-1'?>
<MockElement id="test">
<name>Test</name>
<value>Value</value>
</MockElement>`,
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: `<?xml version="1.0" encoding="UTF-8"?>
<!-- This is a comment -->
<MockElement id="test-comments">
<!-- Another comment -->
<name>Test with comments</name>
<value>Comments should be ignored</value>
</MockElement>`,
expectError: false,
description: "XML with comments should parse successfully",
},
{
name: "XML with CDATA",
xmlData: `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="test-cdata">
<name>CDATA Test</name>
<value><![CDATA[This is CDATA content with <special> characters]]></value>
</MockElement>`,
expectError: false,
description: "XML with CDATA should parse successfully",
},
{
name: "XML with special characters",
xmlData: `<?xml version="1.0" encoding="UTF-8"?>
<MockElement id="test-special">
<name>Special &amp; Characters</name>
<value>&lt;tag&gt; &quot;quoted&quot; &apos;text&apos;</value>
</MockElement>`,
expectError: false,
description: "XML with escaped special characters should parse successfully",
},
{
name: "Minimal valid XML",
xmlData: `<MockElement id="minimal">
<name>Minimal</name>
<value>Test</value>
</MockElement>`,
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())
}
}