Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func TestGetLadderExecutionGate(t *testing.T) {
assert.True(t, *g.ConfigEnabled)
assert.Equal(t, "email_approval", *g.ConfigMode)
assert.Equal(t, "executing", *g.RunStatus)
require.NotNil(t, g.TrancheStatus)
assert.Equal(t, "fired", *g.TrancheStatus)
})

t.Run("non-ladder keys are not an error", func(t *testing.T) {
Expand Down
7 changes: 5 additions & 2 deletions internal/config/store_postgres_ladder_reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,13 @@ func (s *PostgresStore) LockLadderTrancheForRetryTx(ctx context.Context, tx pgx.
// call, about the ladder tranche an execution belongs to. Each field is a
// pointer because the foreign keys are ON DELETE SET NULL: a nil field means
// the row is gone, which the caller must treat as "refuse", not as a default.
// TrancheStatus is the tranche's own status: an operator may have settled the
// tranche while an execution carrying its key is still waiting to run.
type LadderExecutionGate struct {
ConfigEnabled *bool
ConfigMode *string
RunStatus *string
TrancheStatus *string
}

// GetLadderExecutionGate finds the ladder tranche whose id equals an
Expand All @@ -159,11 +162,11 @@ func (s *PostgresStore) GetLadderExecutionGate(ctx context.Context, idempotencyK
}
var g LadderExecutionGate
err := s.db.QueryRow(ctx, `
SELECT c.enabled, c.mode, r.status
SELECT c.enabled, c.mode, r.status, t.status
FROM ladder_tranches t
LEFT JOIN ladder_configs c ON c.id = t.config_id
LEFT JOIN ladder_runs r ON r.id = t.run_id
WHERE t.id::text = $1`, idempotencyKey).Scan(&g.ConfigEnabled, &g.ConfigMode, &g.RunStatus)
WHERE t.id::text = $1`, idempotencyKey).Scan(&g.ConfigEnabled, &g.ConfigMode, &g.RunStatus, &g.TrancheStatus)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
Expand Down
4 changes: 4 additions & 0 deletions internal/purchase/ladder_execution_recheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func (m *Manager) ladderFlagsRefusal(ctx context.Context) error {

func ladderGateRefusal(gate *config.LadderExecutionGate) error {
switch {
case gate.TrancheStatus == nil:
return errors.New("refusing to execute ladder purchase: its tranche status is unknown")
case config.LadderTrancheSettled(ladder.TrancheStatus(*gate.TrancheStatus)):
return fmt.Errorf("refusing to execute ladder purchase: its tranche is %s", *gate.TrancheStatus)
case gate.ConfigEnabled == nil || gate.ConfigMode == nil:
return errors.New("refusing to execute ladder purchase: its ladder config no longer exists")
case !*gate.ConfigEnabled:
Expand Down
19 changes: 15 additions & 4 deletions internal/purchase/ladder_execution_recheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func ptr[T any](v T) *T { return &v }

// openGate is a gate that allows execution; each case flips one field.
func openGate() *config.LadderExecutionGate {
return &config.LadderExecutionGate{ConfigEnabled: ptr(true), ConfigMode: ptr("auto_approve"), RunStatus: ptr("executing")}
return &config.LadderExecutionGate{ConfigEnabled: ptr(true), ConfigMode: ptr("auto_approve"), RunStatus: ptr("executing"), TrancheStatus: ptr("fired")}
}

func ladderGlobal(planning, execution bool) *config.GlobalConfig {
Expand Down Expand Up @@ -85,6 +85,11 @@ func TestLadderRecheck_RefusesBeforeProviderCall(t *testing.T) {
{"run canceled", gateWith(func(g *config.LadderExecutionGate) { g.RunStatus = ptr(string(ladder.RunStatusCancelled)) }), nil, ladderGlobal(true, true), nil, "run was canceled"},
{"config deleted", gateWith(func(g *config.LadderExecutionGate) { g.ConfigEnabled, g.ConfigMode = nil, nil }), nil, ladderGlobal(true, true), nil, "config no longer exists"},
{"run deleted", gateWith(func(g *config.LadderExecutionGate) { g.RunStatus = nil }), nil, ladderGlobal(true, true), nil, "run no longer exists"},
{"tranche failed by operator", gateWith(func(g *config.LadderExecutionGate) { g.TrancheStatus = ptr(string(ladder.TrancheStatusFailed)) }), nil, ladderGlobal(true, true), nil, "its tranche is failed"},
{"tranche completed", gateWith(func(g *config.LadderExecutionGate) { g.TrancheStatus = ptr(string(ladder.TrancheStatusCompleted)) }), nil, ladderGlobal(true, true), nil, "its tranche is completed"},
{"tranche cancelled", gateWith(func(g *config.LadderExecutionGate) { g.TrancheStatus = ptr(string(ladder.TrancheStatusCancelled)) }), nil, ladderGlobal(true, true), nil, "its tranche is cancelled"}, //nolint:misspell // "cancelled" is the stored ladder status value
{"tranche status not known to the code", gateWith(func(g *config.LadderExecutionGate) { g.TrancheStatus = ptr("bogus") }), nil, ladderGlobal(true, true), nil, "its tranche is bogus"},
{"tranche status unknown", gateWith(func(g *config.LadderExecutionGate) { g.TrancheStatus = nil }), nil, ladderGlobal(true, true), nil, "tranche status is unknown"},
{"tranche lookup DB error", nil, errors.New("connection reset"), ladderGlobal(true, true), nil, "tranche lookup failed"},
{"global config read error", openGate(), nil, nil, errors.New("connection reset"), "global config unreadable"},
{"global config nil", openGate(), nil, nil, nil, "global config unavailable"},
Expand All @@ -110,9 +115,15 @@ func TestLadderRecheck_RefusesBeforeProviderCall(t *testing.T) {
}

func TestLadderRecheck_AllowsWhenEverythingIsOn(t *testing.T) {
mgr, store, rec, exec := ladderHarness(t, ladderTrancheKey, openGate(), nil, ladderGlobal(true, true), nil)
runViaSQS(t, mgr, store, exec)
assert.Equal(t, 1, rec.count(), "an armed ladder execution must still buy exactly once")
for _, status := range []ladder.TrancheStatus{ladder.TrancheStatusFired, ladder.TrancheStatusScheduled} {
t.Run(string(status), func(t *testing.T) {
gate := openGate()
gate.TrancheStatus = ptr(string(status))
mgr, store, rec, exec := ladderHarness(t, ladderTrancheKey, gate, nil, ladderGlobal(true, true), nil)
runViaSQS(t, mgr, store, exec)
assert.Equal(t, 1, rec.count(), "an armed ladder execution on a %s tranche must still buy exactly once", status)
})
}
}

// Non-ladder keys must be untouched: the lookup returns no tranche for them,
Expand Down
Loading