落地埋点 ingest/数据看板、首页宫格 CMS 与测评上下架;含账号引导、问答流式与免责声明去重,以及 review P1 审计同事务修复。 Co-authored-by: Cursor <cursoragent@cursor.com>
90 lines
3.0 KiB
Go
90 lines
3.0 KiB
Go
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)
|
|
}
|
|
}
|