package controller import ( "context" "fmt" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/middleware" "github.com/QuantumNous/new-api/model" fingerprint "github.com/QuantumNous/new-api/pkg/model_fingerprint" "github.com/QuantumNous/new-api/service" "github.com/QuantumNous/new-api/setting" "github.com/gin-gonic/gin" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/schema" "net/http/httptest" "os" "strings" "testing" "time" ) func TestStatusPageDatabaseMatrix(t *testing.T) { for _, dialect := range []string{"sqlite", "mysql", "postgres"} { t.Run(dialect, func(t *testing.T) { var driver gorm.Dialector switch dialect { case "sqlite": driver = sqlite.Open(":memory:") case "mysql": if os.Getenv("TEST_MYSQL_DSN") == "" { t.Skip("TEST_MYSQL_DSN unset") } driver = mysql.Open(os.Getenv("TEST_MYSQL_DSN")) case "postgres": if os.Getenv("TEST_POSTGRES_DSN") == "" { t.Skip("TEST_POSTGRES_DSN unset") } driver = postgres.Open(os.Getenv("TEST_POSTGRES_DSN")) } db, err := gorm.Open(driver, &gorm.Config{NamingStrategy: schema.NamingStrategy{TablePrefix: fmt.Sprintf("status_%d_", time.Now().UnixNano())}}) require.NoError(t, err) conn, err := db.DB() require.NoError(t, err) conn.SetMaxOpenConns(1) oldDB, oldLog := model.DB, model.LOG_DB oldType, oldLogType := common.MainDatabaseType(), common.LogDatabaseType() oldRedis, oldSecret, oldSecure := common.RedisEnabled, common.SessionSecret, common.SessionCookieSecure oldGroups := setting.UserUsableGroups2JSONString() model.DB, model.LOG_DB = db, db common.SetDatabaseTypes(common.DatabaseType(dialect), common.DatabaseType(dialect)) common.RedisEnabled, common.SessionSecret, common.SessionCookieSecure = false, "status-test-only", true rows := []any{&model.Option{}, &model.Channel{}, &model.Ability{}, &model.SystemTask{}, &model.DetectionRun{}, &model.DetectionCall{}, &model.DetectionSchedule{}, &model.Log{}, &model.PerfMetric{}, &model.User{}, &model.UserSession{}} require.NoError(t, db.AutoMigrate(rows...)) require.NoError(t, db.Table("audit_logs").AutoMigrate(&model.AuditLog{})) t.Cleanup(func() { require.NoError(t, db.Migrator().DropTable("audit_logs")) for _, row := range rows { require.NoError(t, db.Migrator().DropTable(row)) } model.DB, model.LOG_DB = oldDB, oldLog common.SetDatabaseTypes(oldType, oldLogType) common.RedisEnabled, common.SessionSecret, common.SessionCookieSecure = oldRedis, oldSecret, oldSecure require.NoError(t, setting.UpdateUserUsableGroupsByJSONString(oldGroups)) groupCacheSnapshot.updated = time.Time{} require.NoError(t, conn.Close()) }) var version string q := "select version()" if dialect == "sqlite" { q = "select sqlite_version()" } require.NoError(t, db.Raw(q).Scan(&version).Error) t.Log("database version:", version) require.NoError(t, setting.UpdateUserUsableGroupsByJSONString(`{"internal":"","shared":"","hidden":""}`)) now := time.Now().Unix() channels := []model.Channel{{Id: 1, Name: "private-channel", Key: "private-key", Group: "internal,shared", Models: fingerprint.Model, Status: 1}, {Id: 2, Group: "hidden", Models: fingerprint.Model, Status: 1}, {Id: 3, Group: "internal", Models: "disabled-model", Status: 2}} require.NoError(t, db.Create(&channels).Error) abilities := []model.Ability{{Group: "internal", Model: fingerprint.Model, ChannelId: 1, Enabled: true}, {Group: "shared", Model: fingerprint.Model, ChannelId: 1, Enabled: true}, {Group: "hidden", Model: fingerprint.Model, ChannelId: 2, Enabled: true}, {Group: "internal", Model: "disabled-model", ChannelId: 3, Enabled: true}, {Group: "internal", Model: "disabled-ability", ChannelId: 1}} require.NoError(t, db.Create(&abilities).Error) state, err := common.Marshal(service.AstraSamplingState{Rounds: []service.AstraSamplingRound{{ID: "old-round", ChannelID: 1, StartedAt: now - 20, FinishedAt: now - 10, Result: fingerprint.Result{Status: "match", Closest: fingerprint.Model, ValidSamples: 3}}}}) require.NoError(t, err) require.NoError(t, db.Create(&model.SystemTask{TaskID: "old-task", Type: service.AstraSamplingTaskType, Status: model.SystemTaskStatusSucceeded, CreatedAt: now - 20, State: string(state)}).Error) runs := []model.DetectionRun{{ID: "old-work", RequestKey: "old-work", ChannelID: 1, GroupName: "internal", Kind: "pelican", Status: "succeeded", Quality: "normal", CreatedAt: now - 20, Settled: true, HTML: "

art

", Charged: 17}, {ID: "in-flight", RequestKey: "in-flight", ChannelID: 1, Kind: "pelican", Status: "running", CreatedAt: now - 10}, {ID: "hidden-art", RequestKey: "hidden-art", ChannelID: 2, Kind: "pelican", Status: "succeeded", CreatedAt: now - 20, HTML: "hidden"}} require.NoError(t, db.Create(&runs).Error) tokens := map[int]string{} for _, role := range []int{1, 10} { require.NoError(t, db.Create(&model.User{Id: role, Username: fmt.Sprintf("role-%d", role), Role: role, Status: 1, AuthVersion: 1, AffCode: fmt.Sprintf("role-%d", role)}).Error) sid := fmt.Sprintf("session-%d", role) require.NoError(t, db.Create(&model.UserSession{SID: sid, UserID: role, Version: 1, UserAuthVersion: 1, Status: model.UserSessionStatusActive, RefreshHash: sid, ExpiresAt: now + 3600}).Error) tokens[role], _, err = service.IssueAccessToken(service.AuthIdentity{UserID: role, SessionID: sid, UserAuthVersion: 1, SessionVersion: 1}) require.NoError(t, err) } engine := gin.New() engine.GET("/api/uptime/status", GetGroupStatus) engine.GET("/api/group-status", GetSiteGroupStatus) engine.PUT("/api/uptime/group", middleware.AdminAuth(), middleware.SessionCookieOriginGuard(), UpdateStatusPageGroup) engine.POST("/api/uptime/group/reset", middleware.AdminAuth(), middleware.SessionCookieOriginGuard(), UpdateStatusPageGroup) engine.GET("/api/detections/artwork/:id", GetDetectionArtwork) engine.GET("/api/group-status/artwork/:id", GetSiteDetectionArtwork) request := func(method, path, body, token, origin string) *httptest.ResponseRecorder { req := httptest.NewRequest(method, "https://status.test"+path, strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") if token != "" { req.Header.Set("Authorization", "Bearer "+token) } if origin != "" { req.Header.Set("Origin", origin) } out := httptest.NewRecorder() engine.ServeHTTP(out, req) return out } for _, token := range []string{"", tokens[1]} { assert.Contains(t, []int{401, 403}, request("POST", "/api/uptime/group/reset", `{"group":"internal"}`, token, "https://status.test").Code) } assert.Equal(t, 403, request("POST", "/api/uptime/group/reset", `{"group":"internal"}`, tokens[10], "https://evil.test").Code) assert.Equal(t, 400, request("PUT", "/api/uptime/group", `{"group":"internal"}`, tokens[10], "https://status.test").Code) assert.Equal(t, 404, request("POST", "/api/uptime/group/reset", `{"group":"missing"}`, tokens[10], "https://status.test").Code) for _, body := range []string{`{"group":"internal","alias":"#688","hidden":false}`, `{"group":"hidden","alias":"secret-alias","hidden":true}`} { res := request("PUT", "/api/uptime/group", body, tokens[10], "https://status.test") require.Equal(t, 200, res.Code, res.Body.String()) } var auditCount int64 require.NoError(t, db.Table("audit_logs").Count(&auditCount).Error) assert.Greater(t, auditCount, int64(0)) groupCacheSnapshot.updated = time.Time{} res := request("GET", "/api/uptime/status", "", "", "") require.Equal(t, 200, res.Code, res.Body.String()) for _, secret := range []string{"internal", "secret-alias", "private-channel", "private-key", "disabled-model", "disabled-ability"} { assert.NotContains(t, res.Body.String(), secret) } assert.Contains(t, res.Body.String(), "#688") fallback := model.DetectionRun{ID:"fallback-completed",RequestKey:"fallback-completed",Kind:"pelican",ChannelID:1,GroupName:"internal",Status:"running",Quality:"normal",HTML:"pelican",CreatedAt:now,Deadline:now+600} require.NoError(t,db.Create(&fallback).Error) require.NoError(t,db.Create(&model.DetectionCall{ID:fallback.ID,RunID:fallback.ID,SecretHash:"fallback-only-test",State:"finished"}).Error) processLocalPelican(context.Background(),fallback) require.NoError(t,db.First(&fallback,"id = ?",fallback.ID).Error) assert.Equal(t,"succeeded",fallback.Status) assert.Equal(t,"unknown",fallback.Quality) assert.Equal(t,"pelican",fallback.HTML) assert.True(t,fallback.Settled) require.NoError(t,db.Delete(&model.DetectionCall{},"id = ?",fallback.ID).Error) require.NoError(t,db.Delete(&model.DetectionRun{},"id = ?",fallback.ID).Error) visitorSite := request("GET", "/api/group-status", "", "", "") require.Equal(t, 200, visitorSite.Code) assert.Contains(t, visitorSite.Body.String(), `"group":"hidden"`) assert.Contains(t, visitorSite.Body.String(), `"name":"internal"`) assert.NotContains(t, visitorSite.Body.String(), "#688") site := request("GET", "/api/group-status", "", tokens[1], "") require.Equal(t, 200, site.Code) assert.Contains(t, site.Body.String(), `"group":"internal"`) assert.Contains(t, site.Body.String(), `"name":"internal"`) assert.NotContains(t, site.Body.String(), "#688") assert.Contains(t, site.Body.String(), `"group":"hidden"`) adminSite := request("GET", "/api/group-status", "", tokens[10], "") assert.Contains(t, adminSite.Body.String(), `"group":"hidden"`) assert.Equal(t, 404, request("GET", "/api/detections/artwork/hidden-art", "", "", "").Code) assert.Equal(t, 200, request("GET", "/api/group-status/artwork/hidden-art", "", "", "").Code) require.Equal(t, 200, request("POST", "/api/uptime/group/reset", `{"group":"internal"}`, tokens[10], "https://status.test").Code) require.NoError(t, db.Model(&model.DetectionRun{}).Where("id = ?", "in-flight").Updates(map[string]any{"settled": true, "status": "succeeded", "quality": "normal", "finished_at": now + 10}).Error) groups, err := currentGroupMonitors(context.Background(), true) require.NoError(t, err) byGroup := map[string]Monitor{} for _, m := range groups[0].Monitors { byGroup[m.Group] = m } assert.Empty(t, byGroup["internal"].Fingerprint.History) assert.Nil(t, byGroup["internal"].Fingerprint.Rate) assert.Empty(t, byGroup["internal"].Pelican.History) assert.Len(t, byGroup["shared"].Fingerprint.History, 1) assert.Len(t, byGroup["shared"].Pelican.History, 2) assert.True(t, byGroup["hidden"].Hidden) assert.Equal(t, []string{fingerprint.Model}, byGroup["internal"].Models) require.NoError(t, model.SaveStatusPageGroup(context.Background(), "internal", &model.StatusPageGroup{Alias: "#689", Hidden: true})) settings, err := model.GetStatusPageGroups(context.Background()) require.NoError(t, err) assert.GreaterOrEqual(t, settings["internal"].ResetBefore, now) assert.Equal(t, "#689", settings["internal"].Alias) require.NoError(t, model.SaveStatusPageGroup(context.Background(), "shared", &model.StatusPageGroup{Hidden: true})) assert.Equal(t, 404, request("GET", "/api/detections/artwork/old-work", "", "", "").Code) var retained model.DetectionRun require.NoError(t, db.First(&retained, "id = ?", "old-work").Error) assert.Equal(t, 17, retained.Charged) assert.Equal(t, "

art

", retained.HTML) require.NoError(t, db.Create(&model.DetectionRun{ID: "new-work", RequestKey: "new-work", ChannelID: 1, Kind: "pelican", Status: "succeeded", Quality: "normal", CreatedAt: settings["internal"].ResetBefore + 1, Settled: true}).Error) groups, err = currentGroupMonitors(context.Background(), true) require.NoError(t, err) for _, m := range groups[0].Monitors { if m.Group == "internal" { require.Len(t, m.Pelican.History, 1) assert.Equal(t, "new-work", m.Pelican.History[0].ID) } } }) } }