package api import ( "strings" "testing" "time" ) func TestParsePullCount(t *testing.T) { tests := []struct { name string input string expected int64 }{ {"plain number", "1000", 1000}, {"thousands K", "1.5K", 1500}, {"millions M", "2.3M", 2300000}, {"billions B", "1B", 1000000000}, {"whole K", "500K", 500000}, {"decimal M", "60.3M", 60300000}, {"with whitespace", " 100K ", 100000}, {"empty string", "", 0}, {"invalid", "abc", 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := parsePullCount(tt.input) if result != tt.expected { t.Errorf("parsePullCount(%q) = %d, want %d", tt.input, result, tt.expected) } }) } } func TestDecodeHTMLEntities(t *testing.T) { tests := []struct { name string input string expected string }{ {"apostrophe numeric", "It's", "It's"}, {"quote numeric", ""Hello"", "\"Hello\""}, {"quote named", ""World"", "\"World\""}, {"ampersand", "A & B", "A & B"}, {"less than", "1 < 2", "1 < 2"}, {"greater than", "2 > 1", "2 > 1"}, {"nbsp", "Hello World", "Hello World"}, {"multiple entities", "<div>&</div>", "
&
"}, {"no entities", "Plain text", "Plain text"}, {"empty", "", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := decodeHTMLEntities(tt.input) if result != tt.expected { t.Errorf("decodeHTMLEntities(%q) = %q, want %q", tt.input, result, tt.expected) } }) } } func TestParseRelativeTime(t *testing.T) { now := time.Now() tests := []struct { name string input string wantEmpty bool checkDelta time.Duration }{ {"2 weeks ago", "2 weeks ago", false, 14 * 24 * time.Hour}, {"1 month ago", "1 month ago", false, 30 * 24 * time.Hour}, {"3 days ago", "3 days ago", false, 3 * 24 * time.Hour}, {"5 hours ago", "5 hours ago", false, 5 * time.Hour}, {"30 minutes ago", "30 minutes ago", false, 30 * time.Minute}, {"1 year ago", "1 year ago", false, 365 * 24 * time.Hour}, {"empty string", "", true, 0}, {"invalid format", "recently", true, 0}, {"uppercase", "2 WEEKS AGO", false, 14 * 24 * time.Hour}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := parseRelativeTime(tt.input) if tt.wantEmpty { if result != "" { t.Errorf("parseRelativeTime(%q) = %q, want empty string", tt.input, result) } return } // Parse the result as RFC3339 parsed, err := time.Parse(time.RFC3339, result) if err != nil { t.Fatalf("failed to parse result %q: %v", result, err) } // Check that the delta is approximately correct (within 1 minute tolerance) expectedTime := now.Add(-tt.checkDelta) diff := parsed.Sub(expectedTime) if diff < -time.Minute || diff > time.Minute { t.Errorf("parseRelativeTime(%q) = %v, expected around %v", tt.input, parsed, expectedTime) } }) } } func TestParseSizeToBytes(t *testing.T) { tests := []struct { name string input string expected int64 }{ {"gigabytes", "2.0GB", 2 * 1024 * 1024 * 1024}, {"megabytes", "500MB", 500 * 1024 * 1024}, {"kilobytes", "100KB", 100 * 1024}, {"decimal GB", "1.5GB", int64(1.5 * 1024 * 1024 * 1024)}, {"plain number", "1024", 1024}, {"with whitespace", " 1GB ", 1 * 1024 * 1024 * 1024}, {"empty", "", 0}, {"invalid", "abc", 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := parseSizeToBytes(tt.input) if result != tt.expected { t.Errorf("parseSizeToBytes(%q) = %d, want %d", tt.input, result, tt.expected) } }) } } func TestFormatParamCount(t *testing.T) { tests := []struct { name string input int64 expected string }{ {"billions", 13900000000, "13.9B"}, {"single billion", 1000000000, "1.0B"}, {"millions", 500000000, "500.0M"}, {"single million", 1000000, "1.0M"}, {"thousands", 500000, "500.0K"}, {"single thousand", 1000, "1.0K"}, {"small number", 500, "500"}, {"zero", 0, "0"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := formatParamCount(tt.input) if result != tt.expected { t.Errorf("formatParamCount(%d) = %q, want %q", tt.input, result, tt.expected) } }) } } func TestParseParamSizeToFloat(t *testing.T) { tests := []struct { name string input string expected float64 }{ {"8b", "8b", 8.0}, {"70b", "70b", 70.0}, {"1.5b", "1.5b", 1.5}, {"500m to billions", "500m", 0.5}, {"uppercase B", "8B", 8.0}, {"uppercase M", "500M", 0.5}, {"with whitespace", " 8b ", 8.0}, {"empty", "", 0}, {"invalid", "abc", 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := parseParamSizeToFloat(tt.input) if result != tt.expected { t.Errorf("parseParamSizeToFloat(%q) = %f, want %f", tt.input, result, tt.expected) } }) } } func TestGetSizeRange(t *testing.T) { tests := []struct { name string input string expected string }{ {"small 1b", "1b", "small"}, {"small 3b", "3b", "small"}, {"medium 4b", "4b", "medium"}, {"medium 8b", "8b", "medium"}, {"medium 13b", "13b", "medium"}, {"large 14b", "14b", "large"}, {"large 70b", "70b", "large"}, {"xlarge 405b", "405b", "xlarge"}, {"empty", "", ""}, {"invalid", "abc", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := getSizeRange(tt.input) if result != tt.expected { t.Errorf("getSizeRange(%q) = %q, want %q", tt.input, result, tt.expected) } }) } } func TestGetContextRange(t *testing.T) { tests := []struct { name string input int64 expected string }{ {"standard 4K", 4096, "standard"}, {"standard 8K", 8192, "standard"}, {"extended 16K", 16384, "extended"}, {"extended 32K", 32768, "extended"}, {"large 64K", 65536, "large"}, {"large 128K", 131072, "large"}, {"unlimited 256K", 262144, "unlimited"}, {"unlimited 1M", 1048576, "unlimited"}, {"zero", 0, ""}, {"negative", -1, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := getContextRange(tt.input) if result != tt.expected { t.Errorf("getContextRange(%d) = %q, want %q", tt.input, result, tt.expected) } }) } } func TestExtractFamily(t *testing.T) { tests := []struct { name string input string expected string }{ {"llama3.2", "llama3.2", "llama"}, {"qwen2.5", "qwen2.5", "qwen"}, {"mistral", "mistral", "mistral"}, {"deepseek-r1", "deepseek-r1", "deepseek"}, {"phi_3", "phi_3", "phi"}, {"community model", "username/custom-llama", "custom"}, {"with version", "llama3.2:8b", "llama"}, {"numbers only", "123model", ""}, {"empty", "", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := extractFamily(tt.input) if result != tt.expected { t.Errorf("extractFamily(%q) = %q, want %q", tt.input, result, tt.expected) } }) } } func TestInferModelType(t *testing.T) { tests := []struct { name string input string expected string }{ {"official llama", "llama3.2", "official"}, {"official mistral", "mistral", "official"}, {"community model", "username/model", "community"}, {"nested community", "org/subdir/model", "community"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := inferModelType(tt.input) if result != tt.expected { t.Errorf("inferModelType(%q) = %q, want %q", tt.input, result, tt.expected) } }) } } func TestModelMatchesSizeRanges(t *testing.T) { tests := []struct { name string tags []string sizeRanges []string expected bool }{ { name: "matches small", tags: []string{"1b", "3b"}, sizeRanges: []string{"small"}, expected: true, }, { name: "matches medium", tags: []string{"8b", "14b"}, sizeRanges: []string{"medium"}, expected: true, }, { name: "matches large", tags: []string{"70b"}, sizeRanges: []string{"large"}, expected: true, }, { name: "matches multiple ranges", tags: []string{"8b", "70b"}, sizeRanges: []string{"medium", "large"}, expected: true, }, { name: "no match", tags: []string{"8b"}, sizeRanges: []string{"large", "xlarge"}, expected: false, }, { name: "empty tags", tags: []string{}, sizeRanges: []string{"medium"}, expected: false, }, { name: "empty ranges", tags: []string{"8b"}, sizeRanges: []string{}, expected: false, }, { name: "non-size tags", tags: []string{"latest", "fp16"}, sizeRanges: []string{"medium"}, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := modelMatchesSizeRanges(tt.tags, tt.sizeRanges) if result != tt.expected { t.Errorf("modelMatchesSizeRanges(%v, %v) = %v, want %v", tt.tags, tt.sizeRanges, result, tt.expected) } }) } } func TestParseOllamaParams(t *testing.T) { tests := []struct { name string input string expected map[string]any }{ { name: "temperature", input: "temperature 0.8", expected: map[string]any{ "temperature": 0.8, }, }, { name: "multiple params", input: "temperature 0.8\nnum_ctx 4096\nstop <|im_end|>", expected: map[string]any{ "temperature": 0.8, "num_ctx": float64(4096), "stop": "<|im_end|>", }, }, { name: "empty input", input: "", expected: map[string]any{}, }, { name: "whitespace only", input: " \n \n ", expected: map[string]any{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := parseOllamaParams(tt.input) if len(result) != len(tt.expected) { t.Errorf("parseOllamaParams result length = %d, want %d", len(result), len(tt.expected)) return } for k, v := range tt.expected { if result[k] != v { t.Errorf("parseOllamaParams[%q] = %v, want %v", k, result[k], v) } } }) } } func TestParseLibraryHTML(t *testing.T) { // Test with minimal valid HTML structure html := `

A foundation model

1.5M 8b 70b vision 2 weeks ago

Fast model

500K 7b
` models, err := parseLibraryHTML(html) if err != nil { t.Fatalf("parseLibraryHTML failed: %v", err) } if len(models) != 2 { t.Fatalf("expected 2 models, got %d", len(models)) } // Find llama3.2 model var llama *ScrapedModel for i := range models { if models[i].Slug == "llama3.2" { llama = &models[i] break } } if llama == nil { t.Fatal("llama3.2 model not found") } if llama.Description != "A foundation model" { t.Errorf("description = %q, want %q", llama.Description, "A foundation model") } if llama.PullCount != 1500000 { t.Errorf("pull count = %d, want 1500000", llama.PullCount) } if len(llama.Tags) != 2 || llama.Tags[0] != "8b" || llama.Tags[1] != "70b" { t.Errorf("tags = %v, want [8b, 70b]", llama.Tags) } if len(llama.Capabilities) != 1 || llama.Capabilities[0] != "vision" { t.Errorf("capabilities = %v, want [vision]", llama.Capabilities) } if !strings.HasPrefix(llama.URL, "https://ollama.com/library/") { t.Errorf("URL = %q, want prefix https://ollama.com/library/", llama.URL) } } func TestParseModelPageForSizes(t *testing.T) { html := ` 8b 2.0GB 70b 40.5GB 1b 500MB ` sizes, err := parseModelPageForSizes(html) if err != nil { t.Fatalf("parseModelPageForSizes failed: %v", err) } expected := map[string]int64{ "8b": int64(2.0 * 1024 * 1024 * 1024), "70b": int64(40.5 * 1024 * 1024 * 1024), "1b": int64(500 * 1024 * 1024), } for tag, expectedSize := range expected { if sizes[tag] != expectedSize { t.Errorf("sizes[%q] = %d, want %d", tag, sizes[tag], expectedSize) } } } func TestStripHTML(t *testing.T) { tests := []struct { name string input string expected string }{ {"simple tags", "

Hello

", " Hello "}, {"nested tags", "
Text
", " Text "}, {"self-closing", "
Line
", " Line "}, {"no tags", "Plain text", "Plain text"}, {"empty", "", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := stripHTML(tt.input) if result != tt.expected { t.Errorf("stripHTML(%q) = %q, want %q", tt.input, result, tt.expected) } }) } }