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>
125 lines
4.0 KiB
Go
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.")
|
|
}
|
|
}
|