244 lines
6.1 KiB
Go
244 lines
6.1 KiB
Go
package usecase
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"cmroubao/backend-api/internal/domain"
|
|
)
|
|
|
|
func TestFreightImageCacheIsBestEffortAndCleansReplacedFiles(t *testing.T) {
|
|
repository := &freightImageRepositoryFake{
|
|
jobs: []domain.FreightItemImageJob{
|
|
{
|
|
CreatorSubject: "local-admin",
|
|
FreightOrderItemID: "00000000-0000-4000-8000-000000000001",
|
|
ProductThumbRef: "190",
|
|
},
|
|
{
|
|
CreatorSubject: "local-admin",
|
|
FreightOrderItemID: "00000000-0000-4000-8000-000000000002",
|
|
ProductThumbRef: "191",
|
|
},
|
|
},
|
|
replaced: "old/image.jpg",
|
|
}
|
|
source := &freightImageSourceFake{}
|
|
store := &freightImageStoreFake{}
|
|
service, err := NewFreightImageService(
|
|
repository,
|
|
source,
|
|
store,
|
|
fakeClock{},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("NewFreightImageService() error = %v", err)
|
|
}
|
|
if err := service.CacheRun(
|
|
context.Background(),
|
|
"local-admin",
|
|
"00000000-0000-4000-8000-000000000099",
|
|
); err != nil {
|
|
t.Fatalf("CacheRun() error = %v", err)
|
|
}
|
|
repository.mu.Lock()
|
|
defer repository.mu.Unlock()
|
|
if len(repository.saved) != 2 {
|
|
t.Fatalf("saved images = %+v", repository.saved)
|
|
}
|
|
statusByRef := map[string]domain.FreightItemImage{}
|
|
for _, image := range repository.saved {
|
|
statusByRef[image.ProductThumbRef] = image
|
|
}
|
|
if statusByRef["190"].Status != domain.FreightItemImageReady ||
|
|
statusByRef["190"].StorageKey != "new/image.jpg" ||
|
|
statusByRef["191"].Status != domain.FreightItemImageMissing ||
|
|
statusByRef["191"].ErrorCode == nil ||
|
|
*statusByRef["191"].ErrorCode != "ERP_IMAGE_NOT_FOUND" {
|
|
t.Fatalf("saved image statuses = %+v", statusByRef)
|
|
}
|
|
store.mu.Lock()
|
|
defer store.mu.Unlock()
|
|
if len(store.deleted) != 1 || store.deleted[0] != "old/image.jpg" {
|
|
t.Fatalf("deleted storage keys = %#v", store.deleted)
|
|
}
|
|
}
|
|
|
|
func TestFreightImageOpenDoesNotRevealUnavailableItems(t *testing.T) {
|
|
repository := &freightImageRepositoryFake{
|
|
ready: domain.FreightItemImage{
|
|
CreatorSubject: "local-admin",
|
|
FreightOrderItemID: "00000000-0000-4000-8000-000000000001",
|
|
Status: domain.FreightItemImageReady,
|
|
StorageKey: "ready/image.jpg",
|
|
},
|
|
}
|
|
store := &freightImageStoreFake{
|
|
openContent: []byte("normalized-jpeg"),
|
|
}
|
|
service, err := NewFreightImageService(
|
|
repository,
|
|
&freightImageSourceFake{},
|
|
store,
|
|
fakeClock{},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("NewFreightImageService() error = %v", err)
|
|
}
|
|
result, err := service.OpenItemImage(
|
|
context.Background(),
|
|
"local-admin",
|
|
repository.ready.FreightOrderItemID,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("OpenItemImage() error = %v", err)
|
|
}
|
|
defer result.Content.Close()
|
|
content, _ := io.ReadAll(result.Content)
|
|
if string(content) != "normalized-jpeg" {
|
|
t.Fatalf("image content = %q", content)
|
|
}
|
|
if _, err := service.OpenItemImage(
|
|
context.Background(),
|
|
"another-subject",
|
|
repository.ready.FreightOrderItemID,
|
|
); err == nil {
|
|
t.Fatal("cross-subject OpenItemImage() error = nil")
|
|
} else {
|
|
var typed *Error
|
|
if !errors.As(err, &typed) ||
|
|
typed.Code != "FREIGHT_IMAGE_NOT_FOUND" {
|
|
t.Fatalf("cross-subject error = %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFreightImageDeleteStoredImagesUsesIdempotentStoreCleanup(t *testing.T) {
|
|
store := &freightImageStoreFake{}
|
|
service, err := NewFreightImageService(
|
|
&freightImageRepositoryFake{},
|
|
&freightImageSourceFake{},
|
|
store,
|
|
fakeClock{},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("NewFreightImageService() error = %v", err)
|
|
}
|
|
service.DeleteStoredImages([]string{
|
|
"first/image.jpg",
|
|
"second/image.jpg",
|
|
})
|
|
store.mu.Lock()
|
|
defer store.mu.Unlock()
|
|
if len(store.deleted) != 2 ||
|
|
store.deleted[0] != "first/image.jpg" ||
|
|
store.deleted[1] != "second/image.jpg" {
|
|
t.Fatalf("deleted storage keys = %#v", store.deleted)
|
|
}
|
|
}
|
|
|
|
type freightImageRepositoryFake struct {
|
|
mu sync.Mutex
|
|
jobs []domain.FreightItemImageJob
|
|
saved []domain.FreightItemImage
|
|
replaced string
|
|
ready domain.FreightItemImage
|
|
}
|
|
|
|
func (repository *freightImageRepositoryFake) ListFreightImageJobs(
|
|
context.Context,
|
|
string,
|
|
string,
|
|
int,
|
|
) ([]domain.FreightItemImageJob, error) {
|
|
return append([]domain.FreightItemImageJob(nil), repository.jobs...), nil
|
|
}
|
|
|
|
func (repository *freightImageRepositoryFake) SaveFreightItemImage(
|
|
_ context.Context,
|
|
image domain.FreightItemImage,
|
|
) (*string, error) {
|
|
repository.mu.Lock()
|
|
defer repository.mu.Unlock()
|
|
repository.saved = append(repository.saved, image)
|
|
if image.Status == domain.FreightItemImageReady &&
|
|
repository.replaced != "" {
|
|
value := repository.replaced
|
|
repository.replaced = ""
|
|
return &value, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (repository *freightImageRepositoryFake) GetReadyFreightItemImage(
|
|
_ context.Context,
|
|
creatorSubject, itemID string,
|
|
) (domain.FreightItemImage, error) {
|
|
if repository.ready.CreatorSubject != creatorSubject ||
|
|
repository.ready.FreightOrderItemID != itemID {
|
|
return domain.FreightItemImage{}, ErrRepositoryNotFound
|
|
}
|
|
return repository.ready, nil
|
|
}
|
|
|
|
type freightImageSourceFake struct{}
|
|
|
|
func (*freightImageSourceFake) FetchProductImage(
|
|
_ context.Context,
|
|
productThumbRef string,
|
|
) (FreightSourceImage, error) {
|
|
if productThumbRef == "191" {
|
|
return FreightSourceImage{}, domain.ErrFreightImageNotFound
|
|
}
|
|
return FreightSourceImage{
|
|
Content: io.NopCloser(bytes.NewReader([]byte("source-image"))),
|
|
MediaType: "image/png",
|
|
}, nil
|
|
}
|
|
|
|
type freightImageStoreFake struct {
|
|
mu sync.Mutex
|
|
deleted []string
|
|
openContent []byte
|
|
}
|
|
|
|
func (*freightImageStoreFake) Put(
|
|
context.Context,
|
|
string,
|
|
string,
|
|
io.Reader,
|
|
) (NormalizedReferenceImage, error) {
|
|
return NormalizedReferenceImage{
|
|
StorageKey: "new/image.jpg",
|
|
MediaType: "image/jpeg",
|
|
SizeBytes: 100,
|
|
SHA256: repeatSHA("a"),
|
|
}, nil
|
|
}
|
|
|
|
func (store *freightImageStoreFake) Open(
|
|
context.Context,
|
|
string,
|
|
) (io.ReadCloser, error) {
|
|
return io.NopCloser(bytes.NewReader(store.openContent)), nil
|
|
}
|
|
|
|
func (store *freightImageStoreFake) Delete(
|
|
_ context.Context,
|
|
storageKey string,
|
|
) error {
|
|
store.mu.Lock()
|
|
defer store.mu.Unlock()
|
|
store.deleted = append(store.deleted, storageKey)
|
|
return nil
|
|
}
|
|
|
|
func repeatSHA(value string) string {
|
|
return strings.Repeat(value, 64)
|
|
}
|