Files
Redflag/aggregator-server/internal/database/stuck_command_retry_test.go
jpetree331 59ab7cbd5f test(concurrency): B-2 pre-fix tests for data integrity and concurrency bugs
Pre-fix test suite documenting 7 data integrity and concurrency
bugs. Tests FAIL where they assert correct post-fix behavior,
PASS where they document current buggy state.

Tests added:
- F-B2-1/8 HIGH: Registration not transactional (3 tests)
- F-B2-2 MEDIUM: Command delivery race condition (3 tests)
- F-B2-9 MEDIUM: Token renewal not transactional (2 tests)
- F-B2-4 MEDIUM: No rate limit on GetCommands (3 tests)
- F-B2-5 LOW: Jitter negates rapid mode (2 tests)
- F-B2-10 LOW: No max retry for stuck commands (2 tests)
- F-B2-7 MEDIUM: No exponential backoff on reconnection (2 tests)

Current state: 7 FAIL, 10 PASS. No A/B-1 regressions.
See docs/B2_PreFix_Tests.md for full inventory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 07:45:16 -04:00

125 lines
4.0 KiB
Go

package database_test
// stuck_command_retry_test.go — Pre-fix tests for stuck command retry limit.
//
// F-B2-10 LOW: No maximum retry count for stuck commands. A command that
// always causes the agent to crash will be re-delivered indefinitely.
//
// Run: cd aggregator-server && go test ./internal/database/... -v -run TestStuckCommand
import (
"os"
"path/filepath"
"strings"
"testing"
)
// ---------------------------------------------------------------------------
// Test 6.1 — Documents unlimited retry for stuck commands (F-B2-10)
//
// Category: PASS-NOW (documents the bug)
// ---------------------------------------------------------------------------
func TestStuckCommandHasNoMaxRetryCount(t *testing.T) {
// F-B2-10 LOW: No maximum retry count for stuck commands.
// A command that always causes the agent to crash will be
// delivered and re-delivered indefinitely via the stuck
// command re-delivery path.
// Check agent_commands schema for retry_count column
migrationsDir := filepath.Join("migrations")
files, err := os.ReadDir(migrationsDir)
if err != nil {
t.Fatalf("failed to read migrations directory: %v", err)
}
hasRetryCount := false
for _, f := range files {
if !strings.HasSuffix(f.Name(), ".up.sql") {
continue
}
content, err := os.ReadFile(filepath.Join(migrationsDir, f.Name()))
if err != nil {
continue
}
src := strings.ToLower(string(content))
if strings.Contains(src, "agent_commands") &&
(strings.Contains(src, "retry_count") || strings.Contains(src, "delivery_count") ||
strings.Contains(src, "attempt_count")) {
hasRetryCount = true
}
}
if hasRetryCount {
t.Error("[ERROR] [server] [database] F-B2-10 already fixed: retry_count column exists")
}
// Check GetStuckCommands specifically for a retry limit in its WHERE clause
cmdPath := filepath.Join("queries", "commands.go")
content, err := os.ReadFile(cmdPath)
if err != nil {
t.Logf("[WARNING] [server] [database] could not read commands.go: %v", err)
return
}
src := string(content)
// Find GetStuckCommands function and check its query
stuckIdx := strings.Index(src, "func (q *CommandQueries) GetStuckCommands")
if stuckIdx == -1 {
t.Log("[WARNING] [server] [database] GetStuckCommands function not found")
return
}
stuckBody := src[stuckIdx:]
if len(stuckBody) > 500 {
stuckBody = stuckBody[:500]
}
stuckLower := strings.ToLower(stuckBody)
hasRetryFilter := strings.Contains(stuckLower, "delivery_count <") ||
strings.Contains(stuckLower, "retry_count <") ||
strings.Contains(stuckLower, "max_retries")
if hasRetryFilter {
t.Error("[ERROR] [server] [database] F-B2-10 already fixed: retry limit in GetStuckCommands")
}
t.Log("[INFO] [server] [database] F-B2-10 confirmed: no retry count limit on stuck commands")
}
// ---------------------------------------------------------------------------
// Test 6.2 — Stuck commands must have max retry count (assert fix)
//
// Category: FAIL-NOW / PASS-AFTER-FIX
// ---------------------------------------------------------------------------
func TestStuckCommandHasMaxRetryCount(t *testing.T) {
// F-B2-10: After fix, add retry_count column and cap re-delivery
// at a maximum (e.g., 5 attempts).
migrationsDir := filepath.Join("migrations")
files, err := os.ReadDir(migrationsDir)
if err != nil {
t.Fatalf("failed to read migrations directory: %v", err)
}
hasRetryCount := false
for _, f := range files {
if !strings.HasSuffix(f.Name(), ".up.sql") {
continue
}
content, err := os.ReadFile(filepath.Join(migrationsDir, f.Name()))
if err != nil {
continue
}
src := strings.ToLower(string(content))
if strings.Contains(src, "agent_commands") &&
(strings.Contains(src, "retry_count") || strings.Contains(src, "delivery_count") ||
strings.Contains(src, "attempt_count")) {
hasRetryCount = true
}
}
if !hasRetryCount {
t.Errorf("[ERROR] [server] [database] no retry_count column on agent_commands.\n" +
"F-B2-10: add retry_count and cap re-delivery at max retries.")
}
}