/ tests / scripts / orchestrator-backlog.test.sh
orchestrator-backlog.test.sh
  1  #!/bin/sh
  2  # Tests for claude-orchestrator.sh should_skip_for_backlog() function
  3  #
  4  # Extracts the function and tests it in isolation against BACKLOG_* env vars.
  5  # These gates are the primary throughput control — they prevent upstream stages
  6  # from flooding downstream queues.
  7  
  8  PASS=0
  9  FAIL=0
 10  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
 11  PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
 12  ORCHESTRATOR="$PROJECT_ROOT/scripts/claude-orchestrator.sh"
 13  
 14  TMPDIR=$(mktemp -d)
 15  trap 'rm -rf "$TMPDIR"' EXIT
 16  
 17  # ── Helpers ───────────────────────────────────────────────────────────────────
 18  
 19  pass() { PASS=$((PASS + 1)); echo "  PASS: $1"; }
 20  fail() { FAIL=$((FAIL + 1)); echo "  FAIL: $1"; }
 21  
 22  assert_returns() {
 23    expected="$1"; actual="$2"; msg="$3"
 24    if [ "$actual" = "$expected" ]; then pass "$msg"; else fail "$msg (expected $expected, got $actual)"; fi
 25  }
 26  
 27  # ── Extract function ──────────────────────────────────────────────────────────
 28  
 29  cat > "$TMPDIR/func.sh" << 'FUNCEOF'
 30  #!/bin/sh
 31  log() { :; }
 32  FUNCEOF
 33  
 34  sed -n '/^should_skip_for_backlog()/,/^}/p' "$ORCHESTRATOR" >> "$TMPDIR/func.sh"
 35  . "$TMPDIR/func.sh"
 36  
 37  # Default batch sizes (same as orchestrator defaults)
 38  REWORD_EMAIL_BATCH=5
 39  REWORD_SMS_BATCH=10
 40  STAGE_THROTTLE_MULTIPLIER=3
 41  
 42  # ── Test 1: All backlogs at zero → no skips ──────────────────────────────────
 43  
 44  echo "Test 1: All backlogs at 0 → returns 1 (don't skip) for all types"
 45  BACKLOG_ELIGIBLE_OUTREACH=0
 46  BACKLOG_PROPOSALS=0
 47  BACKLOG_ENRICHED=0
 48  
 49  for batch_type in proposals_email proposals_sms reword_email reword_sms reword_form \
 50    reword_linkedin reword_x proofread enrich_sites score_semantic; do
 51    should_skip_for_backlog "$batch_type"; rc=$?
 52    assert_returns "1" "$rc" "Zero backlog: $batch_type returns 1"
 53  done
 54  
 55  # ── Test 2: Gate 1 — outreach queue saturated ────────────────────────────────
 56  
 57  echo "Test 2: Gate 1 — eligible_outreach > 3×max(reword_email,reword_sms) → skip upstream batches"
 58  # max(5,10) × 3 = 30 — threshold is 30. Values > 30 trigger gate.
 59  BACKLOG_ELIGIBLE_OUTREACH=31
 60  BACKLOG_PROPOSALS=0
 61  BACKLOG_ENRICHED=0
 62  
 63  for batch_type in reword_email reword_sms reword_form reword_linkedin reword_x \
 64    proofread proposals_email proposals_sms enrich_sites score_semantic; do
 65    should_skip_for_backlog "$batch_type"; rc=$?
 66    assert_returns "0" "$rc" "Gate 1 active: $batch_type should skip"
 67  done
 68  
 69  echo "Test 2b: Gate 1 at threshold (exactly 30) → returns 1 (threshold is >, not >=)"
 70  BACKLOG_ELIGIBLE_OUTREACH=30
 71  
 72  should_skip_for_backlog "proposals_email"; rc=$?
 73  assert_returns "1" "$rc" "Gate 1 at threshold: proposals_email returns 1 (not triggered)"
 74  
 75  echo "Test 2c: Gate 1 does not block time-critical tasks"
 76  BACKLOG_ELIGIBLE_OUTREACH=31
 77  
 78  for batch_type in reply_responses classify_replies extract_names oversee classify_errors code_review; do
 79    should_skip_for_backlog "$batch_type"; rc=$?
 80    assert_returns "1" "$rc" "Gate 1: $batch_type is not blocked"
 81  done
 82  
 83  # ── Test 3: Gate 2 — proposals queue saturated ───────────────────────────────
 84  
 85  echo "Test 3: Gate 2 — proposals_drafted > 3×(email+sms batch) → skip proposals and earlier"
 86  # (5+10) × 3 = 45. Values > 45 trigger gate.
 87  BACKLOG_ELIGIBLE_OUTREACH=0
 88  BACKLOG_PROPOSALS=46
 89  BACKLOG_ENRICHED=0
 90  
 91  for batch_type in proposals_email proposals_sms enrich_sites score_semantic; do
 92    should_skip_for_backlog "$batch_type"; rc=$?
 93    assert_returns "0" "$rc" "Gate 2 active: $batch_type should skip"
 94  done
 95  
 96  # reword_* are NOT gated by Gate 2 (only by Gate 1)
 97  for batch_type in reword_email reword_sms reword_form reword_linkedin reword_x proofread; do
 98    should_skip_for_backlog "$batch_type"; rc=$?
 99    assert_returns "1" "$rc" "Gate 2: $batch_type is NOT blocked by proposals backlog"
