Files
fn_registry/functions/infra/services_status_test.go
T
egutierrez 625569485f feat(doctor): add fn doctor CLI + 14 functions for system management
Adds `fn doctor` read-only diagnostic command with subcommands artefacts,
services, sync, uses-functions, unused, and --json flag for agents.
Each subcommand wraps a registry function in functions/infra/.

New functions:
- artefact_doctor, services_status, pc_locations_drift,
  audit_uses_functions, find_unused_functions (Go diagnostics)
- backup_sqlite_db, rotate_backups, wait_for_http, wait_for_port,
  port_kill, tail_journal, pre_commit_hook_install (bash utilities)
- notify_telegram (Go HTTP)
- backup_all pipeline (tag launcher)

Plus prior session leftovers (scan_secrets_in_dirty, append_diary_entry,
git utilities, http_session_cookie_middleware, compile/full-git pipelines).

Fixes pc_locations_drift filepath.Join bug with absolute dir_path.
Documents fn doctor in CLAUDE.md, .claude/rules/fn_doctor.md (rule 23),
docs/architecture.md, CHANGELOG.md (2026-05-07), and diary entry.

First fn doctor uses-functions run found drift in 7/12 apps (deuda
para sincronizar app.md con imports reales).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 01:42:10 +02:00

91 lines
2.2 KiB
Go

package infra
import (
"database/sql"
"os"
"path/filepath"
"testing"
_ "github.com/mattn/go-sqlite3"
)
// NOTE: readFnPC is defined in pc_locations_drift.go with signature (string, error).
// createMinimalRegistry creates a temporary registry.db with the apps table
// but no rows, for testing ServicesStatus with an empty dataset.
func createMinimalRegistry(t *testing.T) string {
t.Helper()
dir := t.TempDir()
dbPath := filepath.Join(dir, "registry.db")
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
t.Fatalf("open temp db: %v", err)
}
_, err = db.Exec(`CREATE TABLE apps (id TEXT, name TEXT, tags TEXT, notes TEXT, description TEXT)`)
if err != nil {
t.Fatalf("create table: %v", err)
}
db.Close()
return dir
}
func TestServicesStatus_NoApps(t *testing.T) {
root := createMinimalRegistry(t)
got, err := ServicesStatus(root)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(got) != 0 {
t.Errorf("expected empty slice, got %d entries", len(got))
}
}
func TestServicesStatus_BadDB(t *testing.T) {
_, err := ServicesStatus("/nonexistent/path")
if err == nil {
t.Error("expected error for missing db, got nil")
}
}
func TestServicesStatus_ParseFirstPort(t *testing.T) {
cases := []struct {
text string
want int
}{
{"listens on port 9090 for requests", 9090},
{"API available at :8080", 8080},
{"no port mentioned", 0},
{"low port 80 and high 9000", 9000}, // 80 < 1024, skip; 9000 ok
{"port 65535 max", 65535},
}
for _, c := range cases {
got := parseFirstPort(c.text)
if got != c.want {
t.Errorf("parseFirstPort(%q) = %d, want %d", c.text, got, c.want)
}
}
}
func TestServicesStatus_ReadFnPC(t *testing.T) {
dir := t.TempDir()
pcFile := filepath.Join(dir, ".fn_pc")
// readFnPC returns the first non-empty line (no comment stripping)
if err := os.WriteFile(pcFile, []byte("home-wsl\n"), 0o644); err != nil {
t.Fatal(err)
}
// Temporarily override HOME so readFnPC picks up our file
old := os.Getenv("HOME")
t.Cleanup(func() { os.Setenv("HOME", old) })
os.Setenv("HOME", dir)
got, err2 := readFnPC()
if err2 != nil {
t.Fatalf("readFnPC error: %v", err2)
}
if got != "home-wsl" {
t.Errorf("readFnPC() = %q, want %q", got, "home-wsl")
}
}