package k8s import ( "context" "strings" "testing" "scm.bstein.dev/bstein/soteria/internal/api" "scm.bstein.dev/bstein/soteria/internal/config" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sfake "k8s.io/client-go/kubernetes/fake" ) func TestJobSupportHelpersCoverFallbackAndFormattingBranches(t *testing.T) { if got := sanitizeRepositorySegment(" "); got != "unknown" { t.Fatalf("expected empty repository segment to fall back to unknown, got %q", got) } if got := keepLastWithDefault(nil); got != 0 { t.Fatalf("expected nil keep-last to default to zero, got %d", got) } negative := -1 if got := keepLastWithDefault(&negative); got != 0 { t.Fatalf("expected negative keep-last to clamp to zero, got %d", got) } if got := appendRepositoryPath("", "isolated/apps/data"); got != "" { t.Fatalf("expected empty base repository to stay empty, got %q", got) } if got := appendRepositoryPath("s3:https://repo/root", ""); got != "s3:https://repo/root" { t.Fatalf("expected empty suffix to preserve base repository, got %q", got) } if got := parseIntWithDefault("7", 1); got != 7 { t.Fatalf("expected valid integer parse, got %d", got) } if got := parseIntWithDefault("-1", 1); got != 1 { t.Fatalf("expected negative integer to fall back, got %d", got) } if got := parseBoolWithDefault("off", true); got != false { t.Fatalf("expected explicit false parse, got %v", got) } if got := parseBoolWithDefault("", true); got != true { t.Fatalf("expected empty bool to fall back, got %v", got) } if got := parseBoolWithDefault("yes", false); got != true { t.Fatalf("expected explicit true parse, got %v", got) } if got := parseBoolWithDefault("0", true); got != false { t.Fatalf("expected numeric false parse, got %v", got) } if got := parseBoolWithDefault("maybe", false); got != false { t.Fatalf("expected unknown value to fall back, got %v", got) } } func TestJobNameBackupCommandAndResticEnvCoverRemainingBranches(t *testing.T) { cfg := &config.Config{ ResticRepository: "s3:https://repo/root", ResticBackupArgs: []string{"--exclude", "*.tmp"}, ResticForgetArgs: []string{"--keep-daily", "7", "--prune"}, S3Endpoint: "https://s3.us-west-001.backblazeb2.com", S3Region: "us-west-001", } name := jobName("backup", "Data_Set.With Spaces.and.extra-long-segment-to-force-truncation-beyond-sixty-three-characters") if len(name) > 63 || strings.Contains(name, "_") || strings.Contains(name, " ") || strings.Contains(name, ".") { t.Fatalf("expected sanitized Kubernetes-safe job name, got %q", name) } if !strings.HasPrefix(name, "soteria-backup-data-set-with-spaces-and-extra") { t.Fatalf("expected job name prefix to be sanitized, got %q", name) } dedupe := false keepLast := 3 cmd := backupCommand(cfg, api.BackupRequest{ PVC: "data", Tags: []string{" nightly ", "", "prod"}, Dedupe: &dedupe, KeepLast: &keepLast, }) if !strings.Contains(cmd, "restic init") || !strings.Contains(cmd, "--tag dedupe=off") || !strings.Contains(cmd, "--keep-last 3") { t.Fatalf("expected backup command to include bootstrap/dedupe/keep-last logic, got %q", cmd) } if !strings.Contains(cmd, "--exclude *.tmp") || !strings.Contains(cmd, "--tag nightly") || !strings.Contains(cmd, "--tag prod") { t.Fatalf("expected backup command to include trimmed tags and extra args, got %q", cmd) } cmd = backupCommand(cfg, api.BackupRequest{PVC: "data"}) if !strings.Contains(cmd, "restic forget --keep-daily 7 --prune") { t.Fatalf("expected backup command to use configured forget args, got %q", cmd) } env := resticEnv(cfg, "restic-secret", "") values := map[string]string{} for _, item := range env { if item.Value != "" { values[item.Name] = item.Value } } if values["RESTIC_REPOSITORY"] != "s3:https://repo/root" || values["RESTIC_S3_ENDPOINT"] != cfg.S3Endpoint || values["AWS_REGION"] != cfg.S3Region { t.Fatalf("expected repository/endpoint/region env vars, got %#v", values) } } func TestCopySecretAndBindSecretToJobCoverErrorBranches(t *testing.T) { client := &Client{Clientset: k8sfake.NewSimpleClientset()} if _, err := client.copySecret(context.Background(), "shared", "missing", "apps", "copy", nil); err == nil { t.Fatalf("expected missing source secret error") } if err := client.bindSecretToJob(context.Background(), "apps", "missing", &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{Name: "job-a"}, }); err == nil { t.Fatalf("expected missing secret bind error") } client = &Client{Clientset: k8sfake.NewSimpleClientset( &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "src", Namespace: "shared"}, Type: corev1.SecretTypeOpaque, Data: map[string][]byte{"token": []byte("atlas")}, }, )} secret, err := client.copySecret(context.Background(), "shared", "src", "apps", "copy", map[string]string{"app": "soteria"}) if err != nil { t.Fatalf("copy secret: %v", err) } if secret.Namespace != "apps" || secret.Name != "copy" || secret.Labels["app"] != "soteria" { t.Fatalf("expected copied secret in destination namespace, got %#v", secret) } }