Files
summercms/modules/surf/serve_test.go
Jakub Zych c38dc1fe48 feat(surf): serve startup summary, shutdown report and http request log
- serve prints a startup summary on a terminal (version, environment, URL,
  admin, database host and name, pending migrations, plugins, drivers) and
  logs it as a 'startup summary' record plus 'listening on <addr>'
- on SIGINT/SIGTERM serve drains, prints and logs a shutdown report and
  returns nil (exit 0), or the drain errors (exit 1)
- the request log is on by default under diary component http with
  request_id; the route pattern, never the path, is logged
- lagoon.PendingMigrations, conga.Worker.Stats, diary.HideOnTTY,
  bonfire.IsTerminal and bonfire.Details back the summary and report
2026-10-21 12:59:07 +02:00

124 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package surf
import (
"bytes"
"os"
"path/filepath"
"slices"
"strings"
"testing"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/bonfire"
"git.golem15.com/golem15/summercms/modules/compass"
"gocloud.dev/blob"
)
func TestPublishUploadsRequiresURL(t *testing.T) {
app := serveTestApp(t, "uploads:\n bucket_url: \"\"\n")
_, err := publishUploads(t.Context(), app)
if err == nil || !strings.Contains(err.Error(), "bucket_url") {
t.Fatalf("got %v, want bucket_url error", err)
}
}
func TestPublishUploadsStoresBucket(t *testing.T) {
app := serveTestApp(t, "uploads:\n bucket_url: \"mem://\"\n public_path_prefix: \"/storage/uploads\"\n")
bucket, err := publishUploads(t.Context(), app)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = bucket.Close() })
got, ok := app.Lookup[*blob.Bucket]()
if !ok || got != bucket {
t.Fatal("publishUploads must store the opened *blob.Bucket")
}
}
func serveTestApp(t *testing.T, storageYAML string) *backpack.App {
t.Helper()
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("name: serve-uploads\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "storage.yaml"), []byte(storageYAML), 0o644); err != nil {
t.Fatal(err)
}
cfg, err := compass.Open(compass.Options{
Dir: dir,
Env: "development",
Environ: []string{"SUMMER_ENV=development"},
})
if err != nil {
t.Fatal(err)
}
return backpack.New(cfg)
}
func TestServeURL(t *testing.T) {
for _, tc := range []struct{ addr, want string }{
{":8123", "http://127.0.0.1:8123/"},
{"127.0.0.1:9000", "http://127.0.0.1:9000/"},
{"[::1]:8080", "http://[::1]:8080/"},
{"0.0.0.0:80", "http://127.0.0.1/"},
{"[::]:8080", "http://127.0.0.1:8080/"},
{"localhost:3000", "http://localhost:3000/"},
{"8080", ""},
{"", ""},
{"127.0.0.1:", ""},
} {
if got := serveURL(tc.addr); got != tc.want {
t.Errorf("serveURL(%q) = %q, want %q", tc.addr, got, tc.want)
}
}
}
// TestServeBanner checks the summary's Admin value and the notes printed
// after the summary for each admin state.
func TestServeBanner(t *testing.T) {
create := "summer admin:create --email you@example.com --superuser"
for _, tc := range []struct {
name string
addr string
prefix string
mounted bool
admins adminPresence
value string
notes []bannerLine
}{
{"present", ":8123", "/backend", true, adminsPresent, "http://127.0.0.1:8123/backend", nil},
{"none", ":8123", "/backend", true, adminsNone, "http://127.0.0.1:8123/backend", []bannerLine{
{warn: true, text: "no administrators yet; create one with " + create}}},
{"missing table", ":8123", "/backend", true, adminsMissingTable, "http://127.0.0.1:8123/backend", []bannerLine{
{warn: true, text: "the admin tables do not exist yet; run summer migrate, then " + create}}},
{"unknown", ":8123", "/backend", true, adminsUnknown, "http://127.0.0.1:8123/backend", []bannerLine{
{text: "sign in with an administrator; " + create + " creates one"}}},
{"off", ":8123", "", false, adminsUnknown, "off — no plugin registers an admin controller", []bannerLine{
{text: "to add an admin, run summer make:admin-controller <vendor.plugin> <Name>, then summer build, summer migrate and " + create + "; the admin needs SUMMER_ADMIN__JWT__SECRET, which summer new writes to .env"}}},
{"unparsable address", "8080", "/backend", true, adminsPresent, "mounted at /backend", nil},
} {
t.Run(tc.name, func(t *testing.T) {
value, notes := adminStatus(serveURL(tc.addr), tc.prefix, tc.mounted, tc.admins)
if value != tc.value || !slices.Equal(notes, tc.notes) {
t.Fatalf("adminStatus:\n got %q %#v\nwant %q %#v", value, notes, tc.value, tc.notes)
}
})
}
}
func TestPrintBanner(t *testing.T) {
var buf bytes.Buffer
out := bonfire.NewOutput(strings.NewReader(""), &buf, &buf)
printBanner(out, []bannerLine{{text: "plain"}, {warn: true, text: "careful"}})
got := buf.String()
if !strings.Contains(got, "ℹ plain") || !strings.Contains(got, "⚠ careful") {
t.Fatalf("printBanner output = %q", got)
}
}
func TestCountAdminsNilDB(t *testing.T) {
if got := countAdmins(t.Context(), nil); got != adminsUnknown {
t.Fatalf("countAdmins(nil) = %v, want adminsUnknown", got)
}
}