Skip to content
This repository was archived by the owner on Jun 25, 2026. It is now read-only.

Commit 65ab3f8

Browse files
committed
test(cli): add auth sync validation and remote command wiring coverage
1 parent f36bc6f commit 65ab3f8

1 file changed

Lines changed: 153 additions & 0 deletions

File tree

cmd/rascal/auth_sync_test.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
)
9+
10+
func TestSyncRemoteAuthValidation(t *testing.T) {
11+
t.Run("missing host", func(t *testing.T) {
12+
err := syncRemoteAuth(syncRemoteAuthConfig{
13+
APIToken: "api",
14+
GitHubRuntime: "runtime",
15+
WebhookSecret: "secret",
16+
})
17+
if err == nil || !strings.Contains(err.Error(), "host is required") {
18+
t.Fatalf("expected missing host error, got: %v", err)
19+
}
20+
})
21+
22+
t.Run("missing auth values", func(t *testing.T) {
23+
err := syncRemoteAuth(syncRemoteAuthConfig{Host: "example-host"})
24+
if err == nil || !strings.Contains(err.Error(), "api token, github runtime token, and webhook secret are required") {
25+
t.Fatalf("expected missing auth values error, got: %v", err)
26+
}
27+
})
28+
29+
t.Run("rejects newlines", func(t *testing.T) {
30+
err := syncRemoteAuth(syncRemoteAuthConfig{
31+
Host: "example-host",
32+
APIToken: "api\nbad",
33+
GitHubRuntime: "runtime",
34+
WebhookSecret: "secret",
35+
})
36+
if err == nil || !strings.Contains(err.Error(), "must not contain newlines") {
37+
t.Fatalf("expected newline rejection error, got: %v", err)
38+
}
39+
})
40+
}
41+
42+
func TestSyncRemoteAuthUploadsEnvUpdateWithoutRestart(t *testing.T) {
43+
logDir := setupSyncCommandFakes(t)
44+
45+
err := syncRemoteAuth(syncRemoteAuthConfig{
46+
Host: "example-host",
47+
APIToken: "api-token",
48+
GitHubRuntime: "runtime-token",
49+
WebhookSecret: "webhook-secret",
50+
Restart: false,
51+
})
52+
if err != nil {
53+
t.Fatalf("syncRemoteAuth: %v", err)
54+
}
55+
56+
payloadPath := filepath.Join(logDir, "scp_payload.env")
57+
payload, err := os.ReadFile(payloadPath)
58+
if err != nil {
59+
t.Fatalf("read uploaded payload: %v", err)
60+
}
61+
gotPayload := string(payload)
62+
for _, want := range []string{
63+
"RASCAL_API_TOKEN=api-token",
64+
"RASCAL_GITHUB_TOKEN=runtime-token",
65+
"RASCAL_GITHUB_WEBHOOK_SECRET=webhook-secret",
66+
} {
67+
if !strings.Contains(gotPayload, want) {
68+
t.Fatalf("uploaded payload missing %q:\n%s", want, gotPayload)
69+
}
70+
}
71+
72+
sshLog, err := os.ReadFile(filepath.Join(logDir, "ssh_calls.log"))
73+
if err != nil {
74+
t.Fatalf("read ssh log: %v", err)
75+
}
76+
sshCalls := string(sshLog)
77+
if !strings.Contains(sshCalls, "mkdir -p /tmp/rascal-bootstrap /etc/rascal") {
78+
t.Fatalf("expected remote mkdir command, got:\n%s", sshCalls)
79+
}
80+
if !strings.Contains(sshCalls, "awk -F=") {
81+
t.Fatalf("expected remote env merge command, got:\n%s", sshCalls)
82+
}
83+
if strings.Contains(sshCalls, `systemctl restart "rascal@$slot"`) {
84+
t.Fatalf("did not expect restart command when Restart=false, got:\n%s", sshCalls)
85+
}
86+
}
87+
88+
func TestSyncRemoteAuthIncludesRestartWhenEnabled(t *testing.T) {
89+
logDir := setupSyncCommandFakes(t)
90+
91+
err := syncRemoteAuth(syncRemoteAuthConfig{
92+
Host: "example-host",
93+
SSHUser: "ubuntu",
94+
SSHPort: 2222,
95+
APIToken: "api-token",
96+
GitHubRuntime: "runtime-token",
97+
WebhookSecret: "webhook-secret",
98+
Restart: true,
99+
})
100+
if err != nil {
101+
t.Fatalf("syncRemoteAuth: %v", err)
102+
}
103+
104+
sshLog, err := os.ReadFile(filepath.Join(logDir, "ssh_calls.log"))
105+
if err != nil {
106+
t.Fatalf("read ssh log: %v", err)
107+
}
108+
sshCalls := string(sshLog)
109+
if !strings.Contains(sshCalls, "-p 2222") {
110+
t.Fatalf("expected ssh port override in calls, got:\n%s", sshCalls)
111+
}
112+
if !strings.Contains(sshCalls, "ubuntu@example-host") {
113+
t.Fatalf("expected ssh user/host target in calls, got:\n%s", sshCalls)
114+
}
115+
if !strings.Contains(sshCalls, `systemctl restart "rascal@$slot"`) {
116+
t.Fatalf("expected restart command when Restart=true, got:\n%s", sshCalls)
117+
}
118+
}
119+
120+
func setupSyncCommandFakes(t *testing.T) string {
121+
t.Helper()
122+
123+
binDir := t.TempDir()
124+
logDir := t.TempDir()
125+
t.Setenv("RASCAL_SYNC_LOG_DIR", logDir)
126+
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
127+
128+
writeFakeExe(t, filepath.Join(binDir, "ssh"), `#!/usr/bin/env bash
129+
set -euo pipefail
130+
log_dir="${RASCAL_SYNC_LOG_DIR:?}"
131+
printf '%s\n' "$*" >> "$log_dir/ssh_calls.log"
132+
exit 0
133+
`)
134+
135+
writeFakeExe(t, filepath.Join(binDir, "scp"), `#!/usr/bin/env bash
136+
set -euo pipefail
137+
log_dir="${RASCAL_SYNC_LOG_DIR:?}"
138+
printf '%s\n' "$*" >> "$log_dir/scp_calls.log"
139+
src="${@: -2:1}"
140+
cp "$src" "$log_dir/scp_payload.env"
141+
exit 0
142+
`)
143+
144+
return logDir
145+
}
146+
147+
func writeFakeExe(t *testing.T, path, body string) {
148+
t.Helper()
149+
if err := os.WriteFile(path, []byte(body), 0o755); err != nil {
150+
t.Fatalf("write fake executable %s: %v", path, err)
151+
}
152+
}
153+

0 commit comments

Comments
 (0)