package cli import ( "strings" "testing" "git.eeqj.de/sneak/vaultik/internal/config" "gopkg.in/yaml.v3" ) // TestDefaultConfigTemplateParses ensures the init template is valid YAML // that unmarshals into the Config struct with the expected snapshots. func TestDefaultConfigTemplateParses(t *testing.T) { var cfg config.Config if err := yaml.Unmarshal([]byte(defaultConfigTemplate), &cfg); err != nil { t.Fatalf("default config template is not valid YAML: %v", err) } if len(cfg.AgeRecipients) != 1 { t.Errorf("expected 1 placeholder age recipient, got %d", len(cfg.AgeRecipients)) } home, ok := cfg.Snapshots["home"] if !ok { t.Fatal("expected 'home' snapshot in default config") } if len(home.Paths) == 0 { t.Error("home snapshot should have at least one path") } if len(home.Exclude) == 0 { t.Error("home snapshot should have exclude patterns") } apps, ok := cfg.Snapshots["apps"] if !ok { t.Fatal("expected 'apps' snapshot in default config") } if len(apps.Paths) != 1 || apps.Paths[0] != "/Applications" { t.Errorf("apps snapshot should back up /Applications, got %v", apps.Paths) } if len(apps.Exclude) == 0 { t.Error("apps snapshot should have exclude patterns") } } const testYAML = `# top comment compression_level: 3 s3: bucket: oldbucket # inline comment region: us-east-1 snapshots: home: paths: - "~" ` func parseTestYAML(t *testing.T) *yaml.Node { t.Helper() var root yaml.Node if err := yaml.Unmarshal([]byte(testYAML), &root); err != nil { t.Fatalf("parsing test yaml: %v", err) } return &root } func TestYAMLPathGet(t *testing.T) { root := parseTestYAML(t) tests := []struct { path string want string err bool }{ {"compression_level", "3", false}, {"s3.bucket", "oldbucket", false}, {"s3.region", "us-east-1", false}, {"s3.nonexistent", "", true}, {"nonexistent", "", true}, {"compression_level.sub", "", true}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { node, err := yamlPathGet(root, splitPath(tt.path)) if tt.err { if err == nil { t.Fatalf("expected error for %q", tt.path) } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if node.Value != tt.want { t.Errorf("get %q = %q, want %q", tt.path, node.Value, tt.want) } }) } } func TestYAMLPathSet(t *testing.T) { root := parseTestYAML(t) // Overwrite existing nested value if err := yamlPathSet(root, splitPath("s3.bucket"), "newbucket"); err != nil { t.Fatalf("set s3.bucket: %v", err) } // Create new nested key with intermediate map if err := yamlPathSet(root, splitPath("s3.endpoint"), "s3.example.com"); err != nil { t.Fatalf("set s3.endpoint: %v", err) } if err := yamlPathSet(root, splitPath("newmap.newkey"), "val"); err != nil { t.Fatalf("set newmap.newkey: %v", err) } // Round-trip and verify values + comment preservation out, err := yaml.Marshal(root) if err != nil { t.Fatalf("marshal: %v", err) } text := string(out) for _, want := range []string{"newbucket", "s3.example.com", "newkey: val", "# top comment", "# inline comment"} { if !contains(text, want) { t.Errorf("round-tripped YAML missing %q:\n%s", want, text) } } got, err := yamlPathGet(root, splitPath("s3.bucket")) if err != nil { t.Fatalf("get after set: %v", err) } if got.Value != "newbucket" { t.Errorf("s3.bucket = %q after set, want newbucket", got.Value) } } func splitPath(s string) []string { return strings.Split(s, ".") } func contains(haystack, needle string) bool { return strings.Contains(haystack, needle) }