0.29.0 — code-quality refactor (plan E1–E5): file splits, sqlc migration, SSH unification, test coverage
E1: split monolithic files — cmd/nomos (main.go → server.go + mcp.go + workers.go),
internal/mcp/tools.go → entity_tools/ops_tools/knowledge_tools/analysis_tools,
internal/httpapi/impl.go → domain files (entities, events, signals, ontology,
fleet_health, client_context, client_lifecycle, entity_mutations, query_audit).
E2: migrate raw pool.Exec queries to sqlc (entities/relationships queries + generated).
E3: unify SSH — consolidate crypto/ssh dial into actuator/client.go (+client_test).
E4/E5: add tests — db/lifecycle, checkdefaults/build, ontology/preconditions, policy/risk.
This commit is contained in:
196
internal/checkdefaults/build_test.go
Normal file
196
internal/checkdefaults/build_test.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package checkdefaults
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dtoro/oikos/internal/ontology"
|
||||
)
|
||||
|
||||
// Table-driven coverage of every implemented buildKind branch and the ssh()
|
||||
// helper's user/port/args propagation. The previous tests exercised only
|
||||
// ping/process/http/resource; updates, capacity, backup, cert-expiry,
|
||||
// vm-status and dns were unverified.
|
||||
func TestBuildKindAllImplementedKinds(t *testing.T) {
|
||||
host := "10.0.0.5"
|
||||
cases := []struct {
|
||||
name string
|
||||
kind string
|
||||
target Target
|
||||
attrs map[string]any
|
||||
host string
|
||||
wantSkip bool // true → expect a reason and zero defs
|
||||
wantDefs int
|
||||
wantKind string
|
||||
wantKey string // a config key to assert
|
||||
wantVal any // its expected value
|
||||
wantReason string // substring when skipping
|
||||
wantInterv int32 // expected interval on the (single) produced def
|
||||
}{
|
||||
{
|
||||
name: "ping with host", kind: KindPing, host: host,
|
||||
wantDefs: 1, wantKind: "ping", wantKey: "host", wantVal: host, wantInterv: 30,
|
||||
},
|
||||
{name: "ping no host skips", kind: KindPing, wantSkip: true, wantReason: "no address"},
|
||||
|
||||
{
|
||||
name: "resource expands to four ssh scripts", kind: KindResource, host: host,
|
||||
wantDefs: 4, wantKind: "ssh-script", wantKey: "host", wantVal: host, wantInterv: 60,
|
||||
},
|
||||
{name: "resource no host skips", kind: KindResource, wantSkip: true, wantReason: "no address"},
|
||||
|
||||
{
|
||||
name: "updates is daily", kind: KindUpdates, host: host,
|
||||
wantDefs: 1, wantKind: "ssh-script", wantKey: "script", wantVal: "updates_check.sh", wantInterv: 86400,
|
||||
},
|
||||
{name: "updates no host skips", kind: KindUpdates, wantSkip: true, wantReason: "no address"},
|
||||
|
||||
{
|
||||
name: "capacity is one disk script", kind: KindCapacity, host: host,
|
||||
wantDefs: 1, wantKind: "ssh-script", wantKey: "script", wantVal: "disk_usage_check.sh", wantInterv: 60,
|
||||
},
|
||||
{name: "capacity no host skips", kind: KindCapacity, wantSkip: true, wantReason: "no address"},
|
||||
|
||||
{
|
||||
name: "backup needs path and host", kind: KindBackup, host: host,
|
||||
attrs: map[string]any{"path": "/backups/db"},
|
||||
wantDefs: 1, wantKind: "backup-freshness", wantKey: "path", wantVal: "/backups/db", wantInterv: 86400,
|
||||
},
|
||||
{name: "backup without path skips", kind: KindBackup, host: host, wantSkip: true, wantReason: "no path"},
|
||||
{name: "backup without host skips", kind: KindBackup, attrs: map[string]any{"path": "/x"}, wantSkip: true, wantReason: "no address"},
|
||||
|
||||
{
|
||||
name: "backup honors backup_max_age_s override", kind: KindBackup, host: host,
|
||||
attrs: map[string]any{"path": "/x", "backup_max_age_s": float64(3600)},
|
||||
wantDefs: 1, wantKey: "max_age_s", wantVal: 3600,
|
||||
},
|
||||
|
||||
{
|
||||
name: "cert-expiry from hostname attr", kind: KindCertExpiry,
|
||||
attrs: map[string]any{"hostname": "media.hubris.network"},
|
||||
wantDefs: 1, wantKind: "cert-expiry", wantKey: "host", wantVal: "media.hubris.network", wantInterv: 3600,
|
||||
},
|
||||
{
|
||||
name: "cert-expiry from dotted name", kind: KindCertExpiry, target: Target{Name: "media.hubris.network"},
|
||||
wantDefs: 1, wantKey: "host", wantVal: "media.hubris.network",
|
||||
},
|
||||
{
|
||||
name: "cert-expiry propagates dial attr", kind: KindCertExpiry,
|
||||
attrs: map[string]any{"hostname": "media.hubris.network", "dial": "10.0.0.2"},
|
||||
wantDefs: 1, wantKey: "dial", wantVal: "10.0.0.2",
|
||||
},
|
||||
{name: "cert-expiry without a host name skips", kind: KindCertExpiry, target: Target{Name: "jellyfin"}, wantSkip: true, wantReason: "no hostname"},
|
||||
|
||||
{
|
||||
name: "vm-status needs pve_id", kind: KindVMStatus, attrs: map[string]any{"pve_id": float64(101)},
|
||||
wantDefs: 1, wantKind: "vm-status", wantInterv: 60,
|
||||
},
|
||||
{name: "vm-status without pve_id skips", kind: KindVMStatus, wantSkip: true, wantReason: "no pve_id"},
|
||||
|
||||
{
|
||||
name: "dns resolves entity name", kind: KindDNS, target: Target{Name: "hubris.network"},
|
||||
wantDefs: 1, wantKind: "dns", wantKey: "name", wantVal: "hubris.network", wantInterv: 300,
|
||||
},
|
||||
{name: "dns without a name skips", kind: KindDNS, target: Target{}, wantSkip: true, wantReason: "no name"},
|
||||
|
||||
{name: "unknown kind skips", kind: "telepathy", host: host, wantSkip: true, wantReason: "no builder"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
defs, reason := buildKind(c.kind, c.target, c.attrs, c.host, "root", 22)
|
||||
if c.wantSkip {
|
||||
if len(defs) != 0 {
|
||||
t.Fatalf("expected zero defs, got %d", len(defs))
|
||||
}
|
||||
if c.wantReason != "" && !strings.Contains(reason, c.wantReason) {
|
||||
t.Errorf("reason = %q, want substring %q", reason, c.wantReason)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(defs) != c.wantDefs {
|
||||
t.Fatalf("got %d defs (%s), want %d", len(defs), reason, c.wantDefs)
|
||||
}
|
||||
if reason != "" {
|
||||
t.Errorf("unexpected skip reason: %q", reason)
|
||||
}
|
||||
if c.wantKind != "" {
|
||||
if got := defs[0].kind; got != c.wantKind {
|
||||
t.Errorf("kind = %q, want %q", got, c.wantKind)
|
||||
}
|
||||
}
|
||||
if c.wantKey != "" {
|
||||
if got := defs[0].config[c.wantKey]; !reflect.DeepEqual(got, c.wantVal) {
|
||||
t.Errorf("config[%q] = %v (%T), want %v (%T)", c.wantKey, got, got, c.wantVal, c.wantVal)
|
||||
}
|
||||
}
|
||||
if c.wantInterv != 0 && defs[0].interval != c.wantInterv {
|
||||
t.Errorf("interval = %d, want %d", defs[0].interval, c.wantInterv)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ssh() must add user/port/args only when they differ from the root/22/empty
|
||||
// defaults, so generated configs stay minimal and stable across re-seeds.
|
||||
func TestBuildKindSSHOnlyEmitsNonDefaultUserPortArgs(t *testing.T) {
|
||||
t.Run("default root 22 omits user and port", func(t *testing.T) {
|
||||
defs, _ := buildKind(KindResource, Target{}, nil, "10.0.0.1", "root", 22)
|
||||
for _, d := range defs {
|
||||
if _, ok := d.config["user"]; ok {
|
||||
t.Errorf("root should not emit user: %v", d.config)
|
||||
}
|
||||
if _, ok := d.config["port"]; ok {
|
||||
t.Errorf("port 22 should not emit port: %v", d.config)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("non-root user and non-22 port are emitted", func(t *testing.T) {
|
||||
defs, _ := buildKind(KindResource, Target{}, nil, "10.0.0.1", "oikos", 2222)
|
||||
if defs[0].config["user"] != "oikos" {
|
||||
t.Errorf("user = %v, want oikos", defs[0].config["user"])
|
||||
}
|
||||
if defs[0].config["port"] != 2222 {
|
||||
t.Errorf("port = %v, want 2222", defs[0].config["port"])
|
||||
}
|
||||
})
|
||||
t.Run("process unit name lands in args", func(t *testing.T) {
|
||||
defs, _ := buildKind(KindProcess, Target{Name: "jellyfin"}, nil, "10.0.0.1", "root", 22)
|
||||
if defs[0].config["args"] != "jellyfin" {
|
||||
t.Errorf("args = %v, want jellyfin", defs[0].config["args"])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// resolveMonitoringAttr implements the entity-level `monitoring` override
|
||||
// (project decision health_checks.monitoring_override): "none"/"" opts out,
|
||||
// a kind-list replaces the type defaults, anything else falls back.
|
||||
func TestResolveMonitoringAttr(t *testing.T) {
|
||||
fallback := ontology.MonitoringResolution{Declared: true, Kinds: []string{"ping"}, Source: "type"}
|
||||
cases := []struct {
|
||||
name string
|
||||
in any
|
||||
want ontology.MonitoringResolution
|
||||
}{
|
||||
{"none opts out", "none", ontology.MonitoringResolution{Declared: true, Source: "attribute"}},
|
||||
{"empty opts out", "", ontology.MonitoringResolution{Declared: true, Source: "attribute"}},
|
||||
{
|
||||
"kind list overrides",
|
||||
[]any{"http", "process"},
|
||||
ontology.MonitoringResolution{Declared: true, Kinds: []string{"http", "process"}, Source: "attribute"},
|
||||
},
|
||||
{"list drops empty and non-string entries", []any{"http", "", 7, "dns"}, ontology.MonitoringResolution{Declared: true, Kinds: []string{"http", "dns"}, Source: "attribute"}},
|
||||
{"non-string scalar falls back to type default", float64(42), fallback},
|
||||
{"nil falls back", nil, fallback},
|
||||
{"unrecognized string falls back", "weird", fallback},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got := resolveMonitoringAttr(c.in, fallback)
|
||||
if !reflect.DeepEqual(got, c.want) {
|
||||
t.Errorf("resolveMonitoringAttr(%v) = %+v, want %+v", c.in, got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user