main_desktop_test.go
Rama main · réplica de lectura del repositorio original.
//go:build desktop
package main
import (
"context"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync/atomic"
"testing"
"testing/fstest"
"k8s.io/klog/v2"
"github.com/sophotechlabs/spinoza/internal/server"
)
func preserveDesktopLogger(t *testing.T) {
t.Helper()
previous := slog.Default()
t.Cleanup(func() {
slog.SetDefault(previous)
klog.SetSlogLogger(previous)
})
}
func useDesktopArgs(t *testing.T, args ...string) {
t.Helper()
original := os.Args
os.Args = append([]string{"spinoza"}, args...)
t.Cleanup(func() {
os.Args = original
})
}
func useDesktopConfigDir(t *testing.T) string {
t.Helper()
root := t.TempDir()
t.Setenv("HOME", root)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(root, "config"))
t.Setenv("AppData", filepath.Join(root, "config"))
dir, err := os.UserConfigDir()
if err != nil {
t.Fatalf("config dir: %v", err)
}
if !strings.HasPrefix(dir, root) {
t.Fatalf("config dir %q is outside the test's own %q", dir, root)
}
return dir
}
func TestRunDesktopReportsAnInvalidLogLevel(t *testing.T) {
useDesktopConfigDir(t)
useDesktopArgs(t, "-log-level", "verbose")
err := runDesktop()
if err == nil || !strings.Contains(err.Error(), "log level") {
t.Fatalf("error = %v, want the invalid log level", err)
}
}
func TestRunDesktopReportsAnUnreadableKubeconfig(t *testing.T) {
preserveDesktopLogger(t)
config := filepath.Join(t.TempDir(), "missing-kubeconfig")
useDesktopConfigDir(t)
useDesktopArgs(t, "-kubeconfig", config, "-context", "missing")
err := runDesktop()
if err == nil || !strings.Contains(err.Error(), "manager:") {
t.Fatalf("error = %v, want the manager startup failure", err)
}
if !strings.Contains(err.Error(), config) {
t.Fatalf("error = %q, want kubeconfig %q named", err, config)
}
}
func TestRunDesktopReportsWhenItsTokenCannotBeWritten(t *testing.T) {
preserveDesktopLogger(t)
configDir := t.TempDir()
config := filepath.Join(configDir, "missing-kubeconfig")
token := filepath.Join(configDir, "missing", "access-token")
useDesktopConfigDir(t)
useDesktopArgs(t, "-kubeconfig", config, "-token-file", token)
err := runDesktop()
if err == nil || !strings.Contains(err.Error(), "token file:") {
t.Fatalf("error = %v, want the token file failure", err)
}
}
func TestBlankPageIsACompleteHTMLResponse(t *testing.T) {
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "http://desktop/", http.NoBody)
blankPage().ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
}
if got := recorder.Header().Get("Content-Type"); got != "text/html; charset=utf-8" {
t.Fatalf("content type = %q", got)
}
if got := recorder.Body.String(); got != "<!doctype html><title>Spinoza</title>" {
t.Fatalf("body = %q", got)
}
}
func TestLogFileCreatesThePrivateDesktopLog(t *testing.T) {
config := useDesktopConfigDir(t)
held, openErr := logFile()
if openErr != nil {
t.Fatalf("log file: %v", openErr)
}
path := held.Name()
if _, writeErr := held.WriteString("started\n"); writeErr != nil {
t.Fatalf("write log: %v", writeErr)
}
if closeErr := held.Close(); closeErr != nil {
t.Fatalf("close log: %v", closeErr)
}
want := filepath.Join(config, "spinoza", "desktop.log")
if path != want {
t.Fatalf("path = %q, want %q", path, want)
}
info, err := os.Stat(path)
if err != nil {
t.Fatalf("stat log: %v", err)
}
if got := info.Mode().Perm(); got != 0o600 {
t.Fatalf("mode = %o, want 600", got)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read log: %v", err)
}
if string(body) != "started\n" {
t.Fatalf("body = %q", body)
}
}
func TestLogFileReportsWhenItsDirectoryCannotBeCreated(t *testing.T) {
config := useDesktopConfigDir(t)
if err := os.MkdirAll(filepath.Dir(config), 0o755); err != nil {
t.Fatalf("seed config parent: %v", err)
}
if err := os.WriteFile(config, []byte("occupied"), 0o600); err != nil {
t.Fatalf("seed config path: %v", err)
}
_, err := logFile()
if err == nil || !strings.Contains(err.Error(), "log:") {
t.Fatalf("error = %v, want the log path failure", err)
}
}
func TestLogFileReportsWhenTheLogPathIsADirectory(t *testing.T) {
config := useDesktopConfigDir(t)
path := filepath.Join(config, "spinoza", "desktop.log")
if err := os.MkdirAll(path, 0o755); err != nil {
t.Fatalf("seed log directory: %v", err)
}
_, err := logFile()
if err == nil || !strings.Contains(err.Error(), "log:") {
t.Fatalf("error = %v, want the open failure", err)
}
}
func TestStartLoggingKeepsAFileWhenOneCanBeCreated(t *testing.T) {
preserveDesktopLogger(t)
useDesktopConfigDir(t)
kept := startLogging(slog.LevelInfo, logText)
if kept == nil {
t.Fatal("logging did not keep the file it created")
}
if err := kept.Close(); err != nil {
t.Fatalf("close log: %v", err)
}
}
func TestStartLoggingFallsBackWhenNoConfigDirectoryExists(t *testing.T) {
preserveDesktopLogger(t)
t.Setenv("XDG_CONFIG_HOME", "")
t.Setenv("HOME", "")
read, write, pipeErr := os.Pipe()
if pipeErr != nil {
t.Fatalf("capture stderr: %v", pipeErr)
}
original := os.Stderr
os.Stderr = write
t.Cleanup(func() {
os.Stderr = original
})
kept := startLogging(slog.LevelInfo, logText)
if closeErr := write.Close(); closeErr != nil {
t.Fatalf("close stderr: %v", closeErr)
}
os.Stderr = original
warning, readErr := io.ReadAll(read)
if readErr != nil {
t.Fatalf("read stderr: %v", readErr)
}
if closeErr := read.Close(); closeErr != nil {
t.Fatalf("close captured stderr: %v", closeErr)
}
if kept != nil {
_ = kept.Close()
t.Fatal("logging kept a file without a config directory")
}
if !strings.Contains(string(warning), "this run will not be written to a log file") {
t.Fatalf("warning = %q", warning)
}
}
func TestRollLogLeavesAMissingFileAlone(t *testing.T) {
path := filepath.Join(t.TempDir(), "desktop.log")
rollLog(path)
if _, err := os.Stat(path + ".1"); !os.IsNotExist(err) {
t.Fatalf("rolled file error = %v, want it not to exist", err)
}
}
func TestRollLogLeavesASmallFileInPlace(t *testing.T) {
path := filepath.Join(t.TempDir(), "desktop.log")
if err := os.WriteFile(path, []byte("one run"), 0o600); err != nil {
t.Fatalf("write log: %v", err)
}
rollLog(path)
if _, err := os.Stat(path); err != nil {
t.Fatalf("original log: %v", err)
}
if _, err := os.Stat(path + ".1"); !os.IsNotExist(err) {
t.Fatalf("rolled file error = %v, want it not to exist", err)
}
}
func TestRollLogMovesAFileAtTheLimit(t *testing.T) {
path := filepath.Join(t.TempDir(), "desktop.log")
held, createErr := os.Create(path)
if createErr != nil {
t.Fatalf("create log: %v", createErr)
}
if truncateErr := held.Truncate(logFileLimit); truncateErr != nil {
_ = held.Close()
t.Fatalf("size log: %v", truncateErr)
}
if closeErr := held.Close(); closeErr != nil {
t.Fatalf("close log: %v", closeErr)
}
rollLog(path)
if _, statErr := os.Stat(path); !os.IsNotExist(statErr) {
t.Fatalf("original file error = %v, want it moved", statErr)
}
info, statErr := os.Stat(path + ".1")
if statErr != nil {
t.Fatalf("rolled log: %v", statErr)
}
if info.Size() != logFileLimit {
t.Fatalf("rolled size = %d, want %d", info.Size(), logFileLimit)
}
}
func TestKubeDirectoryUsesTheCurrentHome(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
got := kubeDirectory()
want := filepath.Join(home, ".kube")
if got != want {
t.Fatalf("directory = %q, want %q", got, want)
}
}
func TestKubeDirectoryIsEmptyWithoutAHome(t *testing.T) {
t.Setenv("HOME", "")
if got := kubeDirectory(); got != "" {
t.Fatalf("directory = %q, want empty", got)
}
}
func TestFilePickerRefusesARequestBeforeTheWindowStarts(t *testing.T) {
var window atomic.Pointer[context.Context]
_, err := filePicker(&window)(t.Context())
want := "the spinoza window is not ready yet"
if err == nil || err.Error() != want {
t.Fatalf("error = %v, want %q", err, want)
}
}
func TestLocalShellStartsAtTheRequestedSize(t *testing.T) {
shell, err := localShell(91, 37)
if err != nil {
t.Fatalf("start shell: %v", err)
}
if shell == nil {
t.Fatal("the local shell was nil")
}
t.Cleanup(shell.Close)
shell.Resize(92, 38)
}
func TestDesktopAssetsProxyBackendRequestsWithTheSessionToken(t *testing.T) {
seenToken := ""
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
seenToken = r.Header.Get(server.AuthHeader)
_, _ = w.Write([]byte("backend response"))
}))
t.Cleanup(backend.Close)
assets := fstest.MapFS{"index.html": {Data: []byte("<html><head></head></html>")}}
service := server.New(nil, assets, "desktop-token")
addr := strings.TrimPrefix(backend.URL, "http://")
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "http://desktop/api/overview", http.NoBody)
desktopAssets(service, assets, addr, "desktop-token").ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
}
if seenToken != "desktop-token" {
t.Fatalf("backend token = %q", seenToken)
}
if recorder.Body.String() != "backend response" {
t.Fatalf("body = %q", recorder.Body.String())
}
}
func TestDesktopIndexNamesItsBackendAndView(t *testing.T) {
assets := fstest.MapFS{"index.html": {Data: []byte("<html><head></head><body>Spinoza</body></html>")}}
service := server.New(nil, assets, "desktop-token")
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "http://desktop/", http.NoBody)
desktopAssets(service, assets, "127.0.0.1:4123", "desktop-token").ServeHTTP(recorder, request)
body := recorder.Body.String()
for _, want := range []string{
`window.__SPINOZA_WS_BASE__="ws://127.0.0.1:4123"`,
`window.__SPINOZA_TOKEN__="desktop-token"`,
`window.__SPINOZA_VIEW__="desktop"`,
} {
if !strings.Contains(body, want) {
t.Fatalf("body = %q, want %q", body, want)
}
}
if got := recorder.Header().Get("Content-Type"); got != "text/html; charset=utf-8" {
t.Fatalf("content type = %q", got)
}
}
func TestDesktopIndexReportsAMissingAsset(t *testing.T) {
assets := fstest.MapFS{"app.js": {Data: []byte("ready")}}
service := server.New(nil, assets, "desktop-token")
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "http://desktop/index.html", http.NoBody)
desktopAssets(service, assets, "127.0.0.1:4123", "desktop-token").ServeHTTP(recorder, request)
if recorder.Code != http.StatusInternalServerError {
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusInternalServerError)
}
if !strings.Contains(recorder.Body.String(), "index missing") {
t.Fatalf("body = %q", recorder.Body.String())
}
}
func TestDesktopAssetsServeStaticFilesWithoutProxying(t *testing.T) {
assets := fstest.MapFS{"app.js": {Data: []byte("ready")}}
service := server.New(nil, assets, "desktop-token")
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "http://desktop/app.js", http.NoBody)
desktopAssets(service, assets, "127.0.0.1:1", "desktop-token").ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
}
if recorder.Body.String() != "ready" {
t.Fatalf("body = %q", recorder.Body.String())
}
}
func TestDesktopBrowserRefusesBeforeTheWindowStarts(t *testing.T) {
assets := fstest.MapFS{"index.html": {Data: []byte("<html><head></head></html>")}}
service := server.New(nil, assets, "desktop-token")
t.Cleanup(service.Close)
var window atomic.Pointer[context.Context]
useViews(service, &window, "127.0.0.1:4123", "desktop-token")
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "http://127.0.0.1/api/view/browser", http.NoBody)
request.Header.Set(server.AuthHeader, "desktop-token")
service.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusInternalServerError {
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusInternalServerError)
}
if !strings.Contains(recorder.Body.String(), "window is not ready") {
t.Fatalf("body = %q", recorder.Body.String())
}
}
func TestDesktopViewCanBeSelectedBeforeWindowStartupCompletes(t *testing.T) {
assets := fstest.MapFS{"index.html": {Data: []byte("<html><head></head></html>")}}
service := server.New(nil, assets, "desktop-token")
t.Cleanup(service.Close)
var window atomic.Pointer[context.Context]
useViews(service, &window, "127.0.0.1:4123", "desktop-token")
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "http://127.0.0.1/api/view/desktop", http.NoBody)
request.Header.Set(server.AuthHeader, "desktop-token")
service.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
}
if !strings.Contains(recorder.Body.String(), `"switched":true`) {
t.Fatalf("body = %q, want the view switch confirmed", recorder.Body.String())
}
}