4baf24f6f8
GET /version(公开)读 /etc/pangolin/version.yaml(每请求重载,免重启);缺省回退。 download_urls 接 /downloads。deploy.sh 装清单(存在则不覆盖, 保住线上 bump)。含 4 测试。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
161 lines
5.0 KiB
Go
161 lines
5.0 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
// buildVersionRouter mounts VersionHandler on a real chi router (mirrors how
|
|
// main.go mounts GET /version) so routing behaves exactly as in production.
|
|
func buildVersionRouter(path string) chi.Router {
|
|
h := NewVersionHandler(path)
|
|
r := chi.NewRouter()
|
|
r.Get("/version", h.Serve)
|
|
return r
|
|
}
|
|
|
|
func TestVersionHandler_ServesManifestFromDisk(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "version.yaml")
|
|
yamlContent := `version: "1.2.3"
|
|
build_number: 10203
|
|
force_update: true
|
|
release_notes: "测试发布说明"
|
|
download_urls:
|
|
android: "https://api.yanmeiai.com/downloads/pangolin-android.apk"
|
|
windows: "https://api.yanmeiai.com/downloads/pangolin-windows-x64-setup.exe"
|
|
macos: ""
|
|
ios: ""
|
|
changelog:
|
|
- version: "1.2.3"
|
|
date: "2026-07-06"
|
|
intro: "小版本更新"
|
|
sections:
|
|
- type: "新增"
|
|
items:
|
|
- "示例条目"
|
|
`
|
|
if err := os.WriteFile(path, []byte(yamlContent), 0o644); err != nil {
|
|
t.Fatalf("write fixture: %v", err)
|
|
}
|
|
|
|
r := buildVersionRouter(path)
|
|
req := httptest.NewRequest("GET", "/version", nil)
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != 200 {
|
|
t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
var got versionManifest
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
|
|
t.Fatalf("unmarshal response: %v; body=%s", err, rec.Body.String())
|
|
}
|
|
|
|
if got.Version != "1.2.3" {
|
|
t.Errorf("version = %q, want %q", got.Version, "1.2.3")
|
|
}
|
|
if got.BuildNumber != 10203 {
|
|
t.Errorf("build_number = %d, want %d", got.BuildNumber, 10203)
|
|
}
|
|
if !got.ForceUpdate {
|
|
t.Errorf("force_update = false, want true")
|
|
}
|
|
if got.ReleaseNotes != "测试发布说明" {
|
|
t.Errorf("release_notes = %q, want %q", got.ReleaseNotes, "测试发布说明")
|
|
}
|
|
if got.DownloadURLs["android"] != "https://api.yanmeiai.com/downloads/pangolin-android.apk" {
|
|
t.Errorf("download_urls.android = %q", got.DownloadURLs["android"])
|
|
}
|
|
if got.DownloadURLs["windows"] != "https://api.yanmeiai.com/downloads/pangolin-windows-x64-setup.exe" {
|
|
t.Errorf("download_urls.windows = %q", got.DownloadURLs["windows"])
|
|
}
|
|
if got.DownloadURLs["macos"] != "" || got.DownloadURLs["ios"] != "" {
|
|
t.Errorf("expected empty macos/ios download URLs, got macos=%q ios=%q", got.DownloadURLs["macos"], got.DownloadURLs["ios"])
|
|
}
|
|
if len(got.Changelog) != 1 || got.Changelog[0].Version != "1.2.3" {
|
|
t.Errorf("changelog = %+v, want 1 entry for version 1.2.3", got.Changelog)
|
|
}
|
|
}
|
|
|
|
func TestVersionHandler_MissingFileReturnsDefault(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "does-not-exist.yaml")
|
|
|
|
r := buildVersionRouter(path)
|
|
req := httptest.NewRequest("GET", "/version", nil)
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != 200 {
|
|
t.Fatalf("status = %d, want 200 (missing file falls back to default manifest); body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
var got versionManifest
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
|
|
t.Fatalf("unmarshal response: %v; body=%s", err, rec.Body.String())
|
|
}
|
|
if got.Version == "" {
|
|
t.Errorf("default manifest: version should not be empty")
|
|
}
|
|
if got.DownloadURLs["android"] == "" {
|
|
t.Errorf("default manifest: download_urls.android should not be empty")
|
|
}
|
|
if got.DownloadURLs["windows"] == "" {
|
|
t.Errorf("default manifest: download_urls.windows should not be empty")
|
|
}
|
|
if got.Changelog == nil {
|
|
t.Errorf("default manifest: changelog should be [] not null")
|
|
}
|
|
}
|
|
|
|
func TestVersionHandler_JSONShape(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "version.yaml")
|
|
if err := os.WriteFile(path, []byte(`version: "1.0.0"
|
|
build_number: 10000
|
|
force_update: false
|
|
release_notes: ""
|
|
download_urls:
|
|
android: "https://example.com/a.apk"
|
|
`), 0o644); err != nil {
|
|
t.Fatalf("write fixture: %v", err)
|
|
}
|
|
|
|
r := buildVersionRouter(path)
|
|
req := httptest.NewRequest("GET", "/version", nil)
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
var raw map[string]json.RawMessage
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &raw); err != nil {
|
|
t.Fatalf("unmarshal raw response: %v", err)
|
|
}
|
|
for _, key := range []string{"version", "build_number", "force_update", "release_notes", "download_urls", "changelog"} {
|
|
if _, ok := raw[key]; !ok {
|
|
t.Errorf("response missing expected top-level key %q; body=%s", key, rec.Body.String())
|
|
}
|
|
}
|
|
// changelog must serialize as an array even when the manifest omits it —
|
|
// front-ends should be able to blindly .map()/range over it.
|
|
if string(raw["changelog"]) != "[]" {
|
|
t.Errorf("changelog = %s, want []", raw["changelog"])
|
|
}
|
|
if ct := rec.Header().Get("Content-Type"); ct != "application/json; charset=utf-8" {
|
|
t.Errorf("Content-Type = %q, want application/json; charset=utf-8", ct)
|
|
}
|
|
}
|
|
|
|
func TestVersionHandler_DefaultPathFallback(t *testing.T) {
|
|
h := NewVersionHandler("")
|
|
if h.path != defaultVersionManifestPath {
|
|
t.Errorf("NewVersionHandler(\"\").path = %q, want %q", h.path, defaultVersionManifestPath)
|
|
}
|
|
}
|