Stabilize the node agent runtime loop.

Keep the NAS-side runtime bounded to the configured export path,
make WebDAV and registration behavior env-driven, and add runtime
coverage so the first storage loop can be verified locally.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Harivansh Rathi 2026-04-01 13:58:24 +00:00
parent a7f85f4871
commit 273af4b0ab
14 changed files with 3294 additions and 36 deletions

View file

@ -0,0 +1,404 @@
package nodeagent
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestLoadConfigResolvesRelativeExportPathFromWorkspaceRoot(t *testing.T) {
t.Parallel()
repoRoot := t.TempDir()
agentDir := filepath.Join(repoRoot, "apps", "node-agent")
if err := os.MkdirAll(agentDir, 0o755); err != nil {
t.Fatalf("create agent dir: %v", err)
}
if err := os.WriteFile(filepath.Join(repoRoot, "pnpm-workspace.yaml"), []byte("packages:\n - apps/*\n"), 0o644); err != nil {
t.Fatalf("write workspace file: %v", err)
}
cfg, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
"BETTERNAS_NODE_MACHINE_ID": "nas-machine-id",
"BETTERNAS_EXPORT_TAGS": "finder, photos, finder",
"BETTERNAS_NODE_REGISTER_ENABLED": "true",
"BETTERNAS_NODE_HEARTBEAT_ENABLED": "true",
"BETTERNAS_CONTROL_PLANE_URL": "http://127.0.0.1:8081/",
"BETTERNAS_CONTROL_PLANE_AUTH_TOKEN": "node-auth-token",
"BETTERNAS_NODE_HEARTBEAT_INTERVAL": "45s",
}),
agentDir,
"nas-box",
)
if err != nil {
t.Fatalf("load config: %v", err)
}
expectedExportPath := filepath.Join(repoRoot, ".state", "nas", "export")
if cfg.ExportPath != expectedExportPath {
t.Fatalf("export path = %q, want %q", cfg.ExportPath, expectedExportPath)
}
if cfg.ListenAddress != defaultListenAddress(defaultPort) {
t.Fatalf("listen address = %q, want %q", cfg.ListenAddress, defaultListenAddress(defaultPort))
}
if cfg.MachineID != "nas-machine-id" {
t.Fatalf("machine id = %q, want nas-machine-id", cfg.MachineID)
}
if cfg.DisplayName != "nas-machine-id" {
t.Fatalf("display name = %q, want nas-machine-id", cfg.DisplayName)
}
if cfg.DirectAddress != "http://localhost:8090" {
t.Fatalf("direct address = %q, want loopback default", cfg.DirectAddress)
}
if cfg.ExportLabel != "export" {
t.Fatalf("export label = %q, want export", cfg.ExportLabel)
}
if len(cfg.ExportTags) != 2 || cfg.ExportTags[0] != "finder" || cfg.ExportTags[1] != "photos" {
t.Fatalf("export tags = %#v, want [finder photos]", cfg.ExportTags)
}
if !cfg.RegisterEnabled {
t.Fatalf("register enabled = false, want true")
}
if !cfg.HeartbeatEnabled {
t.Fatalf("heartbeat enabled = false, want true")
}
if cfg.HeartbeatInterval != 45*time.Second {
t.Fatalf("heartbeat interval = %s, want 45s", cfg.HeartbeatInterval)
}
if cfg.ControlPlaneURL != "http://127.0.0.1:8081" {
t.Fatalf("control plane url = %q, want trimmed url", cfg.ControlPlaneURL)
}
if cfg.ControlPlaneToken != "node-auth-token" {
t.Fatalf("control plane token = %q, want node-auth-token", cfg.ControlPlaneToken)
}
}
func TestLoadConfigDefaultsRegistrationToDisabled(t *testing.T) {
t.Parallel()
cfg, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
}),
t.TempDir(),
"nas-box",
)
if err != nil {
t.Fatalf("load config: %v", err)
}
if cfg.RegisterEnabled {
t.Fatal("register enabled = true, want false")
}
if cfg.HeartbeatEnabled {
t.Fatal("heartbeat enabled = true, want false")
}
if cfg.ControlPlaneURL != "" {
t.Fatalf("control plane url = %q, want empty", cfg.ControlPlaneURL)
}
if cfg.MachineID != "nas-box" {
t.Fatalf("machine id = %q, want nas-box", cfg.MachineID)
}
if cfg.ListenAddress != defaultListenAddress(defaultPort) {
t.Fatalf("listen address = %q, want %q", cfg.ListenAddress, defaultListenAddress(defaultPort))
}
}
func TestLoadConfigDefaultsHeartbeatToDisabledEvenWhenRegistrationEnabled(t *testing.T) {
t.Parallel()
cfg, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
"BETTERNAS_NODE_MACHINE_ID": "nas-machine-id",
"BETTERNAS_NODE_REGISTER_ENABLED": "true",
"BETTERNAS_CONTROL_PLANE_URL": "http://127.0.0.1:8081",
}),
t.TempDir(),
"nas-box",
)
if err != nil {
t.Fatalf("load config: %v", err)
}
if !cfg.RegisterEnabled {
t.Fatal("register enabled = false, want true")
}
if cfg.HeartbeatEnabled {
t.Fatal("heartbeat enabled = true, want false")
}
}
func TestLoadConfigRejectsHeartbeatWithoutRegistration(t *testing.T) {
t.Parallel()
_, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
"BETTERNAS_NODE_HEARTBEAT_ENABLED": "true",
}),
t.TempDir(),
"nas-box",
)
if err == nil {
t.Fatal("expected heartbeat-only config to fail")
}
}
func TestLoadConfigRequiresExportPath(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
values map[string]string
}{
{
name: "missing",
values: map[string]string{},
},
{
name: "blank",
values: map[string]string{
exportPathEnvKey: " ",
},
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
_, err := loadConfig(mapLookup(testCase.values), t.TempDir(), "nas-box")
if err == nil {
t.Fatal("expected missing export path to fail")
}
if !strings.Contains(err.Error(), exportPathEnvKey) {
t.Fatalf("error = %q, want %q", err.Error(), exportPathEnvKey)
}
})
}
}
func TestLoadConfigDefaultsListenAddressToLoopback(t *testing.T) {
t.Parallel()
cfg, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
"PORT": "9100",
}),
t.TempDir(),
"nas-box",
)
if err != nil {
t.Fatalf("load config: %v", err)
}
if cfg.ListenAddress != "127.0.0.1:9100" {
t.Fatalf("listen address = %q, want 127.0.0.1:9100", cfg.ListenAddress)
}
if cfg.DirectAddress != "http://localhost:9100" {
t.Fatalf("direct address = %q, want http://localhost:9100", cfg.DirectAddress)
}
}
func TestLoadConfigUsesExplicitWildcardListenAddress(t *testing.T) {
t.Parallel()
cfg, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
listenAddressEnvKey: ":9090",
}),
t.TempDir(),
"nas-box",
)
if err != nil {
t.Fatalf("load config: %v", err)
}
if cfg.ListenAddress != ":9090" {
t.Fatalf("listen address = %q, want :9090", cfg.ListenAddress)
}
if cfg.DirectAddress != "" {
t.Fatalf("direct address = %q, want empty for wildcard listener", cfg.DirectAddress)
}
}
func TestLoadConfigDerivesDirectAddressFromExplicitHostListenAddress(t *testing.T) {
t.Parallel()
cfg, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
listenAddressEnvKey: "192.0.2.10:9443",
}),
t.TempDir(),
"nas-box",
)
if err != nil {
t.Fatalf("load config: %v", err)
}
if cfg.DirectAddress != "http://192.0.2.10:9443" {
t.Fatalf("direct address = %q, want http://192.0.2.10:9443", cfg.DirectAddress)
}
}
func TestLoadConfigDoesNotDeriveDirectAddressFromWildcardHostListenAddress(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
listenAddress string
}{
{
name: "ipv4 wildcard",
listenAddress: "0.0.0.0:9443",
},
{
name: "ipv6 wildcard",
listenAddress: "[::]:9443",
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
cfg, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
listenAddressEnvKey: testCase.listenAddress,
}),
t.TempDir(),
"nas-box",
)
if err != nil {
t.Fatalf("load config: %v", err)
}
if cfg.DirectAddress != "" {
t.Fatalf("direct address = %q, want empty for %q", cfg.DirectAddress, testCase.listenAddress)
}
})
}
}
func TestLoadConfigRejectsInvalidListenAddress(t *testing.T) {
t.Parallel()
_, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
listenAddressEnvKey: "localhost",
}),
t.TempDir(),
"nas-box",
)
if err == nil {
t.Fatal("expected invalid listen address to fail")
}
if !strings.Contains(err.Error(), listenAddressEnvKey) {
t.Fatalf("error = %q, want %q", err.Error(), listenAddressEnvKey)
}
}
func TestLoadConfigAllowsRegistrationWithoutControlPlaneToken(t *testing.T) {
t.Parallel()
cfg, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
"BETTERNAS_NODE_MACHINE_ID": "nas-machine-id",
"BETTERNAS_NODE_REGISTER_ENABLED": "true",
"BETTERNAS_CONTROL_PLANE_URL": "http://127.0.0.1:8081",
}),
t.TempDir(),
"nas-box",
)
if err != nil {
t.Fatalf("load config: %v", err)
}
if cfg.ControlPlaneToken != "" {
t.Fatalf("control-plane token = %q, want empty", cfg.ControlPlaneToken)
}
}
func TestLoadConfigRejectsRegistrationWithoutMachineID(t *testing.T) {
t.Parallel()
_, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
"BETTERNAS_NODE_REGISTER_ENABLED": "true",
"BETTERNAS_CONTROL_PLANE_URL": "http://127.0.0.1:8081",
}),
t.TempDir(),
"nas-box",
)
if err == nil {
t.Fatal("expected missing machine id to fail")
}
if !strings.Contains(err.Error(), "BETTERNAS_NODE_MACHINE_ID") {
t.Fatalf("error = %q, want missing-machine-id message", err.Error())
}
}
func TestLoadConfigRejectsRegistrationWithoutControlPlaneURL(t *testing.T) {
t.Parallel()
_, err := loadConfig(
mapLookup(map[string]string{
exportPathEnvKey: ".state/nas/export",
"BETTERNAS_NODE_MACHINE_ID": "nas-machine-id",
"BETTERNAS_NODE_REGISTER_ENABLED": "true",
}),
t.TempDir(),
"nas-box",
)
if err == nil {
t.Fatal("expected missing control-plane url to fail")
}
if !strings.Contains(err.Error(), "BETTERNAS_CONTROL_PLANE_URL") {
t.Fatalf("error = %q, want missing-control-plane-url message", err.Error())
}
}
func mapLookup(values map[string]string) envLookup {
return func(key string) (string, bool) {
value, ok := values[key]
return value, ok
}
}