package unsub import ( "context" "errors" "testing" "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/batchresult" ) func TestValidateTaskFailsWhenAnyDetailHasPreExistingErrorReason(t *testing.T) { task := &Task{ Batch: Batch{BatchNo: "B-1"}, Details: []*Detail{ {RowNo: 1, ICCID: "8986"}, {RowNo: 2, ICCID: "8987", Reason: "snapshot has reason"}, }, } if validateTask(task) { t.Fatal("expected validation to fail when any detail has a pre-existing reason") } if task.Details[0].Reason != cascadeFailureReason { t.Fatalf("expected cascade reason on clean detail, got %q", task.Details[0].Reason) } if task.FailureReason == "" { t.Fatal("expected task failure reason to be populated") } } func TestValidateTaskFailsOnEmptyICCID(t *testing.T) { task := &Task{ Batch: Batch{BatchNo: "B-2"}, Details: []*Detail{ {RowNo: 1, ICCID: ""}, }, } if validateTask(task) { t.Fatal("expected empty ICCID to fail validation") } if task.Details[0].Reason != emptyICCIDReason { t.Fatalf("expected empty ICCID reason, got %q", task.Details[0].Reason) } } func TestValidateTaskFailsOnDuplicateICCID(t *testing.T) { task := &Task{ Batch: Batch{BatchNo: "B-3"}, Details: []*Detail{ {RowNo: 1, ICCID: "8986"}, {RowNo: 2, ICCID: "8986"}, }, } if validateTask(task) { t.Fatal("expected duplicate ICCID to fail validation") } if task.Details[0].Reason != duplicateICCIDReason || task.Details[1].Reason != duplicateICCIDReason { t.Fatalf("expected both duplicates flagged, got %q / %q", task.Details[0].Reason, task.Details[1].Reason) } } func TestValidateTaskPassesOnCleanDetails(t *testing.T) { task := &Task{ Batch: Batch{BatchNo: "B-4"}, Details: []*Detail{ {RowNo: 1, ICCID: "8986"}, {RowNo: 2, ICCID: "8987"}, }, } if !validateTask(task) { t.Fatal("expected validation to pass for clean details") } } func TestExecuteHappyPathCommitsAndSavesResult(t *testing.T) { task := &Task{ Batch: Batch{BatchNo: "B-5", BatchType: 108, TenantID: "000000"}, Details: []*Detail{ {RowNo: 1, ICCID: "8986"}, }, } repo := &fakeRepository{task: task} if err := NewExecutor(repo).Execute(context.Background(), "B-5"); err != nil { t.Fatalf("Execute() error = %v", err) } if !repo.committed { t.Fatal("expected business to be committed") } if !repo.resultSaved { t.Fatal("expected result file to be saved") } if repo.validationSaved { t.Fatal("did not expect validation failure path on happy path") } } func TestExecuteValidationFailureSkipsCommit(t *testing.T) { task := &Task{ Batch: Batch{BatchNo: "B-6", BatchType: 108}, Details: []*Detail{ {RowNo: 1, ICCID: "8986"}, {RowNo: 2, ICCID: "8987", Reason: "snapshot error"}, }, } repo := &fakeRepository{task: task} if err := NewExecutor(repo).Execute(context.Background(), "B-6"); err != nil { t.Fatalf("Execute() error = %v", err) } if !repo.validationSaved { t.Fatal("expected validation failure to be persisted") } if repo.committed { t.Fatal("did not expect business to be committed when validation fails") } if !repo.resultSaved { t.Fatal("expected result file to be saved even when validation fails") } } func TestExecuteCommitFailurePropagatesAndSkipsResult(t *testing.T) { task := &Task{ Batch: Batch{BatchNo: "B-7", BatchType: 108}, Details: []*Detail{ {RowNo: 1, ICCID: "8986"}, }, } commitErr := errors.New("transaction failed") repo := &fakeRepository{task: task, commitErr: commitErr} err := NewExecutor(repo).Execute(context.Background(), "B-7") if err == nil || !errors.Is(err, commitErr) { t.Fatalf("Execute() error = %v, want wrap of %v", err, commitErr) } if repo.validationSaved { t.Fatal("validation path should not run when commit failure happens before it") } if repo.resultSaved { t.Fatal("result file should not be saved when commit fails") } } func TestExecuteResultFileFailureOnlyMarksBatchInfo(t *testing.T) { task := &Task{ Batch: Batch{BatchNo: "B-8", BatchType: 108, TenantID: "000000"}, Details: []*Detail{ {RowNo: 1, ICCID: "8986"}, }, } repo := &fakeRepository{task: task, resultErr: errors.New("disk full")} if err := NewExecutor(repo).Execute(context.Background(), "B-8"); err != nil { t.Fatalf("Execute() error = %v, want nil when only result file fails", err) } if !repo.committed { t.Fatal("expected business to be committed before result file failure") } if !repo.resultSaved { t.Fatal("expected result file save attempt to be recorded") } } type fakeRepository struct { task *Task committed bool commitErr error validationSaved bool resultSaved bool resultErr error loadErr error loadCalls int saveCalls int resultRequest batchresult.Request } func (r *fakeRepository) Load(_ context.Context, _ string) (*Task, error) { r.loadCalls++ if r.loadErr != nil { return nil, r.loadErr } return r.task, nil } func (r *fakeRepository) SaveValidationFailure(_ context.Context, _ *Task) error { r.validationSaved = true return nil } func (r *fakeRepository) CommitBusiness(_ context.Context, _ *Task) error { r.committed = true return r.commitErr } func (r *fakeRepository) SaveResultFile(_ context.Context, _ *Task, request batchresult.Request) error { r.saveCalls++ r.resultRequest = request r.resultSaved = true return r.resultErr }