add model_test.go with comprehensive Model struct tests
This commit is contained in:
@@ -0,0 +1,331 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/leonardotrapani/hyprvoice/internal/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestModel_NeedsDownload(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
model Model
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "local model with LocalInfo",
|
||||||
|
model: Model{
|
||||||
|
ID: "base.en",
|
||||||
|
Local: true,
|
||||||
|
LocalInfo: &LocalModelInfo{
|
||||||
|
Filename: "ggml-base.en.bin",
|
||||||
|
Size: "142MB",
|
||||||
|
DownloadURL: "https://example.com/model.bin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cloud model without LocalInfo",
|
||||||
|
model: Model{
|
||||||
|
ID: "whisper-1",
|
||||||
|
Local: false,
|
||||||
|
Endpoint: &EndpointConfig{BaseURL: "https://api.openai.com", Path: "/v1/audio/transcriptions"},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "model with nil LocalInfo",
|
||||||
|
model: Model{
|
||||||
|
ID: "gpt-4o",
|
||||||
|
LocalInfo: nil,
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := tc.model.NeedsDownload(); got != tc.expected {
|
||||||
|
t.Errorf("NeedsDownload() = %v, want %v", got, tc.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModel_IsStreaming(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
model Model
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "streaming model",
|
||||||
|
model: Model{ID: "scribe_v1-streaming", Streaming: true},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "batch model",
|
||||||
|
model: Model{ID: "whisper-1", Streaming: false},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := tc.model.IsStreaming(); got != tc.expected {
|
||||||
|
t.Errorf("IsStreaming() = %v, want %v", got, tc.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModel_SupportsLanguage(t *testing.T) {
|
||||||
|
allCodes := language.AllLanguageCodes()
|
||||||
|
|
||||||
|
multilingualModel := Model{
|
||||||
|
ID: "whisper-large-v3",
|
||||||
|
SupportedLanguages: allCodes,
|
||||||
|
}
|
||||||
|
|
||||||
|
englishOnlyModel := Model{
|
||||||
|
ID: "base.en",
|
||||||
|
SupportedLanguages: []string{"en"},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
model Model
|
||||||
|
code string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "multilingual supports en",
|
||||||
|
model: multilingualModel,
|
||||||
|
code: "en",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multilingual supports es",
|
||||||
|
model: multilingualModel,
|
||||||
|
code: "es",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multilingual supports zh",
|
||||||
|
model: multilingualModel,
|
||||||
|
code: "zh",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "english-only supports en",
|
||||||
|
model: englishOnlyModel,
|
||||||
|
code: "en",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "english-only does not support es",
|
||||||
|
model: englishOnlyModel,
|
||||||
|
code: "es",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "english-only does not support zh",
|
||||||
|
model: englishOnlyModel,
|
||||||
|
code: "zh",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "auto always supported on multilingual",
|
||||||
|
model: multilingualModel,
|
||||||
|
code: "",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "auto always supported on english-only",
|
||||||
|
model: englishOnlyModel,
|
||||||
|
code: "",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty SupportedLanguages still supports auto",
|
||||||
|
model: Model{ID: "empty", SupportedLanguages: []string{}},
|
||||||
|
code: "",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty SupportedLanguages does not support en",
|
||||||
|
model: Model{ID: "empty", SupportedLanguages: []string{}},
|
||||||
|
code: "en",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := tc.model.SupportsLanguage(tc.code); got != tc.expected {
|
||||||
|
t.Errorf("SupportsLanguage(%q) = %v, want %v", tc.code, got, tc.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModel_SupportsAllLanguages(t *testing.T) {
|
||||||
|
allCodes := language.AllLanguageCodes()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
model Model
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "model with all 57 languages",
|
||||||
|
model: Model{
|
||||||
|
ID: "whisper-large-v3",
|
||||||
|
SupportedLanguages: allCodes,
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "english-only model",
|
||||||
|
model: Model{
|
||||||
|
ID: "base.en",
|
||||||
|
SupportedLanguages: []string{"en"},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "model with some languages",
|
||||||
|
model: Model{
|
||||||
|
ID: "partial",
|
||||||
|
SupportedLanguages: []string{"en", "es", "fr", "de"},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "model with empty languages",
|
||||||
|
model: Model{
|
||||||
|
ID: "empty",
|
||||||
|
SupportedLanguages: []string{},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "model with nil languages",
|
||||||
|
model: Model{
|
||||||
|
ID: "nil",
|
||||||
|
SupportedLanguages: nil,
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := tc.model.SupportsAllLanguages(); got != tc.expected {
|
||||||
|
t.Errorf("SupportsAllLanguages() = %v, want %v", got, tc.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModelType_Constants(t *testing.T) {
|
||||||
|
// verify ModelType constants exist and are distinct
|
||||||
|
if Transcription == LLM {
|
||||||
|
t.Error("Transcription and LLM should be different")
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify they're the expected values
|
||||||
|
if Transcription != 0 {
|
||||||
|
t.Errorf("Transcription = %d, want 0", Transcription)
|
||||||
|
}
|
||||||
|
if LLM != 1 {
|
||||||
|
t.Errorf("LLM = %d, want 1", LLM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEndpointConfig_Fields(t *testing.T) {
|
||||||
|
endpoint := EndpointConfig{
|
||||||
|
BaseURL: "https://api.openai.com",
|
||||||
|
Path: "/v1/audio/transcriptions",
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpoint.BaseURL != "https://api.openai.com" {
|
||||||
|
t.Errorf("BaseURL = %q, want 'https://api.openai.com'", endpoint.BaseURL)
|
||||||
|
}
|
||||||
|
if endpoint.Path != "/v1/audio/transcriptions" {
|
||||||
|
t.Errorf("Path = %q, want '/v1/audio/transcriptions'", endpoint.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalModelInfo_Fields(t *testing.T) {
|
||||||
|
info := LocalModelInfo{
|
||||||
|
Filename: "ggml-base.en.bin",
|
||||||
|
Size: "142MB",
|
||||||
|
DownloadURL: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin",
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Filename != "ggml-base.en.bin" {
|
||||||
|
t.Errorf("Filename = %q, want 'ggml-base.en.bin'", info.Filename)
|
||||||
|
}
|
||||||
|
if info.Size != "142MB" {
|
||||||
|
t.Errorf("Size = %q, want '142MB'", info.Size)
|
||||||
|
}
|
||||||
|
if info.DownloadURL != "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin" {
|
||||||
|
t.Errorf("DownloadURL = %q", info.DownloadURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModel_AllFields(t *testing.T) {
|
||||||
|
// verify all Model struct fields can be set and read correctly
|
||||||
|
model := Model{
|
||||||
|
ID: "test-model",
|
||||||
|
Name: "Test Model",
|
||||||
|
Description: "A test model for verification",
|
||||||
|
Type: Transcription,
|
||||||
|
Streaming: true,
|
||||||
|
Local: true,
|
||||||
|
AdapterType: "test-adapter",
|
||||||
|
SupportedLanguages: []string{"en", "es"},
|
||||||
|
Endpoint: &EndpointConfig{
|
||||||
|
BaseURL: "https://api.test.com",
|
||||||
|
Path: "/v1/test",
|
||||||
|
},
|
||||||
|
LocalInfo: &LocalModelInfo{
|
||||||
|
Filename: "test.bin",
|
||||||
|
Size: "100MB",
|
||||||
|
DownloadURL: "https://example.com/test.bin",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if model.ID != "test-model" {
|
||||||
|
t.Errorf("ID = %q, want 'test-model'", model.ID)
|
||||||
|
}
|
||||||
|
if model.Name != "Test Model" {
|
||||||
|
t.Errorf("Name = %q, want 'Test Model'", model.Name)
|
||||||
|
}
|
||||||
|
if model.Description != "A test model for verification" {
|
||||||
|
t.Errorf("Description = %q", model.Description)
|
||||||
|
}
|
||||||
|
if model.Type != Transcription {
|
||||||
|
t.Errorf("Type = %v, want Transcription", model.Type)
|
||||||
|
}
|
||||||
|
if !model.Streaming {
|
||||||
|
t.Error("Streaming should be true")
|
||||||
|
}
|
||||||
|
if !model.Local {
|
||||||
|
t.Error("Local should be true")
|
||||||
|
}
|
||||||
|
if model.AdapterType != "test-adapter" {
|
||||||
|
t.Errorf("AdapterType = %q, want 'test-adapter'", model.AdapterType)
|
||||||
|
}
|
||||||
|
if len(model.SupportedLanguages) != 2 {
|
||||||
|
t.Errorf("SupportedLanguages length = %d, want 2", len(model.SupportedLanguages))
|
||||||
|
}
|
||||||
|
if model.Endpoint == nil {
|
||||||
|
t.Error("Endpoint should not be nil")
|
||||||
|
}
|
||||||
|
if model.LocalInfo == nil {
|
||||||
|
t.Error("LocalInfo should not be nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,3 +62,15 @@ Started: Sun Feb 1 12:22:47 AM CET 2026
|
|||||||
- Added MockStreamingAdapter and comprehensive tests
|
- Added MockStreamingAdapter and comprehensive tests
|
||||||
- Tests verify: start/stop, result accumulation, partial result filtering, error handling, concurrent access
|
- Tests verify: start/stop, result accumulation, partial result filtering, error handling, concurrent access
|
||||||
- All tests passing with -race flag, typecheck passes
|
- All tests passing with -race flag, typecheck passes
|
||||||
|
|
||||||
|
### Task 7: Write tests for Model, Provider, and interfaces
|
||||||
|
- Created `internal/provider/model_test.go`
|
||||||
|
- TestModel_NeedsDownload: local with LocalInfo = true, cloud = false, nil = false
|
||||||
|
- TestModel_IsStreaming: returns Streaming field value
|
||||||
|
- TestModel_SupportsLanguage: multilingual supports all, english-only supports en, auto always true
|
||||||
|
- TestModel_SupportsAllLanguages: true when 57 languages, false otherwise
|
||||||
|
- TestModelType_Constants: Transcription=0, LLM=1
|
||||||
|
- TestEndpointConfig_Fields, TestLocalModelInfo_Fields: struct fields accessible
|
||||||
|
- TestModel_AllFields: comprehensive struct field test
|
||||||
|
- provider_test.go already had GetModel, ModelsOfType, FindModelByID, ModelsForLanguage, ValidateModelLanguage tests
|
||||||
|
- All tests passing, typecheck passes
|
||||||
|
|||||||
+1
-1
@@ -185,7 +185,7 @@
|
|||||||
"Language validation helpers tested with proper error messages",
|
"Language validation helpers tested with proper error messages",
|
||||||
"Typecheck passes"
|
"Typecheck passes"
|
||||||
],
|
],
|
||||||
"passes": false
|
"passes": true
|
||||||
},
|
},
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// PHASE 2: MIGRATE PROVIDERS TO NEW MODEL STRUCTURE
|
// PHASE 2: MIGRATE PROVIDERS TO NEW MODEL STRUCTURE
|
||||||
|
|||||||
Reference in New Issue
Block a user