soteria/internal/k8s/job_helpers_test.go

134 lines
5.0 KiB
Go

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)
}
}