package main import ( "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" ) // sampleScript is a stand-in for the real userscript: a metadata block with a // @version line, plus a body that must survive the rewrite untouched. const sampleScript = `// ==UserScript== // @name Manga Bookmark Sync // @version 1.5.0 // @match https://asurascans.com/* // ==/UserScript== (function () { "use strict"; })(); ` // writeScript drops a userscript in a temp dir with a known mtime and returns // its path plus the version string the handler is expected to stamp. func writeScript(t *testing.T, body string) (path, wantVersion string) { t.Helper() path = filepath.Join(t.TempDir(), "manga-bookmark.user.js") if err := os.WriteFile(path, []byte(body), 0o644); err != nil { t.Fatalf("write script: %v", err) } mod := time.Date(2026, 7, 28, 16, 42, 0, 0, time.UTC) if err := os.Chtimes(path, mod, mod); err != nil { t.Fatalf("chtimes: %v", err) } return path, "2026.07.28.1642" } func newUserscriptServer(t *testing.T, path string) http.Handler { t.Helper() store, err := OpenStore(filepath.Join(t.TempDir(), "test.db")) if err != nil { t.Fatalf("OpenStore: %v", err) } t.Cleanup(func() { store.Close() }) cfg := testConfig() cfg.UserscriptPath = path return newRouter(store, cfg) } func getScript(t *testing.T, srv http.Handler, token string) *httptest.ResponseRecorder { t.Helper() rr := httptest.NewRecorder() srv.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/u/"+token+"/manga-bookmark.user.js", nil)) return rr } func TestUserscriptServedWithStampedVersion(t *testing.T) { path, wantVersion := writeScript(t, sampleScript) rr := getScript(t, newUserscriptServer(t, path), testToken) if rr.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rr.Code) } if ct := rr.Header().Get("Content-Type"); !strings.HasPrefix(ct, "text/javascript") { t.Errorf("Content-Type = %q, want text/javascript", ct) } if cc := rr.Header().Get("Cache-Control"); cc != "no-cache" { t.Errorf("Cache-Control = %q, want no-cache", cc) } body := rr.Body.String() if !strings.Contains(body, "// @version "+wantVersion) { t.Errorf("body has no stamped version %q:\n%s", wantVersion, body) } if strings.Contains(body, "1.5.0") { t.Errorf("body still carries the file's own version:\n%s", body) } // Everything outside the @version line is served verbatim. if !strings.Contains(body, `(function () { "use strict"; })();`) { t.Errorf("body was altered beyond the version line:\n%s", body) } if !strings.Contains(body, "// @name Manga Bookmark Sync") { t.Errorf("metadata block was altered:\n%s", body) } } func TestUserscriptWrongTokenIs404(t *testing.T) { path, _ := writeScript(t, sampleScript) srv := newUserscriptServer(t, path) for _, tok := range []string{"wrong", "", testToken + "x", testToken[:3]} { if got := getScript(t, srv, tok).Code; got != http.StatusNotFound { t.Errorf("token %q: status = %d, want 404", tok, got) } } } func TestUserscriptMissingFileIs404(t *testing.T) { srv := newUserscriptServer(t, filepath.Join(t.TempDir(), "absent.user.js")) if got := getScript(t, srv, testToken).Code; got != http.StatusNotFound { t.Fatalf("status = %d, want 404", got) } } func TestUserscriptWithoutVersionLineServedUnmodified(t *testing.T) { const noVersion = "// ==UserScript==\n// @name x\n// ==/UserScript==\nconsole.log(1);\n" path, _ := writeScript(t, noVersion) rr := getScript(t, newUserscriptServer(t, path), testToken) if rr.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rr.Code) } if rr.Body.String() != noVersion { t.Fatalf("body = %q, want it unmodified", rr.Body.String()) } } // The endpoint must work on a deployment that never set WEB_PASSWORD, since // the web routes are not registered at all in that case. func TestUserscriptServedWithWebUIDisabled(t *testing.T) { path, _ := writeScript(t, sampleScript) store, err := OpenStore(filepath.Join(t.TempDir(), "nopass.db")) if err != nil { t.Fatalf("OpenStore: %v", err) } t.Cleanup(func() { store.Close() }) cfg := testConfig() cfg.WebPassword = "" cfg.UserscriptPath = path if got := getScript(t, newRouter(store, cfg), testToken).Code; got != http.StatusOK { t.Fatalf("status = %d, want 200", got) } }