script_test.go

  1package testscript
  2
  3import (
  4	"context"
  5	"flag"
  6	"fmt"
  7	"os"
  8	"path/filepath"
  9	"strings"
 10	"sync"
 11	"testing"
 12	"time"
 13
 14	"github.com/charmbracelet/soft-serve/server"
 15	"github.com/charmbracelet/soft-serve/server/config"
 16	"github.com/charmbracelet/soft-serve/server/test"
 17	"github.com/rogpeppe/go-internal/testscript"
 18)
 19
 20var update = flag.Bool("update", false, "update script files")
 21
 22func TestScript(t *testing.T) {
 23	flag.Parse()
 24	var lock sync.Mutex
 25
 26	t.Setenv("SOFT_SERVE_TEST_NO_HOOKS", "1")
 27
 28	// we'll use this key to talk with soft serve, and since testscript changes
 29	// the cwd, we need to get its full path here
 30	key, err := filepath.Abs("./testdata/admin1")
 31	if err != nil {
 32		t.Fatal(err)
 33	}
 34
 35	// git does not handle 0600, and on clone, will save the files with its
 36	// default perm, 0644, which is too open for ssh.
 37	for _, f := range []string{
 38		"admin1",
 39		"admin2",
 40		"user1",
 41		"user2",
 42	} {
 43		if err := os.Chmod(filepath.Join("./testdata/", f), 0o600); err != nil {
 44			t.Fatal(err)
 45		}
 46	}
 47
 48	sshArgs := []string{
 49		"-F", "/dev/null",
 50		"-o", "StrictHostKeyChecking=no",
 51		"-o", "UserKnownHostsFile=/dev/null",
 52		"-o", "IdentityAgent=none",
 53		"-o", "IdentitiesOnly=yes",
 54		"-i", key,
 55	}
 56
 57	check := func(ts *testscript.TestScript, err error, neg bool) {
 58		if neg && err == nil {
 59			ts.Fatalf("expected error, got nil")
 60		}
 61		if !neg {
 62			ts.Check(err)
 63		}
 64	}
 65
 66	testscript.Run(t, testscript.Params{
 67		Dir:           "testdata/script",
 68		UpdateScripts: *update,
 69		Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
 70			"soft": func(ts *testscript.TestScript, neg bool, args []string) {
 71				// TODO: maybe use plain ssh client here?
 72				args = append(
 73					sshArgs,
 74					append([]string{
 75						"-p", ts.Getenv("SSH_PORT"),
 76						"localhost",
 77						"--",
 78					}, args...)...,
 79				)
 80				check(ts, ts.Exec("ssh", args...), neg)
 81			},
 82			"git": func(ts *testscript.TestScript, _ bool, args []string) {
 83				ts.Setenv(
 84					"GIT_SSH_COMMAND",
 85					strings.Join(append([]string{"ssh"}, sshArgs...), " "),
 86				)
 87				ts.Check(ts.Exec("git", args...))
 88			},
 89			"mkreadme": func(ts *testscript.TestScript, _ bool, args []string) {
 90				if len(args) != 1 {
 91					ts.Fatalf("must have exactly 1 arg, the filename, got %d", len(args))
 92				}
 93				ts.Check(os.WriteFile(ts.MkAbs(args[0]), []byte("# example\ntest project"), 0o644))
 94			},
 95		},
 96		Setup: func(e *testscript.Env) error {
 97			sshPort := test.RandomPort()
 98			e.Setenv("SSH_PORT", fmt.Sprintf("%d", sshPort))
 99			data := t.TempDir()
100			cfg := config.Config{
101				Name:     "Test Soft Serve",
102				DataPath: data,
103				InitialAdminKeys: []string{
104					"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJI/1tawpdPmzuJcTGTJ+QReqB6cRUdKj4iQIdJUFdrl",
105				},
106				SSH: config.SSHConfig{
107					ListenAddr:    fmt.Sprintf("localhost:%d", sshPort),
108					PublicURL:     fmt.Sprintf("ssh://localhost:%d", sshPort),
109					KeyPath:       filepath.Join(data, "ssh", "soft_serve_host_ed25519"),
110					ClientKeyPath: filepath.Join(data, "ssh", "soft_serve_client_ed25519"),
111				},
112				Git: config.GitConfig{
113					ListenAddr:     fmt.Sprintf("localhost:%d", test.RandomPort()),
114					IdleTimeout:    3,
115					MaxConnections: 32,
116				},
117				HTTP: config.HTTPConfig{
118					ListenAddr: fmt.Sprintf("localhost:%d", test.RandomPort()),
119					PublicURL:  fmt.Sprintf("http://localhost:%d", test.RandomPort()),
120				},
121				Stats: config.StatsConfig{
122					ListenAddr: fmt.Sprintf("localhost:%d", test.RandomPort()),
123				},
124				Log: config.LogConfig{
125					Format:     "text",
126					TimeFormat: time.DateTime,
127				},
128			}
129			ctx := config.WithContext(context.Background(), &cfg)
130
131			// prevent race condition in lipgloss...
132			// this will probably be autofixed when we start using the colors
133			// from the ssh session instead of the server.
134			// XXX: take another look at this soon
135			lock.Lock()
136			srv, err := server.NewServer(ctx)
137			if err != nil {
138				return err
139			}
140			lock.Unlock()
141			go func() {
142				if err := srv.Start(); err != nil {
143					e.T().Fatal(err)
144				}
145			}()
146			e.Defer(func() {
147				ctx, cancel := context.WithTimeout(context.Background(), time.Second)
148				defer cancel()
149				if err := srv.Shutdown(ctx); err != nil {
150					e.T().Fatal(err)
151				}
152			})
153			return nil
154		},
155	})
156}