package tenantbackup import ( "context" "os" "path/filepath" "testing" "time" "github.com/klarkxy/nekonest-cloud/relay/internal/tenantfs" "github.com/klarkxy/nekonest-cloud/relay/internal/tenantstore" "github.com/klarkxy/nekonest/relaycore/protocol" ) func TestCreateVerifyAndRestoreTenantBackup(t *testing.T) { const tenantID = "tenant_0123456789abcdef0123456789abcdef" sourceRoot := t.TempDir() paths, err := tenantfs.Resolve(sourceRoot, tenantID) if err != nil { t.Fatal(err) } store, err := tenantstore.NewWithTransportMode(paths.Database, string(protocol.TransportSealed)) if err != nil { t.Fatal(err) } token, err := store.RegisterDevice("host_0123456789abcdef0123456789abcdef", "Host", "linux") if err != nil { t.Fatal(err) } if err := store.Close(); err != nil { t.Fatal(err) } attachmentID := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" if err := os.WriteFile(filepath.Join(paths.Attachments, attachmentID+".bin"), []byte("sealed payload"), 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(paths.Attachments, attachmentID+".json"), []byte(`{"id":"`+attachmentID+`"}`), 0o600); err != nil { t.Fatal(err) } result, err := Create(context.Background(), sourceRoot, t.TempDir(), tenantID, 7, time.Date(2026, 8, 12, 12, 0, 0, 0, time.UTC)) if err != nil { t.Fatal(err) } if _, err := Verify(context.Background(), result.Path, tenantID, 7, result.ManifestSHA256); err != nil { t.Fatal(err) } if resolved, err := ResolveReference(filepath.Dir(filepath.Dir(result.Path)), result.BackupRef); err != nil || resolved != result.Path { t.Fatalf("backup reference did not resolve: %q err=%v", resolved, err) } targetRoot := t.TempDir() restored, err := Restore(context.Background(), result.Path, targetRoot, tenantID, 7, result.ManifestSHA256) if err != nil { t.Fatal(err) } if retried, err := Restore(context.Background(), result.Path, targetRoot, tenantID, 7, result.ManifestSHA256); err != nil || retried.Root != restored.Root { t.Fatalf("idempotent restore failed: %#v err=%v", retried, err) } restoredStore, err := tenantstore.NewWithTransportMode(restored.Database, string(protocol.TransportSealed)) if err != nil { t.Fatal(err) } defer restoredStore.Close() if !restoredStore.ValidateDeviceToken("host_0123456789abcdef0123456789abcdef", token) { t.Fatal("restored database lost the device credential") } payload, err := os.ReadFile(filepath.Join(restored.Attachments, attachmentID+".bin")) if err != nil || string(payload) != "sealed payload" { t.Fatalf("restored attachment = %q err=%v", payload, err) } } func TestVerifyRejectsTamperedBackup(t *testing.T) { const tenantID = "tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" root := t.TempDir() paths, err := tenantfs.Resolve(root, tenantID) if err != nil { t.Fatal(err) } store, err := tenantstore.NewWithTransportMode(paths.Database, string(protocol.TransportSealed)) if err != nil { t.Fatal(err) } if err := store.Close(); err != nil { t.Fatal(err) } result, err := Create(context.Background(), root, t.TempDir(), tenantID, 1, time.Now().UTC()) if err != nil { t.Fatal(err) } file, err := os.OpenFile(filepath.Join(result.Path, "relay.db"), os.O_WRONLY|os.O_APPEND, 0) if err != nil { t.Fatal(err) } _, _ = file.Write([]byte("tamper")) _ = file.Close() if _, err := Verify(context.Background(), result.Path, tenantID, 1, result.ManifestSHA256); err == nil { t.Fatal("tampered backup was accepted") } }