feat(commerce): 排盘保存需显式同意,默认本地计算不上传
- 前端免费排盘默认不保存,勾选同意后才调用保存接口 - 服务端要求 consentVersion,记录 consentAt,校验并限流公开保存接口 - 付费结算单独披露报告快照与订单信息将由服务端存储 - 管理端展示同意版本与时间,历史无同意证据记录保留并标注 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
ea6828b925
commit
132fef051b
@@ -155,3 +155,88 @@ func TestStripeCheckoutWebhookAndEntitlementHTTPFlow(t *testing.T) {
|
||||
t.Fatalf("expected server-generated premium snapshot, got %d: %s", recoverResponse.Code, recoverResponse.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveChartRecordRequiresConsent(t *testing.T) {
|
||||
router := setupChartRecordAPI(t)
|
||||
response := postChartRecord(router, "198.51.100.41", false)
|
||||
if response.Code != http.StatusBadRequest || !strings.Contains(response.Body.String(), `"code":400`) {
|
||||
t.Fatalf("expected consent to be required, got %d: %s", response.Code, response.Body.String())
|
||||
}
|
||||
var recordCount int64
|
||||
if err := config.GetDB().Model(&model.BaziChartRecord{}).Count(&recordCount).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if recordCount != 0 {
|
||||
t.Fatalf("expected no chart record without consent, got %d", recordCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveChartRecordPersistsConsentViaHTTP(t *testing.T) {
|
||||
router := setupChartRecordAPI(t)
|
||||
response := postChartRecord(router, "198.51.100.42", true)
|
||||
if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), `"code":0`) {
|
||||
t.Fatalf("expected chart record to be saved after consent, got %d: %s", response.Code, response.Body.String())
|
||||
}
|
||||
var record model.BaziChartRecord
|
||||
if err := config.GetDB().Where("report_id = ?", "ZM-CHART123").First(&record).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if record.ConsentVersion != "chart-record-v1" || record.ConsentAt == nil {
|
||||
t.Fatalf("expected persisted consent version and timestamp")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveChartRecordRateLimitedViaHTTP(t *testing.T) {
|
||||
router := setupChartRecordAPI(t)
|
||||
for requestNumber := 0; requestNumber < 11; requestNumber++ {
|
||||
response := postChartRecord(router, "198.51.100.43", true)
|
||||
if requestNumber < 10 && response.Code != http.StatusOK {
|
||||
t.Fatalf("request %d expected HTTP 200, got %d: %s", requestNumber+1, response.Code, response.Body.String())
|
||||
}
|
||||
if requestNumber == 10 && response.Code != http.StatusTooManyRequests {
|
||||
t.Fatalf("expected HTTP 429 after rate limit, got %d: %s", response.Code, response.Body.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveChartRecordRejectsOversizedBodyViaHTTP(t *testing.T) {
|
||||
router := setupChartRecordAPI(t)
|
||||
body := fmt.Sprintf(`{"reportId":"ZM-CHART123","birthDate":"1990-08-08","birthTime":"10:30","gender":"female","city":"上海","consentVersion":"chart-record-v1","snapshot":{"data":"%s"}}`, strings.Repeat("x", 600*1024))
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/commerce/charts", strings.NewReader(body))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.RemoteAddr = "198.51.100.44:1234"
|
||||
response := httptest.NewRecorder()
|
||||
router.ServeHTTP(response, request)
|
||||
if response.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected oversized body to be rejected, got %d", response.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func setupChartRecordAPI(t *testing.T) *gin.Engine {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
t.Setenv("SERVER_ENV", "test")
|
||||
t.Setenv("DB_TYPE", "sqlite")
|
||||
t.Setenv("DB_NAME", fmt.Sprintf("file:chart-consent-http-%d?mode=memory&cache=shared", time.Now().UnixNano()))
|
||||
t.Setenv("JWT_SECRET", "chart-consent-test-secret")
|
||||
if err := config.InitDB(config.Load()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
router := gin.New()
|
||||
router.POST("/api/commerce/charts", middleware.RateLimit(), SaveChartRecord)
|
||||
return router
|
||||
}
|
||||
|
||||
func postChartRecord(router *gin.Engine, clientIP string, consent bool) *httptest.ResponseRecorder {
|
||||
body := `{"reportId":"ZM-CHART123","birthDate":"1990-08-08","birthTime":"10:30","gender":"female","city":"上海","snapshot":{"id":"ZM-CHART123"}`
|
||||
if consent {
|
||||
body += `,"consentVersion":"chart-record-v1"`
|
||||
}
|
||||
body += `}`
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/commerce/charts", strings.NewReader(body))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.RemoteAddr = clientIP + ":1234"
|
||||
response := httptest.NewRecorder()
|
||||
router.ServeHTTP(response, request)
|
||||
return response
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user