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}