refactor(doctor): split doctor.go into modular package files (#653)

* refactor(doctor): split doctor.go into modular package files

Split the 3,171-line doctor.go into logical sub-files within the
cmd/bd/doctor/ package, reducing the main file to 834 lines (74% reduction).

New package structure:
- types.go: DoctorCheck struct and status constants
- installation.go: CheckInstallation, CheckMultipleDatabases, CheckPermissions
- git.go: CheckGitHooks, CheckMergeDriver, CheckSyncBranch* checks
- database.go: CheckDatabaseVersion, CheckSchemaCompatibility, CheckDatabaseJSONLSync
- version.go: CheckCLIVersion, CheckMetadataVersionTracking, CompareVersions
- integrity.go: CheckIDFormat, CheckDependencyCycles, CheckTombstones
- daemon.go: CheckDaemonStatus, CheckVersionMismatch
- quick.go: Quick checks for sync-branch and hooks

Updated tests to use exported doctor.CheckXxx() functions and
doctor.StatusXxx constants.

* fix(doctor): suppress gosec G204 false positives

Add #nosec G204 comments to exec.Command calls in CheckSyncBranchHealth
where variables come from trusted sources (config files or hardcoded
values like "main"/"master"/"origin"), not untrusted user input.
This commit is contained in:
Ryan
2025-12-19 17:29:36 -08:00
committed by GitHub
parent c84a2404b7
commit e9be35e374
11 changed files with 2834 additions and 2292 deletions

View File

@@ -10,6 +10,7 @@ import (
"strings"
"testing"
"github.com/steveyegge/beads/cmd/bd/doctor"
"github.com/steveyegge/beads/internal/git"
)
@@ -226,7 +227,7 @@ func TestDetectHashBasedIDs(t *testing.T) {
}
// Test detection
result := detectHashBasedIDs(db, tt.sampleIDs)
result := doctor.DetectHashBasedIDs(db, tt.sampleIDs)
if result != tt.expected {
t.Errorf("detectHashBasedIDs() = %v, want %v", result, tt.expected)
}
@@ -245,31 +246,31 @@ func TestCheckIDFormat(t *testing.T) {
name: "hash IDs with letters",
issueIDs: []string{"bd-a3f8e9", "bd-b2c4d6", "bd-xyz123"},
createTable: false,
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
},
{
name: "hash IDs all numeric with leading zeros",
issueIDs: []string{"bd-0088", "bd-02a4", "bd-05a1", "bd-0458"},
createTable: false,
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
},
{
name: "hash IDs with child_counters table",
issueIDs: []string{"bd-123", "bd-456"},
createTable: true,
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
},
{
name: "sequential IDs",
issueIDs: []string{"bd-1", "bd-2", "bd-3", "bd-4"},
createTable: false,
expectedStatus: statusWarning,
expectedStatus: doctor.StatusWarning,
},
{
name: "mixed: mostly hash IDs",
issueIDs: []string{"bd-0088", "bd-0134cc5a", "bd-02a4"},
createTable: false,
expectedStatus: statusOK, // Variable length = hash IDs
expectedStatus: doctor.StatusOK, // Variable length = hash IDs
},
}
@@ -326,19 +327,19 @@ func TestCheckIDFormat(t *testing.T) {
db.Close()
// Run check
check := checkIDFormat(tmpDir)
check := doctor.CheckIDFormat(tmpDir)
if check.Status != tt.expectedStatus {
t.Errorf("Expected status %s, got %s (message: %s)", tt.expectedStatus, check.Status, check.Message)
}
if tt.expectedStatus == statusOK && check.Status == statusOK {
if tt.expectedStatus == doctor.StatusOK && check.Status == doctor.StatusOK {
if !strings.Contains(check.Message, "hash-based") {
t.Errorf("Expected hash-based message, got: %s", check.Message)
}
}
if tt.expectedStatus == statusWarning && check.Status == statusWarning {
if tt.expectedStatus == doctor.StatusWarning && check.Status == doctor.StatusWarning {
if check.Fix == "" {
t.Error("Expected fix message for sequential IDs")
}
@@ -350,9 +351,9 @@ func TestCheckIDFormat(t *testing.T) {
func TestCheckInstallation(t *testing.T) {
// Test with missing .beads directory
tmpDir := t.TempDir()
check := checkInstallation(tmpDir)
check := doctor.CheckInstallation(tmpDir)
if check.Status != statusError {
if check.Status != doctor.StatusError {
t.Errorf("Expected error status, got %s", check.Status)
}
if check.Fix == "" {
@@ -365,8 +366,8 @@ func TestCheckInstallation(t *testing.T) {
t.Fatal(err)
}
check = checkInstallation(tmpDir)
if check.Status != statusOK {
check = doctor.CheckInstallation(tmpDir)
if check.Status != doctor.StatusOK {
t.Errorf("Expected ok status, got %s", check.Status)
}
}
@@ -392,9 +393,9 @@ func TestCheckDatabaseVersionJSONLMode(t *testing.T) {
t.Fatal(err)
}
check := checkDatabaseVersion(tmpDir)
check := doctor.CheckDatabaseVersion(tmpDir, Version)
if check.Status != statusOK {
if check.Status != doctor.StatusOK {
t.Errorf("Expected ok status for JSONL mode, got %s", check.Status)
}
if check.Message != "JSONL-only mode" {
@@ -420,9 +421,9 @@ func TestCheckDatabaseVersionFreshClone(t *testing.T) {
t.Fatal(err)
}
check := checkDatabaseVersion(tmpDir)
check := doctor.CheckDatabaseVersion(tmpDir, Version)
if check.Status != statusWarning {
if check.Status != doctor.StatusWarning {
t.Errorf("Expected warning status for fresh clone, got %s", check.Status)
}
if check.Message != "Fresh clone detected (no database)" {
@@ -450,9 +451,9 @@ func TestCompareVersions(t *testing.T) {
}
for _, tc := range tests {
result := compareVersions(tc.v1, tc.v2)
result := doctor.CompareVersions(tc.v1, tc.v2)
if result != tc.expected {
t.Errorf("compareVersions(%q, %q) = %d, expected %d", tc.v1, tc.v2, result, tc.expected)
t.Errorf("doctor.CompareVersions(%q, %q) = %d, expected %d", tc.v1, tc.v2, result, tc.expected)
}
}
}
@@ -467,31 +468,31 @@ func TestCheckMultipleDatabases(t *testing.T) {
{
name: "no databases",
dbFiles: []string{},
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
expectWarning: false,
},
{
name: "single database",
dbFiles: []string{"beads.db"},
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
expectWarning: false,
},
{
name: "multiple databases",
dbFiles: []string{"beads.db", "old.db"},
expectedStatus: statusWarning,
expectedStatus: doctor.StatusWarning,
expectWarning: true,
},
{
name: "backup files ignored",
dbFiles: []string{"beads.db", "beads.backup.db"},
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
expectWarning: false,
},
{
name: "vc.db ignored",
dbFiles: []string{"beads.db", "vc.db"},
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
expectWarning: false,
},
}
@@ -512,7 +513,7 @@ func TestCheckMultipleDatabases(t *testing.T) {
}
}
check := checkMultipleDatabases(tmpDir)
check := doctor.CheckMultipleDatabases(tmpDir)
if check.Status != tc.expectedStatus {
t.Errorf("Expected status %s, got %s", tc.expectedStatus, check.Status)
@@ -532,9 +533,9 @@ func TestCheckPermissions(t *testing.T) {
t.Fatal(err)
}
check := checkPermissions(tmpDir)
check := doctor.CheckPermissions(tmpDir)
if check.Status != statusOK {
if check.Status != doctor.StatusOK {
t.Errorf("Expected ok status for writable directory, got %s: %s", check.Status, check.Message)
}
}
@@ -550,13 +551,13 @@ func TestCheckDatabaseJSONLSync(t *testing.T) {
name: "no database",
hasDB: false,
hasJSONL: true,
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
},
{
name: "no JSONL",
hasDB: true,
hasJSONL: false,
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
},
}
@@ -588,7 +589,7 @@ func TestCheckDatabaseJSONLSync(t *testing.T) {
}
}
check := checkDatabaseJSONLSync(tmpDir)
check := doctor.CheckDatabaseJSONLSync(tmpDir)
if check.Status != tc.expectedStatus {
t.Errorf("Expected status %s, got %s", tc.expectedStatus, check.Status)
@@ -617,7 +618,7 @@ invalid json line here
t.Fatal(err)
}
count, prefixes, err := countJSONLIssues(jsonlPath)
count, prefixes, err := doctor.CountJSONLIssues(jsonlPath)
// Should count valid issues (3)
if count != 3 {
@@ -649,35 +650,35 @@ func TestCheckGitHooks(t *testing.T) {
name: "not a git repository",
hasGitDir: false,
installedHooks: []string{},
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
expectWarning: false,
},
{
name: "all hooks installed",
hasGitDir: true,
installedHooks: []string{"pre-commit", "post-merge", "pre-push"},
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
expectWarning: false,
},
{
name: "no hooks installed",
hasGitDir: true,
installedHooks: []string{},
expectedStatus: statusWarning,
expectedStatus: doctor.StatusWarning,
expectWarning: true,
},
{
name: "some hooks installed",
hasGitDir: true,
installedHooks: []string{"pre-commit"},
expectedStatus: statusWarning,
expectedStatus: doctor.StatusWarning,
expectWarning: true,
},
{
name: "partial hooks installed",
hasGitDir: true,
installedHooks: []string{"pre-commit", "post-merge"},
expectedStatus: statusWarning,
expectedStatus: doctor.StatusWarning,
expectWarning: true,
},
}
@@ -724,7 +725,7 @@ func TestCheckGitHooks(t *testing.T) {
}
}
check := checkGitHooks()
check := doctor.CheckGitHooks()
if check.Status != tc.expectedStatus {
t.Errorf("Expected status %s, got %s", tc.expectedStatus, check.Status)
@@ -741,6 +742,140 @@ func TestCheckGitHooks(t *testing.T) {
}
}
func TestCheckClaudePlugin(t *testing.T) {
tests := []struct {
name string
claudeCodeEnv string
expectedStatus string
expectedMsg string
}{
{
name: "not running in claude code",
claudeCodeEnv: "",
expectedStatus: doctor.StatusOK,
expectedMsg: "N/A (not running in Claude Code)",
},
{
name: "not running in claude code (0)",
claudeCodeEnv: "0",
expectedStatus: doctor.StatusOK,
expectedMsg: "N/A (not running in Claude Code)",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Save original env
origEnv := os.Getenv("CLAUDECODE")
defer func() {
if origEnv == "" {
os.Unsetenv("CLAUDECODE")
} else {
os.Setenv("CLAUDECODE", origEnv)
}
}()
// Set test env
if tc.claudeCodeEnv == "" {
os.Unsetenv("CLAUDECODE")
} else {
os.Setenv("CLAUDECODE", tc.claudeCodeEnv)
}
check := doctor.CheckClaudePlugin()
if check.Status != tc.expectedStatus {
t.Errorf("Expected status %s, got %s", tc.expectedStatus, check.Status)
}
if check.Message != tc.expectedMsg {
t.Errorf("Expected message %q, got %q", tc.expectedMsg, check.Message)
}
})
}
}
func TestGetClaudePluginVersion(t *testing.T) {
tests := []struct {
name string
pluginJSON string
expectInstalled bool
expectVersion string
expectError bool
}{
{
name: "plugin installed",
pluginJSON: `{
"version": 1,
"plugins": {
"beads@beads-marketplace": {
"version": "0.21.3"
}
}
}`,
expectInstalled: true,
expectVersion: "0.21.3",
expectError: false,
},
{
name: "plugin not installed",
pluginJSON: `{
"version": 1,
"plugins": {
"other-plugin@marketplace": {
"version": "1.0.0"
}
}
}`,
expectInstalled: false,
expectVersion: "",
expectError: false,
},
{
name: "invalid json",
pluginJSON: `{invalid json`,
expectInstalled: false,
expectVersion: "",
expectError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Create temp dir with plugin file
tmpHome := t.TempDir()
pluginDir := filepath.Join(tmpHome, ".claude", "plugins")
if err := os.MkdirAll(pluginDir, 0750); err != nil {
t.Fatal(err)
}
pluginPath := filepath.Join(pluginDir, "installed_plugins.json")
if err := os.WriteFile(pluginPath, []byte(tc.pluginJSON), 0600); err != nil {
t.Fatal(err)
}
// Temporarily override home directory
origHome := os.Getenv("HOME")
os.Setenv("HOME", tmpHome)
defer os.Setenv("HOME", origHome)
version, installed, err := doctor.GetClaudePluginVersion()
if tc.expectError && err == nil {
t.Error("Expected error, got nil")
}
if !tc.expectError && err != nil {
t.Errorf("Unexpected error: %v", err)
}
if installed != tc.expectInstalled {
t.Errorf("Expected installed=%v, got %v", tc.expectInstalled, installed)
}
if version != tc.expectVersion {
t.Errorf("Expected version %q, got %q", tc.expectVersion, version)
}
})
}
}
func TestCheckMetadataVersionTracking(t *testing.T) {
tests := []struct {
name string
@@ -758,7 +893,7 @@ func TestCheckMetadataVersionTracking(t *testing.T) {
data, _ := json.Marshal(cfg)
return os.WriteFile(filepath.Join(beadsDir, "metadata.json"), data, 0644)
},
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
expectWarning: false,
},
{
@@ -771,7 +906,7 @@ func TestCheckMetadataVersionTracking(t *testing.T) {
data, _ := json.Marshal(cfg)
return os.WriteFile(filepath.Join(beadsDir, "metadata.json"), data, 0644)
},
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
expectWarning: false,
},
{
@@ -784,7 +919,7 @@ func TestCheckMetadataVersionTracking(t *testing.T) {
data, _ := json.Marshal(cfg)
return os.WriteFile(filepath.Join(beadsDir, "metadata.json"), data, 0644)
},
expectedStatus: statusWarning,
expectedStatus: doctor.StatusWarning,
expectWarning: true,
},
{
@@ -797,7 +932,7 @@ func TestCheckMetadataVersionTracking(t *testing.T) {
data, _ := json.Marshal(cfg)
return os.WriteFile(filepath.Join(beadsDir, "metadata.json"), data, 0644)
},
expectedStatus: statusWarning,
expectedStatus: doctor.StatusWarning,
expectWarning: true,
},
{
@@ -810,7 +945,7 @@ func TestCheckMetadataVersionTracking(t *testing.T) {
data, _ := json.Marshal(cfg)
return os.WriteFile(filepath.Join(beadsDir, "metadata.json"), data, 0644)
},
expectedStatus: statusWarning,
expectedStatus: doctor.StatusWarning,
expectWarning: true,
},
{
@@ -818,7 +953,7 @@ func TestCheckMetadataVersionTracking(t *testing.T) {
setupMetadata: func(beadsDir string) error {
return os.WriteFile(filepath.Join(beadsDir, "metadata.json"), []byte("{invalid json}"), 0644)
},
expectedStatus: statusError,
expectedStatus: doctor.StatusError,
expectWarning: false,
},
{
@@ -827,7 +962,7 @@ func TestCheckMetadataVersionTracking(t *testing.T) {
// Don't create metadata.json
return nil
},
expectedStatus: statusWarning,
expectedStatus: doctor.StatusWarning,
expectWarning: true,
},
}
@@ -845,13 +980,13 @@ func TestCheckMetadataVersionTracking(t *testing.T) {
t.Fatal(err)
}
check := checkMetadataVersionTracking(tmpDir)
check := doctor.CheckMetadataVersionTracking(tmpDir, Version)
if check.Status != tc.expectedStatus {
t.Errorf("Expected status %s, got %s (message: %s)", tc.expectedStatus, check.Status, check.Message)
}
if tc.expectWarning && check.Status == statusWarning && check.Fix == "" {
if tc.expectWarning && check.Status == doctor.StatusWarning && check.Fix == "" {
t.Error("Expected fix message for warning status")
}
})
@@ -874,9 +1009,9 @@ func TestIsValidSemver(t *testing.T) {
}
for _, tc := range tests {
result := isValidSemver(tc.version)
result := doctor.IsValidSemver(tc.version)
if result != tc.expected {
t.Errorf("isValidSemver(%q) = %v, expected %v", tc.version, result, tc.expected)
t.Errorf("doctor.IsValidSemver(%q) = %v, expected %v", tc.version, result, tc.expected)
}
}
}
@@ -896,14 +1031,14 @@ func TestParseVersionParts(t *testing.T) {
}
for _, tc := range tests {
result := parseVersionParts(tc.version)
result := doctor.ParseVersionParts(tc.version)
if len(result) != len(tc.expected) {
t.Errorf("parseVersionParts(%q) returned %d parts, expected %d", tc.version, len(result), len(tc.expected))
t.Errorf("doctor.ParseVersionParts(%q) returned %d parts, expected %d", tc.version, len(result), len(tc.expected))
continue
}
for i := range result {
if result[i] != tc.expected[i] {
t.Errorf("parseVersionParts(%q)[%d] = %d, expected %d", tc.version, i, result[i], tc.expected[i])
t.Errorf("doctor.ParseVersionParts(%q)[%d] = %d, expected %d", tc.version, i, result[i], tc.expected[i])
}
}
}
@@ -921,7 +1056,7 @@ func TestCheckSyncBranchConfig(t *testing.T) {
setupFunc: func(t *testing.T, tmpDir string) {
// No .beads directory
},
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
expectWarning: false,
},
{
@@ -932,7 +1067,7 @@ func TestCheckSyncBranchConfig(t *testing.T) {
t.Fatal(err)
}
},
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
expectWarning: false,
},
{
@@ -954,7 +1089,7 @@ func TestCheckSyncBranchConfig(t *testing.T) {
// Set env var (simulates config.yaml or BEADS_SYNC_BRANCH)
t.Setenv("BEADS_SYNC_BRANCH", "beads-sync")
},
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
expectWarning: false,
},
// Note: Tests for "not configured" scenarios are difficult because viper
@@ -968,7 +1103,7 @@ func TestCheckSyncBranchConfig(t *testing.T) {
tmpDir := t.TempDir()
tc.setupFunc(t, tmpDir)
result := checkSyncBranchConfig(tmpDir)
result := doctor.CheckSyncBranchConfig(tmpDir)
if result.Status != tc.expectedStatus {
t.Errorf("Expected status %q, got %q", tc.expectedStatus, result.Status)
@@ -1110,49 +1245,49 @@ func TestCheckSyncBranchHookCompatibility(t *testing.T) {
syncBranchEnv: "beads-sync",
hasGitDir: false,
hookVersion: "",
expectedStatus: statusOK, // N/A case
expectedStatus: doctor.StatusOK, // N/A case
},
{
name: "sync-branch configured, no pre-push hook",
syncBranchEnv: "beads-sync",
hasGitDir: true,
hookVersion: "",
expectedStatus: statusOK, // Covered by other check
expectedStatus: doctor.StatusOK, // Covered by other check
},
{
name: "sync-branch configured, custom hook",
syncBranchEnv: "beads-sync",
hasGitDir: true,
hookVersion: "custom",
expectedStatus: statusWarning,
expectedStatus: doctor.StatusWarning,
},
{
name: "sync-branch configured, old hook (0.24.2)",
syncBranchEnv: "beads-sync",
hasGitDir: true,
hookVersion: "0.24.2",
expectedStatus: statusError,
expectedStatus: doctor.StatusError,
},
{
name: "sync-branch configured, old hook (0.28.0)",
syncBranchEnv: "beads-sync",
hasGitDir: true,
hookVersion: "0.28.0",
expectedStatus: statusError,
expectedStatus: doctor.StatusError,
},
{
name: "sync-branch configured, compatible hook (0.29.0)",
syncBranchEnv: "beads-sync",
hasGitDir: true,
hookVersion: "0.29.0",
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
},
{
name: "sync-branch configured, newer hook (0.30.0)",
syncBranchEnv: "beads-sync",
hasGitDir: true,
hookVersion: "0.30.0",
expectedStatus: statusOK,
expectedStatus: doctor.StatusOK,
},
}
@@ -1175,9 +1310,6 @@ func TestCheckSyncBranchHookCompatibility(t *testing.T) {
// Create pre-push hook if specified
if tc.hookVersion != "" {
hooksDir := filepath.Join(tmpDir, ".git", "hooks")
if err := os.MkdirAll(hooksDir, 0755); err != nil {
t.Fatal(err)
}
hookPath := filepath.Join(hooksDir, "pre-push")
var hookContent string
if tc.hookVersion == "custom" {
@@ -1191,14 +1323,14 @@ func TestCheckSyncBranchHookCompatibility(t *testing.T) {
}
}
check := checkSyncBranchHookCompatibility(tmpDir)
check := doctor.CheckSyncBranchHookCompatibility(tmpDir)
if check.Status != tc.expectedStatus {
t.Errorf("Expected status %s, got %s (message: %s)", tc.expectedStatus, check.Status, check.Message)
}
// Error case should have a fix message
if tc.expectedStatus == statusError && check.Fix == "" {
if tc.expectedStatus == doctor.StatusError && check.Fix == "" {
t.Error("Expected fix message for error status")
}
})
@@ -1249,9 +1381,6 @@ func TestCheckSyncBranchHookQuick(t *testing.T) {
if tc.hookVersion != "" {
hooksDir := filepath.Join(tmpDir, ".git", "hooks")
if err := os.MkdirAll(hooksDir, 0755); err != nil {
t.Fatal(err)
}
hookPath := filepath.Join(hooksDir, "pre-push")
hookContent := fmt.Sprintf("#!/bin/sh\n# bd-hooks-version: %s\nexit 0\n", tc.hookVersion)
if err := os.WriteFile(hookPath, []byte(hookContent), 0755); err != nil {
@@ -1260,7 +1389,7 @@ func TestCheckSyncBranchHookQuick(t *testing.T) {
}
}
issue := checkSyncBranchHookQuick(tmpDir)
issue := doctor.CheckSyncBranchHookQuick(tmpDir)
if tc.expectIssue && issue == "" {
t.Error("Expected issue to be reported, got empty string")
@@ -1271,62 +1400,3 @@ func TestCheckSyncBranchHookQuick(t *testing.T) {
})
}
}
// TestExtractManualFix tests the extractManualFix function (GH#403)
func TestExtractManualFix(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "manually pattern with git config",
input: "Run 'bd doctor --fix' to update to correct config, or manually: git config merge.beads.driver \"bd merge %A %O %A %B\"",
expected: "git config merge.beads.driver \"bd merge %A %O %A %B\"",
},
{
name: "manually pattern with newline",
input: "Run 'bd doctor --fix' to auto-migrate, or manually:\nbd init && bd import",
expected: "bd init && bd import",
},
{
name: "or alternative after bd doctor --fix",
input: "Run 'bd doctor --fix' or bd init",
expected: "bd init",
},
{
name: "or alternative before bd doctor --fix",
input: "Run: bd init (safe to re-run) or bd doctor --fix",
expected: "bd init (safe to re-run)",
},
{
name: "just bd doctor --fix",
input: "Run 'bd doctor --fix'",
expected: "",
},
{
name: "no bd doctor --fix at all",
input: "Run 'bd init' to initialize the database",
expected: "Run 'bd init' to initialize the database",
},
{
name: "empty input",
input: "",
expected: "",
},
{
name: "update configuration suggestion",
input: "Run 'bd doctor --fix' to update the configuration",
expected: "",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := extractManualFix(tc.input)
if result != tc.expected {
t.Errorf("extractManualFix(%q) = %q, expected %q", tc.input, result, tc.expected)
}
})
}
}