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
+70 -13
View File
@@ -16,10 +16,10 @@ import (
"time"
)
const (
var (
userAgent = "akai-fetch/1.0"
iaSearchURL = "https://archive.org/advancedsearch.php"
iaBaseURL = "https://archive.org"
userAgent = "akai-fetch/1.0"
)
type SearchResult struct {
@@ -121,6 +121,15 @@ Serve flags:
// ─── Search ────────────────────────────────────────────────────────────
func cmdSearch(args []string) {
defer func() {
if e := recover(); e != nil {
if fe, ok := e.(fatalErr); ok {
osExit(fe.code)
return
}
panic(e)
}
}()
fs := flag.NewFlagSet("search", flag.ExitOnError)
query := fs.String("query", "subject:akai AND subject:sampler", "search query")
filter := fs.String("filter", "", "extra term AND-ed onto the query")
@@ -167,7 +176,7 @@ func searchArchive(query string, limit int) []SearchDoc {
if err != nil {
fatal("search request: %v", err)
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
body, err := io.ReadAll(resp.Body)
if err != nil {
@@ -184,6 +193,15 @@ func searchArchive(query string, limit int) []SearchDoc {
// ─── List ──────────────────────────────────────────────────────────────
func cmdList(args []string) {
defer func() {
if e := recover(); e != nil {
if fe, ok := e.(fatalErr); ok {
osExit(fe.code)
return
}
panic(e)
}
}()
if len(args) < 1 {
fatal("usage: akai-fetch list <identifier>")
}
@@ -200,7 +218,7 @@ func cmdList(args []string) {
}
}
func getItemFiles(identifier string) []MetadataFile {
var getItemFiles = func(identifier string) []MetadataFile {
u := fmt.Sprintf("%s/metadata/%s", iaBaseURL, url.PathEscape(identifier))
client := &http.Client{Timeout: 30 * time.Second}
req, _ := http.NewRequest("GET", u, nil)
@@ -209,7 +227,7 @@ func getItemFiles(identifier string) []MetadataFile {
if err != nil {
fatal("metadata request: %v", err)
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
body, err := io.ReadAll(resp.Body)
if err != nil {
@@ -243,6 +261,15 @@ type downloadJob struct {
}
func cmdDownload(args []string) {
defer func() {
if e := recover(); e != nil {
if fe, ok := e.(fatalErr); ok {
osExit(fe.code)
return
}
panic(e)
}
}()
fs := flag.NewFlagSet("download", flag.ExitOnError)
identifier := fs.String("i", "", "archive.org item identifier")
idFile := fs.String("f", "", "file with identifiers (one per line)")
@@ -329,8 +356,10 @@ func cmdDownload(args []string) {
fmt.Printf("\nDownloaded %s total\n", humanSize(totalDownloaded))
}
func downloadFile(job downloadJob) (int64, error) {
os.MkdirAll(filepath.Dir(job.OutPath), 0755)
var downloadFile = func(job downloadJob) (int64, error) {
if err := os.MkdirAll(filepath.Dir(job.OutPath), 0755); err != nil {
return 0, fmt.Errorf("mkdir: %w", err)
}
// Check if file already exists and has the expected size
if fi, err := os.Stat(job.OutPath); err == nil {
@@ -359,7 +388,7 @@ func downloadFile(job downloadJob) (int64, error) {
if err != nil {
return 0, fmt.Errorf("get: %w", err)
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
return 0, fmt.Errorf("HTTP %d", resp.StatusCode)
@@ -369,7 +398,7 @@ func downloadFile(job downloadJob) (int64, error) {
if err != nil {
return 0, fmt.Errorf("open: %w", err)
}
defer outFile.Close()
defer func() { _ = outFile.Close() }()
written, err := io.Copy(outFile, resp.Body)
if err != nil {
@@ -381,6 +410,15 @@ func downloadFile(job downloadJob) (int64, error) {
// ─── Extract ───────────────────────────────────────────────────────────
func cmdExtract(args []string) {
defer func() {
if e := recover(); e != nil {
if fe, ok := e.(fatalErr); ok {
osExit(fe.code)
return
}
panic(e)
}
}()
fs := flag.NewFlagSet("extract", flag.ExitOnError)
isoDir := fs.String("dir", ".", "directory with ISO files")
outDir := fs.String("out", "./wavs", "output directory for WAVs")
@@ -462,6 +500,15 @@ func sanitiseISOName(path string) string {
// ─── Pipeline ──────────────────────────────────────────────────────────
func cmdPipeline(args []string) {
defer func() {
if e := recover(); e != nil {
if fe, ok := e.(fatalErr); ok {
osExit(fe.code)
return
}
panic(e)
}
}()
fs := flag.NewFlagSet("pipeline", flag.ExitOnError)
query := fs.String("query", "subject:akai AND subject:sampler", "search query")
filter := fs.String("filter", "", "extra term AND-ed onto the query")
@@ -527,9 +574,17 @@ func cmdPipeline(args []string) {
// ─── Helpers ───────────────────────────────────────────────────────────
var osExit = os.Exit
type fatalErr struct {
code int
message string
}
func fatal(format string, args ...any) {
fmt.Fprintf(os.Stderr, "error: "+format+"\n", args...)
os.Exit(1)
msg := fmt.Sprintf("error: "+format+"\n", args...)
fmt.Fprintf(os.Stderr, msg, args...)
panic(fatalErr{code: 1, message: msg})
}
func truncate(s string, n int) string {
@@ -556,6 +611,8 @@ func humanSize(bytes int64) string {
}
func init() {
// Ensure we can parse flags even with -count or other test flags
flag.CommandLine.Parse([]string{})
if err := flag.CommandLine.Parse([]string{}); err != nil {
// In init context, best we can do is note the issue
fmt.Fprintf(os.Stderr, "flag init: %v\n", err)
}
}