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:
2025-05-25 15:23:48 +02:00
parent 9de7222ec3
commit b01260e765
17 changed files with 4431 additions and 191 deletions

View File

@ -4,17 +4,18 @@ package exporters
import (
"fmt"
"os"
"strings"
"github.com/fumiama/go-docx"
"github.com/kjanat/articulate-parser/internal/interfaces"
"github.com/kjanat/articulate-parser/internal/models"
"github.com/kjanat/articulate-parser/internal/services"
"github.com/unidoc/unioffice/document"
)
// DocxExporter implements the Exporter interface for DOCX format.
// It converts Articulate Rise course data into a Microsoft Word document
// using the unioffice/document package.
// using the go-docx package.
type DocxExporter struct {
// htmlCleaner is used to convert HTML content to plain text
htmlCleaner *services.HTMLCleaner
@ -45,21 +46,17 @@ func NewDocxExporter(htmlCleaner *services.HTMLCleaner) interfaces.Exporter {
// Returns:
// - An error if creating or saving the document fails
func (e *DocxExporter) Export(course *models.Course, outputPath string) error {
doc := document.New()
doc := docx.New()
// Add title
titlePara := doc.AddParagraph()
titleRun := titlePara.AddRun()
titleRun.AddText(course.Course.Title)
titleRun.Properties().SetBold(true)
titleRun.Properties().SetSize(16)
titlePara.AddText(course.Course.Title).Size("32").Bold()
// Add description if available
if course.Course.Description != "" {
descPara := doc.AddParagraph()
descRun := descPara.AddRun()
cleanDesc := e.htmlCleaner.CleanHTML(course.Course.Description)
descRun.AddText(cleanDesc)
descPara.AddText(cleanDesc)
}
// Add each lesson
@ -72,7 +69,20 @@ func (e *DocxExporter) Export(course *models.Course, outputPath string) error {
outputPath = outputPath + ".docx"
}
return doc.SaveToFile(outputPath)
// Create the file
file, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("failed to create output file: %w", err)
}
defer file.Close()
// Save the document
_, err = doc.WriteTo(file)
if err != nil {
return fmt.Errorf("failed to save document: %w", err)
}
return nil
}
// exportLesson adds a lesson to the document with appropriate formatting.
@ -81,20 +91,16 @@ func (e *DocxExporter) Export(course *models.Course, outputPath string) error {
// Parameters:
// - doc: The Word document being created
// - lesson: The lesson data model to export
func (e *DocxExporter) exportLesson(doc *document.Document, lesson *models.Lesson) {
func (e *DocxExporter) exportLesson(doc *docx.Docx, lesson *models.Lesson) {
// Add lesson title
lessonPara := doc.AddParagraph()
lessonRun := lessonPara.AddRun()
lessonRun.AddText(fmt.Sprintf("Lesson: %s", lesson.Title))
lessonRun.Properties().SetBold(true)
lessonRun.Properties().SetSize(14)
lessonPara.AddText(fmt.Sprintf("Lesson: %s", lesson.Title)).Size("28").Bold()
// Add lesson description if available
if lesson.Description != "" {
descPara := doc.AddParagraph()
descRun := descPara.AddRun()
cleanDesc := e.htmlCleaner.CleanHTML(lesson.Description)
descRun.AddText(cleanDesc)
descPara.AddText(cleanDesc)
}
// Add each item in the lesson
@ -109,14 +115,11 @@ func (e *DocxExporter) exportLesson(doc *document.Document, lesson *models.Lesso
// Parameters:
// - doc: The Word document being created
// - item: The item data model to export
func (e *DocxExporter) exportItem(doc *document.Document, item *models.Item) {
func (e *DocxExporter) exportItem(doc *docx.Docx, item *models.Item) {
// Add item type as heading
if item.Type != "" {
itemPara := doc.AddParagraph()
itemRun := itemPara.AddRun()
itemRun.AddText(strings.Title(item.Type))
itemRun.Properties().SetBold(true)
itemRun.Properties().SetSize(12)
itemPara.AddText(strings.Title(item.Type)).Size("24").Bold()
}
// Add sub-items
@ -132,58 +135,48 @@ func (e *DocxExporter) exportItem(doc *document.Document, item *models.Item) {
// Parameters:
// - doc: The Word document being created
// - subItem: The sub-item data model to export
func (e *DocxExporter) exportSubItem(doc *document.Document, subItem *models.SubItem) {
func (e *DocxExporter) exportSubItem(doc *docx.Docx, subItem *models.SubItem) {
// Add title if available
if subItem.Title != "" {
subItemPara := doc.AddParagraph()
subItemRun := subItemPara.AddRun()
subItemRun.AddText(" " + subItem.Title) // Indented
subItemRun.Properties().SetBold(true)
subItemPara.AddText(" " + subItem.Title).Bold() // Indented
}
// Add heading if available
if subItem.Heading != "" {
headingPara := doc.AddParagraph()
headingRun := headingPara.AddRun()
cleanHeading := e.htmlCleaner.CleanHTML(subItem.Heading)
headingRun.AddText(" " + cleanHeading) // Indented
headingRun.Properties().SetBold(true)
headingPara.AddText(" " + cleanHeading).Bold() // Indented
}
// Add paragraph content if available
if subItem.Paragraph != "" {
contentPara := doc.AddParagraph()
contentRun := contentPara.AddRun()
cleanContent := e.htmlCleaner.CleanHTML(subItem.Paragraph)
contentRun.AddText(" " + cleanContent) // Indented
contentPara.AddText(" " + cleanContent) // Indented
}
// Add answers if this is a question
if len(subItem.Answers) > 0 {
answersPara := doc.AddParagraph()
answersRun := answersPara.AddRun()
answersRun.AddText(" Answers:")
answersRun.Properties().SetBold(true)
answersPara.AddText(" Answers:").Bold()
for i, answer := range subItem.Answers {
answerPara := doc.AddParagraph()
answerRun := answerPara.AddRun()
prefix := fmt.Sprintf(" %d. ", i+1)
if answer.Correct {
prefix += "✓ "
}
cleanAnswer := e.htmlCleaner.CleanHTML(answer.Title)
answerRun.AddText(prefix + cleanAnswer)
answerPara.AddText(prefix + cleanAnswer)
}
}
// Add feedback if available
if subItem.Feedback != "" {
feedbackPara := doc.AddParagraph()
feedbackRun := feedbackPara.AddRun()
cleanFeedback := e.htmlCleaner.CleanHTML(subItem.Feedback)
feedbackRun.AddText(" Feedback: " + cleanFeedback)
feedbackRun.Properties().SetItalic(true)
feedbackPara.AddText(" Feedback: " + cleanFeedback).Italic()
}
}