package manifest import ( "errors" "os" "path/filepath" "slices" "testing" ) func TestFindWalksParents(t *testing.T) { root := t.TempDir() manifestPath := filepath.Join(root, DefaultFile) if err := os.WriteFile(manifestPath, []byte("[update]\n"), 0o600); err != nil { t.Fatalf("WriteFile manifest: %v", err) } startDir := filepath.Join(root, "cmd", "graylog") if err := os.MkdirAll(startDir, 0o755); err != nil { t.Fatalf("MkdirAll startDir: %v", err) } got, err := Find(startDir) if err != nil { t.Fatalf("Find returned error: %v", err) } if got != manifestPath { t.Fatalf("Find = %q, want %q", got, manifestPath) } } func TestFindReturnsNotExistWhenMissing(t *testing.T) { _, err := Find(t.TempDir()) if err == nil { t.Fatal("expected error") } if !errors.Is(err, os.ErrNotExist) { t.Fatalf("error = %v, want os.ErrNotExist", err) } } func TestLoadParsesUpdateConfig(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, DefaultFile) const content = ` [update] source_name = " Gitea releases " driver = " Gitea " repository = " org/repo " base_url = "https://gitea.example.com/" latest_release_url = "https://gitea.example.com/api/releases/latest" asset_name_template = "{binary}_{os}_{arch}{ext}" checksum_asset_name = "{asset}.sha256" checksum_required = true signature_asset_name = "{asset}.sig" signature_required = true signature_public_key = " 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef " signature_public_key_env_names = [" MCP_PUBKEY ", "", "MCP_RELEASE_PUBKEY"] token_header = " Authorization " token_prefix = " token " token_env_names = [" GITEA_TOKEN ", "", "GITEA_RELEASE_TOKEN"] ` if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatalf("WriteFile manifest: %v", err) } file, err := Load(path) if err != nil { t.Fatalf("Load returned error: %v", err) } source := file.Update.ReleaseSource() if source.Name != "Gitea releases" { t.Fatalf("source name = %q", source.Name) } if source.Driver != "gitea" { t.Fatalf("driver = %q", source.Driver) } if source.Repository != "org/repo" { t.Fatalf("repository = %q", source.Repository) } if source.BaseURL != "https://gitea.example.com" { t.Fatalf("base URL = %q", source.BaseURL) } if source.LatestReleaseURL != "https://gitea.example.com/api/releases/latest" { t.Fatalf("latest release URL = %q", source.LatestReleaseURL) } if source.AssetNameTemplate != "{binary}_{os}_{arch}{ext}" { t.Fatalf("asset name template = %q", source.AssetNameTemplate) } if source.ChecksumAssetName != "{asset}.sha256" { t.Fatalf("checksum asset name = %q", source.ChecksumAssetName) } if !source.ChecksumRequired { t.Fatal("checksum required should be true") } if source.SignatureAssetName != "{asset}.sig" { t.Fatalf("signature asset name = %q", source.SignatureAssetName) } if !source.SignatureRequired { t.Fatal("signature required should be true") } if source.SignaturePublicKey == "" { t.Fatal("signature public key should be set") } if len(source.SignaturePublicKeyEnvNames) != 2 { t.Fatalf("signature env names = %v", source.SignaturePublicKeyEnvNames) } if source.TokenHeader != "Authorization" { t.Fatalf("token header = %q", source.TokenHeader) } if source.TokenPrefix != "token" { t.Fatalf("token prefix = %q", source.TokenPrefix) } if len(source.TokenEnvNames) != 2 { t.Fatalf("token env names = %v", source.TokenEnvNames) } } func TestLoadDefaultFindsManifest(t *testing.T) { root := t.TempDir() path := filepath.Join(root, DefaultFile) if err := os.WriteFile(path, []byte("[update]\nlatest_release_url = \"https://example.com/latest\"\n"), 0o600); err != nil { t.Fatalf("WriteFile manifest: %v", err) } startDir := filepath.Join(root, "cmd", "tool") if err := os.MkdirAll(startDir, 0o755); err != nil { t.Fatalf("MkdirAll startDir: %v", err) } file, gotPath, err := LoadDefault(startDir) if err != nil { t.Fatalf("LoadDefault returned error: %v", err) } if gotPath != path { t.Fatalf("path = %q, want %q", gotPath, path) } if file.Update.LatestReleaseURL != "https://example.com/latest" { t.Fatalf("latest release URL = %q", file.Update.LatestReleaseURL) } } func TestLoadReturnsParseError(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, DefaultFile) if err := os.WriteFile(path, []byte("[update\n"), 0o600); err != nil { t.Fatalf("WriteFile manifest: %v", err) } _, err := Load(path) if err == nil { t.Fatal("expected error") } } func TestLoadParsesExtendedManifestMetadata(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, DefaultFile) const content = ` binary_name = " my-mcp " docs_url = " https://docs.example.com/mcp " [update] latest_release_url = "https://example.com/latest" [environment] known = [" MCP_PROFILE ", "", "MCP_TOKEN"] [secret_store] backend_policy = " auto " [profiles] default = " prod " known = [" default ", "", "prod"] [bootstrap] description = " Client MCP interne " ` if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatalf("WriteFile manifest: %v", err) } file, err := Load(path) if err != nil { t.Fatalf("Load returned error: %v", err) } if file.BinaryName != "my-mcp" { t.Fatalf("binary name = %q", file.BinaryName) } if file.DocsURL != "https://docs.example.com/mcp" { t.Fatalf("docs URL = %q", file.DocsURL) } if !slices.Equal(file.Environment.Known, []string{"MCP_PROFILE", "MCP_TOKEN"}) { t.Fatalf("environment known = %v", file.Environment.Known) } if file.SecretStore.BackendPolicy != "auto" { t.Fatalf("secret store policy = %q", file.SecretStore.BackendPolicy) } if file.Profiles.Default != "prod" { t.Fatalf("default profile = %q", file.Profiles.Default) } if !slices.Equal(file.Profiles.Known, []string{"default", "prod"}) { t.Fatalf("profiles known = %v", file.Profiles.Known) } if file.Bootstrap.Description != "Client MCP interne" { t.Fatalf("bootstrap description = %q", file.Bootstrap.Description) } bootstrap := file.BootstrapInfo() if bootstrap.BinaryName != "my-mcp" { t.Fatalf("bootstrap binary name = %q", bootstrap.BinaryName) } if bootstrap.DocsURL != "https://docs.example.com/mcp" { t.Fatalf("bootstrap docs URL = %q", bootstrap.DocsURL) } if bootstrap.DefaultProfile != "prod" { t.Fatalf("bootstrap default profile = %q", bootstrap.DefaultProfile) } if !slices.Equal(bootstrap.Profiles, []string{"default", "prod"}) { t.Fatalf("bootstrap profiles = %v", bootstrap.Profiles) } scaffold := file.ScaffoldInfo() if scaffold.SecretStorePolicy != "auto" { t.Fatalf("scaffold secret store policy = %q", scaffold.SecretStorePolicy) } if !slices.Equal(scaffold.KnownEnvironmentVariables, []string{"MCP_PROFILE", "MCP_TOKEN"}) { t.Fatalf("scaffold known environment variables = %v", scaffold.KnownEnvironmentVariables) } } func TestLoadParsesSecretStoreBitwardenCache(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, DefaultFile) const content = ` [secret_store] backend_policy = "bitwarden-cli" bitwarden_cache = false ` if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatalf("WriteFile manifest: %v", err) } file, err := Load(path) if err != nil { t.Fatalf("Load returned error: %v", err) } if file.SecretStore.BitwardenCache == nil { t.Fatal("bitwarden cache option is nil, want explicit false pointer") } if *file.SecretStore.BitwardenCache { t.Fatal("bitwarden cache option = true, want false") } } func TestLoadLeavesOmittedBitwardenCacheUnset(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, DefaultFile) const content = ` [secret_store] backend_policy = "bitwarden-cli" ` if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatalf("WriteFile manifest: %v", err) } file, err := Load(path) if err != nil { t.Fatalf("Load returned error: %v", err) } if file.SecretStore.BitwardenCache != nil { t.Fatalf("bitwarden cache option = %v, want nil when omitted", *file.SecretStore.BitwardenCache) } } func TestLoadParsesConfigFields(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, DefaultFile) const content = ` [[config.fields]] name = " base_url " flag = "base-url" env = "BASE_URL" config_key = "base_url" type = " url " label = " Graylog URL " required = true sources = [" flag ", "env", "config"] [[config.fields]] name = "api_token" flag = "api-token" env = "API_TOKEN" secret_key_template = "profile/{profile}/api-token" type = "secret" required = true sources = ["flag", "env", "secret"] ` if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatalf("WriteFile manifest: %v", err) } file, err := Load(path) if err != nil { t.Fatalf("Load returned error: %v", err) } if len(file.Config.Fields) != 2 { t.Fatalf("config fields = %d, want 2", len(file.Config.Fields)) } baseURL := file.Config.Fields[0] if baseURL.Name != "base_url" { t.Fatalf("base URL name = %q", baseURL.Name) } if baseURL.Flag != "base-url" { t.Fatalf("base URL flag = %q", baseURL.Flag) } if baseURL.Env != "BASE_URL" { t.Fatalf("base URL env = %q", baseURL.Env) } if baseURL.ConfigKey != "base_url" { t.Fatalf("base URL config key = %q", baseURL.ConfigKey) } if baseURL.Type != "url" { t.Fatalf("base URL type = %q", baseURL.Type) } if baseURL.Label != "Graylog URL" { t.Fatalf("base URL label = %q", baseURL.Label) } if !baseURL.Required { t.Fatal("base URL should be required") } if !slices.Equal(baseURL.Sources, []string{"flag", "env", "config"}) { t.Fatalf("base URL sources = %v", baseURL.Sources) } token := file.Config.Fields[1] if token.SecretKeyTemplate != "profile/{profile}/api-token" { t.Fatalf("token secret key template = %q", token.SecretKeyTemplate) } if !slices.Equal(token.Sources, []string{"flag", "env", "secret"}) { t.Fatalf("token sources = %v", token.Sources) } } func TestLoadEmbeddedParsesContent(t *testing.T) { file, source, err := LoadEmbedded(` [update] latest_release_url = "https://example.com/latest" `) if err != nil { t.Fatalf("LoadEmbedded returned error: %v", err) } if source != EmbeddedSource { t.Fatalf("source = %q, want %q", source, EmbeddedSource) } if file.Update.LatestReleaseURL != "https://example.com/latest" { t.Fatalf("latest release URL = %q", file.Update.LatestReleaseURL) } } func TestLoadEmbeddedReturnsNotExistWhenEmpty(t *testing.T) { _, _, err := LoadEmbedded(" ") if !errors.Is(err, os.ErrNotExist) { t.Fatalf("error = %v, want os.ErrNotExist", err) } } func TestLoadDefaultOrEmbeddedPrefersManifestFile(t *testing.T) { root := t.TempDir() path := filepath.Join(root, DefaultFile) if err := os.WriteFile(path, []byte("[update]\nlatest_release_url = \"https://example.com/from-file\"\n"), 0o600); err != nil { t.Fatalf("WriteFile manifest: %v", err) } file, source, err := LoadDefaultOrEmbedded(root, ` [update] latest_release_url = "https://example.com/from-embedded" `) if err != nil { t.Fatalf("LoadDefaultOrEmbedded returned error: %v", err) } if source != path { t.Fatalf("source = %q, want %q", source, path) } if file.Update.LatestReleaseURL != "https://example.com/from-file" { t.Fatalf("latest release URL = %q", file.Update.LatestReleaseURL) } } func TestLoadDefaultOrEmbeddedUsesEmbeddedWhenFileMissing(t *testing.T) { file, source, err := LoadDefaultOrEmbedded(t.TempDir(), ` [update] latest_release_url = "https://example.com/from-embedded" `) if err != nil { t.Fatalf("LoadDefaultOrEmbedded returned error: %v", err) } if source != EmbeddedSource { t.Fatalf("source = %q, want %q", source, EmbeddedSource) } if file.Update.LatestReleaseURL != "https://example.com/from-embedded" { t.Fatalf("latest release URL = %q", file.Update.LatestReleaseURL) } }