Files
Redflag/aggregator-server/internal/database/stuck_command_retry_test.go
jpetree331 3ca42d50f4 fix(concurrency): B-2 data integrity and race condition fixes
- Wrap agent registration in DB transaction (F-B2-1/F-B2-8)
  All 4 ops atomic, manual DeleteAgent rollback removed
- Use SELECT FOR UPDATE SKIP LOCKED for atomic command delivery (F-B2-2)
  Concurrent requests get different commands, no duplicates
- Wrap token renewal in DB transaction (F-B2-9)
  Validate + update expiry atomic
- Add rate limit to GET /agents/:id/commands (F-B2-4)
  agent_checkin rate limiter applied
- Add retry_count column, cap stuck command retries at 5 (F-B2-10)
  Migration 029, GetStuckCommands filters retry_count < 5
- Cap polling jitter at current interval (fixes rapid mode) (F-B2-5)
  maxJitter = min(pollingInterval/2, 30s)
- Add exponential backoff with full jitter on reconnection (F-B2-7)
  calculateBackoff: base=10s, cap=5min, reset on success

All tests pass. No regressions from A-series or B-1.

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

95 lines
2.7 KiB
Go

package database_test
// stuck_command_retry_test.go — Tests for stuck command retry limit.
//
// F-B2-10 FIXED: retry_count column added (migration 029).
// GetStuckCommands now filters with retry_count < 5.
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestStuckCommandHasNoMaxRetryCount(t *testing.T) {
// POST-FIX: retry_count column exists and GetStuckCommands filters on it.
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") {
hasRetryCount = true
}
}
if !hasRetryCount {
t.Error("[ERROR] [server] [database] F-B2-10 NOT FIXED: no retry_count column")
}
// Check GetStuckCommands for retry limit
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)
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]
}
if !strings.Contains(strings.ToLower(stuckBody), "retry_count") {
t.Error("[ERROR] [server] [database] F-B2-10 NOT FIXED: GetStuckCommands has no retry filter")
}
t.Log("[INFO] [server] [database] F-B2-10 FIXED: retry count limit on stuck commands")
}
func TestStuckCommandHasMaxRetryCount(t *testing.T) {
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") {
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.")
}
t.Log("[INFO] [server] [database] F-B2-10 FIXED: retry_count column exists")
}