Skip to content
File

Blob: ui/embed_test.go

go106 lines
1package ui
2 
3import (
4 "io/fs"
5 "net/http"
6 "net/http/httptest"
7 "net/url"
8 "regexp"
9 "strings"
10 "testing"
11)
12 
13func TestEmbeddedFrontends(t *testing.T) {
14 for _, app := range []struct {
15 name string
16 prefix string
17 page http.Handler
18 assets http.Handler
19 }{
20 {"client", "/ui/", ClientPage(), Assets()},
21 {"operator", "/_internal/ui/", OperatorPage(), Assets()},
22 } {
23 t.Run(app.name, func(t *testing.T) {
24 page := httptest.NewRecorder()
25 app.page.ServeHTTP(page, httptest.NewRequest(http.MethodGet, "/", nil))
26 if page.Code != http.StatusOK || !strings.Contains(page.Header().Get("Content-Type"), "text/html") {
27 t.Fatalf("page: %d %v", page.Code, page.Header())
28 }
29 if page.Header().Get("Cache-Control") != "no-cache" {
30 t.Fatal("HTML must revalidate to avoid stale asset references")
31 }
32 mounted := http.StripPrefix(strings.TrimSuffix(app.prefix, "/"), app.assets)
33 refs := regexp.MustCompile(`(?:src|href)="([^"]+)"`).FindAllStringSubmatch(page.Body.String(), -1)
34 if len(refs) < 2 {
35 t.Fatal("expected a script and stylesheet")
36 }
37 for _, ref := range refs {
38 if !strings.HasPrefix(ref[1], app.prefix) {
39 t.Fatalf("asset escaped its UI prefix: %s", ref[1])
40 }
41 response := httptest.NewRecorder()
42 mounted.ServeHTTP(response, httptest.NewRequest(http.MethodGet, ref[1], nil))
43 if response.Code != http.StatusOK || response.Body.Len() == 0 {
44 t.Fatalf("asset %s: status=%d bytes=%d", ref[1], response.Code, response.Body.Len())
45 }
46 if strings.HasSuffix(ref[1], ".css") {
47 base, err := url.Parse("http://preview" + ref[1])
48 if err != nil {
49 t.Fatal(err)
50 }
51 for _, font := range regexp.MustCompile(`url\(([^)]+\.woff2[^)]*)\)`).FindAllStringSubmatch(response.Body.String(), -1) {
52 fontURL, err := base.Parse(strings.Trim(font[1], "\"'"))
53 if err != nil {
54 t.Fatal(err)
55 }
56 got := httptest.NewRecorder()
57 mounted.ServeHTTP(got, httptest.NewRequest(http.MethodGet, fontURL.String(), nil))
58 if got.Code != http.StatusOK || !strings.HasPrefix(got.Body.String(), "wOF2") {
59 t.Fatalf("CSS font URL %s did not serve WOFF2", fontURL)
60 }
61 }
62 }
63 }
64 // All generated static assets remain reachable beneath either mount.
65 err := fs.WalkDir(output, "dist", func(name string, entry fs.DirEntry, err error) error {
66 if err != nil {
67 return err
68 }
69 if entry.IsDir() {
70 return nil
71 }
72 name = strings.TrimPrefix(name, "dist/")
73 if !strings.HasPrefix(name, "assets/") && !strings.HasPrefix(name, "fonts/") {
74 return nil
75 }
76 response := httptest.NewRecorder()
77 mounted.ServeHTTP(response, httptest.NewRequest(http.MethodGet, app.prefix+name, nil))
78 if response.Code != http.StatusOK {
79 t.Errorf("build asset %s: %d", name, response.Code)
80 }
81 return nil
82 })
83 if err != nil {
84 t.Fatal(err)
85 }
86 
87 head := httptest.NewRecorder()
88 app.page.ServeHTTP(head, httptest.NewRequest(http.MethodHead, "/", nil))
89 if head.Code != http.StatusOK || head.Body.Len() != 0 {
90 t.Fatal("HEAD should return an empty successful response")
91 }
92 })
93 }
94}
95 
96func TestUIAssetsExposeOnlyStaticBuildFiles(t *testing.T) {
97 handler := Assets()
98 for _, name := range []string{"operator/index.html", "client/index.html", ".vite/manifest.json", "assets/../client/index.html", "client/main.ts", "fonts/", "assets/", ""} {
99 response := httptest.NewRecorder()
100 handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/"+name, nil))
101 if response.Code != http.StatusNotFound {
102 t.Errorf("unexpected access to %q: %d", name, response.Code)
103 }
104 }
105}