package controller import ( "context" "encoding/json" "errors" "net/http" "net/http/httptest" "strings" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/topfans/backend/pkg/models" "github.com/topfans/backend/gateway/service" ) // fakeAppDownloadSvc 是 AppDownloadSvc 的 in-memory fake type fakeAppDownloadSvc struct { configs []models.AppDownloadConfig getAllErr error syncErr error syncCalled bool lastSyncReq *service.SyncVersionRequest } func (f *fakeAppDownloadSvc) GetAllNativeApp(ctx context.Context) ([]models.AppDownloadConfig, error) { if f.getAllErr != nil { return nil, f.getAllErr } return f.configs, nil } func (f *fakeAppDownloadSvc) SyncVersion(ctx context.Context, req *service.SyncVersionRequest) error { f.syncCalled = true f.lastSyncReq = req return f.syncErr } func setupTestRouter(ctrl *AppDownloadController) *gin.Engine { gin.SetMode(gin.TestMode) r := gin.New() r.GET("/api/v1/app/download-urls", ctrl.GetDownloadUrls) r.POST("/api/v1/admin/app/versions/sync", ctrl.SyncVersion) return r } // ========== GET /api/v1/app/download-urls ========== func TestGetDownloadUrls_Success(t *testing.T) { gin.SetMode(gin.TestMode) fakeSvc := &fakeAppDownloadSvc{ configs: []models.AppDownloadConfig{ {Platform: "android", Type: "native_app", DownloadURL: "https://app.apk", Version: "1.0.5"}, {Platform: "ios", Type: "native_app", DownloadURL: "https://apps.apple.com/...", Version: "1.0.5"}, }, } ctrl := NewAppDownloadController(fakeSvc) router := setupTestRouter(ctrl) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/app/download-urls", nil) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) assert.Equal(t, float64(0), resp["code"]) data := resp["data"].(map[string]interface{}) assert.NotNil(t, data["android"]) assert.NotNil(t, data["ios"]) } func TestGetDownloadUrls_EmptyData(t *testing.T) { gin.SetMode(gin.TestMode) fakeSvc := &fakeAppDownloadSvc{configs: []models.AppDownloadConfig{}} ctrl := NewAppDownloadController(fakeSvc) router := setupTestRouter(ctrl) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/app/download-urls", nil) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) assert.Equal(t, float64(0), resp["code"]) data := resp["data"].(map[string]interface{}) assert.Nil(t, data["android"]) assert.Nil(t, data["ios"]) } func TestGetDownloadUrls_ServiceError(t *testing.T) { gin.SetMode(gin.TestMode) fakeSvc := &fakeAppDownloadSvc{getAllErr: errors.New("db down")} ctrl := NewAppDownloadController(fakeSvc) router := setupTestRouter(ctrl) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/app/download-urls", nil) router.ServeHTTP(w, req) assert.Equal(t, 500, w.Code) } // ========== POST /api/v1/admin/app/versions/sync ========== func TestSyncVersion_InvalidJSON(t *testing.T) { gin.SetMode(gin.TestMode) fakeSvc := &fakeAppDownloadSvc{} ctrl := NewAppDownloadController(fakeSvc) router := setupTestRouter(ctrl) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/admin/app/versions/sync", strings.NewReader(`{"android": "not_an_object"}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, 400, w.Code) } func TestSyncVersion_MissingRequiredField(t *testing.T) { gin.SetMode(gin.TestMode) fakeSvc := &fakeAppDownloadSvc{} ctrl := NewAppDownloadController(fakeSvc) router := setupTestRouter(ctrl) // 缺少必填字段 url w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/admin/app/versions/sync", strings.NewReader(`{"android": {"version": "1.0.5", "type": "native_app"}}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, 400, w.Code) } func TestSyncVersion_Success(t *testing.T) { gin.SetMode(gin.TestMode) fakeSvc := &fakeAppDownloadSvc{} ctrl := NewAppDownloadController(fakeSvc) router := setupTestRouter(ctrl) body := `{ "android": {"url": "https://app.apk", "version": "1.0.5", "type": "native_app"}, "ios": {"url": "https://apps.apple.com/...", "version": "1.0.5", "type": "native_app"} }` w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/admin/app/versions/sync", strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.True(t, fakeSvc.syncCalled) assert.NotNil(t, fakeSvc.lastSyncReq.Android) assert.NotNil(t, fakeSvc.lastSyncReq.IOS) }