cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Test 012 serve end to end through the system ssh client in libghostty

Dami committed 69c6ae5
The harness can run another program than 012; the new test starts
012 serve with a fresh key and host key, connects with ssh checking the
host key, and types, resizes, saves inside the served directory, is
refused a file outside it and quits.
commit
69c6ae51414aae558ec11877e507acc47d7f074e
parent
59d49485013326455fe55aed142c11dffd22dae3
author
Dami <dami.etoile@gmail.com>
2 files changed, +118 -1
Me2e/harness_test.go9++-
Ae2e/ssh_test.go110++++++++++++++++++++++++++++++
M e2e/harness_test.go +8 -1
A e2e/ssh_test.go +110 -0
@@ -0,0 +1,110 @@
1+package e2e2+3+import (4+ "bufio"5+ "os"6+ "os/exec"7+ "path/filepath"8+ "regexp"9+ "strings"10+ "testing"11+ "time"12+)13+14+// serveSSH starts 012 serve on a random loopback port for served,15+// authorizing a new key, and returns the system's ssh client and the16+// arguments that reach the server with it, checking the host key.17+func serveSSH(t *testing.T, top, served string) (string, []string) {18+ t.Helper()19+ sshPath, err1 := exec.LookPath("ssh")20+ keygen, err2 := exec.LookPath("ssh-keygen")21+ if err1 != nil || err2 != nil {22+ t.Skip("needs ssh and ssh-keygen")23+ }24+ id := filepath.Join(top, "id_ed25519")25+ if out, err := exec.Command(keygen, "-q", "-t", "ed25519", "-N", "", "-C", "e2e", "-f", id).CombinedOutput(); err != nil {26+ t.Fatalf("ssh-keygen: %v\n%s", err, out)27+ }28+ pub, err := os.ReadFile(id + ".pub")29+ if err != nil {30+ t.Fatal(err)31+ }32+ ak := filepath.Join(top, "authorized_keys")33+ if err := os.WriteFile(ak, pub, 0o600); err != nil {34+ t.Fatal(err)35+ }36+ hostKey := filepath.Join(top, "config", "host_key")37+ srv := exec.Command(binPath, "serve", "--listen", "127.0.0.1:0", "--authorized-keys", ak, "--host-key", hostKey, served)38+ srv.Env = append(os.Environ(), "TYPESAFE_API_KEY=", "TYPESAFE_BASE_URL=")39+ stderr, err := srv.StderrPipe()40+ if err != nil {41+ t.Fatal(err)42+ }43+ if err := srv.Start(); err != nil {44+ t.Fatal(err)45+ }46+ t.Cleanup(func() {47+ srv.Process.Kill()48+ srv.Wait()49+ })50+ // "012 serving DIR on 127.0.0.1:PORT"51+ port := make(chan string, 1)52+ go func() {53+ re := regexp.MustCompile(` on 127\.0\.0\.1:(\d+)$`)54+ sc := bufio.NewScanner(stderr)55+ for sc.Scan() {56+ if m := re.FindStringSubmatch(sc.Text()); m != nil {57+ port <- m[1]58+ }59+ }60+ }()61+ var p string62+ select {63+ case p = <-port:64+ case <-time.After(waitTimeout):65+ t.Fatal("012 serve didn't start")66+ }67+ hostPub, err := os.ReadFile(hostKey + ".pub")68+ if err != nil {69+ t.Fatal(err)70+ }71+ knownHosts := filepath.Join(top, "known_hosts")72+ os.WriteFile(knownHosts, []byte("[127.0.0.1]:"+p+" "+string(hostPub)), 0o600)73+ return sshPath, []string{"-F", "/dev/null", "-i", id, "-e", "none", "-p", p,74+ "-o", "IdentitiesOnly=yes", "-o", "IdentityAgent=none", "-o", "BatchMode=yes",75+ "-o", "UserKnownHostsFile=" + knownHosts, "-o", "StrictHostKeyChecking=yes", "-o", "LogLevel=ERROR",76+ "-t", "127.0.0.1"}77+}78+79+// 012 serve through a real ssh client in a real terminal: the first80+// screen, typing, resizing, saving inside the served directory, a81+// refused escape, and quitting closing the connection.82+func TestServeOverSSH(t *testing.T) {83+ top := t.TempDir()84+ served := filepath.Join(top, "served")85+ os.Mkdir(served, 0o755)86+ sshPath, args := serveSSH(t, top, served)87+88+ s := startWith(t, options{dir: top, program: sshPath}, args...)89+ s.waitFor("Sheet1")90+ s.keys("hello", "<enter>")91+ s.waitForLine(gridRow1, " 1 hello")92+93+ // The window follows the client's terminal.94+ s.resize(120, 40)95+ s.eventually("column K after resizing", func() bool { return strings.Contains(s.line(3), " K") })96+97+ s.keys("<ctrl+s>", "greeting", "<enter>")98+ s.eventually("greeting.012 in the served directory", func() bool {99+ _, err := os.Stat(filepath.Join(served, "greeting.012"))100+ return err == nil101+ })102+ s.waitFor("greeting.012")103+104+ s.keys("<ctrl+o>", "../id_ed25519", "<enter>")105+ s.waitFor("outside the served directory")106+ s.keys("<esc>")107+108+ s.keys("<ctrl+q>")109+ s.waitExit()110+}