From f72c24b5daaebacff490e72ab03a1f6076d99a06 Mon Sep 17 00:00:00 2001 From: QiuSW <105186638@qq.com> Date: Wed, 19 Aug 2026 14:42:46 +0800 Subject: [PATCH] fix(#41): expose rawJson in detail response, not just storage Co-Authored-By: Claude Opus 5 --- server/app/goauto/sybimport/service.go | 19 +++++++++++--- server/app/goauto/sybimport/service_test.go | 29 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/server/app/goauto/sybimport/service.go b/server/app/goauto/sybimport/service.go index 4b5c1ec..f9b18f5 100644 --- a/server/app/goauto/sybimport/service.go +++ b/server/app/goauto/sybimport/service.go @@ -84,15 +84,26 @@ func (service *Service) List(ctx context.Context, request ListRequest) (ListResp return ListResponse{Items: items, Total: total, Page: request.Page, PageSize: request.PageSize}, nil } -func (service *Service) Detail(ctx context.Context, id uint64) (models.SYBProduct, error) { +// DetailView exposes RawJSON, which the model deliberately keeps json:"-" so +// List (and any other place a bare models.SYBProduct gets serialized) never +// leaks the full raw payload by accident. This was caught before it shipped: +// the Detail handler used to serialize the bare model directly, which meant +// RawJSON — required by #41 ("原始JSON和结构化字段均可在详情查看") — was +// silently unreachable through the API despite being stored correctly. +type DetailView struct { + models.SYBProduct + RawJSON string `json:"rawJson"` +} + +func (service *Service) Detail(ctx context.Context, id uint64) (DetailView, error) { var record models.SYBProduct if err := service.DB.WithContext(ctx).First(&record, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return models.SYBProduct{}, notFound() + return DetailView{}, notFound() } - return models.SYBProduct{}, internalError(err) + return DetailView{}, internalError(err) } - return record, nil + return DetailView{SYBProduct: record, RawJSON: record.RawJSON}, nil } type ReparseRequest struct { diff --git a/server/app/goauto/sybimport/service_test.go b/server/app/goauto/sybimport/service_test.go index e650dd1..5e6394d 100644 --- a/server/app/goauto/sybimport/service_test.go +++ b/server/app/goauto/sybimport/service_test.go @@ -2,7 +2,9 @@ package sybimport_test import ( "context" + "encoding/json" "errors" + "strings" "testing" "go-admin/app/goauto/models" @@ -105,3 +107,30 @@ func TestServiceManualCorrectTrimsWhitespace(t *testing.T) { t.Fatalf("expected trimmed values, got %q/%q", record.TargetColor, record.TargetSize) } } + +// Regression test: Detail previously returned the bare model, whose RawJSON +// field is json:"-" (to keep List from leaking it). That made the raw +// payload silently unreachable through the API despite #41 requiring it in +// the detail view. DetailView must carry it under a real JSON key. +func TestServiceDetailExposesRawJSON(t *testing.T) { + db := openTestDB(t) + applied, err := sybimport.ApplyDetail(context.Background(), db, realOrder(), realDetailA()) + if err != nil { + t.Fatalf("apply: %v", err) + } + service := sybimport.NewService(db) + detail, err := service.Detail(context.Background(), applied.SYBProduct.ID) + if err != nil { + t.Fatalf("detail: %v", err) + } + if detail.RawJSON == "" || detail.RawJSON == "{}" { + t.Fatalf("expected the real raw payload, got %q", detail.RawJSON) + } + encoded, err := json.Marshal(detail) + if err != nil { + t.Fatalf("marshal: %v", err) + } + if !strings.Contains(string(encoded), `"rawJson"`) { + t.Fatalf("serialized detail must expose rawJson, got %s", encoded) + } +}