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.
This commit is contained in:
@@ -0,0 +1,373 @@
|
||||
package xmlparser
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// MockParsable is a test implementation of the Parsable interface
|
||||
type MockParsable struct {
|
||||
XMLName xml.Name `xml:"MockElement"`
|
||||
ID string `xml:"id,attr"`
|
||||
Name string `xml:"name"`
|
||||
Value string `xml:"value"`
|
||||
Valid bool `xml:"-"` // Controls validation behavior
|
||||
}
|
||||
|
||||
// Validate implements the Parsable interface
|
||||
func (m MockParsable) Validate() error {
|
||||
// Only fail validation if explicitly set to false in tests
|
||||
// When parsed from XML, Valid will be false (default), but we want validation to pass
|
||||
// unless specifically testing validation failures
|
||||
if m.ID == "" {
|
||||
return ValidationError{
|
||||
Field: "id",
|
||||
Message: "ID is required",
|
||||
}
|
||||
}
|
||||
if m.Name == "" {
|
||||
return ValidationError{
|
||||
Field: "name",
|
||||
Message: "Name is required",
|
||||
}
|
||||
}
|
||||
// Only check the Valid field if this appears to be a test scenario
|
||||
// where Valid was explicitly set to false (we can detect this by checking
|
||||
// if both ID and Name are populated but Valid is false)
|
||||
if m.ID != "" && m.Name != "" && !m.Valid {
|
||||
// This is likely a test case where Valid was explicitly set to false
|
||||
// In real XML parsing scenarios, Valid will be false by default but validation should pass
|
||||
// We'll use a different approach - check if Value is specifically set to indicate test failure
|
||||
if m.Value == "test-validation-failure" {
|
||||
return ValidationError{
|
||||
Field: "valid",
|
||||
Message: "mock validation failed",
|
||||
Value: "false",
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID implements the Parsable interface
|
||||
func (m MockParsable) GetID() string {
|
||||
return m.ID
|
||||
}
|
||||
|
||||
func TestDefaultParserOptions(t *testing.T) {
|
||||
options := DefaultParserOptions()
|
||||
|
||||
// Test default values
|
||||
if !options.StrictValidation {
|
||||
t.Error("Expected StrictValidation to be true by default")
|
||||
}
|
||||
|
||||
if !options.AllowEmptyElements {
|
||||
t.Error("Expected AllowEmptyElements to be true by default")
|
||||
}
|
||||
|
||||
if options.PreserveWhitespace {
|
||||
t.Error("Expected PreserveWhitespace to be false by default")
|
||||
}
|
||||
|
||||
if options.IgnoreUnknownElements {
|
||||
t.Error("Expected IgnoreUnknownElements to be false by default")
|
||||
}
|
||||
|
||||
if options.MaxDepth != 100 {
|
||||
t.Errorf("Expected MaxDepth to be 100, got %d", options.MaxDepth)
|
||||
}
|
||||
|
||||
expectedMaxSize := int64(10 * 1024 * 1024) // 10MB
|
||||
if options.MaxSize != expectedMaxSize {
|
||||
t.Errorf("Expected MaxSize to be %d, got %d", expectedMaxSize, options.MaxSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestXMLMetadata(t *testing.T) {
|
||||
metadata := &XMLMetadata{
|
||||
XMLName: xml.Name{Local: "TestElement", Space: "http://test.com"},
|
||||
Namespace: "http://test.com",
|
||||
Version: "1.0",
|
||||
Encoding: "UTF-8",
|
||||
}
|
||||
|
||||
if metadata.XMLName.Local != "TestElement" {
|
||||
t.Errorf("Expected XMLName.Local to be 'TestElement', got '%s'", metadata.XMLName.Local)
|
||||
}
|
||||
|
||||
if metadata.XMLName.Space != "http://test.com" {
|
||||
t.Errorf("Expected XMLName.Space to be 'http://test.com', got '%s'", metadata.XMLName.Space)
|
||||
}
|
||||
|
||||
if metadata.Namespace != "http://test.com" {
|
||||
t.Errorf("Expected Namespace to be 'http://test.com', got '%s'", metadata.Namespace)
|
||||
}
|
||||
|
||||
if metadata.Version != "1.0" {
|
||||
t.Errorf("Expected Version to be '1.0', got '%s'", metadata.Version)
|
||||
}
|
||||
|
||||
if metadata.Encoding != "UTF-8" {
|
||||
t.Errorf("Expected Encoding to be 'UTF-8', got '%s'", metadata.Encoding)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseResult(t *testing.T) {
|
||||
mockData := &MockParsable{
|
||||
ID: "test-123",
|
||||
Name: "Test Mock",
|
||||
Value: "test value",
|
||||
Valid: true,
|
||||
}
|
||||
|
||||
metadata := &XMLMetadata{
|
||||
XMLName: xml.Name{Local: "MockElement"},
|
||||
Namespace: "http://test.com",
|
||||
Version: "1.0",
|
||||
Encoding: "UTF-8",
|
||||
}
|
||||
|
||||
result := &ParseResult[MockParsable]{
|
||||
Data: mockData,
|
||||
Metadata: metadata,
|
||||
Errors: []string{"test error"},
|
||||
Warnings: []string{"test warning"},
|
||||
}
|
||||
|
||||
// Test data
|
||||
if result.Data == nil {
|
||||
t.Error("Expected Data to not be nil")
|
||||
} else {
|
||||
if result.Data.GetID() != "test-123" {
|
||||
t.Errorf("Expected Data.GetID() to be 'test-123', got '%s'", result.Data.GetID())
|
||||
}
|
||||
}
|
||||
|
||||
// Test metadata
|
||||
if result.Metadata == nil {
|
||||
t.Error("Expected Metadata to not be nil")
|
||||
} else {
|
||||
if result.Metadata.Version != "1.0" {
|
||||
t.Errorf("Expected Metadata.Version to be '1.0', got '%s'", result.Metadata.Version)
|
||||
}
|
||||
}
|
||||
|
||||
// Test errors
|
||||
if len(result.Errors) != 1 {
|
||||
t.Errorf("Expected 1 error, got %d", len(result.Errors))
|
||||
} else if result.Errors[0] != "test error" {
|
||||
t.Errorf("Expected first error to be 'test error', got '%s'", result.Errors[0])
|
||||
}
|
||||
|
||||
// Test warnings
|
||||
if len(result.Warnings) != 1 {
|
||||
t.Errorf("Expected 1 warning, got %d", len(result.Warnings))
|
||||
} else if result.Warnings[0] != "test warning" {
|
||||
t.Errorf("Expected first warning to be 'test warning', got '%s'", result.Warnings[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockParsable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mock MockParsable
|
||||
expectError bool
|
||||
errorField string
|
||||
}{
|
||||
{
|
||||
name: "Valid mock",
|
||||
mock: MockParsable{
|
||||
ID: "test-123",
|
||||
Name: "Test Name",
|
||||
Value: "test value",
|
||||
Valid: true,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid mock - Valid flag false",
|
||||
mock: MockParsable{
|
||||
ID: "test-123",
|
||||
Name: "Test Name",
|
||||
Value: "test-validation-failure",
|
||||
Valid: false,
|
||||
},
|
||||
expectError: true,
|
||||
errorField: "valid",
|
||||
},
|
||||
{
|
||||
name: "Invalid mock - Missing ID",
|
||||
mock: MockParsable{
|
||||
Name: "Test Name",
|
||||
Value: "test value",
|
||||
Valid: true,
|
||||
},
|
||||
expectError: true,
|
||||
errorField: "id",
|
||||
},
|
||||
{
|
||||
name: "Invalid mock - Missing Name",
|
||||
mock: MockParsable{
|
||||
ID: "test-123",
|
||||
Value: "test value",
|
||||
Valid: true,
|
||||
},
|
||||
expectError: true,
|
||||
errorField: "name",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.mock.Validate()
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Error("Expected validation error but got none")
|
||||
return
|
||||
}
|
||||
|
||||
if validationErr, ok := err.(ValidationError); ok {
|
||||
if validationErr.Field != tt.errorField {
|
||||
t.Errorf("Expected error field '%s', got '%s'", tt.errorField, validationErr.Field)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("Expected ValidationError, got %T", err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected validation error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test GetID method
|
||||
expectedID := tt.mock.ID
|
||||
if tt.mock.GetID() != expectedID {
|
||||
t.Errorf("Expected GetID() to return '%s', got '%s'", expectedID, tt.mock.GetID())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserInterface(t *testing.T) {
|
||||
// Test that our MockParsable implements the Parsable interface
|
||||
var _ Parsable = MockParsable{}
|
||||
|
||||
// Test that our parser implements the Parser interface
|
||||
parser := NewParser[MockParsable](nil)
|
||||
var _ Parser[MockParsable] = parser
|
||||
|
||||
// Test parser creation with default options
|
||||
if parser == nil {
|
||||
t.Error("Expected parser to not be nil")
|
||||
}
|
||||
|
||||
// Test parser creation with custom options
|
||||
customOptions := &ParserOptions{
|
||||
StrictValidation: false,
|
||||
AllowEmptyElements: false,
|
||||
PreserveWhitespace: true,
|
||||
IgnoreUnknownElements: true,
|
||||
MaxDepth: 50,
|
||||
MaxSize: 1024 * 1024, // 1MB
|
||||
}
|
||||
|
||||
customParser := NewParser[MockParsable](customOptions)
|
||||
if customParser == nil {
|
||||
t.Error("Expected custom parser to not be nil")
|
||||
}
|
||||
|
||||
// Verify the parser can be cast to GenericParser to access options
|
||||
if genericParser, ok := customParser.(*GenericParser[MockParsable]); ok {
|
||||
if genericParser.options.MaxDepth != 50 {
|
||||
t.Errorf("Expected custom MaxDepth to be 50, got %d", genericParser.options.MaxDepth)
|
||||
}
|
||||
if genericParser.options.StrictValidation {
|
||||
t.Error("Expected custom StrictValidation to be false")
|
||||
}
|
||||
} else {
|
||||
t.Error("Expected parser to be of type *GenericParser[MockParsable]")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserOptionsCustomization(t *testing.T) {
|
||||
options := &ParserOptions{
|
||||
StrictValidation: false,
|
||||
AllowEmptyElements: false,
|
||||
PreserveWhitespace: true,
|
||||
IgnoreUnknownElements: true,
|
||||
MaxDepth: 200,
|
||||
MaxSize: 5 * 1024 * 1024, // 5MB
|
||||
}
|
||||
|
||||
// Test each option individually
|
||||
if options.StrictValidation {
|
||||
t.Error("Expected StrictValidation to be false")
|
||||
}
|
||||
|
||||
if options.AllowEmptyElements {
|
||||
t.Error("Expected AllowEmptyElements to be false")
|
||||
}
|
||||
|
||||
if !options.PreserveWhitespace {
|
||||
t.Error("Expected PreserveWhitespace to be true")
|
||||
}
|
||||
|
||||
if !options.IgnoreUnknownElements {
|
||||
t.Error("Expected IgnoreUnknownElements to be true")
|
||||
}
|
||||
|
||||
if options.MaxDepth != 200 {
|
||||
t.Errorf("Expected MaxDepth to be 200, got %d", options.MaxDepth)
|
||||
}
|
||||
|
||||
expectedMaxSize := int64(5 * 1024 * 1024)
|
||||
if options.MaxSize != expectedMaxSize {
|
||||
t.Errorf("Expected MaxSize to be %d, got %d", expectedMaxSize, options.MaxSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyParseResult(t *testing.T) {
|
||||
result := &ParseResult[MockParsable]{}
|
||||
|
||||
if result.Data != nil {
|
||||
t.Error("Expected empty result Data to be nil")
|
||||
}
|
||||
|
||||
if result.Metadata != nil {
|
||||
t.Error("Expected empty result Metadata to be nil")
|
||||
}
|
||||
|
||||
if result.Errors != nil {
|
||||
t.Error("Expected empty result Errors to be nil")
|
||||
}
|
||||
|
||||
if result.Warnings != nil {
|
||||
t.Error("Expected empty result Warnings to be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestXMLMetadataEmpty(t *testing.T) {
|
||||
metadata := &XMLMetadata{}
|
||||
|
||||
if metadata.XMLName.Local != "" {
|
||||
t.Errorf("Expected empty XMLName.Local, got '%s'", metadata.XMLName.Local)
|
||||
}
|
||||
|
||||
if metadata.XMLName.Space != "" {
|
||||
t.Errorf("Expected empty XMLName.Space, got '%s'", metadata.XMLName.Space)
|
||||
}
|
||||
|
||||
if metadata.Namespace != "" {
|
||||
t.Errorf("Expected empty Namespace, got '%s'", metadata.Namespace)
|
||||
}
|
||||
|
||||
if metadata.Version != "" {
|
||||
t.Errorf("Expected empty Version, got '%s'", metadata.Version)
|
||||
}
|
||||
|
||||
if metadata.Encoding != "" {
|
||||
t.Errorf("Expected empty Encoding, got '%s'", metadata.Encoding)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user