100  done
101  
102  # ── Test 4: Gate 3 — enriched queue saturated ────────────────────────────────
103  
104  echo "Test 4: Gate 3 — enriched > 3×enrich_batch → skip enrich and scoring"
105  BACKLOG_ELIGIBLE_OUTREACH=0
106  BACKLOG_PROPOSALS=0
107  ENRICH_SITES_BATCH=5
108  BACKLOG_ENRICHED=16  # > 5×3 = 15
109  
110  for batch_type in enrich_sites score_semantic; do
111    should_skip_for_backlog "$batch_type"; rc=$?
112    assert_returns "0" "$rc" "Gate 3 active: $batch_type should skip"
113  done
114  
115  # Proposals and reword not affected by Gate 3
116  for batch_type in proposals_email proposals_sms reword_email; do
117    should_skip_for_backlog "$batch_type"; rc=$?
118    assert_returns "1" "$rc" "Gate 3: $batch_type is NOT blocked by enriched backlog"
119  done
120  
121  # ── Test 5: Gate thresholds use STAGE_THROTTLE_MULTIPLIER ────────────────────
122  
123  echo "Test 5: STAGE_THROTTLE_MULTIPLIER=5 raises thresholds"
124  STAGE_THROTTLE_MULTIPLIER=5
125  REWORD_EMAIL_BATCH=5
126  REWORD_SMS_BATCH=10
127  BACKLOG_ELIGIBLE_OUTREACH=31  # > 3×10=30 but not > 5×10=50
128  BACKLOG_PROPOSALS=0
129  BACKLOG_ENRICHED=0
130  
131  # With multiplier=5, threshold is 10×5=50. eligible=31 < 50, so no skip.
132  should_skip_for_backlog "proposals_email"; rc=$?
133  assert_returns "1" "$rc" "Multiplier=5: threshold is 50, eligible=31 → no skip"
134  
135  # Reset multiplier
136  STAGE_THROTTLE_MULTIPLIER=3
137  
138  # ── Test 6: Unknown batch type → returns 1 ───────────────────────────────────
139  
140  echo "Test 6: Unknown batch type → returns 1 regardless of backlogs"
141  BACKLOG_ELIGIBLE_OUTREACH=9999
142  BACKLOG_PROPOSALS=9999
143  BACKLOG_ENRICHED=9999
144  
145  should_skip_for_backlog "unknown_batch_type"; rc=$?
146  assert_returns "1" "$rc" "Unknown batch type returns 1 (no gate applies)"
147  
148  should_skip_for_backlog "code_review"; rc=$?
149  assert_returns "1" "$rc" "code_review returns 1 (not in any gate)"
150  
151  # ── Summary ───────────────────────────────────────────────────────────────────
152  
153  echo ""
154  echo "Results: $PASS passed, $FAIL failed"
155  [ "$FAIL" -eq 0 ] || exit 1