Files
RenewCMS/test/update_dispatch_test.go
T

187 lines
5.7 KiB
Go

package test
import (
"RenewCMS/internal/domain/update"
"bytes"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
func runBinary(t *testing.T, binary string, workDir string, args ...string) (string, string, int) {
t.Helper()
command := exec.Command(binary, args...)
command.Dir = workDir
command.Env = emptyEnvironment()
var stdout, stderr bytes.Buffer
command.Stdout, command.Stderr = &stdout, &stderr
err := command.Run()
code := 0
if exitErr, ok := err.(*exec.ExitError); ok {
code = exitErr.ExitCode()
} else if err != nil {
t.Fatal(err)
}
return stdout.String(), stderr.String(), code
}
func assertEmptyDirectory(t *testing.T, directory string) {
t.Helper()
if entries, _ := os.ReadDir(directory); len(entries) != 0 {
t.Errorf("the working directory must stay empty, got %d entries", len(entries))
}
}
func TestCommandDispatcher(t *testing.T) {
binary := buildServerBinary(t, "-X main.version=v9.9.9")
t.Run("--version prints the version and exits 0", func(t *testing.T) {
workDir := t.TempDir()
stdout, _, code := runBinary(t, binary, workDir, "--version")
if code != 0 || strings.TrimSpace(stdout) != "v9.9.9" {
t.Errorf("code = %d, stdout = %q", code, stdout)
}
assertEmptyDirectory(t, workDir)
})
t.Run("an unknown subcommand prints a usage message and does not start the server", func(t *testing.T) {
workDir := t.TempDir()
stdout, stderr, code := runBinary(t, binary, workDir, "frobnicate")
if code == 0 || !strings.Contains(stderr, "usage:") || stdout != "" {
t.Errorf("code = %d, stdout = %q, stderr = %q", code, stdout, stderr)
}
assertEmptyDirectory(t, workDir)
})
t.Run("apply-update runs without .env and creates no data directory", func(t *testing.T) {
workDir := t.TempDir()
_, stderr, code := runBinary(t, binary, workDir, "apply-update")
if code == 0 || strings.Contains(stderr, "PUBLIC_URL") || strings.Contains(stderr, ".env") {
t.Errorf("code = %d, stderr = %q", code, stderr)
}
assertEmptyDirectory(t, workDir)
})
t.Run("apply-update with its flags never reads .env or opens the database", func(t *testing.T) {
if runtime.GOOS == "linux" {
t.Skip("on Linux the helper would act on the machine")
}
workDir := t.TempDir()
_, stderr, _ := runBinary(t, binary, workDir, "apply-update", "--binary", filepath.Join(workDir, "bin"), "--data-dir", filepath.Join(workDir, "data"))
if strings.Contains(stderr, "PUBLIC_URL") {
t.Errorf("stderr = %q", stderr)
}
if _, err := os.Stat(filepath.Join(workDir, "data")); err == nil {
t.Error("no data directory may be created")
}
})
t.Run("enable-browser-updates and disable-browser-updates refuse off Linux", func(t *testing.T) {
if runtime.GOOS == "linux" {
t.Skip("on Linux these commands would act on the machine's systemd")
}
workDir := t.TempDir()
_, stderr, code := runBinary(t, binary, workDir, "enable-browser-updates")
stdout, _, disableCode := runBinary(t, binary, workDir, "disable-browser-updates")
if code == 0 || strings.TrimSpace(stderr) != update.ErrNeedsLinux.Error() {
t.Errorf("enable: code = %d, stderr = %q", code, stderr)
}
if disableCode != 0 || !strings.Contains(stdout, "not enabled") {
t.Errorf("disable: code = %d, stdout = %q", disableCode, stdout)
}
assertEmptyDirectory(t, workDir)
})
t.Run("no arguments is the normal server start", func(t *testing.T) {
workDir := t.TempDir()
_, stderr, code := runBinary(t, binary, workDir)
if code == 0 || !strings.Contains(stderr, "PUBLIC_URL") {
t.Errorf("code = %d, stderr = %q", code, stderr)
}
})
}
func freeTCPPort(t *testing.T) string {
t.Helper()
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
return fmt.Sprint(listener.Addr().(*net.TCPAddr).Port)
}
func TestServerWritesRunningInfoBeforeServing(t *testing.T) {
if os.Getenv("RENEWCMS_TEST_SPAWN_SERVER") == "" {
t.Skip("spawns the real server, which triggers Windows firewall prompts; set RENEWCMS_TEST_SPAWN_SERVER=1 to run")
}
binary := buildServerBinary(t, "-X main.version=v9.9.9")
workDir := t.TempDir()
dataDir := filepath.Join(workDir, "data")
port := freeTCPPort(t)
command := exec.Command(binary)
command.Dir = workDir
command.Env = append(emptyEnvironment(),
"ENVIRONMENT=development", "PORT="+port, "PUBLIC_URL=http://localhost", "JWT_SECRET=test-secret-for-dispatch",
"SMTP_LOGIN=a@example.com", "SMTP_SENDER=a@example.com", "SMTP_PASSWORD=x", "SMTP_HOST=localhost", "SMTP_PORT=2525",
"DB_FILE="+filepath.Join(dataDir, "renewcms.db"), "UPLOAD_DIR="+filepath.Join(dataDir, "uploads"),
"UPDATE_FEED_URL=https://git.example")
var stderr bytes.Buffer
command.Stderr = &stderr
started := time.Now()
if err := command.Start(); err != nil {
t.Fatal(err)
}
defer func() {
_ = command.Process.Kill()
_, _ = command.Process.Wait()
}()
healthy := false
for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
response, err := http.Get("http://127.0.0.1:" + port + "/healthz")
if err == nil {
response.Body.Close()
healthy = response.StatusCode == http.StatusOK
if healthy {
break
}
}
}
if !healthy {
t.Fatalf("the server never answered /healthz: %s", stderr.String())
}
content, err := os.ReadFile(filepath.Join(dataDir, update.RunningInfoFileName))
if err != nil {
t.Fatalf("update-running.json must exist as soon as /healthz answers: %v", err)
}
var info update.RunningInfo
if err := json.Unmarshal(content, &info); err != nil {
t.Fatal(err)
}
if info.Version != "v9.9.9" || info.Pid != command.Process.Pid || info.StartedAt.Before(started.Add(-2*time.Second)) || info.StartedAt.After(time.Now()) {
t.Errorf("info = %+v, pid = %d", info, command.Process.Pid)
}
}