From 9e3328a6aaefe4bd1aa0ec3e8bf6e93d6033180b Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 20 Jul 2025 21:20:40 +0300 Subject: test: add comprehensive test suite for audio and anki packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add tests for audio package (62.8% coverage) - OpenAI provider tests with mocking - Provider interface and fallback mechanism tests - Bulgarian text validation tests - Audio caching functionality tests - Add tests for anki package (84.8% coverage) - CSV generation tests - APKG package generation tests - Card management and formatting tests - Directory scanning and media handling tests - Add test utilities and mocks - Mock implementations for external dependencies - Test helpers for common operations - Utilities for creating test directories and files - Update Taskfile.yaml with comprehensive test targets - test: Run all tests - test-verbose: Run with verbose output - test-coverage: Run with coverage report - test-coverage-html: Generate HTML coverage report - test-race: Run with race detector - test-short: Run only short tests - test-all: Comprehensive suite with coverage and race detection - clean: Remove build artifacts and test files - Fix existing image package tests - Remove tests for non-existent methods - Update tests to match actual implementation - Skip tests requiring live OpenAI API This provides a solid foundation for ensuring code quality and catching regressions. πŸ€– Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode --- internal/testutil/mocks.go | 187 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 internal/testutil/mocks.go (limited to 'internal/testutil/mocks.go') diff --git a/internal/testutil/mocks.go b/internal/testutil/mocks.go new file mode 100644 index 0000000..811b840 --- /dev/null +++ b/internal/testutil/mocks.go @@ -0,0 +1,187 @@ +package testutil + +import ( + "context" + "fmt" + "io" + "strings" +) + +// MockHTTPClient mocks HTTP client for testing +type MockHTTPClient struct { + Responses map[string]*MockResponse + Errors map[string]error + Calls []string +} + +// MockResponse represents a mocked HTTP response +type MockResponse struct { + StatusCode int + Body string + Headers map[string]string +} + +// Get mocks an HTTP GET request +func (m *MockHTTPClient) Get(url string) (*MockResponse, error) { + m.Calls = append(m.Calls, fmt.Sprintf("GET %s", url)) + + if err, ok := m.Errors[url]; ok { + return nil, err + } + + if resp, ok := m.Responses[url]; ok { + return resp, nil + } + + return &MockResponse{ + StatusCode: 404, + Body: "Not Found", + }, nil +} + +// Post mocks an HTTP POST request +func (m *MockHTTPClient) Post(url string, body interface{}) (*MockResponse, error) { + m.Calls = append(m.Calls, fmt.Sprintf("POST %s", url)) + + if err, ok := m.Errors[url]; ok { + return nil, err + } + + if resp, ok := m.Responses[url]; ok { + return resp, nil + } + + return &MockResponse{ + StatusCode: 404, + Body: "Not Found", + }, nil +} + +// MockOpenAIClient mocks OpenAI API client +type MockOpenAIClient struct { + TTSResponses map[string][]byte + ImageResponses map[string]string + Errors map[string]error + Calls []string +} + +// CreateSpeech mocks OpenAI TTS API +func (m *MockOpenAIClient) CreateSpeech(ctx context.Context, text, voice, model string) (io.ReadCloser, error) { + call := fmt.Sprintf("TTS: %s (voice=%s, model=%s)", text, voice, model) + m.Calls = append(m.Calls, call) + + key := fmt.Sprintf("%s-%s-%s", text, voice, model) + if err, ok := m.Errors[key]; ok { + return nil, err + } + + if data, ok := m.TTSResponses[key]; ok { + return io.NopCloser(strings.NewReader(string(data))), nil + } + + // Default response + return io.NopCloser(strings.NewReader("mock audio data")), nil +} + +// CreateImage mocks OpenAI DALL-E API +func (m *MockOpenAIClient) CreateImage(ctx context.Context, prompt string) (string, error) { + call := fmt.Sprintf("Image: %s", prompt) + m.Calls = append(m.Calls, call) + + if err, ok := m.Errors[prompt]; ok { + return "", err + } + + if url, ok := m.ImageResponses[prompt]; ok { + return url, nil + } + + // Default response + return "https://example.com/mock-image.jpg", nil +} + +// MockFileSystem mocks file system operations +type MockFileSystem struct { + Files map[string][]byte + Errors map[string]error + Calls []string +} + +// ReadFile mocks reading a file +func (m *MockFileSystem) ReadFile(path string) ([]byte, error) { + m.Calls = append(m.Calls, fmt.Sprintf("READ %s", path)) + + if err, ok := m.Errors[path]; ok { + return nil, err + } + + if data, ok := m.Files[path]; ok { + return data, nil + } + + return nil, fmt.Errorf("file not found: %s", path) +} + +// WriteFile mocks writing a file +func (m *MockFileSystem) WriteFile(path string, data []byte) error { + m.Calls = append(m.Calls, fmt.Sprintf("WRITE %s (%d bytes)", path, len(data))) + + if err, ok := m.Errors[path]; ok { + return err + } + + m.Files[path] = data + return nil +} + +// Exists mocks checking if a file exists +func (m *MockFileSystem) Exists(path string) bool { + m.Calls = append(m.Calls, fmt.Sprintf("EXISTS %s", path)) + _, exists := m.Files[path] + return exists +} + +// MockTranslator mocks translation service +type MockTranslator struct { + Translations map[string]string + Errors map[string]error + Calls []string +} + +// Translate mocks translating text +func (m *MockTranslator) Translate(ctx context.Context, text, fromLang, toLang string) (string, error) { + call := fmt.Sprintf("Translate: %s (%s->%s)", text, fromLang, toLang) + m.Calls = append(m.Calls, call) + + if err, ok := m.Errors[text]; ok { + return "", err + } + + if translation, ok := m.Translations[text]; ok { + return translation, nil + } + + // Default mock translation + return fmt.Sprintf("mock translation of %s", text), nil +} + +// TestDataGenerator generates test data +type TestDataGenerator struct{} + +// GenerateBulgarianWord generates a test Bulgarian word +func (g *TestDataGenerator) GenerateBulgarianWord() string { + words := []string{"ябълка", "ΠΊΠΎΡ‚ΠΊΠ°", "ΠΊΡƒΡ‡Π΅", "хляб", "Π²ΠΎΠ΄Π°", "ΠΊΠ½ΠΈΠ³Π°", "стол", "ΠΏΡ€ΠΎΠ·ΠΎΡ€Π΅Ρ†"} + return words[0] // Simple implementation, could be randomized +} + +// GenerateAudioData generates mock audio data +func (g *TestDataGenerator) GenerateAudioData() []byte { + // Simple mock MP3 header + return []byte{0xFF, 0xFB, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00} +} + +// GenerateImageData generates mock image data +func (g *TestDataGenerator) GenerateImageData() []byte { + // Simple mock JPEG header + return []byte{0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46} +} -- cgit v1.2.3