soteria/internal/k8s/job_manifests_test.go

116 lines
4.9 KiB
Go

package k8s
import (
"strings"
"testing"
"scm.bstein.dev/bstein/soteria/internal/api"
"scm.bstein.dev/bstein/soteria/internal/config"
)
func TestBuildBackupJobAppliesSelectorsServiceAccountAndMetadata(t *testing.T) {
cfg := &config.Config{
ResticImage: "restic/restic:latest",
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",
JobTTLSeconds: 3600,
JobNodeSelector: map[string]string{"hardware": "rpi5"},
WorkerServiceAccount: "soteria-worker",
}
keepLast := 3
req := api.BackupRequest{
Namespace: "apps",
PVC: "data",
Tags: []string{"nightly"},
KeepLast: &keepLast,
}
job := buildBackupJob(cfg, req, "backup-job", "restic-secret", "s3:https://repo/root/isolated/apps/data", false, keepLast)
if job.Name != "backup-job" || job.Namespace != "apps" {
t.Fatalf("expected backup job identity, got %#v", job.ObjectMeta)
}
if job.Labels[labelPVC] != "data" || job.Annotations[annotationDedupeEnabled] != "false" || job.Annotations[annotationKeepLast] != "3" {
t.Fatalf("expected pvc/dedupe/keep-last metadata, got labels=%#v annotations=%#v", job.Labels, job.Annotations)
}
if got := job.Spec.Template.Spec.NodeSelector["hardware"]; got != "rpi5" {
t.Fatalf("expected node selector to be applied, got %#v", job.Spec.Template.Spec.NodeSelector)
}
if got := job.Spec.Template.Spec.ServiceAccountName; got != "soteria-worker" {
t.Fatalf("expected worker service account, got %q", got)
}
if len(job.Spec.Template.Spec.Containers) != 1 {
t.Fatalf("expected one backup container, got %#v", job.Spec.Template.Spec.Containers)
}
container := job.Spec.Template.Spec.Containers[0]
if container.Name != "restic" || container.Image != "restic/restic:latest" {
t.Fatalf("expected restic container, got %#v", container)
}
if len(container.Args) != 1 || !strings.Contains(container.Args[0], "restic backup /data") || !strings.Contains(container.Args[0], "--keep-last 3") {
t.Fatalf("expected backup command payload, got %#v", container.Args)
}
if len(job.Spec.Template.Spec.Volumes) != 2 || job.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim == nil {
t.Fatalf("expected pvc + cache volumes, got %#v", job.Spec.Template.Spec.Volumes)
}
if got := *job.Spec.BackoffLimit; got != 0 {
t.Fatalf("expected zero backoff limit, got %d", got)
}
if got := *job.Spec.TTLSecondsAfterFinished; got != 3600 {
t.Fatalf("expected ttl from config, got %d", got)
}
}
func TestBuildRestoreJobCoversDefaultAndTargetPVCRestoreShapes(t *testing.T) {
cfg := &config.Config{
ResticImage: "restic/restic:latest",
ResticRepository: "s3:https://repo/root",
S3Endpoint: "https://s3.us-west-001.backblazeb2.com",
S3Region: "us-west-001",
JobTTLSeconds: 1800,
JobNodeSelector: map[string]string{"hardware": "rpi5"},
WorkerServiceAccount: "soteria-worker",
}
defaultJob := buildRestoreJob(cfg, api.RestoreTestRequest{
Namespace: "apps",
}, "restore-job", "restic-secret", "latest", "s3:https://repo/root")
if got := defaultJob.Spec.Template.Spec.Volumes[0].EmptyDir; got == nil {
t.Fatalf("expected default restore target to use emptyDir, got %#v", defaultJob.Spec.Template.Spec.Volumes[0])
}
if got := defaultJob.Spec.Template.Spec.ServiceAccountName; got != "soteria-worker" {
t.Fatalf("expected worker service account, got %q", got)
}
if got := defaultJob.Spec.Template.Spec.NodeSelector["hardware"]; got != "rpi5" {
t.Fatalf("expected node selector to be applied, got %#v", defaultJob.Spec.Template.Spec.NodeSelector)
}
targetPVCJob := buildRestoreJob(cfg, api.RestoreTestRequest{
Namespace: "apps",
TargetPVC: "restore-data",
}, "restore-job", "restic-secret", "snapshot-1", "s3:https://repo/root")
if targetPVCJob.Labels[labelPVC] != "restore-data" {
t.Fatalf("expected restore pvc label, got %#v", targetPVCJob.Labels)
}
if claim := targetPVCJob.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim; claim == nil || claim.ClaimName != "restore-data" {
t.Fatalf("expected target pvc volume mount, got %#v", targetPVCJob.Spec.Template.Spec.Volumes[0])
}
if len(targetPVCJob.Spec.Template.Spec.Containers) != 1 || !strings.Contains(targetPVCJob.Spec.Template.Spec.Containers[0].Args[0], "restic restore snapshot-1") {
t.Fatalf("expected restore command payload, got %#v", targetPVCJob.Spec.Template.Spec.Containers)
}
}
func TestRestoreCommandAndInt32PtrHelpers(t *testing.T) {
command := restoreCommand("snapshot-42")
if !strings.Contains(command, "restic restore snapshot-42") || !strings.Contains(command, "cp -a /cache/restore") {
t.Fatalf("expected restore shell command, got %q", command)
}
value := int32Ptr(17)
if value == nil || *value != 17 {
t.Fatalf("expected int32 pointer helper to preserve value, got %#v", value)
}
}