Files
cmroubao/backend-api/internal/usecase/asset_service_test.go
T

202 lines
4.6 KiB
Go
Raw Normal View History

package usecase
import (
"bytes"
"context"
"errors"
"io"
"strings"
"testing"
"cmroubao/backend-api/internal/domain"
)
func TestAssetServiceUploadPersistsNormalizedMetadata(t *testing.T) {
repository := &fakeAssetRepository{}
store := &fakeReferenceImageStore{
result: NormalizedReferenceImage{
StorageKey: "aa/file.jpg",
InputMediaType: "image/png",
InputSHA256: strings.Repeat("1", 64),
MediaType: "image/jpeg",
SizeBytes: 123,
SHA256: strings.Repeat("2", 64),
},
}
service := mustAssetService(t, repository, store)
result, err := service.UploadTaskReference(
context.Background(),
UploadTaskReferenceCommand{
CreatorSubject: " local-admin ",
IdempotencyKey: " upload-1 ",
DeclaredMediaType: "image/png",
Content: bytes.NewReader([]byte("fixture")),
},
)
if err != nil {
t.Fatalf("UploadTaskReference() error = %v", err)
}
if result.Replayed ||
result.Asset.CreatorSubject != "local-admin" ||
result.Asset.Purpose != domain.AssetPurposeTaskReference ||
result.Asset.StorageKey != store.result.StorageKey ||
len(repository.hash) != 64 {
t.Fatalf("result/repository = %+v/%+v", result, repository)
}
}
func TestAssetServiceDeletesNewFileWhenRepositoryReplaysOrFails(
t *testing.T,
) {
for _, test := range []struct {
name string
replay bool
createErr error
wantErr bool
}{
{name: "replay", replay: true},
{name: "failure", createErr: ErrRepositoryUnavailable, wantErr: true},
} {
t.Run(test.name, func(t *testing.T) {
repository := &fakeAssetRepository{
replay: test.replay,
createErr: test.createErr,
}
store := &fakeReferenceImageStore{
result: NormalizedReferenceImage{
StorageKey: "aa/candidate.jpg",
InputMediaType: "image/jpeg",
InputSHA256: strings.Repeat("1", 64),
MediaType: "image/jpeg",
SizeBytes: 123,
SHA256: strings.Repeat("2", 64),
},
}
service := mustAssetService(t, repository, store)
_, err := service.UploadTaskReference(
context.Background(),
UploadTaskReferenceCommand{
CreatorSubject: "local-admin",
IdempotencyKey: "upload-1",
DeclaredMediaType: "image/jpeg",
Content: bytes.NewReader([]byte("fixture")),
},
)
if (err != nil) != test.wantErr {
t.Fatalf("UploadTaskReference() error = %v", err)
}
if store.deleted != "aa/candidate.jpg" {
t.Fatalf("deleted key = %q", store.deleted)
}
})
}
}
func TestAssetServiceMapsImageValidationError(t *testing.T) {
service := mustAssetService(
t,
&fakeAssetRepository{},
&fakeReferenceImageStore{
err: &ImageStoreError{
Kind: ImageStoreErrorInvalid,
Cause: errors.New("private decoder detail"),
},
},
)
_, err := service.UploadTaskReference(
context.Background(),
UploadTaskReferenceCommand{
CreatorSubject: "local-admin",
IdempotencyKey: "upload-1",
DeclaredMediaType: "image/jpeg",
Content: bytes.NewReader([]byte("bad")),
},
)
assertUsecaseError(t, err, ErrorKindInvalid, "ASSET_IMAGE_INVALID")
if strings.Contains(err.Error(), "private") {
t.Fatal("public error leaked decoder detail")
}
}
type fakeAssetRepository struct {
asset domain.Asset
replay bool
createErr error
hash string
}
func (repository *fakeAssetRepository) CreateAssetIdempotent(
_ context.Context,
asset domain.Asset,
_ string,
hash string,
) (domain.Asset, bool, error) {
repository.asset = asset
repository.hash = hash
if repository.createErr != nil {
return asset, false, repository.createErr
}
if repository.replay {
return asset, false, nil
}
return asset, true, nil
}
func (repository *fakeAssetRepository) GetAsset(
context.Context,
string,
string,
) (domain.Asset, error) {
return repository.asset, nil
}
type fakeReferenceImageStore struct {
result NormalizedReferenceImage
err error
deleted string
}
func (store *fakeReferenceImageStore) Put(
context.Context,
string,
string,
io.Reader,
) (NormalizedReferenceImage, error) {
return store.result, store.err
}
func (store *fakeReferenceImageStore) Open(
context.Context,
string,
) (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(nil)), nil
}
func (store *fakeReferenceImageStore) Delete(
_ context.Context,
key string,
) error {
store.deleted = key
return nil
}
func mustAssetService(
t *testing.T,
repository AssetRepository,
store ReferenceImageStore,
) *AssetService {
t.Helper()
service, err := NewAssetService(
repository,
store,
fakeClock{},
&sequenceIDs{},
)
if err != nil {
t.Fatalf("NewAssetService() error = %v", err)
}
return service
}
var _ Clock = fakeClock{}