158 lines
4.7 KiB
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)
|
|
}
|
|
}
|