feat(ops): ECR-007 行为分析与 ECR-008 内容运营后台
落地埋点 ingest/数据看板、首页宫格 CMS 与测评上下架;含账号引导、问答流式与免责声明去重,以及 review P1 审计同事务修复。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAnalyticsOpsB(t *testing.T) {
|
||||
r, _ := setupAPI(t)
|
||||
sid := "s_test_" + time.Now().Format("150405")
|
||||
now := time.Now().UTC().Format(time.RFC3339Nano)
|
||||
|
||||
env, key := doJSON(t, r, http.MethodPost, "/api/v1/analytics/events", map[string]any{
|
||||
"items": []map[string]any{
|
||||
{"name": "session_start", "session_id": sid, "client_ts": now, "props": map[string]any{"cold": true}},
|
||||
{"name": "page_view", "session_id": sid, "page_path": "/portrait", "client_ts": now},
|
||||
{"name": "page_view", "session_id": sid, "page_path": "/ask", "client_ts": now},
|
||||
{"name": "page_leave", "session_id": sid, "page_path": "/portrait", "client_ts": now,
|
||||
"props": map[string]any{"dwell_ms": 1500}},
|
||||
{"name": "ui_click", "session_id": sid, "client_ts": now,
|
||||
"props": map[string]any{"element_id": "home_cta", "page_path": "/"}},
|
||||
{"name": "portrait_completed", "session_id": sid, "client_ts": now},
|
||||
{"name": "session_end", "session_id": sid, "client_ts": now,
|
||||
"props": map[string]any{"exit_page": "/ask", "duration_ms": 8000}},
|
||||
},
|
||||
}, "")
|
||||
var accepted struct {
|
||||
Accepted int `json:"accepted"`
|
||||
}
|
||||
if err := json.Unmarshal(env.Data, &accepted); err != nil || accepted.Accepted < 6 {
|
||||
t.Fatalf("accepted=%v err=%v body=%s", accepted, err, string(env.Data))
|
||||
}
|
||||
_ = key
|
||||
|
||||
loginEnv, code := doAdminJSON(t, r, http.MethodPost, "/api/v1/admin/auth/login", map[string]string{
|
||||
"username": "admin", "password": "change-me",
|
||||
}, "")
|
||||
if code != http.StatusOK || loginEnv.Code != 0 {
|
||||
t.Fatalf("admin login http=%d code=%d", code, loginEnv.Code)
|
||||
}
|
||||
var login struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
_ = json.Unmarshal(loginEnv.Data, &login)
|
||||
if login.Token == "" {
|
||||
t.Fatal("missing admin token")
|
||||
}
|
||||
|
||||
from := time.Now().UTC().Add(-24 * time.Hour).Format("2006-01-02")
|
||||
to := time.Now().UTC().Format("2006-01-02")
|
||||
q := "?from=" + from + "&to=" + to
|
||||
|
||||
ov, code := doAdminJSON(t, r, http.MethodGet, "/api/v1/admin/analytics/overview"+q, nil, login.Token)
|
||||
if code != http.StatusOK || ov.Code != 0 {
|
||||
t.Fatalf("overview http=%d code=%d msg=%s", code, ov.Code, ov.Message)
|
||||
}
|
||||
var overview map[string]any
|
||||
_ = json.Unmarshal(ov.Data, &overview)
|
||||
if sessions, _ := overview["sessions"].(float64); sessions < 1 {
|
||||
t.Fatalf("expected sessions>=1 got %v", overview)
|
||||
}
|
||||
|
||||
pg, code := doAdminJSON(t, r, http.MethodGet, "/api/v1/admin/analytics/pages"+q, nil, login.Token)
|
||||
if code != http.StatusOK || pg.Code != 0 {
|
||||
t.Fatalf("pages http=%d code=%d", code, pg.Code)
|
||||
}
|
||||
ex, code := doAdminJSON(t, r, http.MethodGet, "/api/v1/admin/analytics/exits"+q, nil, login.Token)
|
||||
if code != http.StatusOK || ex.Code != 0 {
|
||||
t.Fatalf("exits http=%d code=%d", code, ex.Code)
|
||||
}
|
||||
var exits struct {
|
||||
Items []struct {
|
||||
ExitPage string `json:"exit_page"`
|
||||
Count int `json:"count"`
|
||||
} `json:"items"`
|
||||
}
|
||||
_ = json.Unmarshal(ex.Data, &exits)
|
||||
found := false
|
||||
for _, it := range exits.Items {
|
||||
if it.ExitPage == "/ask" && it.Count >= 1 {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("expected exit /ask in %v", exits.Items)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user