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>
| M | e2e/ | 9 | ++- |
| A | e2e/ | 110 | ++++++++++++++++++++++++++++++ |
@@ -72,6 +72,9 @@ type options struct {
7272 // images; off by default, so screens show the text charts every7373 // terminal gets.7474 graphics bool75+ // program runs instead of 012, such as an ssh client reaching 01276+ // serve.77+ program string7578 }7679 7780 // start launches 012 in dir (a fresh temp dir if empty) with args.@@ -118,7 +121,11 @@ func startWith(t *testing.T, o options, args ...string) *session {
118121 t.Fatal(err)119122 }120123 121- s.cmd = exec.Command(binPath, args...)124+ program := binPath125+ if o.program != "" {126+ program = o.program127+ }128+ s.cmd = exec.Command(program, args...)122129 s.cmd.Dir = o.dir123130 // Tests never reach the real JEV service: the key is cleared unless a124131 // test points JEV at a fake server through o.env.@@ -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+}