package api import ( "encoding/json" "io" "log/slog" "net/http" "net/http/httptest" "os" "path/filepath" "testing" ) // withClientAPKDir points the handlers at a fresh temp dir for each // test and restores the env var on cleanup. Returns the dir. func withClientAPKDir(t *testing.T) string { t.Helper() dir := t.TempDir() prev, hadPrev := os.LookupEnv("MINSTREL_CLIENT_APK_DIR") os.Setenv("MINSTREL_CLIENT_APK_DIR", dir) t.Cleanup(func() { if hadPrev { os.Setenv("MINSTREL_CLIENT_APK_DIR", prev) } else { os.Unsetenv("MINSTREL_CLIENT_APK_DIR") } }) return dir } func TestClientVersion_404WhenNoAPK(t *testing.T) { withClientAPKDir(t) h := &handlers{logger: slog.New(slog.NewTextHandler(io.Discard, nil))} rr := httptest.NewRecorder() h.handleClientVersion(rr, httptest.NewRequest(http.MethodGet, "/api/client/version", nil)) if rr.Code != http.StatusNotFound { t.Errorf("want 404, got %d", rr.Code) } } func TestClientVersion_404WhenAPKButNoVersion(t *testing.T) { dir := withClientAPKDir(t) if err := os.WriteFile(filepath.Join(dir, clientAPKFilename), []byte("fake apk"), 0o644); err != nil { t.Fatal(err) } h := &handlers{logger: slog.New(slog.NewTextHandler(io.Discard, nil))} rr := httptest.NewRecorder() h.handleClientVersion(rr, httptest.NewRequest(http.MethodGet, "/api/client/version", nil)) if rr.Code != http.StatusNotFound { t.Errorf("want 404, got %d", rr.Code) } } func TestClientVersion_200WithBothFiles(t *testing.T) { dir := withClientAPKDir(t) body := []byte("fake apk content") if err := os.WriteFile(filepath.Join(dir, clientAPKFilename), body, 0o644); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, clientVersionFile), []byte("v2026.05.10\n"), 0o644); err != nil { t.Fatal(err) } h := &handlers{logger: slog.New(slog.NewTextHandler(io.Discard, nil))} rr := httptest.NewRecorder() h.handleClientVersion(rr, httptest.NewRequest(http.MethodGet, "/api/client/version", nil)) if rr.Code != http.StatusOK { t.Fatalf("want 200, got %d (body: %s)", rr.Code, rr.Body.String()) } var resp clientVersionResponse if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil { t.Fatal(err) } if resp.Version != "v2026.05.10" { t.Errorf("version: want trimmed v2026.05.10, got %q", resp.Version) } if resp.APKURL != "/api/client/apk" { t.Errorf("apk_url: want /api/client/apk, got %q", resp.APKURL) } if resp.SizeBytes != int64(len(body)) { t.Errorf("size_bytes: want %d, got %d", len(body), resp.SizeBytes) } } func TestClientAPK_404WhenMissing(t *testing.T) { withClientAPKDir(t) h := &handlers{logger: slog.New(slog.NewTextHandler(io.Discard, nil))} rr := httptest.NewRecorder() h.handleClientAPK(rr, httptest.NewRequest(http.MethodGet, "/api/client/apk", nil)) if rr.Code != http.StatusNotFound { t.Errorf("want 404, got %d", rr.Code) } } func TestClientAPK_StreamsWithCorrectContentType(t *testing.T) { dir := withClientAPKDir(t) body := []byte("PK\x03\x04 fake apk bytes") if err := os.WriteFile(filepath.Join(dir, clientAPKFilename), body, 0o644); err != nil { t.Fatal(err) } h := &handlers{logger: slog.New(slog.NewTextHandler(io.Discard, nil))} rr := httptest.NewRecorder() h.handleClientAPK(rr, httptest.NewRequest(http.MethodGet, "/api/client/apk", nil)) if rr.Code != http.StatusOK { t.Fatalf("want 200, got %d", rr.Code) } if got := rr.Header().Get("Content-Type"); got != "application/vnd.android.package-archive" { t.Errorf("Content-Type: want application/vnd.android.package-archive, got %q", got) } if rr.Body.Len() != len(body) { t.Errorf("body length: want %d, got %d", len(body), rr.Body.Len()) } if got := rr.Body.Bytes(); string(got) != string(body) { t.Errorf("body bytes mismatch") } }