244 lines
6.1 KiB
Go
244 lines
6.1 KiB
Go
package agenthttp
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
"zlh-agent/internal/state"
|
|
)
|
|
|
|
func TestProtectedRoutesRequireAgentToken(t *testing.T) {
|
|
t.Setenv("ZLH_AGENT_TOKEN", "test-token")
|
|
|
|
protected := []struct {
|
|
method string
|
|
path string
|
|
body string
|
|
}{
|
|
{http.MethodGet, "/status", ""},
|
|
{http.MethodGet, "/ready", ""},
|
|
{http.MethodPost, "/config", "{}"},
|
|
{http.MethodPost, "/start", ""},
|
|
{http.MethodPost, "/stop", ""},
|
|
{http.MethodPost, "/restart", ""},
|
|
{http.MethodGet, "/game/files/list", ""},
|
|
{http.MethodGet, "/game/files/read", ""},
|
|
{http.MethodGet, "/game/files/download", ""},
|
|
{http.MethodPost, "/game/files/upload", ""},
|
|
{http.MethodPost, "/game/files/revert", ""},
|
|
{http.MethodGet, "/game/files/stat", ""},
|
|
{http.MethodGet, "/game/backups", ""},
|
|
{http.MethodPost, "/game/backups", ""},
|
|
{http.MethodPost, "/game/backups/restore", `{"id":"backup-1"}`},
|
|
{http.MethodDelete, "/game/backups/backup-1", ""},
|
|
{http.MethodGet, "/game/mods", ""},
|
|
{http.MethodPost, "/game/mods/install", "{}"},
|
|
{http.MethodPost, "/game/mods/mod-1", "{}"},
|
|
{http.MethodPost, "/agent/update", "{}"},
|
|
{http.MethodGet, "/agent/update/status", ""},
|
|
{http.MethodGet, "/metrics/process", ""},
|
|
{http.MethodPost, "/dev/codeserver/start", ""},
|
|
{http.MethodPost, "/dev/codeserver/stop", ""},
|
|
{http.MethodPost, "/dev/codeserver/restart", ""},
|
|
}
|
|
|
|
mux := NewMux()
|
|
for _, tc := range protected {
|
|
t.Run(tc.method+" "+tc.path, func(t *testing.T) {
|
|
req := httptest.NewRequest(tc.method, tc.path, strings.NewReader(tc.body))
|
|
rec := httptest.NewRecorder()
|
|
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Fatalf("without token status = %d, want %d", rec.Code, http.StatusUnauthorized)
|
|
}
|
|
|
|
req = httptest.NewRequest(tc.method, tc.path, strings.NewReader(tc.body))
|
|
req.Header.Set("Authorization", "Bearer test-token")
|
|
rec = httptest.NewRecorder()
|
|
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code == http.StatusUnauthorized {
|
|
t.Fatalf("with token status = %d, want authorized request to reach handler", rec.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProtectedRoutesFailClosedWhenAgentTokenMissing(t *testing.T) {
|
|
t.Setenv("ZLH_AGENT_TOKEN", "")
|
|
|
|
mux := NewMux()
|
|
req := httptest.NewRequest(http.MethodGet, "/status", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusUnauthorized)
|
|
}
|
|
}
|
|
|
|
func TestPublicRoutesDoNotRequireAgentToken(t *testing.T) {
|
|
t.Setenv("ZLH_AGENT_TOKEN", "")
|
|
|
|
mux := NewMux()
|
|
for _, path := range []string{"/health", "/version"} {
|
|
t.Run(path, func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, path, nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code == http.StatusUnauthorized {
|
|
t.Fatalf("status = %d, want public route", rec.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConsoleWebSocketRequiresAgentToken(t *testing.T) {
|
|
t.Setenv("ZLH_AGENT_TOKEN", "test-token")
|
|
|
|
server := httptest.NewServer(NewMux())
|
|
defer server.Close()
|
|
|
|
conn, resp, err := websocket.DefaultDialer.Dial(strings.Replace(server.URL, "http://", "ws://", 1)+"/console/stream", nil)
|
|
if conn != nil {
|
|
_ = conn.Close()
|
|
}
|
|
if err == nil {
|
|
t.Fatalf("websocket dial without token succeeded")
|
|
}
|
|
if resp == nil {
|
|
t.Fatalf("websocket dial response is nil: %v", err)
|
|
}
|
|
if resp.StatusCode != http.StatusUnauthorized {
|
|
t.Fatalf("status = %d, want %d", resp.StatusCode, http.StatusUnauthorized)
|
|
}
|
|
}
|
|
|
|
func TestEnsureDevCodeServerInstallsAndStartsWhenRequested(t *testing.T) {
|
|
restoreCodeServerTestHooks(t)
|
|
|
|
cfg := &state.Config{
|
|
ContainerType: "dev",
|
|
Runtime: "node",
|
|
EnableCodeServer: true,
|
|
}
|
|
|
|
installed := false
|
|
running := false
|
|
installCalls := 0
|
|
startCalls := 0
|
|
verifyCalls := 0
|
|
|
|
codeServerInstalled = func() bool { return installed }
|
|
codeServerRunning = func() bool { return running }
|
|
codeServerInstall = func(state.Config) error {
|
|
installCalls++
|
|
installed = true
|
|
return nil
|
|
}
|
|
codeServerStart = func(state.Config) error {
|
|
startCalls++
|
|
running = true
|
|
return nil
|
|
}
|
|
codeServerVerify = func() error {
|
|
verifyCalls++
|
|
return nil
|
|
}
|
|
|
|
if err := ensureDevCodeServer(cfg); err != nil {
|
|
t.Fatalf("ensureDevCodeServer: %v", err)
|
|
}
|
|
if installCalls != 1 {
|
|
t.Fatalf("installCalls = %d, want 1", installCalls)
|
|
}
|
|
if startCalls != 1 {
|
|
t.Fatalf("startCalls = %d, want 1", startCalls)
|
|
}
|
|
if verifyCalls != 1 {
|
|
t.Fatalf("verifyCalls = %d, want 1", verifyCalls)
|
|
}
|
|
}
|
|
|
|
func TestEnsureDevCodeServerStartsInstalledStoppedAddon(t *testing.T) {
|
|
restoreCodeServerTestHooks(t)
|
|
|
|
cfg := &state.Config{
|
|
ContainerType: "dev",
|
|
Runtime: "node",
|
|
Addons: []string{"codeserver"},
|
|
}
|
|
|
|
running := false
|
|
installCalls := 0
|
|
startCalls := 0
|
|
|
|
codeServerInstalled = func() bool { return true }
|
|
codeServerRunning = func() bool { return running }
|
|
codeServerInstall = func(state.Config) error {
|
|
installCalls++
|
|
return nil
|
|
}
|
|
codeServerStart = func(state.Config) error {
|
|
startCalls++
|
|
running = true
|
|
return nil
|
|
}
|
|
codeServerVerify = func() error { return nil }
|
|
|
|
if err := ensureDevCodeServer(cfg); err != nil {
|
|
t.Fatalf("ensureDevCodeServer: %v", err)
|
|
}
|
|
if installCalls != 0 {
|
|
t.Fatalf("installCalls = %d, want 0", installCalls)
|
|
}
|
|
if startCalls != 1 {
|
|
t.Fatalf("startCalls = %d, want 1", startCalls)
|
|
}
|
|
}
|
|
|
|
func TestEnsureDevCodeServerSkipsWhenNotRequested(t *testing.T) {
|
|
restoreCodeServerTestHooks(t)
|
|
|
|
called := false
|
|
codeServerInstalled = func() bool {
|
|
called = true
|
|
return false
|
|
}
|
|
|
|
if err := ensureDevCodeServer(&state.Config{ContainerType: "dev", Runtime: "node"}); err != nil {
|
|
t.Fatalf("ensureDevCodeServer: %v", err)
|
|
}
|
|
if called {
|
|
t.Fatalf("code-server hooks were called for config without code-server request")
|
|
}
|
|
}
|
|
|
|
func restoreCodeServerTestHooks(t *testing.T) {
|
|
t.Helper()
|
|
|
|
oldInstall := codeServerInstall
|
|
oldStart := codeServerStart
|
|
oldVerify := codeServerVerify
|
|
oldInstalled := codeServerInstalled
|
|
oldRunning := codeServerRunning
|
|
|
|
t.Cleanup(func() {
|
|
codeServerInstall = oldInstall
|
|
codeServerStart = oldStart
|
|
codeServerVerify = oldVerify
|
|
codeServerInstalled = oldInstalled
|
|
codeServerRunning = oldRunning
|
|
})
|
|
}
|