| 1 | package sheets |
| 2 | |
| 3 | import ( |
| 4 | "io/fs" |
| 5 | "path" |
| 6 | |
| 7 | "amadan.net/oficina/estilo.git/busy" |
| 8 | "bytes" |
| 9 | "crypto/sha256" |
| 10 | "fmt" |
| 11 | "net/http" |
| 12 | "net/http/httptest" |
| 13 | "strings" |
| 14 | "testing" |
| 15 | |
| 16 | "amadan.net/oficina/estilo.git/theme" |
| 17 | "amadan.net/rastrillo/rastrillo/ui" |
| 18 | ) |
| 19 | |
| 20 | func TestSharedThemeAssetFingerprintAndCache(t *testing.T) { |
| 21 | app := New(Config{}) |
| 22 | page := do(t, app, httptest.NewRequest(http.MethodGet, "/signin", nil)) |
| 23 | if page.Code != http.StatusOK { |
| 24 | t.Fatalf("signin status %d", page.Code) |
| 25 | } |
| 26 | // Calma with Sheets' accent (estilo row 8), not rastrillo's day. |
| 27 | want, ok := theme.ForApp("sheets") |
| 28 | if !ok || !bytes.Equal(sheetsTheme, want) { |
| 29 | t.Fatal("Sheets does not serve estilo's theme for sheets") |
| 30 | } |
| 31 | for _, item := range []struct { |
| 32 | name string |
| 33 | data []byte |
| 34 | }{ |
| 35 | {"theme", sheetsTheme}, {"tokens", ui.TokensCSS()}, |
| 36 | } { |
| 37 | sum := sha256.Sum256(item.data) |
| 38 | hashed := fmt.Sprintf("/static/%s.%x.css", item.name, sum[:8]) |
| 39 | if !strings.Contains(page.Body.String(), `href="`+hashed+`"`) { |
| 40 | t.Fatalf("missing selected %s fingerprint", item.name) |
| 41 | } |
| 42 | for _, path := range []string{hashed, "/static/" + item.name + ".css", "/static/" + item.name + ".0000000000000000.css"} { |
| 43 | res := do(t, app, httptest.NewRequest(http.MethodGet, path, nil)) |
| 44 | if res.Code != http.StatusOK || !bytes.Equal(res.Body.Bytes(), item.data) { |
| 45 | t.Fatalf("%s asset response mismatch status%d", item.name, res.Code) |
| 46 | } |
| 47 | if !strings.HasPrefix(res.Header().Get("Content-Type"), "text/css") { |
| 48 | t.Fatal("asset MIME mismatch") |
| 49 | } |
| 50 | want := "no-cache" |
| 51 | if path == hashed { |
| 52 | want = "public, max-age=31536000, immutable" |
| 53 | } |
| 54 | if res.Header().Get("Cache-Control") != want { |
| 55 | t.Fatal("asset cache policy mismatch") |
| 56 | } |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | // The layout names Onest's stylesheet under estilo's versioned prefix, and |
| 62 | // the app serves it there: when estilo moves the fonts, this fails rather |
| 63 | // than every page quietly falling back to the system face. |
| 64 | func TestOnestIsLinkedAndServed(t *testing.T) { |
| 65 | app := New(Config{}) |
| 66 | page := do(t, app, httptest.NewRequest(http.MethodGet, "/signin", nil)) |
| 67 | css := theme.FontPrefix + "fonts.css" |
| 68 | if !strings.Contains(page.Body.String(), `href="`+css+`"`) { |
| 69 | t.Fatalf("layout does not link %s", css) |
| 70 | } |
| 71 | for _, path := range []string{css, theme.FontPrefix + "onest-latin.woff2"} { |
| 72 | if res := do(t, app, httptest.NewRequest(http.MethodGet, path, nil)); res.Code != http.StatusOK { |
| 73 | t.Fatalf("GET %s = %d", path, res.Code) |
| 74 | } |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | // The busy rule (estilo's busy): the page links busy.css after tokens.css |
| 79 | // and busy.js after the shim, all three are served, and the shim served is |
| 80 | // estilo's — rastrillo's without the old busy rule, which would fight the |
| 81 | // new one. |
| 82 | func TestBusyRuleIsLinkedAndServed(t *testing.T) { |
| 83 | app := New(Config{}) |
| 84 | page := do(t, app, httptest.NewRequest(http.MethodGet, "/signin", nil)).Body.String() |
| 85 | for _, name := range []string{"static/estilo/busy.css", "static/rastrillo.js", "static/estilo/busy.js"} { |
| 86 | body := map[string][]byte{"static/estilo/busy.css": busy.CSS(), "static/rastrillo.js": busy.ShimJS(), "static/estilo/busy.js": busy.JS()}[name] |
| 87 | sum := sha256.Sum256(body) |
| 88 | hashed := "/" + strings.TrimSuffix(name, path.Ext(name)) + fmt.Sprintf(".%x", sum[:8]) + path.Ext(name) |
| 89 | if !strings.Contains(page, `"`+hashed+`"`) { |
| 90 | t.Errorf("sign-in page does not link %s", hashed) |
| 91 | } |
| 92 | if res := do(t, app, httptest.NewRequest(http.MethodGet, hashed, nil)); res.Code != http.StatusOK || !bytes.Equal(res.Body.Bytes(), body) { |
| 93 | t.Errorf("GET %s = %d or the wrong bytes", hashed, res.Code) |
| 94 | } |
| 95 | } |
| 96 | if i, j := strings.Index(page, "static/rastrillo."), strings.Index(page, "static/estilo/busy."); i < 0 || j < 0 { |
| 97 | t.Error("the shim or busy.js is missing") |
| 98 | } |
| 99 | // No rastrillo script on disk. `rastrillo doctor --fix` adds the ones |
| 100 | // the library ships and the app lacks, and the shim it adds at this |
| 101 | // pin is the old one, with the busy rule inside: loaded beside |
| 102 | // estilo's busy.js, the two rules fight. Delete what --fix adds. |
| 103 | for _, name := range []string{"static/rastrillo.js", "static/busy.js", "static/select.js", "static/datetime.js", "static/calendar.js"} { |
| 104 | if _, err := fs.Stat(staticFS, name); err == nil { |
| 105 | t.Errorf("%s is on disk; Sheets serves estilo's shim and busy.js, and no other rastrillo script", name) |
| 106 | } |
| 107 | } |
| 108 | } |
| 109 | |