From f588a797f9d0bd20396067bf030391ef684d30c1 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sat, 10 Oct 2026 12:58:10 +0100 Subject: [PATCH] rust: Go-generated compatibility fixtures (db, vault, export, recording) --- cmd/genfixtures/main.go | 89 ++++++++++++++++++ rust/core/tests/fixtures/profiles.link | Bin 0 -> 8222 bytes rust/core/tests/fixtures/recordings/rec1.cast | 3 + 3 files changed, 92 insertions(+) create mode 100644 cmd/genfixtures/main.go create mode 100644 rust/core/tests/fixtures/profiles.link create mode 100644 rust/core/tests/fixtures/recordings/rec1.cast diff --git a/cmd/genfixtures/main.go b/cmd/genfixtures/main.go new file mode 100644 index 0000000..19f118c --- /dev/null +++ b/cmd/genfixtures/main.go @@ -0,0 +1,89 @@ +// Command genfixtures writes the Go-generated compatibility fixtures that the +// Rust port must read unchanged. Run from the repo root: +// +// go run ./cmd/genfixtures rust/core/tests/fixtures +package main + +import ( + "log" + "os" + "path/filepath" + "time" + + "github.com/andydixon/link/internal/exportimport" + "github.com/andydixon/link/internal/models" + "github.com/andydixon/link/internal/recording" + "github.com/andydixon/link/internal/secrets" + "github.com/andydixon/link/internal/storage" +) + +func must(err error) { + if err != nil { + log.Fatal(err) + } +} + +func main() { + out := os.Args[1] + must(os.RemoveAll(out)) + must(os.MkdirAll(out, 0o755)) + + store, err := storage.NewStore(filepath.Join(out, "link.db")) + must(err) + ts := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC) + var profiles []models.Profile + for i, proto := range []models.Protocol{models.ProtocolSSH, models.ProtocolTelnet, models.ProtocolRAW, models.ProtocolGitHub, models.ProtocolVNC, models.ProtocolIRC} { + p := models.NewProfile(proto) + p.ID = "00000000-0000-0000-0000-00000000000" + string(rune('1'+i)) + p.DisplayName = "Fixture " + string(proto) + p.Host = "host-" + string(proto) + ".example" + p.Tags = []string{"fixture", string(proto)} + p.Folder = "Fixtures" + p.Notes = "notes ✓" + p.CreatedAt, p.UpdatedAt = ts, ts + switch proto { + case models.ProtocolSSH: + p.Username = "alice" + p.Favourite = true + p.LastConnected = &ts + p.SSH.PrivateKeyPath = "/home/alice/.ssh/id_ed25519" + p.SSH.JumpHosts = []models.JumpHost{{Host: "jump.example", Port: 2222, Username: "bob", AuthMethod: "publickey", SecretMode: models.SecretModeSaved}} + p.SSH.PortForwards = []models.PortForward{{ID: "pf1", Direction: models.PortForwardLocal, BindHost: "127.0.0.1", BindPort: 8080, DestHost: "localhost", DestPort: 80, Enabled: true, Label: "web"}} + p.SSH.EnvVars = map[string]string{"LANG": "en_GB.UTF-8"} + p.SSH.LoginScripts = []models.LoginScriptStep{{Type: "wait", Value: "$ ", Timeout: 5}, {Type: "send", Value: "uptime"}} + p.SSH.ProxyType = "socks5" + p.SSH.ProxyHost = "proxy.example" + p.SSH.ProxyPort = 1080 + p.SSH.AutoReconnect = true + case models.ProtocolRAW: + p.RAW.PayloadPresets = []models.PayloadPreset{{ID: "pp1", Name: "hello", Payload: "aGVsbG8=", Format: "text"}} + case models.ProtocolIRC: + p.IRC.AutoJoinRooms = []string{"#rust", "#go"} + p.IRC.UseTLS = true + } + must(store.CreateProfile(&p)) + profiles = append(profiles, p) + } + must(store.SaveKnownHost(&models.KnownHost{ID: "kh1", Host: "host-ssh.example", Port: 22, KeyType: "ssh-ed25519", Fingerprint: "SHA256:abc", PublicKey: "AAAA", FirstSeen: ts, LastSeen: ts, Trusted: true})) + must(store.CreateSnippet(&models.Snippet{ID: "sn1", Name: "uptime", Command: "uptime", Description: "load", Folder: "ops", Tags: []string{"x"}, CreatedAt: ts, UpdatedAt: ts})) + must(store.SaveIRCLastRooms(profiles[5].ID, []string{"#rust", "#nexus"})) + prefs := models.DefaultAppPreferences() + prefs.TerminalTheme = "dracula" + prefs.SidebarWidth = 320 + must(store.SavePreferences(&prefs)) + must(store.Close()) + + vault := secrets.NewSecretStore(filepath.Join(out, "vault")) + must(vault.Initialise("correct horse battery staple")) + must(vault.SetSecret("profile:00000000-0000-0000-0000-000000000001:password", "s3cret-ssh")) + must(vault.SetSecret("profile:00000000-0000-0000-0000-000000000001:totp", "JBSWY3DPEHPK3PXP")) + + must(exportimport.EncryptAndExport(profiles, "export-pass", filepath.Join(out, "profiles.link"))) + + rec, err := recording.NewRecorder(filepath.Join(out, "recordings"), "rec1", "Fixture — host", 80, 24) + must(err) + rec.WriteOutput([]byte("hello \x1b[1mworld\x1b[0m\r\n")) + rec.WriteInput([]byte("ls\r")) + must(rec.Close()) + log.Println("fixtures written to", out) +} diff --git a/rust/core/tests/fixtures/profiles.link b/rust/core/tests/fixtures/profiles.link new file mode 100644 index 0000000000000000000000000000000000000000..cf045f9f13e5b9059ff9e8241db378376582cae3 GIT binary patch literal 8222 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