mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-01-16 11:02: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:
790
internal/models/models_test.go
Normal file
790
internal/models/models_test.go
Normal file
@ -0,0 +1,790 @@
|
||||
// Package models_test provides tests for the data models.
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestCourse_JSONMarshalUnmarshal tests JSON marshaling and unmarshaling of Course.
|
||||
func TestCourse_JSONMarshalUnmarshal(t *testing.T) {
|
||||
original := Course{
|
||||
ShareID: "test-share-id",
|
||||
Author: "Test Author",
|
||||
Course: CourseInfo{
|
||||
ID: "course-123",
|
||||
Title: "Test Course",
|
||||
Description: "A test course description",
|
||||
Color: "#FF5733",
|
||||
NavigationMode: "menu",
|
||||
Lessons: []Lesson{
|
||||
{
|
||||
ID: "lesson-1",
|
||||
Title: "First Lesson",
|
||||
Description: "Lesson description",
|
||||
Type: "lesson",
|
||||
Icon: "icon-1",
|
||||
Ready: true,
|
||||
CreatedAt: "2023-01-01T00:00:00Z",
|
||||
UpdatedAt: "2023-01-02T00:00:00Z",
|
||||
},
|
||||
},
|
||||
ExportSettings: &ExportSettings{
|
||||
Title: "Export Title",
|
||||
Format: "scorm",
|
||||
},
|
||||
},
|
||||
LabelSet: LabelSet{
|
||||
ID: "labelset-1",
|
||||
Name: "Test Labels",
|
||||
},
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal Course to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
var unmarshaled Course
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal Course from JSON: %v", err)
|
||||
}
|
||||
|
||||
// Compare structures
|
||||
if !reflect.DeepEqual(original, unmarshaled) {
|
||||
t.Errorf("Marshaled and unmarshaled Course structs do not match")
|
||||
t.Logf("Original: %+v", original)
|
||||
t.Logf("Unmarshaled: %+v", unmarshaled)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCourseInfo_JSONMarshalUnmarshal tests JSON marshaling and unmarshaling of CourseInfo.
|
||||
func TestCourseInfo_JSONMarshalUnmarshal(t *testing.T) {
|
||||
original := CourseInfo{
|
||||
ID: "course-456",
|
||||
Title: "Another Test Course",
|
||||
Description: "Another test description",
|
||||
Color: "#33FF57",
|
||||
NavigationMode: "linear",
|
||||
Lessons: []Lesson{
|
||||
{
|
||||
ID: "lesson-2",
|
||||
Title: "Second Lesson",
|
||||
Type: "section",
|
||||
Items: []Item{
|
||||
{
|
||||
ID: "item-1",
|
||||
Type: "text",
|
||||
Family: "text",
|
||||
Variant: "paragraph",
|
||||
Items: []SubItem{
|
||||
{
|
||||
Title: "Sub Item Title",
|
||||
Heading: "Sub Item Heading",
|
||||
Paragraph: "Sub item paragraph content",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
CoverImage: &Media{
|
||||
Image: &ImageMedia{
|
||||
Key: "img-123",
|
||||
Type: "jpg",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
OriginalUrl: "https://example.com/image.jpg",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal CourseInfo to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
var unmarshaled CourseInfo
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal CourseInfo from JSON: %v", err)
|
||||
}
|
||||
|
||||
// Compare structures
|
||||
if !reflect.DeepEqual(original, unmarshaled) {
|
||||
t.Errorf("Marshaled and unmarshaled CourseInfo structs do not match")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLesson_JSONMarshalUnmarshal tests JSON marshaling and unmarshaling of Lesson.
|
||||
func TestLesson_JSONMarshalUnmarshal(t *testing.T) {
|
||||
original := Lesson{
|
||||
ID: "lesson-test",
|
||||
Title: "Test Lesson",
|
||||
Description: "Test lesson description",
|
||||
Type: "lesson",
|
||||
Icon: "lesson-icon",
|
||||
Ready: true,
|
||||
CreatedAt: "2023-06-01T12:00:00Z",
|
||||
UpdatedAt: "2023-06-01T13:00:00Z",
|
||||
Position: map[string]interface{}{"x": 1, "y": 2},
|
||||
Items: []Item{
|
||||
{
|
||||
ID: "item-test",
|
||||
Type: "multimedia",
|
||||
Family: "media",
|
||||
Variant: "video",
|
||||
Items: []SubItem{
|
||||
{
|
||||
Caption: "Video caption",
|
||||
Media: &Media{
|
||||
Video: &VideoMedia{
|
||||
Key: "video-123",
|
||||
URL: "https://example.com/video.mp4",
|
||||
Type: "mp4",
|
||||
Duration: 120,
|
||||
OriginalUrl: "https://example.com/video.mp4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Settings: map[string]interface{}{"autoplay": false},
|
||||
Data: map[string]interface{}{"metadata": "test"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal Lesson to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
var unmarshaled Lesson
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal Lesson from JSON: %v", err)
|
||||
}
|
||||
|
||||
// Compare structures
|
||||
if !compareLessons(original, unmarshaled) {
|
||||
t.Errorf("Marshaled and unmarshaled Lesson structs do not match")
|
||||
}
|
||||
}
|
||||
|
||||
// TestItem_JSONMarshalUnmarshal tests JSON marshaling and unmarshaling of Item.
|
||||
func TestItem_JSONMarshalUnmarshal(t *testing.T) {
|
||||
original := Item{
|
||||
ID: "item-json-test",
|
||||
Type: "knowledgeCheck",
|
||||
Family: "assessment",
|
||||
Variant: "multipleChoice",
|
||||
Items: []SubItem{
|
||||
{
|
||||
Title: "What is the answer?",
|
||||
Answers: []Answer{
|
||||
{Title: "Option A", Correct: false},
|
||||
{Title: "Option B", Correct: true},
|
||||
{Title: "Option C", Correct: false},
|
||||
},
|
||||
Feedback: "Well done!",
|
||||
},
|
||||
},
|
||||
Settings: map[string]interface{}{
|
||||
"allowRetry": true,
|
||||
"showAnswer": true,
|
||||
},
|
||||
Data: map[string]interface{}{
|
||||
"points": 10,
|
||||
"weight": 1.5,
|
||||
},
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal Item to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
var unmarshaled Item
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal Item from JSON: %v", err)
|
||||
}
|
||||
|
||||
// Compare structures
|
||||
if !compareItem(original, unmarshaled) {
|
||||
t.Errorf("Marshaled and unmarshaled Item structs do not match")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSubItem_JSONMarshalUnmarshal tests JSON marshaling and unmarshaling of SubItem.
|
||||
func TestSubItem_JSONMarshalUnmarshal(t *testing.T) {
|
||||
original := SubItem{
|
||||
Title: "Test SubItem Title",
|
||||
Heading: "Test SubItem Heading",
|
||||
Paragraph: "Test paragraph with content",
|
||||
Caption: "Test caption",
|
||||
Feedback: "Test feedback message",
|
||||
Answers: []Answer{
|
||||
{Title: "First answer", Correct: true},
|
||||
{Title: "Second answer", Correct: false},
|
||||
},
|
||||
Media: &Media{
|
||||
Image: &ImageMedia{
|
||||
Key: "subitem-img",
|
||||
Type: "png",
|
||||
Width: 400,
|
||||
Height: 300,
|
||||
OriginalUrl: "https://example.com/subitem.png",
|
||||
CrushedKey: "crushed-123",
|
||||
UseCrushedKey: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal SubItem to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
var unmarshaled SubItem
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal SubItem from JSON: %v", err)
|
||||
}
|
||||
|
||||
// Compare structures
|
||||
if !reflect.DeepEqual(original, unmarshaled) {
|
||||
t.Errorf("Marshaled and unmarshaled SubItem structs do not match")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAnswer_JSONMarshalUnmarshal tests JSON marshaling and unmarshaling of Answer.
|
||||
func TestAnswer_JSONMarshalUnmarshal(t *testing.T) {
|
||||
original := Answer{
|
||||
Title: "Test answer text",
|
||||
Correct: true,
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal Answer to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
var unmarshaled Answer
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal Answer from JSON: %v", err)
|
||||
}
|
||||
|
||||
// Compare structures
|
||||
if !reflect.DeepEqual(original, unmarshaled) {
|
||||
t.Errorf("Marshaled and unmarshaled Answer structs do not match")
|
||||
}
|
||||
}
|
||||
|
||||
// TestMedia_JSONMarshalUnmarshal tests JSON marshaling and unmarshaling of Media.
|
||||
func TestMedia_JSONMarshalUnmarshal(t *testing.T) {
|
||||
// Test with Image
|
||||
originalImage := Media{
|
||||
Image: &ImageMedia{
|
||||
Key: "media-img-test",
|
||||
Type: "jpeg",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
OriginalUrl: "https://example.com/media.jpg",
|
||||
CrushedKey: "crushed-media",
|
||||
UseCrushedKey: false,
|
||||
},
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(originalImage)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal Media with Image to JSON: %v", err)
|
||||
}
|
||||
|
||||
var unmarshaledImage Media
|
||||
err = json.Unmarshal(jsonData, &unmarshaledImage)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal Media with Image from JSON: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(originalImage, unmarshaledImage) {
|
||||
t.Errorf("Marshaled and unmarshaled Media with Image do not match")
|
||||
}
|
||||
|
||||
// Test with Video
|
||||
originalVideo := Media{
|
||||
Video: &VideoMedia{
|
||||
Key: "media-video-test",
|
||||
URL: "https://example.com/media.mp4",
|
||||
Type: "mp4",
|
||||
Duration: 300,
|
||||
Poster: "https://example.com/poster.jpg",
|
||||
Thumbnail: "https://example.com/thumb.jpg",
|
||||
InputKey: "input-123",
|
||||
OriginalUrl: "https://example.com/original.mp4",
|
||||
},
|
||||
}
|
||||
|
||||
jsonData, err = json.Marshal(originalVideo)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal Media with Video to JSON: %v", err)
|
||||
}
|
||||
|
||||
var unmarshaledVideo Media
|
||||
err = json.Unmarshal(jsonData, &unmarshaledVideo)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal Media with Video from JSON: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(originalVideo, unmarshaledVideo) {
|
||||
t.Errorf("Marshaled and unmarshaled Media with Video do not match")
|
||||
}
|
||||
}
|
||||
|
||||
// TestImageMedia_JSONMarshalUnmarshal tests JSON marshaling and unmarshaling of ImageMedia.
|
||||
func TestImageMedia_JSONMarshalUnmarshal(t *testing.T) {
|
||||
original := ImageMedia{
|
||||
Key: "image-media-test",
|
||||
Type: "gif",
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
OriginalUrl: "https://example.com/image.gif",
|
||||
CrushedKey: "crushed-gif",
|
||||
UseCrushedKey: true,
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal ImageMedia to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
var unmarshaled ImageMedia
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal ImageMedia from JSON: %v", err)
|
||||
}
|
||||
|
||||
// Compare structures
|
||||
if !reflect.DeepEqual(original, unmarshaled) {
|
||||
t.Errorf("Marshaled and unmarshaled ImageMedia structs do not match")
|
||||
}
|
||||
}
|
||||
|
||||
// TestVideoMedia_JSONMarshalUnmarshal tests JSON marshaling and unmarshaling of VideoMedia.
|
||||
func TestVideoMedia_JSONMarshalUnmarshal(t *testing.T) {
|
||||
original := VideoMedia{
|
||||
Key: "video-media-test",
|
||||
URL: "https://example.com/video.webm",
|
||||
Type: "webm",
|
||||
Duration: 450,
|
||||
Poster: "https://example.com/poster.jpg",
|
||||
Thumbnail: "https://example.com/thumbnail.jpg",
|
||||
InputKey: "upload-456",
|
||||
OriginalUrl: "https://example.com/original.webm",
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal VideoMedia to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
var unmarshaled VideoMedia
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal VideoMedia from JSON: %v", err)
|
||||
}
|
||||
|
||||
// Compare structures
|
||||
if !reflect.DeepEqual(original, unmarshaled) {
|
||||
t.Errorf("Marshaled and unmarshaled VideoMedia structs do not match")
|
||||
}
|
||||
}
|
||||
|
||||
// TestExportSettings_JSONMarshalUnmarshal tests JSON marshaling and unmarshaling of ExportSettings.
|
||||
func TestExportSettings_JSONMarshalUnmarshal(t *testing.T) {
|
||||
original := ExportSettings{
|
||||
Title: "Custom Export Title",
|
||||
Format: "xAPI",
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal ExportSettings to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
var unmarshaled ExportSettings
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal ExportSettings from JSON: %v", err)
|
||||
}
|
||||
|
||||
// Compare structures
|
||||
if !reflect.DeepEqual(original, unmarshaled) {
|
||||
t.Errorf("Marshaled and unmarshaled ExportSettings structs do not match")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLabelSet_JSONMarshalUnmarshal tests JSON marshaling and unmarshaling of LabelSet.
|
||||
func TestLabelSet_JSONMarshalUnmarshal(t *testing.T) {
|
||||
original := LabelSet{
|
||||
ID: "labelset-test",
|
||||
Name: "Test Label Set",
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal LabelSet to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
var unmarshaled LabelSet
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal LabelSet from JSON: %v", err)
|
||||
}
|
||||
|
||||
// Compare structures
|
||||
if !reflect.DeepEqual(original, unmarshaled) {
|
||||
t.Errorf("Marshaled and unmarshaled LabelSet structs do not match")
|
||||
}
|
||||
}
|
||||
|
||||
// TestEmptyStructures tests marshaling and unmarshaling of empty structures.
|
||||
func TestEmptyStructures(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
data interface{}
|
||||
}{
|
||||
{"Empty Course", Course{}},
|
||||
{"Empty CourseInfo", CourseInfo{}},
|
||||
{"Empty Lesson", Lesson{}},
|
||||
{"Empty Item", Item{}},
|
||||
{"Empty SubItem", SubItem{}},
|
||||
{"Empty Answer", Answer{}},
|
||||
{"Empty Media", Media{}},
|
||||
{"Empty ImageMedia", ImageMedia{}},
|
||||
{"Empty VideoMedia", VideoMedia{}},
|
||||
{"Empty ExportSettings", ExportSettings{}},
|
||||
{"Empty LabelSet", LabelSet{}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(tc.data)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal %s to JSON: %v", tc.name, err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
result := reflect.New(reflect.TypeOf(tc.data)).Interface()
|
||||
err = json.Unmarshal(jsonData, result)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal %s from JSON: %v", tc.name, err)
|
||||
}
|
||||
|
||||
// Basic validation that no errors occurred
|
||||
if len(jsonData) == 0 {
|
||||
t.Errorf("%s should produce some JSON output", tc.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNilPointerSafety tests that nil pointers in optional fields are handled correctly.
|
||||
func TestNilPointerSafety(t *testing.T) {
|
||||
course := Course{
|
||||
ShareID: "nil-test",
|
||||
Course: CourseInfo{
|
||||
ID: "nil-course",
|
||||
Title: "Nil Pointer Test",
|
||||
CoverImage: nil, // Test nil pointer
|
||||
ExportSettings: nil, // Test nil pointer
|
||||
Lessons: []Lesson{
|
||||
{
|
||||
ID: "lesson-nil",
|
||||
Title: "Lesson with nil media",
|
||||
Items: []Item{
|
||||
{
|
||||
ID: "item-nil",
|
||||
Type: "text",
|
||||
Items: []SubItem{
|
||||
{
|
||||
Title: "SubItem with nil media",
|
||||
Media: nil, // Test nil pointer
|
||||
},
|
||||
},
|
||||
Media: nil, // Test nil pointer
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
jsonData, err := json.Marshal(course)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal Course with nil pointers to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal from JSON
|
||||
var unmarshaled Course
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal Course with nil pointers from JSON: %v", err)
|
||||
}
|
||||
|
||||
// Basic validation
|
||||
if unmarshaled.ShareID != "nil-test" {
|
||||
t.Error("ShareID should be preserved")
|
||||
}
|
||||
if unmarshaled.Course.Title != "Nil Pointer Test" {
|
||||
t.Error("Course title should be preserved")
|
||||
}
|
||||
}
|
||||
|
||||
// TestJSONTagsPresence tests that JSON tags are properly defined.
|
||||
func TestJSONTagsPresence(t *testing.T) {
|
||||
// Test that important fields have JSON tags
|
||||
courseType := reflect.TypeOf(Course{})
|
||||
if courseType.Kind() == reflect.Struct {
|
||||
field, found := courseType.FieldByName("ShareID")
|
||||
if !found {
|
||||
t.Error("ShareID field not found")
|
||||
} else {
|
||||
tag := field.Tag.Get("json")
|
||||
if tag == "" {
|
||||
t.Error("ShareID should have json tag")
|
||||
}
|
||||
if tag != "shareId" {
|
||||
t.Errorf("ShareID json tag should be 'shareId', got '%s'", tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test CourseInfo
|
||||
courseInfoType := reflect.TypeOf(CourseInfo{})
|
||||
if courseInfoType.Kind() == reflect.Struct {
|
||||
field, found := courseInfoType.FieldByName("NavigationMode")
|
||||
if !found {
|
||||
t.Error("NavigationMode field not found")
|
||||
} else {
|
||||
tag := field.Tag.Get("json")
|
||||
if tag == "" {
|
||||
t.Error("NavigationMode should have json tag")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkCourse_JSONMarshal benchmarks JSON marshaling of Course.
|
||||
func BenchmarkCourse_JSONMarshal(b *testing.B) {
|
||||
course := Course{
|
||||
ShareID: "benchmark-id",
|
||||
Author: "Benchmark Author",
|
||||
Course: CourseInfo{
|
||||
ID: "benchmark-course",
|
||||
Title: "Benchmark Course",
|
||||
Lessons: []Lesson{
|
||||
{
|
||||
ID: "lesson-1",
|
||||
Title: "Lesson 1",
|
||||
Items: []Item{
|
||||
{
|
||||
ID: "item-1",
|
||||
Type: "text",
|
||||
Items: []SubItem{
|
||||
{Title: "SubItem 1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = json.Marshal(course)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkCourse_JSONUnmarshal benchmarks JSON unmarshaling of Course.
|
||||
func BenchmarkCourse_JSONUnmarshal(b *testing.B) {
|
||||
course := Course{
|
||||
ShareID: "benchmark-id",
|
||||
Author: "Benchmark Author",
|
||||
Course: CourseInfo{
|
||||
ID: "benchmark-course",
|
||||
Title: "Benchmark Course",
|
||||
Lessons: []Lesson{
|
||||
{
|
||||
ID: "lesson-1",
|
||||
Title: "Lesson 1",
|
||||
Items: []Item{
|
||||
{
|
||||
ID: "item-1",
|
||||
Type: "text",
|
||||
Items: []SubItem{
|
||||
{Title: "SubItem 1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
jsonData, _ := json.Marshal(course)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var result Course
|
||||
_ = json.Unmarshal(jsonData, &result)
|
||||
}
|
||||
}
|
||||
|
||||
// compareMaps compares two interface{} values that should be maps
|
||||
func compareMaps(original, unmarshaled interface{}) bool {
|
||||
origMap, origOk := original.(map[string]interface{})
|
||||
unMap, unOk := unmarshaled.(map[string]interface{})
|
||||
|
||||
if !origOk || !unOk {
|
||||
// If not maps, use deep equal
|
||||
return reflect.DeepEqual(original, unmarshaled)
|
||||
}
|
||||
|
||||
if len(origMap) != len(unMap) {
|
||||
return false
|
||||
}
|
||||
|
||||
for key, origVal := range origMap {
|
||||
unVal, exists := unMap[key]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
// Handle numeric type conversion from JSON
|
||||
switch origVal := origVal.(type) {
|
||||
case int:
|
||||
if unFloat, ok := unVal.(float64); ok {
|
||||
if float64(origVal) != unFloat {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case float64:
|
||||
if unFloat, ok := unVal.(float64); ok {
|
||||
if origVal != unFloat {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
if !reflect.DeepEqual(origVal, unVal) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// compareLessons compares two Lesson structs accounting for JSON type conversion
|
||||
func compareLessons(original, unmarshaled Lesson) bool {
|
||||
// Compare all fields except Position and Items
|
||||
if original.ID != unmarshaled.ID ||
|
||||
original.Title != unmarshaled.Title ||
|
||||
original.Description != unmarshaled.Description ||
|
||||
original.Type != unmarshaled.Type ||
|
||||
original.Icon != unmarshaled.Icon ||
|
||||
original.Ready != unmarshaled.Ready ||
|
||||
original.CreatedAt != unmarshaled.CreatedAt ||
|
||||
original.UpdatedAt != unmarshaled.UpdatedAt {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare Position
|
||||
if !compareMaps(original.Position, unmarshaled.Position) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare Items
|
||||
return compareItems(original.Items, unmarshaled.Items)
|
||||
}
|
||||
|
||||
// compareItems compares two Item slices accounting for JSON type conversion
|
||||
func compareItems(original, unmarshaled []Item) bool {
|
||||
if len(original) != len(unmarshaled) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range original {
|
||||
if !compareItem(original[i], unmarshaled[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// compareItem compares two Item structs accounting for JSON type conversion
|
||||
func compareItem(original, unmarshaled Item) bool {
|
||||
// Compare basic fields
|
||||
if original.ID != unmarshaled.ID ||
|
||||
original.Type != unmarshaled.Type ||
|
||||
original.Family != unmarshaled.Family ||
|
||||
original.Variant != unmarshaled.Variant {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare Settings and Data
|
||||
if !compareMaps(original.Settings, unmarshaled.Settings) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !compareMaps(original.Data, unmarshaled.Data) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare Items (SubItems)
|
||||
if len(original.Items) != len(unmarshaled.Items) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range original.Items {
|
||||
if !reflect.DeepEqual(original.Items[i], unmarshaled.Items[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Compare Media
|
||||
if !reflect.DeepEqual(original.Media, unmarshaled.Media) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user