package router import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "testing" "chatapp3-golang/internal/config" "chatapp3-golang/internal/integration" "chatapp3-golang/internal/model" "chatapp3-golang/internal/repo" "chatapp3-golang/internal/service/apppopup" "github.com/gin-gonic/gin" "gorm.io/driver/sqlite" "gorm.io/gorm" ) type appPopupAuthStub struct{} func (appPopupAuthStub) AuthenticateToken(context.Context, string) (integration.UserCredential, error) { return integration.UserCredential{UserID: 123, SysOrigin: "LIKEI"}, nil } func (appPopupAuthStub) AuthenticateConsoleToken(context.Context, string) (integration.ConsoleAccount, error) { return integration.ConsoleAccount{}, nil } func TestAppPopupEntryRoute(t *testing.T) { gin.SetMode(gin.TestMode) db := newRouterAppPopupDB(t) if err := db.Create(&model.AppPopupConfig{ ID: 1001, SysOrigin: "LIKEI", PopupKey: "invite", Scene: "APP_ENTER", Enabled: true, LimitDays: 7, Priority: 100, Version: 1, ContentJSON: `{"title":"Invite friends","image":"https://cdn.example.com/invite.png"}`, JumpType: "H5", JumpURL: "/h5/app-invite/landing.html", }).Error; err != nil { t.Fatalf("create config: %v", err) } service := apppopup.NewService(config.Config{}, db, nil) engine := NewRouter(config.Config{}, &repo.Repository{}, appPopupAuthStub{}, Services{AppPopup: service}) body := bytes.NewBufferString(`{"scene":"APP_ENTER","platform":"ios","appVersion":"3.12.0"}`) req := httptest.NewRequest(http.MethodPost, "/app/popups/entry", body) req.Header.Set("Authorization", "Bearer token") req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() engine.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d body = %s", rec.Code, rec.Body.String()) } var payload map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { t.Fatalf("decode response: %v", err) } data, ok := payload["data"].(map[string]any) if !ok { t.Fatalf("data missing: %#v", payload) } queue, ok := data["queue"].([]any) if !ok || len(queue) != 1 || queue[0] != "invite" { t.Fatalf("queue = %#v, want [invite]", data["queue"]) } invite, ok := data["invite"].(map[string]any) if !ok { t.Fatalf("invite missing: %#v", data) } if invite["value"] != true || invite["limit"] != float64(7) { t.Fatalf("invite = %#v, want value true limit 7", invite) } } func TestAppPopupEntryRouteRequiresAuth(t *testing.T) { gin.SetMode(gin.TestMode) db := newRouterAppPopupDB(t) service := apppopup.NewService(config.Config{}, db, nil) engine := NewRouter(config.Config{}, &repo.Repository{}, appPopupAuthStub{}, Services{AppPopup: service}) req := httptest.NewRequest(http.MethodPost, "/app/popups/entry", bytes.NewBufferString(`{}`)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() engine.ServeHTTP(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("status = %d body = %s, want 401", rec.Code, rec.Body.String()) } } func TestAppPopupEntryRouteAllowsEmptyBody(t *testing.T) { gin.SetMode(gin.TestMode) db := newRouterAppPopupDB(t) if err := db.Create(&model.AppPopupConfig{ ID: 1001, SysOrigin: "LIKEI", PopupKey: "invite", Scene: "APP_ENTER", Enabled: true, LimitDays: 7, Priority: 100, Version: 1, UserScopeType: "ALL", }).Error; err != nil { t.Fatalf("create config: %v", err) } service := apppopup.NewService(config.Config{}, db, nil) engine := NewRouter(config.Config{}, &repo.Repository{}, appPopupAuthStub{}, Services{AppPopup: service}) req := httptest.NewRequest(http.MethodPost, "/app/popups/entry", nil) req.Header.Set("Authorization", "Bearer token") rec := httptest.NewRecorder() engine.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d body = %s, want 200", rec.Code, rec.Body.String()) } } func TestAppPopupAdminRoutes(t *testing.T) { gin.SetMode(gin.TestMode) db := newRouterAppPopupDB(t) service := apppopup.NewService(config.Config{}, db, nil) engine := NewRouter(config.Config{}, &repo.Repository{}, appPopupAuthStub{}, Services{AppPopup: service}) saveBody := bytes.NewBufferString(`{"sysOrigin":"LIKEI","popupKey":"invite","enabled":true,"limitDays":7,"description":"邀请弹窗"}`) saveReq := httptest.NewRequest(http.MethodPost, "/app-system/home-popups/configs/save", saveBody) saveReq.Header.Set("Authorization", "Bearer console-token") saveReq.Header.Set("Content-Type", "application/json") saveRec := httptest.NewRecorder() engine.ServeHTTP(saveRec, saveReq) if saveRec.Code != http.StatusOK { t.Fatalf("save status = %d body = %s", saveRec.Code, saveRec.Body.String()) } listReq := httptest.NewRequest(http.MethodGet, "/app-system/home-popups/configs?sysOrigin=LIKEI", nil) listReq.Header.Set("Authorization", "Bearer console-token") listRec := httptest.NewRecorder() engine.ServeHTTP(listRec, listReq) if listRec.Code != http.StatusOK { t.Fatalf("list status = %d body = %s", listRec.Code, listRec.Body.String()) } var listPayload map[string]any if err := json.Unmarshal(listRec.Body.Bytes(), &listPayload); err != nil { t.Fatalf("decode list response: %v", err) } data := listPayload["data"].(map[string]any) records := data["records"].([]any) if len(records) != 1 { t.Fatalf("records = %#v, want one", records) } record := records[0].(map[string]any) id := record["id"].(string) deleteReq := httptest.NewRequest(http.MethodDelete, "/app-system/home-popups/configs/"+id, nil) deleteReq.Header.Set("Authorization", "Bearer console-token") deleteRec := httptest.NewRecorder() engine.ServeHTTP(deleteRec, deleteReq) if deleteRec.Code != http.StatusOK { t.Fatalf("delete status = %d body = %s", deleteRec.Code, deleteRec.Body.String()) } } func newRouterAppPopupDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate(&model.AppPopupConfig{}); err != nil { t.Fatalf("migrate app popup config: %v", err) } return db }