package k8s import ( "context" "errors" "strings" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" dynamicfake "k8s.io/client-go/dynamic/fake" k8stesting "k8s.io/client-go/testing" ) func TestGetLonghornBackupBySnapshotCoversLookupPaths(t *testing.T) { matchingOlder := longhornBackupObject("backup-old", "snap-a", "Completed", "", 100, "2026-04-20T10:00:00Z") matchingNewer := longhornBackupObject("backup-new", "snap-a", "Error", "storage cap exceeded", 0, "2026-04-20T11:00:00Z") other := longhornBackupObject("backup-other", "snap-b", "Completed", "", 100, "2026-04-20T12:00:00Z") client := &Client{Dynamic: newLonghornBackupDynamic(matchingOlder, matchingNewer, other)} backup, found, err := client.GetLonghornBackupBySnapshot(context.Background(), "snap-a") if err != nil || !found { t.Fatalf("expected matching backup, found=%v err=%v", found, err) } if backup.Name != "backup-new" || backup.State != "Error" || backup.Error != "storage cap exceeded" || backup.Progress != 0 { t.Fatalf("expected newest matching backup with status fields, got %#v", backup) } if _, found, err := client.GetLonghornBackupBySnapshot(context.Background(), "missing"); err != nil || found { t.Fatalf("expected missing snapshot to be cleanly absent, found=%v err=%v", found, err) } } func TestGetLonghornBackupBySnapshotValidatesClientAndInput(t *testing.T) { client := &Client{Dynamic: newLonghornBackupDynamic()} if _, _, err := client.GetLonghornBackupBySnapshot(context.Background(), " "); err == nil || !strings.Contains(err.Error(), "snapshot name is required") { t.Fatalf("expected snapshot validation error, got %v", err) } client.Dynamic = nil if _, _, err := client.GetLonghornBackupBySnapshot(context.Background(), "snap-a"); err == nil || !strings.Contains(err.Error(), "dynamic Kubernetes client is unavailable") { t.Fatalf("expected missing dynamic client error, got %v", err) } } func TestGetLonghornBackupBySnapshotWrapsListErrorAndFallbackTimestamp(t *testing.T) { client := &Client{Dynamic: newLonghornBackupDynamic(longhornBackupObject("backup-a", "snap-a", "Completed", "", 100, ""))} backup, found, err := client.GetLonghornBackupBySnapshot(context.Background(), "snap-a") if err != nil || !found { t.Fatalf("expected fallback timestamp backup, found=%v err=%v", found, err) } if backup.CreatedAt == "" || backup.SnapshotName != "snap-a" { t.Fatalf("expected fallback timestamp and snapshot name, got %#v", backup) } failingDynamic := newLonghornBackupDynamic() failingDynamic.PrependReactor("list", "backups", func(k8stesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("list exploded") }) client.Dynamic = failingDynamic if _, _, err := client.GetLonghornBackupBySnapshot(context.Background(), "snap-a"); err == nil || !strings.Contains(err.Error(), "list exploded") { t.Fatalf("expected wrapped list error, got %v", err) } } func newLonghornBackupDynamic(objects ...runtime.Object) *dynamicfake.FakeDynamicClient { return dynamicfake.NewSimpleDynamicClientWithCustomListKinds( runtime.NewScheme(), map[schema.GroupVersionResource]string{longhornBackupGVR: "BackupList"}, objects..., ) } func longhornBackupObject(name, snapshotName, state, errorMessage string, progress int64, createdAt string) *unstructured.Unstructured { item := &unstructured.Unstructured{Object: map[string]any{ "apiVersion": "longhorn.io/v1beta2", "kind": "Backup", "metadata": map[string]any{ "name": name, "namespace": longhornSystemNamespace, }, "spec": map[string]any{ "snapshotName": snapshotName, }, "status": map[string]any{ "state": state, "error": errorMessage, "progress": progress, }, }} if createdAt != "" { _ = unstructured.SetNestedField(item.Object, createdAt, "status", "backupCreatedAt") } item.SetCreationTimestamp(metav1.Now()) return item }