feat(t240): cache freight item images

This commit is contained in:
QiuSW
2026-07-29 15:12:05 +08:00
parent 5f17fc11aa
commit c7e152aaa1
28 changed files with 1728 additions and 30 deletions
@@ -479,6 +479,41 @@ func TestAdminFreightAPIImportsAllItemsWithoutPII(t *testing.T) {
detail.Header().Get("Cache-Control") != "no-store" {
t.Fatalf("freight detail status/body = %d / %s", detail.Code, detail.Body)
}
var detailBody struct {
Items []struct {
ID string `json:"id"`
ImageStatus string `json:"image_status"`
ImageURL string `json:"image_url"`
} `json:"items"`
}
decodeResponse(t, detail, &detailBody)
if len(detailBody.Items) != 2 ||
detailBody.Items[0].ImageStatus != "READY" ||
detailBody.Items[0].ImageURL == "" {
t.Fatalf("freight detail images = %+v", detailBody.Items)
}
imageResponse := performAdminRequest(
t,
router,
http.MethodGet,
detailBody.Items[0].ImageURL,
"",
nil,
"",
)
if imageResponse.Code != http.StatusOK ||
imageResponse.Header().Get("Content-Type") != "image/jpeg" ||
imageResponse.Header().Get("Cache-Control") != "private, no-store" ||
imageResponse.Header().Get("X-Content-Type-Options") != "nosniff" ||
imageResponse.Header().Get("ETag") == "" ||
!bytes.HasPrefix(imageResponse.Body.Bytes(), []byte{0xff, 0xd8}) {
t.Fatalf(
"freight image status/headers/body = %d / %#v / %x",
imageResponse.Code,
imageResponse.Header(),
imageResponse.Body.Bytes(),
)
}
replay := performAdminRequest(
t,
router,
@@ -875,6 +910,9 @@ func TestAdminOrderAuthorizationIsIdempotentAndRevisioned(t *testing.T) {
if err != nil {
t.Fatalf("migration.New() error = %v", err)
}
if err := runner.Down(context.Background()); err != nil {
t.Fatalf("image migration down: %v", err)
}
if err := runner.Down(context.Background()); err != nil {
t.Fatalf("metadata migration down: %v", err)
}
@@ -1211,12 +1249,22 @@ func newAdminIntegrationFixture(t *testing.T) *adminIntegrationFixture {
if err != nil {
t.Fatalf("usecase.NewOrderAuthorizationService() error = %v", err)
}
freightImages, err := usecase.NewFreightImageService(
repositories,
staticFreightSource{},
files,
clock,
)
if err != nil {
t.Fatalf("usecase.NewFreightImageService() error = %v", err)
}
freight, err := usecase.NewFreightService(
repositories,
staticFreightSource{},
clock,
ids,
time.Second,
usecase.WithFreightImageCache(freightImages),
)
if err != nil {
t.Fatalf("usecase.NewFreightService() error = %v", err)
@@ -1236,6 +1284,7 @@ func newAdminIntegrationFixture(t *testing.T) *adminIntegrationFixture {
Results: results,
Authorizations: authorizations,
Freight: freight,
FreightImages: freightImages,
Procurement: procurement,
},
emptyAdminWeb{},
@@ -1270,6 +1319,8 @@ func (staticFreightSource) QueryOrder(
quantityTwo := 2
priceOne := int64(12950)
priceTwo := int64(8800)
thumbOne := "190"
thumbTwo := "191"
return domain.FreightSourceBatch{
SchemaVersion: 1,
Query: domain.FreightSourceQuery{
@@ -1289,6 +1340,7 @@ func (staticFreightSource) QueryOrder(
Quantity: &quantityOne,
OriginalUnitPriceMinor: &priceOne,
OriginalCurrency: domain.FreightCurrencyTWD,
ProductThumbRef: &thumbOne,
},
{
ExternalItemID: "89",
@@ -1298,12 +1350,41 @@ func (staticFreightSource) QueryOrder(
Quantity: &quantityTwo,
OriginalUnitPriceMinor: &priceTwo,
OriginalCurrency: domain.FreightCurrencyTWD,
ProductThumbRef: &thumbTwo,
},
},
}},
}, nil
}
func (staticFreightSource) FetchProductImage(
context.Context,
string,
) (usecase.FreightSourceImage, error) {
var imageBytes bytes.Buffer
source := image.NewRGBA(image.Rect(0, 0, 8, 6))
for y := 0; y < 6; y++ {
for x := 0; x < 8; x++ {
source.Set(
x,
y,
color.RGBA{R: uint8(x * 20), G: 80, B: 160, A: 255},
)
}
}
if err := jpeg.Encode(
&imageBytes,
source,
&jpeg.Options{Quality: 85},
); err != nil {
return usecase.FreightSourceImage{}, err
}
return usecase.FreightSourceImage{
Content: io.NopCloser(bytes.NewReader(imageBytes.Bytes())),
MediaType: "image/jpeg",
}, nil
}
func (staticFreightSource) QueryCreatedRange(
_ context.Context,
createdFrom, createdTo string,