Files
inventory-plus-plus/server/ui/router_test.go
T
Claude 8b3a6e4666 Add golden-file HTML snapshot test for server/ui pages
Issue #33 asked how UI/UX tests should be run, including raw-HTML and
PNG snapshots. This adds the first raw-HTML-snapshot layer from the
proposal posted on that issue: an integration test that exercises
server/ui's real routing and templating path end-to-end (real
templates, real Store types, the actual response middleware) and diffs
the output against a golden file, so unintentional markup changes show
up as a reviewable diff.

Starts with a single case (the logged-out home page) to establish the
pattern; extending to authenticated pages via testdb.SeedOAuthSession
is noted in the test's doc comment. No golden file is committed yet -
it needs to be generated once, with TEST_DATABASE_URL set, by running
the test with its -update flag, then reviewed and committed; until
then the case skips rather than failing, matching this repo's
skip-gracefully convention for missing test infrastructure.
2026-08-21 22:18:54 +00:00

129 lines
4.1 KiB
Go

package ui_test
import (
"flag"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
"ruben/inventory2/domains/accounts"
"ruben/inventory2/domains/authentication"
etsy_platform "ruben/inventory2/domains/platforms/etsy"
"ruben/inventory2/domains/raw_events"
"ruben/inventory2/domains/reports"
"ruben/inventory2/internal/testdb"
"ruben/inventory2/server/auth"
"ruben/inventory2/server/response"
"ruben/inventory2/server/ui"
)
// update regenerates the golden files under testdata/golden from the
// current rendered output, instead of comparing against them. Review the
// diff like any other golden file before committing it, e.g.:
//
// TEST_DATABASE_URL=... go test ./server/ui/... -run TestServeTemplate_Golden -update
var update = flag.Bool("update", false, "update golden files in testdata/golden instead of comparing against them")
// TestServeTemplate_Golden is a raw-HTML snapshot/regression test: it
// exercises server/ui's real routing + templating path end-to-end (real
// templates, real Store types backed by a real Postgres connection, the
// same response.HandleResponses/HandleErrors middleware production uses -
// nothing about the render path is mocked) and diffs the rendered HTML
// against a committed golden file, so an unintentional template/markup
// change shows up as a reviewable diff instead of shipping silently.
//
// This is the "raw HTML snapshot" layer described in issue #33's UI-testing
// proposal. A browser-screenshot layer (e.g. chromedp, for catching CSS/
// layout regressions raw HTML can't) and Penpot-design-fidelity comparisons
// are separate, not-yet-implemented layers of that same proposal.
//
// Only covers logged-out pages for now (no seeded oauth session/cookie);
// extend the tests table below using testdb.SeedOAuthSession plus an
// "access_token" cookie on the request to cover authenticated pages.
//
// Like other integration tests in this repo, it's skipped (not failed) if
// TEST_DATABASE_URL is unset. If a golden file hasn't been generated yet,
// the individual case is also skipped (not failed) rather than breaking
// the suite - run with -update once, review the generated file, and commit
// it to turn that case into a real regression check.
func TestServeTemplate_Golden(t *testing.T) {
pool := testdb.Pool(t)
logger := testdb.Logger()
accts := accounts.NewStore(logger, pool)
rawEvents := raw_events.NewStore(logger, pool)
reps := reports.NewStore(logger, pool, accts)
etsy := etsy_platform.NewPlatform(
logger,
func(acctID int64) string { return "" },
"", "",
pool,
)
authr := authentication.NewDev(pool, logger)
authM := auth.NewService(logger, authr, accts)
gin.SetMode(gin.TestMode)
router := gin.New()
router.Use(
authM.Identify,
response.HandleResponses,
response.HandleErrors,
)
ui.Routes(
logger,
router.Group("/ui"),
"/ui",
rawEvents,
accts,
reps,
etsy,
authM.Authenticate(),
true,
)
tests := []struct {
name string
path string
wantStatus int
}{
{name: "home-logged-out", path: "/ui", wantStatus: http.StatusOK},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, tt.path, nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, tt.wantStatus, rec.Code, "unexpected status for %s", tt.path)
got := rec.Body.Bytes()
golden := filepath.Join("testdata", "golden", tt.name+".html")
if *update {
require.NoError(t, os.MkdirAll(filepath.Dir(golden), 0o755))
require.NoError(t, os.WriteFile(golden, got, 0o644))
return
}
want, err := os.ReadFile(golden)
if os.IsNotExist(err) {
t.Skipf(
"golden file %s does not exist yet; generate it once via `go test ./server/ui/... -run TestServeTemplate_Golden -update` (requires TEST_DATABASE_URL), then review and commit it",
golden,
)
}
require.NoError(t, err, "reading golden file %s", golden)
require.Equal(t, string(want), string(got), "rendered output for %s no longer matches %s; if this change is intentional, regenerate with -update", tt.path, golden)
})
}
}