134 lines
5.0 KiB
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)
|
|
}
|
|
}
|