package datacenter import ( "encoding/json" "net/http" "net/http/httptest" "strings" "sync" "testing" "time" ) func sample() Record { return Record{ VideoID: "12345", CompanyPlatform: "黄果短剧", PublishedData: "01/09/2026", ScriptName: "剧本A", DramaName: "短剧A", DramaNo: "第1集", VideoURL: "https://cdn.example.com/a.mp4", PlatformURL: "https://example.com/a", Views: 10000, Clicks: 8200, Likes: 300, Comments: Int(0), } } func TestFillDefaults(t *testing.T) { r := sample() r.fillDefaults() if r.Company != DefaultCompany { t.Fatalf("company = %q", r.Company) } if r.DataDate != time.Now().UTC().Format(DateLayout) { t.Fatalf("data_date = %q", r.DataDate) } if r.RoleID == nil { t.Fatal("role_id 应为空数组而不是 null") } // 显式传的日期不能被覆盖 r2 := sample() r2.DataDate = "21/09/2026" r2.fillDefaults() if r2.DataDate != "21/09/2026" { t.Fatalf("data_date 被覆盖为 %q", r2.DataDate) } } func TestValidate(t *testing.T) { s := sample() if err := s.validate(); err != nil && !strings.Contains(err.Error(), "data_date") { t.Fatalf("sample 不应报错: %v", err) } bad := sample() bad.ScriptName = "" bad.VideoURL = "" if err := bad.validate(); err == nil || !strings.Contains(err.Error(), "script_name") || !strings.Contains(err.Error(), "video_url") { t.Fatalf("应报缺 script_name/video_url: %v", err) } wrongDate := sample() wrongDate.DataDate = "2026-09-21" if err := wrongDate.validate(); err == nil || !strings.Contains(err.Error(), "DD/MM/YYYY") { t.Fatalf("应报日期格式错误: %v", err) } } func TestJSONShape(t *testing.T) { b, err := JSON(sample()) if err != nil { t.Fatal(err) } var got struct { Records []map[string]any `json:"records"` } if err := json.Unmarshal(b, &got); err != nil { t.Fatal(err) } row := got.Records[0] if row["data_date"] != time.Now().UTC().Format(DateLayout) { t.Fatalf("data_date = %v", row["data_date"]) } if row["company"] != DefaultCompany { t.Fatalf("company = %v", row["company"]) } if comments, ok := row["comments"].(float64); !ok || comments != 0 { t.Fatalf("comments 应该发出 0,实际 %v", row["comments"]) } if shares, ok := row["shares"]; ok { t.Fatalf("shares 没填就不应出现,实际 %v", shares) } } // TestSendAsync 验证异步上报不阻塞、按顺序发完,且 Close 会等队列清空。 func TestSendAsync(t *testing.T) { var mu sync.Mutex var got []string // 收到的 video_id,按到达顺序 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req struct { Records []struct { VideoID string `json:"video_id"` } `json:"records"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { t.Errorf("解码请求失败: %v", err) } mu.Lock() for _, rec := range req.Records { got = append(got, rec.VideoID) } mu.Unlock() w.WriteHeader(http.StatusOK) })) defer srv.Close() c := New("ld_test") c.Endpoint = srv.URL c.OnError = func(err error) { t.Errorf("异步上报不应报错: %v", err) } for _, id := range []string{"a", "b", "c"} { r := sample() r.VideoID = id c.SendAsync(r) } c.Close() // 等发完 mu.Lock() defer mu.Unlock() if strings.Join(got, ",") != "a,b,c" { t.Fatalf("发送顺序不对: %v", got) } } // TestSendAsyncAfterClose 验证 Close 之后再上报不会 panic,而是走 OnError。 func TestSendAsyncAfterClose(t *testing.T) { c := New("ld_test") var reported []error c.OnError = func(err error) { reported = append(reported, err) } c.Close() // 没调过 SendAsync,应该是空操作 c.SendAsync(sample()) c.Close() // 重复调也安全 if len(reported) != 1 || !strings.Contains(reported[0].Error(), "已 Close") { t.Fatalf("应报一次「已 Close」: %v", reported) } }