161 lines
5.2 KiB
Go
161 lines
5.2 KiB
Go
package services
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"nearle/models"
|
|
"nearle/repositories"
|
|
)
|
|
|
|
/*
|
|
What these defend.
|
|
|
|
A receipt exists for one reason: the ingest service's batch id is the ONLY
|
|
credential for reading a result back, it is handed out exactly once, and an
|
|
unreviewed drop is deleted after seven days. So the failure that matters is not
|
|
a wrong count — it is a receipt that gets stored without the id, or a receipt
|
|
whose status is reported as something more finished than it is.
|
|
|
|
`pending` in particular is not a formality. Nothing runs on arrival at the
|
|
ingest service; a drop waits for an admin to press Start. A receipt that
|
|
defaulted to `queued` would claim the pipeline had the file when it is in fact
|
|
waiting for a person, and the counts beside it — all zero, because nothing has
|
|
run — would read as an import that found nothing.
|
|
*/
|
|
|
|
// fakeUploadRepo records what reached the repository. Embedded rather than
|
|
// implemented: a method these tests do not exercise is nil and panics loudly if
|
|
// the service starts calling it, which is the failure worth having.
|
|
type fakeUploadRepo struct {
|
|
repositories.CatalogueUploadRepository
|
|
|
|
recorded []models.CatalogueUpload
|
|
updated []models.CatalogueUpload
|
|
shelved []struct {
|
|
batchID string
|
|
shelved, skipped int
|
|
}
|
|
err error
|
|
}
|
|
|
|
func (f *fakeUploadRepo) Record(upload *models.CatalogueUpload) error {
|
|
if f.err != nil {
|
|
return f.err
|
|
}
|
|
f.recorded = append(f.recorded, *upload)
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeUploadRepo) UpdateStatus(upload *models.CatalogueUpload) error {
|
|
if f.err != nil {
|
|
return f.err
|
|
}
|
|
f.updated = append(f.updated, *upload)
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeUploadRepo) MarkShelved(batchID string, shelved, skipped int) error {
|
|
if f.err != nil {
|
|
return f.err
|
|
}
|
|
f.shelved = append(f.shelved, struct {
|
|
batchID string
|
|
shelved, skipped int
|
|
}{batchID, shelved, skipped})
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeUploadRepo) List(tenantID, locationID, pageNo, pageSize int) ([]models.CatalogueUpload, error) {
|
|
return nil, f.err
|
|
}
|
|
|
|
// A drop that has only just been accepted has not run, and the receipt has to
|
|
// say so. `queued` would be wrong in the specific way that matters: it means
|
|
// "approved, waiting for the worker", and this file is waiting for a person.
|
|
func TestRecordDefaultsToPendingNotQueued(t *testing.T) {
|
|
repo := &fakeUploadRepo{}
|
|
svc := NewCatalogueUploadService(repo)
|
|
|
|
upload := &models.CatalogueUpload{Batchid: "abc123", Tenantid: 1135}
|
|
if err := svc.Record(upload); err != nil {
|
|
t.Fatalf("Record: %v", err)
|
|
}
|
|
|
|
if len(repo.recorded) != 1 {
|
|
t.Fatalf("want 1 recorded receipt, got %d", len(repo.recorded))
|
|
}
|
|
if got := repo.recorded[0].Laststatus; got != "pending" {
|
|
t.Errorf("a just-accepted drop should read pending, got %q", got)
|
|
}
|
|
}
|
|
|
|
// A caller who already knows the status keeps it. The default is a fallback for
|
|
// a fresh drop, not an override — a receipt being re-recorded from a poll that
|
|
// already saw `running` must not be walked backwards.
|
|
func TestRecordKeepsAnExplicitStatus(t *testing.T) {
|
|
repo := &fakeUploadRepo{}
|
|
svc := NewCatalogueUploadService(repo)
|
|
|
|
if err := svc.Record(&models.CatalogueUpload{
|
|
Batchid: "abc123", Tenantid: 1135, Laststatus: "running",
|
|
}); err != nil {
|
|
t.Fatalf("Record: %v", err)
|
|
}
|
|
if got := repo.recorded[0].Laststatus; got != "running" {
|
|
t.Errorf("want the caller's own status preserved, got %q", got)
|
|
}
|
|
}
|
|
|
|
// A repository failure is the caller's problem, not something to swallow. The
|
|
// receipt is the only durable copy of the batch id, so a write that did not
|
|
// happen must never look like one that did.
|
|
func TestRecordSurfacesRepositoryFailure(t *testing.T) {
|
|
boom := errors.New("unique violation")
|
|
svc := NewCatalogueUploadService(&fakeUploadRepo{err: boom})
|
|
|
|
err := svc.Record(&models.CatalogueUpload{Batchid: "abc123", Tenantid: 1135})
|
|
if !errors.Is(err, boom) {
|
|
t.Fatalf("want the repository error surfaced, got %v", err)
|
|
}
|
|
}
|
|
|
|
// Zero is a real answer. A run that inserted nothing because every product was
|
|
// already present is a complete success, and the counts have to be storable as
|
|
// zero rather than treated as "not supplied".
|
|
func TestUpdateStatusCarriesZeroCounts(t *testing.T) {
|
|
repo := &fakeUploadRepo{}
|
|
svc := NewCatalogueUploadService(repo)
|
|
|
|
if err := svc.UpdateStatus(&models.CatalogueUpload{
|
|
Batchid: "abc123", Laststatus: "done", Inserted: 0, Skipped: 12,
|
|
}); err != nil {
|
|
t.Fatalf("UpdateStatus: %v", err)
|
|
}
|
|
if len(repo.updated) != 1 {
|
|
t.Fatalf("want 1 update, got %d", len(repo.updated))
|
|
}
|
|
if repo.updated[0].Inserted != 0 || repo.updated[0].Skipped != 12 {
|
|
t.Errorf("counts mangled: %+v", repo.updated[0])
|
|
}
|
|
}
|
|
|
|
// Shelving is a separate claim from ingestion and travels separately. The
|
|
// ingest service confirms the shared global catalogue, which holds no price and
|
|
// no stock; only this records that a shop can actually sell the products.
|
|
func TestMarkShelvedPassesBothCounts(t *testing.T) {
|
|
repo := &fakeUploadRepo{}
|
|
svc := NewCatalogueUploadService(repo)
|
|
|
|
if err := svc.MarkShelved("abc123", 18, 2); err != nil {
|
|
t.Fatalf("MarkShelved: %v", err)
|
|
}
|
|
if len(repo.shelved) != 1 {
|
|
t.Fatalf("want 1 shelving record, got %d", len(repo.shelved))
|
|
}
|
|
got := repo.shelved[0]
|
|
if got.batchID != "abc123" || got.shelved != 18 || got.skipped != 2 {
|
|
t.Errorf("want abc123/18/2, got %v", got)
|
|
}
|
|
}
|