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