282 lines
7.6 KiB
Go
282 lines
7.6 KiB
Go
package sqlite_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"image"
|
|
"image/jpeg"
|
|
"testing"
|
|
"time"
|
|
|
|
"cmroubao/backend-api/internal/domain"
|
|
"cmroubao/backend-api/internal/platform/assetstore"
|
|
repository "cmroubao/backend-api/internal/repository/sqlite"
|
|
"cmroubao/backend-api/internal/usecase"
|
|
)
|
|
|
|
func TestProcurementAutomaticallyCopiesAndCanReplaceERPReference(
|
|
t *testing.T,
|
|
) {
|
|
db := openDatabase(t)
|
|
repositories, _ := repository.New(db)
|
|
ctx := context.Background()
|
|
now := time.Date(2026, 7, 29, 8, 0, 0, 0, time.UTC)
|
|
userID := uuid(1700)
|
|
seedFreightUser(t, db, userID, now)
|
|
run := freightRun(1701, userID, now)
|
|
createAndStartFreightRun(t, repositories, run, "auto-reference", "1")
|
|
batch := freightBatch(1710, "a", "b")
|
|
firstThumb := "190100"
|
|
secondThumb := "190101"
|
|
batch.Orders[0].Items[0].ProductThumbRef = &firstThumb
|
|
batch.Orders[0].Items[1].ProductThumbRef = &secondThumb
|
|
if err := repositories.CompleteFreightSync(
|
|
ctx,
|
|
run,
|
|
batch,
|
|
now.Add(time.Second),
|
|
); err != nil {
|
|
t.Fatalf("CompleteFreightSync() error = %v", err)
|
|
}
|
|
orders, _ := repositories.ListFreightOrders(ctx, "local-admin", 10)
|
|
detail, _ := repositories.GetFreightOrder(
|
|
ctx,
|
|
"local-admin",
|
|
orders[0].ID,
|
|
)
|
|
files, err := assetstore.New(t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("assetstore.New() error = %v", err)
|
|
}
|
|
firstImage := saveReadyFreightImage(
|
|
t,
|
|
ctx,
|
|
repositories,
|
|
files,
|
|
detail.Items[0],
|
|
firstThumb,
|
|
now.Add(2*time.Second),
|
|
)
|
|
ids := &procurementIDs{next: 1800}
|
|
service, err := usecase.NewProcurementService(
|
|
repositories,
|
|
procurementClock{now: now.Add(3 * time.Second)},
|
|
ids,
|
|
usecase.WithProcurementReferenceStore(files),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("NewProcurementService() error = %v", err)
|
|
}
|
|
command := usecase.CreateProcurementRequestCommand{
|
|
CreatorSubject: "local-admin",
|
|
ActorUserID: userID,
|
|
FreightOrderItemID: detail.Items[0].ID,
|
|
ConfirmProcurementNeeded: true,
|
|
}
|
|
created, err := service.CreateRequest(ctx, command)
|
|
if err != nil ||
|
|
created.Request.Status != domain.ProcurementReady ||
|
|
created.Request.ReferenceAssetID == nil ||
|
|
created.Request.ReferenceOrigin == nil ||
|
|
*created.Request.ReferenceOrigin != domain.ProcurementReferenceERP ||
|
|
created.Request.ReferenceSourceImageSHA256 == nil ||
|
|
*created.Request.ReferenceSourceImageSHA256 != firstImage.SHA256 {
|
|
t.Fatalf("automatic request = %+v, %v", created, err)
|
|
}
|
|
autoAsset, err := repositories.GetAsset(
|
|
ctx,
|
|
"local-admin",
|
|
*created.Request.ReferenceAssetID,
|
|
)
|
|
if err != nil || autoAsset.StorageKey == firstImage.StorageKey {
|
|
t.Fatalf("automatic asset = %+v, %v", autoAsset, err)
|
|
}
|
|
assertStoredImageExists(t, ctx, files, firstImage.StorageKey)
|
|
assertStoredImageExists(t, ctx, files, autoAsset.StorageKey)
|
|
|
|
replayed, err := service.CreateRequest(ctx, command)
|
|
if err != nil || !replayed.Replayed ||
|
|
replayed.Request.ID != created.Request.ID ||
|
|
replayed.Request.ReferenceAssetID == nil ||
|
|
*replayed.Request.ReferenceAssetID != autoAsset.ID {
|
|
t.Fatalf("automatic replay = %+v, %v", replayed, err)
|
|
}
|
|
var assetCount int
|
|
if err := db.QueryRow(
|
|
`SELECT count(*) FROM assets WHERE creator_subject = ?`,
|
|
"local-admin",
|
|
).Scan(&assetCount); err != nil || assetCount != 1 {
|
|
t.Fatalf("asset count after replay = %d, %v", assetCount, err)
|
|
}
|
|
|
|
manualID := uuid(1900)
|
|
manualNormalized := putTestImage(t, ctx, files, manualID)
|
|
manual := domain.Asset{
|
|
ID: manualID,
|
|
CreatorSubject: "local-admin",
|
|
Purpose: domain.AssetPurposeTaskReference,
|
|
MediaType: manualNormalized.MediaType,
|
|
SizeBytes: manualNormalized.SizeBytes,
|
|
SHA256: manualNormalized.SHA256,
|
|
StorageKey: manualNormalized.StorageKey,
|
|
CreatedAt: now.Add(4 * time.Second),
|
|
}
|
|
if _, _, err := repositories.CreateAssetIdempotent(
|
|
ctx,
|
|
manual,
|
|
"manual-reference",
|
|
repeatHex("a"),
|
|
); err != nil {
|
|
t.Fatalf("CreateAssetIdempotent() error = %v", err)
|
|
}
|
|
replaced, err := service.BindReference(
|
|
ctx,
|
|
usecase.BindProcurementReferenceCommand{
|
|
CreatorSubject: "local-admin",
|
|
ActorUserID: userID,
|
|
RequestID: created.Request.ID,
|
|
ImageAssetID: manual.ID,
|
|
},
|
|
)
|
|
if err != nil ||
|
|
replaced.ReferenceOrigin == nil ||
|
|
*replaced.ReferenceOrigin != domain.ProcurementReferenceManual ||
|
|
replaced.ReferenceAssetID == nil ||
|
|
*replaced.ReferenceAssetID != manual.ID {
|
|
t.Fatalf("manual replacement = %+v, %v", replaced, err)
|
|
}
|
|
if _, err := repositories.GetAsset(
|
|
ctx,
|
|
"local-admin",
|
|
autoAsset.ID,
|
|
); err == nil {
|
|
t.Fatal("replaced automatic asset remains in database")
|
|
}
|
|
assertStoredImageMissing(t, ctx, files, autoAsset.StorageKey)
|
|
assertStoredImageExists(t, ctx, files, firstImage.StorageKey)
|
|
assertStoredImageExists(t, ctx, files, manual.StorageKey)
|
|
|
|
legacy, _ := usecase.NewProcurementService(
|
|
repositories,
|
|
procurementClock{now: now.Add(5 * time.Second)},
|
|
ids,
|
|
)
|
|
pending, err := legacy.CreateRequest(
|
|
ctx,
|
|
usecase.CreateProcurementRequestCommand{
|
|
CreatorSubject: "local-admin",
|
|
ActorUserID: userID,
|
|
FreightOrderItemID: detail.Items[1].ID,
|
|
ConfirmProcurementNeeded: true,
|
|
},
|
|
)
|
|
if err != nil || pending.Request.Status != domain.ProcurementNeedsImage {
|
|
t.Fatalf("pending request = %+v, %v", pending, err)
|
|
}
|
|
saveReadyFreightImage(
|
|
t,
|
|
ctx,
|
|
repositories,
|
|
files,
|
|
detail.Items[1],
|
|
secondThumb,
|
|
now.Add(6*time.Second),
|
|
)
|
|
upgraded, err := service.CreateRequest(
|
|
ctx,
|
|
usecase.CreateProcurementRequestCommand{
|
|
CreatorSubject: "local-admin",
|
|
ActorUserID: userID,
|
|
FreightOrderItemID: detail.Items[1].ID,
|
|
ConfirmProcurementNeeded: true,
|
|
},
|
|
)
|
|
if err != nil || !upgraded.Replayed ||
|
|
upgraded.Request.ID != pending.Request.ID ||
|
|
upgraded.Request.Status != domain.ProcurementReady {
|
|
t.Fatalf("upgraded request = %+v, %v", upgraded, err)
|
|
}
|
|
}
|
|
|
|
func saveReadyFreightImage(
|
|
t *testing.T,
|
|
ctx context.Context,
|
|
repositories *repository.Store,
|
|
files *assetstore.Store,
|
|
item domain.FreightOrderItem,
|
|
thumb string,
|
|
now time.Time,
|
|
) domain.FreightItemImage {
|
|
t.Helper()
|
|
normalized := putTestImage(t, ctx, files, item.ID)
|
|
candidate := domain.FreightItemImage{
|
|
CreatorSubject: "local-admin",
|
|
FreightOrderItemID: item.ID,
|
|
ProductThumbRef: thumb,
|
|
Status: domain.FreightItemImageReady,
|
|
MediaType: normalized.MediaType,
|
|
SizeBytes: normalized.SizeBytes,
|
|
SHA256: normalized.SHA256,
|
|
StorageKey: normalized.StorageKey,
|
|
UpdatedAt: now,
|
|
}
|
|
if _, err := repositories.SaveFreightItemImage(ctx, candidate); err != nil {
|
|
t.Fatalf("SaveFreightItemImage() error = %v", err)
|
|
}
|
|
return candidate
|
|
}
|
|
|
|
func putTestImage(
|
|
t *testing.T,
|
|
ctx context.Context,
|
|
files *assetstore.Store,
|
|
id string,
|
|
) usecase.NormalizedReferenceImage {
|
|
t.Helper()
|
|
var content bytes.Buffer
|
|
if err := jpeg.Encode(
|
|
&content,
|
|
image.NewRGBA(image.Rect(0, 0, 8, 6)),
|
|
nil,
|
|
); err != nil {
|
|
t.Fatalf("jpeg.Encode() error = %v", err)
|
|
}
|
|
normalized, err := files.Put(
|
|
ctx,
|
|
id,
|
|
"image/jpeg",
|
|
bytes.NewReader(content.Bytes()),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("files.Put() error = %v", err)
|
|
}
|
|
return normalized
|
|
}
|
|
|
|
func assertStoredImageExists(
|
|
t *testing.T,
|
|
ctx context.Context,
|
|
files *assetstore.Store,
|
|
key string,
|
|
) {
|
|
t.Helper()
|
|
content, err := files.Open(ctx, key)
|
|
if err != nil {
|
|
t.Fatalf("files.Open(%q) error = %v", key, err)
|
|
}
|
|
_ = content.Close()
|
|
}
|
|
|
|
func assertStoredImageMissing(
|
|
t *testing.T,
|
|
ctx context.Context,
|
|
files *assetstore.Store,
|
|
key string,
|
|
) {
|
|
t.Helper()
|
|
if content, err := files.Open(ctx, key); err == nil {
|
|
_ = content.Close()
|
|
t.Fatalf("files.Open(%q) succeeded after cleanup", key)
|
|
}
|
|
}
|