ci / Go (lint, test, vuln) (push) Successful in 1m58s
ci / Development environment (compose) (push) Failing after 14s
ci / Protobuf (push) Successful in 6s
ci / Web (lint, test, audit, build) (push) Successful in 32s
ci / Shell and licence headers (push) Successful in 6s
ci / Release build (dry run) (push) Successful in 1m12s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
84 lines
2.7 KiB
Go
84 lines
2.7 KiB
Go
// SPDX-License-Identifier: LicenseRef-DDI-Proprietary
|
|
|
|
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
)
|
|
|
|
func get(t *testing.T, h http.Handler, target string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, target, nil))
|
|
return rec
|
|
}
|
|
|
|
func builtFS() fstest.MapFS {
|
|
return fstest.MapFS{
|
|
"index.html": {Data: []byte("<html>app</html>")},
|
|
"favicon.svg": {Data: []byte("<svg/>")},
|
|
"assets/index-abc.js": {Data: []byte("console.log(1)")},
|
|
}
|
|
}
|
|
|
|
func TestServesIndexForRootAndClientRoutes(t *testing.T) {
|
|
h := NewHandler(builtFS())
|
|
for _, target := range []string{"/", "/index.html", "/networks", "/networks/10.0.0.0/addresses"} {
|
|
rec := get(t, h, target)
|
|
if rec.Code != http.StatusOK || rec.Body.String() != "<html>app</html>" {
|
|
t.Errorf("%s: got %d %q", target, rec.Code, rec.Body.String())
|
|
}
|
|
if rec.Header().Get("Cache-Control") != "no-cache" {
|
|
t.Errorf("%s: index must be revalidated, got %q", target, rec.Header().Get("Cache-Control"))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestServesAssetsImmutable(t *testing.T) {
|
|
rec := get(t, NewHandler(builtFS()), "/assets/index-abc.js")
|
|
if rec.Code != http.StatusOK || rec.Body.String() != "console.log(1)" {
|
|
t.Fatalf("got %d %q", rec.Code, rec.Body.String())
|
|
}
|
|
if !strings.Contains(rec.Header().Get("Cache-Control"), "immutable") {
|
|
t.Errorf("hashed asset should be immutable, got %q", rec.Header().Get("Cache-Control"))
|
|
}
|
|
}
|
|
|
|
func TestMissingFileWithExtensionIs404(t *testing.T) {
|
|
if rec := get(t, NewHandler(builtFS()), "/assets/missing.js"); rec.Code != http.StatusNotFound {
|
|
t.Errorf("status = %d, want 404", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestPathTraversalStaysInside(t *testing.T) {
|
|
rec := get(t, NewHandler(builtFS()), "/../../etc/passwd")
|
|
if rec.Code != http.StatusOK || rec.Body.String() != "<html>app</html>" {
|
|
t.Errorf("traversal must resolve inside the FS, got %d %q", rec.Code, rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestNotBuiltPage(t *testing.T) {
|
|
h := NewHandler(fstest.MapFS{".gitkeep": {}})
|
|
for _, target := range []string{"/", "/index.html", "/networks"} {
|
|
rec := get(t, h, target)
|
|
if rec.Code != http.StatusServiceUnavailable || !strings.Contains(rec.Body.String(), "make web") {
|
|
t.Errorf("%s: got %d %q", target, rec.Code, rec.Body.String())
|
|
}
|
|
}
|
|
if rec := get(t, h, "/assets/app.js"); rec.Code != http.StatusNotFound {
|
|
t.Errorf("missing asset: got %d, want 404", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestEmbeddedHandlerWorks(t *testing.T) {
|
|
// Works whether or not the frontend has been built.
|
|
rec := get(t, Handler(), "/")
|
|
if rec.Code != http.StatusOK && rec.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("unexpected status %d", rec.Code)
|
|
}
|
|
}
|