package web import ( "context" "encoding/json" "errors" "net/http" "net/http/httptest" "testing" "gitgud.io/mike/mpv-manager/pkg/config" "gitgud.io/mike/mpv-manager/pkg/installer" "gitgud.io/mike/mpv-manager/pkg/platform" ) // stubFetchReleaseInfo replaces the manifest fetch for the duration of a test. func stubFetchReleaseInfo(t *testing.T, info *installer.ReleaseInfo, err error) { t.Helper() original := fetchReleaseInfo fetchReleaseInfo = func(context.Context) (*installer.ReleaseInfo, error) { return info, err } t.Cleanup(func() { fetchReleaseInfo = original }) } func getManifestStatus(t *testing.T, server *Server) (int, map[string]interface{}) { t.Helper() req := httptest.NewRequest(http.MethodGet, "/api/manifest-status", nil) w := httptest.NewRecorder() server.handleManifestStatusAPI(w, req) var body map[string]interface{} if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil { t.Fatalf("Failed to decode manifest-status response: %v", err) } return w.Code, body } func TestHandleManifestStatusAPI_EmptyManifest(t *testing.T) { server := &Server{releaseInfo: &installer.ReleaseInfo{}} code, body := getManifestStatus(t, server) if code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, code) } if body["available"] != false { t.Errorf("Expected available=false for empty manifest, got %v", body["available"]) } if _, ok := body["version"]; ok { t.Errorf("Expected no version field for empty manifest, got %v", body["version"]) } } func TestHandleManifestStatusAPI_NilManifest(t *testing.T) { server := &Server{} code, body := getManifestStatus(t, server) if code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, code) } if body["available"] != false { t.Errorf("Expected available=false for nil manifest, got %v", body["available"]) } } func TestHandleManifestStatusAPI_PopulatedManifest(t *testing.T) { server := &Server{releaseInfo: &installer.ReleaseInfo{Version: "1.2.3"}} code, body := getManifestStatus(t, server) if code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, code) } if body["available"] != true { t.Errorf("Expected available=true for populated manifest, got %v", body["available"]) } if body["version"] != "1.2.3" { t.Errorf("Expected version=1.2.3, got %v", body["version"]) } } func TestHandleManifestRefreshAPI_MethodNotAllowed(t *testing.T) { server := &Server{} for _, method := range []string{http.MethodGet, http.MethodPut, http.MethodDelete} { req := httptest.NewRequest(method, "/api/manifest/refresh", nil) w := httptest.NewRecorder() server.handleManifestRefreshAPI(w, req) if w.Code != http.StatusMethodNotAllowed { t.Errorf("Expected status %d for %s, got %d", http.StatusMethodNotAllowed, method, w.Code) } } } func TestHandleManifestRefreshAPI_Failure(t *testing.T) { stubFetchReleaseInfo(t, nil, errors.New("network unreachable")) server := &Server{releaseInfo: &installer.ReleaseInfo{}} req := httptest.NewRequest(http.MethodPost, "/api/manifest/refresh", nil) w := httptest.NewRecorder() server.handleManifestRefreshAPI(w, req) if w.Code != http.StatusServiceUnavailable { t.Errorf("Expected status %d, got %d", http.StatusServiceUnavailable, w.Code) } var body map[string]interface{} if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil { t.Fatalf("Failed to decode refresh response: %v", err) } if body["available"] != false { t.Errorf("Expected available=false, got %v", body["available"]) } if body["error"] != "network unreachable" { t.Errorf("Expected error message, got %v", body["error"]) } // Failed refresh must not clobber the existing (empty) manifest if server.releaseInfo.Version != "" { t.Errorf("Expected releaseInfo to remain empty after failed refresh, got %q", server.releaseInfo.Version) } } func TestHandleManifestRefreshAPI_Success(t *testing.T) { stubFetchReleaseInfo(t, &installer.ReleaseInfo{Version: "9.9.9"}, nil) p := platform.Detect() cfg, _ := config.Load() releaseInfo := installer.ReleaseInfo{} inst := installer.NewInstaller(releaseInfo, config.GetInstallPath()) server := NewServer(p, cfg, inst, &releaseInfo) req := httptest.NewRequest(http.MethodPost, "/api/manifest/refresh", nil) w := httptest.NewRecorder() server.handleManifestRefreshAPI(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, w.Code) } var body map[string]interface{} if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil { t.Fatalf("Failed to decode refresh response: %v", err) } if body["available"] != true { t.Errorf("Expected available=true, got %v", body["available"]) } if body["version"] != "9.9.9" { t.Errorf("Expected version=9.9.9, got %v", body["version"]) } // The server publishes the new immutable manifest. The base installer is a // dependency template and must not be mutated under concurrent jobs; new // operation installers receive the refreshed snapshot. if server.releaseInfo.Version != "9.9.9" { t.Errorf("Expected server releaseInfo version 9.9.9, got %q", server.releaseInfo.Version) } if server.installer.ReleaseInfo.Version != "" { t.Errorf("Expected base installer manifest to remain immutable, got %q", server.installer.ReleaseInfo.Version) } if got := server.installerForOperation(t.TempDir()).ReleaseInfo.Version; got != "9.9.9" { t.Errorf("Expected operation installer version 9.9.9, got %q", got) } }