package httpapi import ( "bytes" "encoding/json" "io" "mime/multipart" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "testing" "time" "scout/internal/registry" ) func TestUploadListLatestAndDownload(t *testing.T) { store, err := registry.New(t.TempDir()) if err != nil { t.Fatal(err) } server := httptest.NewServer((&Server{Registry: store, DeveloperToken: "测试令牌"}).Handler()) defer server.Close() unauthorized, err := http.Post(server.URL+"/api/v1/packages/upload", "application/json", nil) if err != nil { t.Fatal(err) } if unauthorized.StatusCode != http.StatusUnauthorized { t.Fatalf("未授权状态错误: %d", unauthorized.StatusCode) } unauthorized.Body.Close() body := &bytes.Buffer{} writer := multipart.NewWriter(body) _ = writer.WriteField("name", "demo-core") _ = writer.WriteField("version", "1.0.0") _ = writer.WriteField("platform", "any") _ = writer.WriteField("architecture", "universal") fileWriter, err := writer.CreateFormFile("file", "demo.pkg") if err != nil { t.Fatal(err) } _, _ = fileWriter.Write([]byte("demo package")) _ = writer.Close() request, err := http.NewRequest(http.MethodPost, server.URL+"/api/v1/packages/upload", body) if err != nil { t.Fatal(err) } request.Header.Set("Content-Type", writer.FormDataContentType()) request.Header.Set("Authorization", "Bearer 测试令牌") response, err := http.DefaultClient.Do(request) if err != nil { t.Fatal(err) } if response.StatusCode != http.StatusCreated { t.Fatalf("上传状态错误: %d", response.StatusCode) } var uploaded registry.Package if err := json.NewDecoder(response.Body).Decode(&uploaded); err != nil { t.Fatal(err) } response.Body.Close() if uploaded.Name != "demo-core" || uploaded.Version != "1.0.0" { t.Fatalf("上传结果错误: %+v", uploaded) } listResponse, err := http.Get(server.URL + "/api/v1/packages") if err != nil { t.Fatal(err) } var listed struct { Packages []registry.Package `json:"packages"` } if err := json.NewDecoder(listResponse.Body).Decode(&listed); err != nil { t.Fatal(err) } listResponse.Body.Close() if len(listed.Packages) != 1 { t.Fatalf("包列表数量错误: %d", len(listed.Packages)) } downloadResponse, err := http.Get(server.URL + "/api/v1/packages/demo-core/1.0.0/download") if err != nil { t.Fatal(err) } content, err := io.ReadAll(downloadResponse.Body) if err != nil { t.Fatal(err) } downloadResponse.Body.Close() if downloadResponse.StatusCode != http.StatusOK || string(content) != "demo package" { t.Fatalf("下载结果错误: %d %q", downloadResponse.StatusCode, content) } if downloadResponse.Header.Get("X-Scout-SHA256") != uploaded.SHA256 { t.Fatal("下载校验值不一致") } } func TestDownloadUsesFallbackFileNameForLegacyPackage(t *testing.T) { root := t.TempDir() versionDir := filepath.Join(root, "legacy-demo", "1.0.0") if err := os.MkdirAll(versionDir, 0o755); err != nil { t.Fatal(err) } legacy := registry.Package{ Name: "legacy-demo", Version: "1.0.0", Status: "published", Size: 6, UploadedAt: time.Now().UTC(), } metadata, err := json.Marshal(legacy) if err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(versionDir, "metadata.json"), metadata, 0o644); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(versionDir, "package.bin"), []byte("legacy"), 0o644); err != nil { t.Fatal(err) } store, err := registry.New(root) if err != nil { t.Fatal(err) } server := httptest.NewServer((&Server{Registry: store}).Handler()) defer server.Close() response, err := http.Get(server.URL + "/api/v1/packages/legacy-demo/1.0.0/download") if err != nil { t.Fatal(err) } defer response.Body.Close() if response.StatusCode != http.StatusOK { t.Fatalf("历史包下载状态错误: %d", response.StatusCode) } if got := response.Header.Get("Content-Disposition"); got != `attachment; filename="legacy-demo-1.0.0.pkg"` { t.Fatalf("历史包下载文件名错误: %s", got) } } func TestStreamFiltersPackage(t *testing.T) { store, err := registry.New(t.TempDir()) if err != nil { t.Fatal(err) } server := httptest.NewServer((&Server{Registry: store, DeveloperToken: "测试令牌"}).Handler()) defer server.Close() request, err := http.NewRequest(http.MethodGet, server.URL+"/api/v1/updates/stream?packages=target", nil) if err != nil { t.Fatal(err) } client := &http.Client{} streamResponse, err := client.Do(request) if err != nil { t.Fatal(err) } defer streamResponse.Body.Close() if streamResponse.StatusCode != http.StatusOK { t.Fatalf("通知状态错误: %d", streamResponse.StatusCode) } if streamResponse.Header.Get("Content-Type") != "text/event-stream; charset=utf-8" { t.Fatalf("通知类型错误: %s", streamResponse.Header.Get("Content-Type")) } _ = url.Values{} }