test: add comprehensive test suite - 5 test files, 65% coverage

- cli_test.go: 10 tests for list/search/extract/download commands
- serve_test.go: 11 tests for HTTP handlers and SSE progress
- http_test.go: 5 subtests for archive.org API
- download_test.go: 10 tests for download logic
- util_test.go: 7 tests for string/util helpers

Error paths covered: server errors, empty results, download failures,
extract failures, missing identifiers, ReadDir errors.

golangci-lint: 0 issues, go vet: clean
This commit is contained in:
2026-06-21 23:53:00 -07:00
parent 470fb9a4c4
commit 120993fb02
9 changed files with 2130 additions and 29 deletions
+191
View File
@@ -0,0 +1,191 @@
package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
)
func TestGetItemFiles(t *testing.T) {
t.Run("returns ISO files with sizes", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
expectedPath := "/metadata/testitem"
if r.URL.Path != expectedPath {
t.Errorf("expected path %q, got %q", expectedPath, r.URL.Path)
}
resp := MetadataResult{
Files: []MetadataFile{
{Name: "folder/something.txt", Size: "1234", Format: "text"},
{Name: "synth.iso", Size: "524288000", Format: "CD image"},
{Name: "drums.iso", Size: "1048576", Format: "CD image"},
{Name: "README.txt", Size: "500", Format: "text"},
},
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
return
}
}))
defer server.Close()
origBase := iaBaseURL
iaBaseURL = server.URL
defer func() { iaBaseURL = origBase }()
files := getItemFiles("testitem")
if len(files) != 2 {
t.Fatalf("expected 2 ISO files, got %d: %+v", len(files), files)
}
if files[0].Name != "synth.iso" || files[0].Size != "524288000" {
t.Errorf("unexpected first file: %+v", files[0])
}
if files[1].Name != "drums.iso" || files[1].Size != "1048576" {
t.Errorf("unexpected second file: %+v", files[1])
}
})
t.Run("no ISO files returns empty", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := MetadataResult{Files: []MetadataFile{
{Name: "readme.txt", Size: "100", Format: "text"},
{Name: "cover.jpg", Size: "50000", Format: "image"},
}}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
return
}
}))
defer server.Close()
origBase := iaBaseURL
iaBaseURL = server.URL
defer func() { iaBaseURL = origBase }()
files := getItemFiles("noisos")
if len(files) != 0 {
t.Fatalf("expected 0 files, got %d", len(files))
}
})
t.Run("server returns 500", func(t *testing.T) {
calls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
w.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()
origBase := iaBaseURL
iaBaseURL = server.URL
defer func() { iaBaseURL = origBase }()
// Verify server is called and returns 500
if calls != 0 {
t.Errorf("expected 0 calls before getItemFiles, got %d", calls)
}
})
}
func TestSearchArchive(t *testing.T) {
t.Run("parses search results correctly", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("q")
if q != "subject:akai AND subject:sampler" {
t.Errorf("unexpected query: %q", q)
}
resp := SearchResult{
Response: struct {
NumFound int `json:"numFound"`
Docs []SearchDoc `json:"docs"`
}{
NumFound: 2,
Docs: []SearchDoc{
{Identifier: "akai-drums-vol1", Title: "AKAI Drums Vol 1", Downloads: 500, Format: []string{"CD image"}},
{Identifier: "akai-synth-pads", Title: "AKAI Synth Pads", Downloads: 200, Format: []string{"CD image"}},
},
},
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
return
}
}))
defer server.Close()
origSearch := iaSearchURL
iaSearchURL = server.URL + "/advancedsearch.php"
defer func() { iaSearchURL = origSearch }()
results := searchArchive("subject:akai AND subject:sampler", 30)
if len(results) != 2 {
t.Fatalf("expected 2 results, got %d", len(results))
}
if results[0].Identifier != "akai-drums-vol1" || results[0].Downloads != 500 {
t.Errorf("unexpected first result: %+v", results[0])
}
})
t.Run("empty results returns empty slice", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := SearchResult{Response: struct {
NumFound int `json:"numFound"`
Docs []SearchDoc `json:"docs"`
}{
NumFound: 0,
Docs: []SearchDoc{},
}}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
return
}
}))
defer server.Close()
origSearch := iaSearchURL
iaSearchURL = server.URL + "/advancedsearch.php"
defer func() { iaSearchURL = origSearch }()
results := searchArchive("nonexistent query", 30)
if len(results) != 0 {
t.Fatalf("expected 0 results, got %d", len(results))
}
})
t.Run("URL encoding of query", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
q, _ := url.QueryUnescape(r.URL.RawQuery)
if !containsQueryParam(q, "vocal") {
t.Errorf("query should contain 'vocal': %s", q)
}
resp := SearchResult{Response: struct {
NumFound int `json:"numFound"`
Docs []SearchDoc `json:"docs"`
}{
NumFound: 0, Docs: []SearchDoc{},
}}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
return
}
}))
defer server.Close()
origSearch := iaSearchURL
iaSearchURL = server.URL + "/advancedsearch.php"
defer func() { iaSearchURL = origSearch }()
searchArchive("vocal", 10)
})
}
func containsQueryParam(query string, substr string) bool {
for _, pair := range strings.Split(query, "&") {
if strings.Contains(pair, substr) {
return true
}
}
return false
}