134 lines
6.7 KiB
Go
134 lines
6.7 KiB
Go
package cmd
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Pingu-Studio/MaidnCLI/internal/config"
|
|
)
|
|
|
|
func TestCreateForgejoRegistryTokenRequiresConfig(t *testing.T) {
|
|
originalConfigPath, originalCreate := bootstrapConfigPath, bootstrapCreateForgejoRegistryToken
|
|
defer func() {
|
|
bootstrapConfigPath = originalConfigPath
|
|
bootstrapCreateForgejoRegistryToken = originalCreate
|
|
}()
|
|
bootstrapConfigPath = ""
|
|
bootstrapCreateForgejoRegistryToken = true
|
|
if err := runBootstrap(nil, nil); err == nil {
|
|
t.Fatal("--create-forgejo-registry-token accepted a missing --config")
|
|
}
|
|
}
|
|
|
|
func TestSeedForgejoOperationalCredentialsUsesEncryptedUpsertBoundary(t *testing.T) {
|
|
original := upsertOperationalSecret
|
|
t.Cleanup(func() { upsertOperationalSecret = original })
|
|
cfg := config.Config{
|
|
Git: config.GitConfig{Username: "webhook-bot", Token: "test-pat"},
|
|
SOPS: config.SOPSConfig{OperationalSecretsPath: "secrets.sops.yaml", AgeKeyPath: "age-key.txt"},
|
|
}
|
|
type call struct{ path, key, value string }
|
|
var calls []call
|
|
upsertOperationalSecret = func(secretsPath, ageKeyPath, path, key, value string) error {
|
|
if secretsPath != cfg.SOPS.OperationalSecretsPath || ageKeyPath != cfg.SOPS.AgeKeyPath {
|
|
t.Fatal("credential upsert used unexpected secret paths")
|
|
}
|
|
calls = append(calls, call{path, key, value})
|
|
return nil
|
|
}
|
|
|
|
if err := seedForgejoOperationalCredentials(cfg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(calls) != 2 || calls[0] != (call{"cicd/forgejo", "username", cfg.Git.Username}) || calls[1] != (call{"cicd/forgejo", "token", cfg.Git.Token}) {
|
|
t.Fatal("configured Forgejo credentials were not passed only to the encrypted upsert boundary")
|
|
}
|
|
}
|
|
|
|
func TestSeedForgejoOperationalCredentialsHidesTokenOnUpsertFailure(t *testing.T) {
|
|
original := upsertOperationalSecret
|
|
t.Cleanup(func() { upsertOperationalSecret = original })
|
|
cfg := config.Config{Git: config.GitConfig{Username: "webhook-bot", Token: "test-pat"}}
|
|
upsertOperationalSecret = func(_, _, _, key, _ string) error {
|
|
if key == "token" {
|
|
return errors.New("unavailable")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
err := seedForgejoOperationalCredentials(cfg)
|
|
if err == nil || !strings.Contains(err.Error(), "save Forgejo token for webhook registration") || strings.Contains(err.Error(), cfg.Git.Token) {
|
|
t.Fatal("credential upsert failure was not clear and token-safe")
|
|
}
|
|
}
|
|
|
|
func TestCreateOrReuseForgejoDeliveryStatusTokenCreatesSeedsAndRedacts(t *testing.T) {
|
|
originalRead, originalPrompt, originalCreate, originalUpsert, originalInitialize := readOperationalSecrets, promptForgejoDeliveryStatusToken, createForgejoDeliveryStatusToken, upsertOperationalSecret, initializeOpenBao
|
|
t.Cleanup(func() {
|
|
readOperationalSecrets, promptForgejoDeliveryStatusToken, createForgejoDeliveryStatusToken, upsertOperationalSecret, initializeOpenBao = originalRead, originalPrompt, originalCreate, originalUpsert, originalInitialize
|
|
})
|
|
cfg := config.Config{Git: config.GitConfig{BaseURL: "https://git.example.test", Username: "delivery-bot"}, SOPS: config.SOPSConfig{OperationalSecretsPath: "secrets.sops.yaml", AgeKeyPath: "age-key.txt"}}
|
|
readOperationalSecrets = func(_, _ string) (map[string]map[string]string, error) { return map[string]map[string]string{}, nil }
|
|
promptForgejoDeliveryStatusToken = func() (string, string, error) { return "password", "otp", nil }
|
|
createForgejoDeliveryStatusToken = func(baseURL, username, password, otp string) (string, error) {
|
|
if baseURL != cfg.Git.BaseURL || username != cfg.Git.Username || password != "password" || otp != "otp" {
|
|
t.Fatal("delivery token creation crossed an unexpected credential boundary")
|
|
}
|
|
return "delivery-token", nil
|
|
}
|
|
seeded := false
|
|
upsertOperationalSecret = func(path, agePath, secretPath, key, value string) error {
|
|
seeded = path == cfg.SOPS.OperationalSecretsPath && agePath == cfg.SOPS.AgeKeyPath && secretPath == "cicd/forgejo-delivery-status" && key == "token" && value == "delivery-token"
|
|
return nil
|
|
}
|
|
initializeOpenBao = func(config.Config) error { return errors.New("OpenBao rejected delivery-token") }
|
|
err := createOrReuseForgejoDeliveryStatusToken(cfg)
|
|
if !seeded || err == nil || strings.Contains(err.Error(), "delivery-token") {
|
|
t.Fatalf("delivery token create/seed failure leaked or skipped a credential: seeded=%t err=%v", seeded, err)
|
|
}
|
|
}
|
|
|
|
func TestCreateOrReuseForgejoDeliveryStatusTokenReusesAndReseeds(t *testing.T) {
|
|
originalRead, originalPrompt, originalCreate, originalUpsert, originalInitialize := readOperationalSecrets, promptForgejoDeliveryStatusToken, createForgejoDeliveryStatusToken, upsertOperationalSecret, initializeOpenBao
|
|
t.Cleanup(func() {
|
|
readOperationalSecrets, promptForgejoDeliveryStatusToken, createForgejoDeliveryStatusToken, upsertOperationalSecret, initializeOpenBao = originalRead, originalPrompt, originalCreate, originalUpsert, originalInitialize
|
|
})
|
|
cfg := config.Config{SOPS: config.SOPSConfig{OperationalSecretsPath: "secrets.sops.yaml", AgeKeyPath: "age-key.txt"}}
|
|
readOperationalSecrets = func(_, _ string) (map[string]map[string]string, error) {
|
|
return map[string]map[string]string{"cicd/forgejo-delivery-status": {"token": "existing-token"}}, nil
|
|
}
|
|
promptForgejoDeliveryStatusToken = func() (string, string, error) { t.Fatal("existing delivery token must not prompt"); return "", "", nil }
|
|
createForgejoDeliveryStatusToken = func(string, string, string, string) (string, error) {
|
|
t.Fatal("existing delivery token must not be recreated")
|
|
return "", nil
|
|
}
|
|
upsertOperationalSecret = func(string, string, string, string, string) error {
|
|
t.Fatal("existing delivery token must not be rewritten")
|
|
return nil
|
|
}
|
|
seeded := false
|
|
initializeOpenBao = func(received config.Config) error {
|
|
seeded = received.SOPS.OperationalSecretsPath == cfg.SOPS.OperationalSecretsPath
|
|
return nil
|
|
}
|
|
if err := createOrReuseForgejoDeliveryStatusToken(cfg); err != nil || !seeded {
|
|
t.Fatalf("delivery token reuse did not reseed OpenBao: seeded=%t err=%v", seeded, err)
|
|
}
|
|
}
|
|
|
|
func TestCreateOrReuseForgejoDeliveryStatusTokenRejectsAmbiguousState(t *testing.T) {
|
|
originalRead, originalPrompt := readOperationalSecrets, promptForgejoDeliveryStatusToken
|
|
t.Cleanup(func() { readOperationalSecrets, promptForgejoDeliveryStatusToken = originalRead, originalPrompt })
|
|
readOperationalSecrets = func(_, _ string) (map[string]map[string]string, error) {
|
|
return map[string]map[string]string{"cicd/forgejo-delivery-status": {"token": "", "legacy": "value"}}, nil
|
|
}
|
|
promptForgejoDeliveryStatusToken = func() (string, string, error) {
|
|
t.Fatal("ambiguous state must not create a replacement token")
|
|
return "", "", nil
|
|
}
|
|
if err := createOrReuseForgejoDeliveryStatusToken(config.Config{}); err == nil || !strings.Contains(err.Error(), "ambiguous") {
|
|
t.Fatalf("ambiguous delivery token state = %v", err)
|
|
}
|
|
}
|