Files
summercms/modules/surf/serve_test.go
Jakub Zych da21670870 feat(surf): print the serve URL and admin status on startup
- Add surf.Router.AdminPrefix (additive): the admin mount path and true
  when BuildRouter mounted the cabana admin, false otherwise
- serve binds with net.Listen before printing, so a busy port fails
  without a misleading URL, then prints the listen address with a
  browser URL and an admin status line: the admin URL, a warning naming
  summer admin:create when backend_users is empty or missing, or
  admin: off with the steps to add an admin controller
- The administrator count is bounded by a 2 s timeout and never fails boot
- route:list ends with the same admin status line
- surf and cabana READMEs and the console and routing docs describe the output
2026-10-08 16:36:21 +02:00

125 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)
}
}
}
func TestServeBanner(t *testing.T) {
listen := bannerLine{text: "listening on :8123, open http://127.0.0.1:8123/"}
mounted := bannerLine{text: "admin: http://127.0.0.1:8123/backend"}
create := "summer admin:create --email you@example.com --superuser"
for _, tc := range []struct {
name string
addr string
prefix string
mounted bool
admins adminPresence
want []bannerLine
}{
{"present", ":8123", "/backend", true, adminsPresent, []bannerLine{listen, mounted}},
{"none", ":8123", "/backend", true, adminsNone, []bannerLine{listen, mounted,
{warn: true, text: "no administrators yet; create one with " + create}}},
{"missing table", ":8123", "/backend", true, adminsMissingTable, []bannerLine{listen, mounted,
{warn: true, text: "the admin tables do not exist yet; run summer migrate, then " + create}}},
{"unknown", ":8123", "/backend", true, adminsUnknown, []bannerLine{listen, mounted,
{text: "sign in with an administrator; " + create + " creates one"}}},
{"off", ":8123", "", false, adminsUnknown, []bannerLine{listen,
{text: "admin: off — no plugin registers an admin controller"},
{text: "to add one, 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, []bannerLine{
{text: "listening on 8080"}, {text: "admin: mounted at /backend"}}},
} {
t.Run(tc.name, func(t *testing.T) {
got := serveBanner(tc.addr, tc.prefix, tc.mounted, tc.admins)
if !slices.Equal(got, tc.want) {
t.Fatalf("serveBanner:\n got %#v\nwant %#v", got, tc.want)
}
})
}
}
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)
}
}