aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/web_test.go
diff options
context:
space:
mode:
authorgramanas <grm@eyesin.space>2026-09-12 11:24:17 +0300
committergramanas <grm@eyesin.space>2026-09-12 11:24:17 +0300
commit3eb04b1a2bdf9e53231fe862cfd76327371a9741 (patch)
treeb38b2d82a47233fd8e0bb18c59e4a8f3dd2412d7 /internal/web/web_test.go
downloadblogspace-3eb04b1a2bdf9e53231fe862cfd76327371a9741.tar.gz
blogspace-3eb04b1a2bdf9e53231fe862cfd76327371a9741.tar.bz2
blogspace-3eb04b1a2bdf9e53231fe862cfd76327371a9741.zip
Initial multi-tenant blog host
Go + Postgres application serving a management dashboard on the base domain and one public blog per subdomain. Markdown posts organised in pages, form-based theme customisation, image uploads stored in Postgres, JWT cookie sessions with CSRF, superadmin user management, RSS feeds. Docker/compose deployment and a Makefile-driven dev environment with seed data. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web/web_test.go')
-rw-r--r--internal/web/web_test.go73
1 files changed, 73 insertions, 0 deletions
diff --git a/internal/web/web_test.go b/internal/web/web_test.go
new file mode 100644
index 0000000..42e37a7
--- /dev/null
+++ b/internal/web/web_test.go
@@ -0,0 +1,73 @@
+package web
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+
+ "github.com/gramanas/blogspace/internal/config"
+)
+
+func TestHostname(t *testing.T) {
+ cases := map[string]string{"Example.com:8080": "example.com", "a.example.com": "a.example.com", "example.com.": "example.com", "[::1]:80": "::1"}
+ for in, want := range cases {
+ if got := hostname(in); got != want {
+ t.Errorf("hostname(%q) = %q, want %q", in, got, want)
+ }
+ }
+}
+
+// Host routing that does not need the database: unknown hosts and reserved/nested subdomains 404 before any lookup.
+func TestHostRoutingWithoutDB(t *testing.T) {
+ cfg := &config.Config{BaseDomain: "example.com", JWTSecret: []byte("x"), MaxUploadBytes: 1 << 20}
+ s := NewServer(cfg, nil)
+ for _, host := range []string{"evil.com", "a.b.example.com", "www.blog.example.com", "static.example.com", "notexample.com"} {
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest("GET", "/", nil)
+ req.Host = host
+ s.ServeHTTP(rec, req)
+ if rec.Code != http.StatusNotFound {
+ t.Errorf("host %q: got %d, want 404", host, rec.Code)
+ }
+ }
+ // root domain (and www) reach the management mux: /login renders without DB access
+ for _, host := range []string{"example.com", "www.example.com", "example.com:8080"} {
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest("GET", "/login", nil)
+ req.Host = host
+ s.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), "Log in") {
+ t.Errorf("host %q /login: got %d", host, rec.Code)
+ }
+ }
+}
+
+func TestThemeNormalizeAndCSS(t *testing.T) {
+ th := ParseTheme([]byte(`{"bg_color":"red;}body{display:none","font":"comic","bg_image":"../etc","nav_position":"left-sidebar"}`))
+ if th.BgColor != DefaultTheme().BgColor || th.Font != "sans" || th.BgImage != "" || th.NavPosition != "left-sidebar" {
+ t.Errorf("normalize: %+v", th)
+ }
+ f := url.Values{"bg_color": {"#ABCDEF"}, "content_width": {"wide"}, "header_show_title": {"on"}, "bg_image": {"not-a-uuid"}}
+ th = ThemeFromForm(DefaultTheme(), f)
+ if th.BgColor != "#abcdef" || th.ContentWidth != "wide" || !th.HeaderShowTitle || th.BgImage != "" {
+ t.Errorf("from form: %+v", th)
+ }
+ css := th.CSS()
+ if !strings.Contains(css, "background-color:#abcdef") || !strings.Contains(css, "max-width:1100px") || strings.Contains(css, "display:none") {
+ t.Errorf("css: %s", css)
+ }
+}
+
+func TestAllTemplatesParse(t *testing.T) {
+ tpl := newTemplates(false, funcs)
+ for _, name := range []string{"auth/login.html", "dashboard/overview.html", "dashboard/pages.html", "dashboard/page_form.html",
+ "dashboard/posts.html", "dashboard/post_form.html", "dashboard/design.html", "dashboard/images.html", "dashboard/settings.html",
+ "dashboard/password.html", "dashboard/confirm.html", "admin/index.html", "admin/new_user.html", "admin/delete_user.html",
+ "blog/page.html", "blog/post.html", "blog/404.html"} {
+ if _, err := tpl.get(name); err != nil {
+ t.Errorf("%s: %v", name, err)
+ }
+ }
+}