iop/apps/agent/internal/command/config_test.go

158 lines
4.7 KiB
Go

package command
import (
"crypto/sha256"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"iop/packages/go/agentconfig"
)
// trackedRuntimeGlobalPath returns the repository-owned runtime config path.
func trackedRuntimeGlobalPath(t *testing.T) string {
t.Helper()
return filepath.Join(repoRoot(t), "configs", "iop-agent.runtime.yaml")
}
// trackedRuntimeLocalPath returns the device-owned runtime example path.
func trackedRuntimeLocalPath(t *testing.T) string {
t.Helper()
return filepath.Join(repoRoot(t), "configs", "iop-agent.local.example.yaml")
}
// repoRoot returns the repository root by walking up from this file's package.
func repoRoot(t *testing.T) string {
t.Helper()
_, file, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("Caller(0) failed")
}
return filepath.Dir(filepath.Dir(filepath.Dir(filepath.Dir(filepath.Dir(file)))))
}
// TestTrackedRuntimeExamplesLoad verifies the tracked repo-global and user-local
// example configurations parse through the strict loader and produce a valid
// merged snapshot with the expected top-level fields.
func TestTrackedRuntimeExamplesLoad(t *testing.T) {
globalPath := trackedRuntimeGlobalPath(t)
localPath := trackedRuntimeLocalPath(t)
globalBytes, err := os.ReadFile(globalPath)
if err != nil {
t.Fatalf("read repo-global fixture: %v", err)
}
localBytes, err := os.ReadFile(localPath)
if err != nil {
t.Fatalf("read user-local fixture: %v", err)
}
snapshot, err := agentconfig.LoadRuntimeConfigBytes(globalBytes, localBytes)
if err != nil {
t.Fatalf("LoadRuntimeConfigBytes(tracked fixtures): %v", err)
}
config := snapshot.Config()
if got, want := config.Version, agentconfig.RuntimeConfigSchemaVersion; got != want {
t.Errorf("config version = %q, want %q", got, want)
}
if config.Defaults.DefaultProfile == "" {
t.Error("default profile is empty, want non-empty")
}
if config.Selection.Timezone == "" {
t.Error("selection timezone is empty, want non-empty")
}
if config.Isolation.DefaultMode == "" {
t.Error("isolation default_mode is empty, want non-empty")
}
if config.Retention.CompletedDays <= 0 {
t.Errorf("retention completed_days = %d, want positive", config.Retention.CompletedDays)
}
if len(config.Catalog.Providers) != 0 {
t.Error("catalog has providers embedded in runtime snapshot, want empty (catalog is separate)")
}
catalogPath := filepath.Join(repoRoot(t), "configs", "iop-agent.providers.yaml")
catalog, err := agentconfig.Load(catalogPath)
if err != nil {
t.Fatalf("load canonical provider catalog: %v", err)
}
if len(catalog.Providers) == 0 {
t.Fatal("canonical catalog has no providers, want at least one")
}
if len(catalog.Profiles) == 0 {
t.Fatal("canonical catalog has no profiles, want at least one")
}
if config.Defaults.DefaultProfile != "" {
if _, ok := catalog.ResolveProfile(config.Defaults.DefaultProfile); !ok {
t.Errorf("default profile %q does not resolve in canonical catalog", config.Defaults.DefaultProfile)
}
}
for alias, profile := range config.Defaults.ProfileAliases {
if _, ok := catalog.ResolveProfile(profile); !ok {
t.Errorf("alias %q maps to profile %q which does not resolve in canonical catalog", alias, profile)
}
}
if config.Selection.Default.Profile != "" {
if _, ok := catalog.ResolveProfile(config.Selection.Default.Profile); !ok {
t.Errorf("selection default profile %q does not resolve in canonical catalog", config.Selection.Default.Profile)
}
}
for _, rule := range config.Selection.Rules {
if rule.Target.Profile != "" {
if _, ok := catalog.ResolveProfile(rule.Target.Profile); !ok {
t.Errorf("selection rule %q target profile %q does not resolve in canonical catalog", rule.ID, rule.Target.Profile)
}
}
}
if config.Device.StateRoot == "" {
t.Error("device state_root is empty, want non-empty")
}
if !strings.HasPrefix(snapshot.Revision(), "sha256:") {
t.Errorf("revision = %q, want sha256: prefix", snapshot.Revision())
}
}
// TestValidateDoesNotMutateRepoConfig verifies that loading the tracked
// repo-global configuration does not alter its on-disk bytes.
func TestValidateDoesNotMutateRepoConfig(t *testing.T) {
globalPath := trackedRuntimeGlobalPath(t)
localPath := trackedRuntimeLocalPath(t)
beforeBytes, err := os.ReadFile(globalPath)
if err != nil {
t.Fatalf("read repo-global before load: %v", err)
}
beforeDigest := sha256.Sum256(beforeBytes)
_, err = agentconfig.LoadRuntimeConfig(globalPath, localPath)
if err != nil {
t.Fatalf("LoadRuntimeConfig(tracked fixtures): %v", err)
}
afterBytes, err := os.ReadFile(globalPath)
if err != nil {
t.Fatalf("read repo-global after load: %v", err)
}
afterDigest := sha256.Sum256(afterBytes)
if beforeDigest != afterDigest {
t.Fatalf("repo-global digest changed: before=%x after=%x", beforeDigest, afterDigest)
}
}