package settlement import ( "context" "errors" "testing" "time" "gl/domain/ledger" ) type memoryRepository struct{ record ledger.Settlement } func (m *memoryRepository) ClaimDue(context.Context, string, int, time.Time) ([]ledger.Settlement, error) { return []ledger.Settlement{m.record}, nil } func (m *memoryRepository) Save(_ context.Context, record ledger.Settlement, _ string) error { m.record = record return nil } type submitter struct { hash string err error } func (s submitter) Submit(context.Context, ledger.Settlement) (string, error) { return s.hash, s.err } func TestWorkerPersistsSubmission(t *testing.T) { repository := &memoryRepository{record: ledger.Settlement{ID: "s1", SourceService: "wallet", SourceTxID: "42", IdempotencyKey: "kuknos:42", Status: ledger.SettlementPending, AvailableAt: time.Unix(1, 0)}} worker := Worker{Repository: repository, Submitter: submitter{hash: "hash-42"}, WorkerID: "worker-1", Now: func() time.Time { return time.Unix(2, 0) }} if err := worker.RunOnce(context.Background(), 1); err != nil { t.Fatal(err) } if repository.record.Status != ledger.SettlementConfirmed || repository.record.TransactionHash != "hash-42" || repository.record.Attempts != 1 { t.Fatalf("unexpected settlement: %+v", repository.record) } } func TestWorkerSchedulesRetryAfterFirstFailedAttempt(t *testing.T) { repository := &memoryRepository{record: ledger.Settlement{ID: "s1", SourceService: "wallet", SourceTxID: "42", IdempotencyKey: "kuknos:42", Status: ledger.SettlementPending, AvailableAt: time.Unix(1, 0)}} worker := Worker{Repository: repository, Submitter: submitter{err: errors.New("timeout")}, WorkerID: "worker-1", MaxAttempts: 3, Now: func() time.Time { return time.Unix(2, 0) }} if err := worker.RunOnce(context.Background(), 1); err != nil { t.Fatal(err) } if repository.record.Status != ledger.SettlementRetryable || repository.record.Attempts != 1 || !repository.record.AvailableAt.Equal(time.Unix(3, 0)) { t.Fatalf("unexpected settlement: %+v", repository.record) } } func TestWorkerMovesExhaustedSubmissionToManualReview(t *testing.T) { repository := &memoryRepository{record: ledger.Settlement{ID: "s1", SourceService: "wallet", SourceTxID: "42", IdempotencyKey: "kuknos:42", Status: ledger.SettlementRetryable, Attempts: 2, AvailableAt: time.Unix(1, 0)}} worker := Worker{Repository: repository, Submitter: submitter{err: errors.New("timeout")}, WorkerID: "worker-1", MaxAttempts: 3, Now: func() time.Time { return time.Unix(2, 0) }} if err := worker.RunOnce(context.Background(), 1); err != nil { t.Fatal(err) } if repository.record.Status != ledger.SettlementManualReview { t.Fatalf("status=%s", repository.record.Status) } }