mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-01-16 13:42:10 +01:00
Add comprehensive unit tests for services and main package
- Implement tests for the app service, including course processing from file and URI. - Create mock implementations for CourseParser and Exporter to facilitate testing. - Add tests for HTML cleaner service to validate HTML content cleaning functionality. - Develop tests for the parser service, covering course fetching and loading from files. - Introduce tests for utility functions in the main package, ensuring URI validation and string joining. - Include benchmarks for performance evaluation of key functions.
This commit is contained in:
444
internal/exporters/factory_test.go
Normal file
444
internal/exporters/factory_test.go
Normal file
@ -0,0 +1,444 @@
|
||||
// Package exporters_test provides tests for the exporter factory.
|
||||
package exporters
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kjanat/articulate-parser/internal/services"
|
||||
)
|
||||
|
||||
// TestNewFactory tests the NewFactory constructor.
|
||||
func TestNewFactory(t *testing.T) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
if factory == nil {
|
||||
t.Fatal("NewFactory() returned nil")
|
||||
}
|
||||
|
||||
// Type assertion to check internal structure
|
||||
factoryImpl, ok := factory.(*Factory)
|
||||
if !ok {
|
||||
t.Fatal("NewFactory() returned wrong type")
|
||||
}
|
||||
|
||||
if factoryImpl.htmlCleaner == nil {
|
||||
t.Error("htmlCleaner should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFactory_CreateExporter tests the CreateExporter method for all supported formats.
|
||||
func TestFactory_CreateExporter(t *testing.T) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
format string
|
||||
expectedType string
|
||||
expectedFormat string
|
||||
shouldError bool
|
||||
}{
|
||||
{
|
||||
name: "markdown format",
|
||||
format: "markdown",
|
||||
expectedType: "*exporters.MarkdownExporter",
|
||||
expectedFormat: "markdown",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "md format alias",
|
||||
format: "md",
|
||||
expectedType: "*exporters.MarkdownExporter",
|
||||
expectedFormat: "markdown",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "docx format",
|
||||
format: "docx",
|
||||
expectedType: "*exporters.DocxExporter",
|
||||
expectedFormat: "docx",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "word format alias",
|
||||
format: "word",
|
||||
expectedType: "*exporters.DocxExporter",
|
||||
expectedFormat: "docx",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "unsupported format",
|
||||
format: "pdf",
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
name: "empty format",
|
||||
format: "",
|
||||
shouldError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
exporter, err := factory.CreateExporter(tc.format)
|
||||
|
||||
if tc.shouldError {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for format '%s', but got nil", tc.format)
|
||||
}
|
||||
if exporter != nil {
|
||||
t.Errorf("Expected nil exporter for unsupported format '%s'", tc.format)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error creating exporter for format '%s': %v", tc.format, err)
|
||||
}
|
||||
|
||||
if exporter == nil {
|
||||
t.Fatalf("CreateExporter returned nil for supported format '%s'", tc.format)
|
||||
}
|
||||
|
||||
// Check type
|
||||
exporterType := reflect.TypeOf(exporter).String()
|
||||
if exporterType != tc.expectedType {
|
||||
t.Errorf("Expected exporter type '%s' for format '%s', got '%s'", tc.expectedType, tc.format, exporterType)
|
||||
}
|
||||
|
||||
// Check supported format
|
||||
supportedFormat := exporter.GetSupportedFormat()
|
||||
if supportedFormat != tc.expectedFormat {
|
||||
t.Errorf("Expected supported format '%s' for format '%s', got '%s'", tc.expectedFormat, tc.format, supportedFormat)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFactory_CreateExporter_CaseInsensitive tests that format strings are case-insensitive.
|
||||
func TestFactory_CreateExporter_CaseInsensitive(t *testing.T) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
testCases := []struct {
|
||||
format string
|
||||
expectedFormat string
|
||||
}{
|
||||
{"MARKDOWN", "markdown"},
|
||||
{"Markdown", "markdown"},
|
||||
{"MarkDown", "markdown"},
|
||||
{"MD", "markdown"},
|
||||
{"Md", "markdown"},
|
||||
{"DOCX", "docx"},
|
||||
{"Docx", "docx"},
|
||||
{"DocX", "docx"},
|
||||
{"WORD", "docx"},
|
||||
{"Word", "docx"},
|
||||
{"WoRd", "docx"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.format, func(t *testing.T) {
|
||||
exporter, err := factory.CreateExporter(tc.format)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error for format '%s': %v", tc.format, err)
|
||||
}
|
||||
|
||||
if exporter == nil {
|
||||
t.Fatalf("CreateExporter returned nil for format '%s'", tc.format)
|
||||
}
|
||||
|
||||
supportedFormat := exporter.GetSupportedFormat()
|
||||
if supportedFormat != tc.expectedFormat {
|
||||
t.Errorf("Expected supported format '%s' for format '%s', got '%s'", tc.expectedFormat, tc.format, supportedFormat)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFactory_CreateExporter_ErrorMessages tests error messages for unsupported formats.
|
||||
func TestFactory_CreateExporter_ErrorMessages(t *testing.T) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
testCases := []string{
|
||||
"pdf",
|
||||
"html",
|
||||
"txt",
|
||||
"json",
|
||||
"xml",
|
||||
"unknown",
|
||||
"123",
|
||||
"markdown-invalid",
|
||||
}
|
||||
|
||||
for _, format := range testCases {
|
||||
t.Run(format, func(t *testing.T) {
|
||||
exporter, err := factory.CreateExporter(format)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for unsupported format '%s', got nil", format)
|
||||
}
|
||||
|
||||
if exporter != nil {
|
||||
t.Errorf("Expected nil exporter for unsupported format '%s', got %v", format, exporter)
|
||||
}
|
||||
|
||||
// Check error message contains the format
|
||||
if err != nil && !strings.Contains(err.Error(), format) {
|
||||
t.Errorf("Error message should contain the unsupported format '%s', got: %s", format, err.Error())
|
||||
}
|
||||
|
||||
// Check error message has expected prefix
|
||||
if err != nil && !strings.Contains(err.Error(), "unsupported export format") {
|
||||
t.Errorf("Error message should contain 'unsupported export format', got: %s", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFactory_GetSupportedFormats tests the GetSupportedFormats method.
|
||||
func TestFactory_GetSupportedFormats(t *testing.T) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
formats := factory.GetSupportedFormats()
|
||||
|
||||
if formats == nil {
|
||||
t.Fatal("GetSupportedFormats() returned nil")
|
||||
}
|
||||
|
||||
expected := []string{"markdown", "md", "docx", "word"}
|
||||
|
||||
// Sort both slices for comparison
|
||||
sort.Strings(formats)
|
||||
sort.Strings(expected)
|
||||
|
||||
if !reflect.DeepEqual(formats, expected) {
|
||||
t.Errorf("Expected formats %v, got %v", expected, formats)
|
||||
}
|
||||
|
||||
// Verify all returned formats can create exporters
|
||||
for _, format := range formats {
|
||||
exporter, err := factory.CreateExporter(format)
|
||||
if err != nil {
|
||||
t.Errorf("Format '%s' from GetSupportedFormats() should be creatable, got error: %v", format, err)
|
||||
}
|
||||
if exporter == nil {
|
||||
t.Errorf("Format '%s' from GetSupportedFormats() should create non-nil exporter", format)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFactory_GetSupportedFormats_Immutable tests that the returned slice is safe to modify.
|
||||
func TestFactory_GetSupportedFormats_Immutable(t *testing.T) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
// Get formats twice
|
||||
formats1 := factory.GetSupportedFormats()
|
||||
formats2 := factory.GetSupportedFormats()
|
||||
|
||||
// Modify first slice
|
||||
if len(formats1) > 0 {
|
||||
formats1[0] = "modified"
|
||||
}
|
||||
|
||||
// Check that second call returns unmodified data
|
||||
if len(formats2) > 0 && formats2[0] == "modified" {
|
||||
t.Error("GetSupportedFormats() should return independent slices")
|
||||
}
|
||||
|
||||
// Verify original functionality still works
|
||||
formats3 := factory.GetSupportedFormats()
|
||||
if len(formats3) == 0 {
|
||||
t.Error("GetSupportedFormats() should still return formats after modification")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFactory_ExporterTypes tests that created exporters are of correct types.
|
||||
func TestFactory_ExporterTypes(t *testing.T) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
// Test markdown exporter
|
||||
markdownExporter, err := factory.CreateExporter("markdown")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create markdown exporter: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := markdownExporter.(*MarkdownExporter); !ok {
|
||||
t.Error("Markdown exporter should be of type *MarkdownExporter")
|
||||
}
|
||||
|
||||
// Test docx exporter
|
||||
docxExporter, err := factory.CreateExporter("docx")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create docx exporter: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := docxExporter.(*DocxExporter); !ok {
|
||||
t.Error("DOCX exporter should be of type *DocxExporter")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFactory_HTMLCleanerPropagation tests that HTMLCleaner is properly passed to exporters.
|
||||
func TestFactory_HTMLCleanerPropagation(t *testing.T) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
// Test with markdown exporter
|
||||
markdownExporter, err := factory.CreateExporter("markdown")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create markdown exporter: %v", err)
|
||||
}
|
||||
|
||||
markdownImpl, ok := markdownExporter.(*MarkdownExporter)
|
||||
if !ok {
|
||||
t.Fatal("Failed to cast to MarkdownExporter")
|
||||
}
|
||||
|
||||
if markdownImpl.htmlCleaner == nil {
|
||||
t.Error("HTMLCleaner should be propagated to MarkdownExporter")
|
||||
}
|
||||
|
||||
// Test with docx exporter
|
||||
docxExporter, err := factory.CreateExporter("docx")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create docx exporter: %v", err)
|
||||
}
|
||||
|
||||
docxImpl, ok := docxExporter.(*DocxExporter)
|
||||
if !ok {
|
||||
t.Fatal("Failed to cast to DocxExporter")
|
||||
}
|
||||
|
||||
if docxImpl.htmlCleaner == nil {
|
||||
t.Error("HTMLCleaner should be propagated to DocxExporter")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFactory_MultipleExporterCreation tests creating multiple exporters of same type.
|
||||
func TestFactory_MultipleExporterCreation(t *testing.T) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
// Create multiple markdown exporters
|
||||
exporter1, err := factory.CreateExporter("markdown")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create first markdown exporter: %v", err)
|
||||
}
|
||||
|
||||
exporter2, err := factory.CreateExporter("md")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create second markdown exporter: %v", err)
|
||||
}
|
||||
|
||||
// They should be different instances
|
||||
if exporter1 == exporter2 {
|
||||
t.Error("Factory should create independent exporter instances")
|
||||
}
|
||||
|
||||
// But both should be MarkdownExporter type
|
||||
if _, ok := exporter1.(*MarkdownExporter); !ok {
|
||||
t.Error("First exporter should be MarkdownExporter")
|
||||
}
|
||||
|
||||
if _, ok := exporter2.(*MarkdownExporter); !ok {
|
||||
t.Error("Second exporter should be MarkdownExporter")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFactory_WithNilHTMLCleaner tests factory behavior with nil HTMLCleaner.
|
||||
func TestFactory_WithNilHTMLCleaner(t *testing.T) {
|
||||
// This tests edge case - should not panic but behavior may vary
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Factory should handle nil HTMLCleaner gracefully, but panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
factory := NewFactory(nil)
|
||||
|
||||
if factory == nil {
|
||||
t.Fatal("NewFactory(nil) returned nil")
|
||||
}
|
||||
|
||||
// Try to create an exporter - this might fail or succeed depending on implementation
|
||||
_, err := factory.CreateExporter("markdown")
|
||||
|
||||
// We don't assert on the error since nil HTMLCleaner handling is implementation-dependent
|
||||
// The important thing is that it doesn't panic
|
||||
_ = err
|
||||
}
|
||||
|
||||
// TestFactory_FormatNormalization tests that format strings are properly normalized.
|
||||
func TestFactory_FormatNormalization(t *testing.T) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
// Test formats with extra whitespace
|
||||
testCases := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"markdown", "markdown"},
|
||||
{"MARKDOWN", "markdown"},
|
||||
{"Markdown", "markdown"},
|
||||
{"docx", "docx"},
|
||||
{"DOCX", "docx"},
|
||||
{"Docx", "docx"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
exporter, err := factory.CreateExporter(tc.input)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create exporter for '%s': %v", tc.input, err)
|
||||
}
|
||||
|
||||
format := exporter.GetSupportedFormat()
|
||||
if format != tc.expected {
|
||||
t.Errorf("Expected format '%s' for input '%s', got '%s'", tc.expected, tc.input, format)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkFactory_CreateExporter benchmarks the CreateExporter method.
|
||||
func BenchmarkFactory_CreateExporter(b *testing.B) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = factory.CreateExporter("markdown")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkFactory_CreateExporter_Docx benchmarks creating DOCX exporters.
|
||||
func BenchmarkFactory_CreateExporter_Docx(b *testing.B) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = factory.CreateExporter("docx")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkFactory_GetSupportedFormats benchmarks the GetSupportedFormats method.
|
||||
func BenchmarkFactory_GetSupportedFormats(b *testing.B) {
|
||||
htmlCleaner := services.NewHTMLCleaner()
|
||||
factory := NewFactory(htmlCleaner)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = factory.GetSupportedFormats()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user