Split backend into internal packages by responsibility
All Go files lived flat in backend/ as one package main. Move store,
latest-chapter polling, sessions, HTTP middleware, the JSON API, the
userscript handler, and the web UI (with its templates/static assets)
into backend/internal/{store,latest,session,httpmw,api,userscript,web},
each with an exported API. main.go becomes the composition root wiring
them into newRouter; root-level tests cover the assembled router while
package-local tests cover unit behavior. Update Dockerfile/.dockerignore
for the new internal/ tree and CLAUDE.md to describe the layout.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+89
-85
@@ -10,6 +10,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"mangabm/backend/internal/session"
|
||||
"mangabm/backend/internal/store"
|
||||
"mangabm/backend/internal/web"
|
||||
)
|
||||
|
||||
const testPassword = "hunter2"
|
||||
@@ -22,22 +26,22 @@ func webConfig() Config {
|
||||
|
||||
// newWebTestServer returns the full router plus the store behind it, so tests
|
||||
// can seed rows and assert on what the handlers wrote back.
|
||||
func newWebTestServer(t *testing.T, cfg Config) (http.Handler, *Store) {
|
||||
func newWebTestServer(t *testing.T, cfg Config) (http.Handler, *store.Store) {
|
||||
t.Helper()
|
||||
store, err := OpenStore(filepath.Join(t.TempDir(), "test.db"))
|
||||
st, err := store.Open(filepath.Join(t.TempDir(), "test.db"))
|
||||
if err != nil {
|
||||
t.Fatalf("OpenStore: %v", err)
|
||||
t.Fatalf("store.Open: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { store.Close() })
|
||||
return newRouter(store, cfg), store
|
||||
t.Cleanup(func() { st.Close() })
|
||||
return newRouter(st, cfg), st
|
||||
}
|
||||
|
||||
// sessionCookie returns a cookie a handler will accept for cfg's API token.
|
||||
func sessionCookie(t *testing.T, cfg Config) *http.Cookie {
|
||||
t.Helper()
|
||||
return &http.Cookie{
|
||||
Name: sessionCookieName,
|
||||
Value: signSession(sessionKey(cfg.Token, cfg.WebPassword), time.Now().Add(time.Hour).UnixMilli()),
|
||||
Name: session.CookieName,
|
||||
Value: session.Sign(session.Key(cfg.Token, cfg.WebPassword), time.Now().Add(time.Hour).UnixMilli()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +60,8 @@ func TestIndexWithoutSessionShowsLogin(t *testing.T) {
|
||||
|
||||
func TestIndexWithSessionShowsList(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
if _, err := store.Upsert(Bookmark{
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
if _, err := st.Upsert(store.Bookmark{
|
||||
Key: "asura:solo", Site: "asura", SeriesID: "solo",
|
||||
Title: "Solo Leveling", LastChapter: "45", LastChapterNum: 45,
|
||||
UpdatedAt: time.Now().UnixMilli(),
|
||||
@@ -90,8 +94,8 @@ func TestLoginSuccessSetsCookie(t *testing.T) {
|
||||
t.Fatalf("POST /login status = %d, want 303", rr.Code)
|
||||
}
|
||||
cookies := rr.Result().Cookies()
|
||||
if len(cookies) != 1 || cookies[0].Name != sessionCookieName || cookies[0].Value == "" {
|
||||
t.Fatalf("POST /login cookies = %+v, want one non-empty %s", cookies, sessionCookieName)
|
||||
if len(cookies) != 1 || cookies[0].Name != session.CookieName || cookies[0].Value == "" {
|
||||
t.Fatalf("POST /login cookies = %+v, want one non-empty %s", cookies, session.CookieName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,14 +126,14 @@ func TestLoginRateLimited(t *testing.T) {
|
||||
srv.ServeHTTP(rr, req)
|
||||
return rr
|
||||
}
|
||||
for i := 0; i < loginMaxFailures; i++ {
|
||||
for i := 0; i < session.MaxFailures; i++ {
|
||||
if code := post().Code; code != http.StatusUnauthorized {
|
||||
t.Fatalf("attempt %d status = %d, want 401", i+1, code)
|
||||
}
|
||||
}
|
||||
rr := post()
|
||||
if rr.Code != http.StatusTooManyRequests {
|
||||
t.Fatalf("attempt %d status = %d, want 429", loginMaxFailures+1, rr.Code)
|
||||
t.Fatalf("attempt %d status = %d, want 429", session.MaxFailures+1, rr.Code)
|
||||
}
|
||||
if after := rr.Header().Get("Retry-After"); after == "" {
|
||||
t.Fatal("429 response has no Retry-After header")
|
||||
@@ -202,9 +206,9 @@ func TestStaticAssetsServed(t *testing.T) {
|
||||
}
|
||||
|
||||
// seed inserts one bookmark and returns it as stored.
|
||||
func seed(t *testing.T, store *Store, b Bookmark) Bookmark {
|
||||
func seed(t *testing.T, st *store.Store, b store.Bookmark) store.Bookmark {
|
||||
t.Helper()
|
||||
stored, err := store.Upsert(b)
|
||||
stored, err := st.Upsert(b)
|
||||
if err != nil {
|
||||
t.Fatalf("Upsert: %v", err)
|
||||
}
|
||||
@@ -245,8 +249,8 @@ func TestUIRoutesRequireSession(t *testing.T) {
|
||||
|
||||
func TestFavoriteTogglesWithoutReordering(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
before := seed(t, store, Bookmark{
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
before := seed(t, st, store.Bookmark{
|
||||
Key: "asura:solo", Site: "asura", SeriesID: "solo",
|
||||
Title: "Solo Leveling", LastChapter: "45", LastChapterNum: 45,
|
||||
UpdatedAt: 1_000_000,
|
||||
@@ -258,7 +262,7 @@ func TestFavoriteTogglesWithoutReordering(t *testing.T) {
|
||||
t.Fatalf("favorite status = %d, want 200", rr.Code)
|
||||
}
|
||||
|
||||
after, ok, err := store.Get("asura:solo")
|
||||
after, ok, err := st.Get("asura:solo")
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("Get after favorite: %v ok=%v", err, ok)
|
||||
}
|
||||
@@ -276,7 +280,7 @@ func TestFavoriteTogglesWithoutReordering(t *testing.T) {
|
||||
// Toggling again turns it back off.
|
||||
rr = httptest.NewRecorder()
|
||||
srv.ServeHTTP(rr, uiRequest(t, cfg, http.MethodPost, "/ui/bookmarks/asura:solo/favorite", nil))
|
||||
back, _, _ := store.Get("asura:solo")
|
||||
back, _, _ := st.Get("asura:solo")
|
||||
if back.Favorite {
|
||||
t.Fatal("Favorite = true after a second toggle, want false")
|
||||
}
|
||||
@@ -294,8 +298,8 @@ func TestFavoriteTogglesWithoutReordering(t *testing.T) {
|
||||
// selector, just a regression guard against reintroducing the bare-id form.
|
||||
func TestCardHxTargetIsValidSelectorForColonKey(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seed(t, store, Bookmark{
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seed(t, st, store.Bookmark{
|
||||
Key: "asura:solo", Site: "asura", SeriesID: "solo",
|
||||
Title: "Solo Leveling", LastChapter: "45", LastChapterNum: 45,
|
||||
UpdatedAt: 1_000_000,
|
||||
@@ -320,8 +324,8 @@ func TestCardHxTargetIsValidSelectorForColonKey(t *testing.T) {
|
||||
|
||||
func TestChapterOverrideMovesUpdatedAt(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
before := seed(t, store, Bookmark{
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
before := seed(t, st, store.Bookmark{
|
||||
Key: "asura:solo", Site: "asura", SeriesID: "solo",
|
||||
Title: "Solo Leveling", LastChapter: "45", LastChapterNum: 45,
|
||||
LastChapterURL: "https://example.test/ch/45", SeriesURL: "https://example.test/solo",
|
||||
@@ -335,7 +339,7 @@ func TestChapterOverrideMovesUpdatedAt(t *testing.T) {
|
||||
t.Fatalf("chapter override status = %d, want 200", rr.Code)
|
||||
}
|
||||
|
||||
after, ok, err := store.Get("asura:solo")
|
||||
after, ok, err := st.Get("asura:solo")
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("Get after override: %v ok=%v", err, ok)
|
||||
}
|
||||
@@ -355,8 +359,8 @@ func TestChapterOverrideMovesUpdatedAt(t *testing.T) {
|
||||
|
||||
func TestChapterOverrideNoOpPreservesURLAndUpdatedAt(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
before := seed(t, store, Bookmark{
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
before := seed(t, st, store.Bookmark{
|
||||
Key: "asura:solo", Site: "asura", SeriesID: "solo",
|
||||
Title: "Solo Leveling", LastChapter: "45.0", LastChapterNum: 45,
|
||||
LastChapterURL: "https://example.test/ch/45", SeriesURL: "https://example.test/solo",
|
||||
@@ -375,7 +379,7 @@ func TestChapterOverrideNoOpPreservesURLAndUpdatedAt(t *testing.T) {
|
||||
t.Fatalf("chapter no-op status = %d, want 200", rr.Code)
|
||||
}
|
||||
|
||||
after, ok, err := store.Get("asura:solo")
|
||||
after, ok, err := st.Get("asura:solo")
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("Get after no-op override: %v ok=%v", err, ok)
|
||||
}
|
||||
@@ -395,8 +399,8 @@ func TestChapterOverrideNoOpPreservesURLAndUpdatedAt(t *testing.T) {
|
||||
|
||||
func TestChapterOverrideRejectsBadInput(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seed(t, store, Bookmark{
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seed(t, st, store.Bookmark{
|
||||
Key: "asura:solo", Site: "asura", SeriesID: "solo",
|
||||
Title: "Solo Leveling", LastChapterNum: 45, UpdatedAt: 1_000_000,
|
||||
})
|
||||
@@ -409,7 +413,7 @@ func TestChapterOverrideRejectsBadInput(t *testing.T) {
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want 400", rr.Code)
|
||||
}
|
||||
after, _, _ := store.Get("asura:solo")
|
||||
after, _, _ := st.Get("asura:solo")
|
||||
if after.LastChapterNum != 45 {
|
||||
t.Fatalf("chapter changed to %v on invalid input", after.LastChapterNum)
|
||||
}
|
||||
@@ -440,8 +444,8 @@ func TestMutationsOnMissingKey(t *testing.T) {
|
||||
|
||||
func TestUIDeleteRemovesRow(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seed(t, store, Bookmark{
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seed(t, st, store.Bookmark{
|
||||
Key: "asura:solo", Site: "asura", SeriesID: "solo",
|
||||
Title: "Solo Leveling", UpdatedAt: 1_000_000,
|
||||
})
|
||||
@@ -460,19 +464,19 @@ func TestUIDeleteRemovesRow(t *testing.T) {
|
||||
if !strings.Contains(body, `id="new-count" hx-swap-oob="true"`) {
|
||||
t.Fatalf("delete body = %q, want the out-of-band badge", body)
|
||||
}
|
||||
if _, ok, _ := store.Get("asura:solo"); ok {
|
||||
if _, ok, _ := st.Get("asura:solo"); ok {
|
||||
t.Fatal("row still present after delete")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIListFavouritesTab(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seed(t, store, Bookmark{
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seed(t, st, store.Bookmark{
|
||||
Key: "asura:solo", Site: "asura", SeriesID: "solo",
|
||||
Title: "Solo Leveling", Favorite: true, UpdatedAt: 2_000_000,
|
||||
})
|
||||
seed(t, store, Bookmark{
|
||||
seed(t, st, store.Bookmark{
|
||||
Key: "demonic:tower", Site: "demonic", SeriesID: "tower",
|
||||
Title: "Tower of God", Favorite: false, UpdatedAt: 1_000_000,
|
||||
})
|
||||
@@ -493,14 +497,14 @@ func TestUIListFavouritesTab(t *testing.T) {
|
||||
|
||||
func TestUIListNewTab(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seed(t, store, Bookmark{
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seed(t, st, store.Bookmark{
|
||||
Key: "asura:solo", Site: "asura", SeriesID: "solo",
|
||||
Title: "Solo Leveling", LastChapterNum: 10,
|
||||
LatestChapter: "Chapter 12", LatestChapterNum: floatPtr(12),
|
||||
UpdatedAt: 2_000_000,
|
||||
})
|
||||
seed(t, store, Bookmark{
|
||||
seed(t, st, store.Bookmark{
|
||||
Key: "demonic:tower", Site: "demonic", SeriesID: "tower",
|
||||
Title: "Tower of God", LastChapterNum: 5,
|
||||
LatestChapter: "Chapter 5", LatestChapterNum: floatPtr(5),
|
||||
@@ -524,22 +528,22 @@ func TestUIListNewTab(t *testing.T) {
|
||||
// seedStatusRows puts one series in each bucket, the archived one also
|
||||
// favourited and with a new chapter out, so a leak into any reading-bucket tab
|
||||
// shows up as a failure rather than passing by accident.
|
||||
func seedStatusRows(t *testing.T, store *Store) {
|
||||
func seedStatusRows(t *testing.T, st *store.Store) {
|
||||
t.Helper()
|
||||
// floatPtr already exists in store_test.go — same package, reuse it.
|
||||
rows := []Bookmark{
|
||||
rows := []store.Bookmark{
|
||||
{Key: "asura:reading", Site: "asura", SeriesID: "reading", Title: "ReadingOne",
|
||||
Status: statusReading, LastChapterNum: 10, Favorite: true,
|
||||
Status: store.StatusReading, LastChapterNum: 10, Favorite: true,
|
||||
LatestChapter: "11", LatestChapterNum: floatPtr(11)},
|
||||
{Key: "asura:archived", Site: "asura", SeriesID: "archived", Title: "ArchivedOne",
|
||||
Status: statusArchived, LastChapterNum: 5, Favorite: true,
|
||||
Status: store.StatusArchived, LastChapterNum: 5, Favorite: true,
|
||||
LatestChapter: "99", LatestChapterNum: floatPtr(99)},
|
||||
{Key: "asura:finished", Site: "asura", SeriesID: "finished", Title: "FinishedOne",
|
||||
Status: statusFinished, LastChapterNum: 200, Favorite: true},
|
||||
Status: store.StatusFinished, LastChapterNum: 200, Favorite: true},
|
||||
}
|
||||
for _, b := range rows {
|
||||
b.UpdatedAt = time.Now().UnixMilli()
|
||||
if _, err := store.Upsert(b); err != nil {
|
||||
if _, err := st.Upsert(b); err != nil {
|
||||
t.Fatalf("seed %s: %v", b.Key, err)
|
||||
}
|
||||
}
|
||||
@@ -547,8 +551,8 @@ func seedStatusRows(t *testing.T, store *Store) {
|
||||
|
||||
func TestTabsShowOnlyTheirBucket(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, store)
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, st)
|
||||
|
||||
cases := []struct {
|
||||
tab string
|
||||
@@ -604,16 +608,16 @@ func stripOf(t *testing.T, srv http.Handler, cfg Config, tab string) string {
|
||||
// updated_at-ordered list below it does not already say — and only on All.
|
||||
func TestRecentStripCarriesUnreadOnlyAndOnlyOnAll(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, store) // ReadingOne is at 10 with 11 out; the rest are not reading
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, st) // ReadingOne is at 10 with 11 out; the rest are not reading
|
||||
|
||||
// A reading series that is caught up has nothing waiting, so it stays out.
|
||||
caught := Bookmark{
|
||||
caught := store.Bookmark{
|
||||
Key: "asura:caught", Site: "asura", SeriesID: "caught", Title: "CaughtUpOne",
|
||||
Status: statusReading, LastChapterNum: 40, LatestChapter: "40",
|
||||
Status: store.StatusReading, LastChapterNum: 40, LatestChapter: "40",
|
||||
LatestChapterNum: floatPtr(40), UpdatedAt: time.Now().UnixMilli(),
|
||||
}
|
||||
if _, err := store.Upsert(caught); err != nil {
|
||||
if _, err := st.Upsert(caught); err != nil {
|
||||
t.Fatalf("seed %s: %v", caught.Key, err)
|
||||
}
|
||||
|
||||
@@ -633,12 +637,12 @@ func TestRecentStripCarriesUnreadOnlyAndOnlyOnAll(t *testing.T) {
|
||||
}
|
||||
|
||||
// Nothing new anywhere: the strip has nothing to say and does not render.
|
||||
reading, _, err := store.Get("asura:reading")
|
||||
reading, _, err := st.Get("asura:reading")
|
||||
if err != nil {
|
||||
t.Fatalf("Get: %v", err)
|
||||
}
|
||||
reading.LatestChapterNum = floatPtr(reading.LastChapterNum)
|
||||
if _, err := store.Upsert(reading); err != nil {
|
||||
if _, err := st.Upsert(reading); err != nil {
|
||||
t.Fatalf("Upsert: %v", err)
|
||||
}
|
||||
// The section still ships (an out-of-band swap needs the id to exist) but
|
||||
@@ -652,23 +656,23 @@ func TestRecentStripCarriesUnreadOnlyAndOnlyOnAll(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The strip never grows past recentCount, however many series are waiting.
|
||||
// The strip never grows past web.RecentCount, however many series are waiting.
|
||||
func TestRecentStripCapped(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
for i := 0; i <= recentCount; i++ {
|
||||
b := Bookmark{
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
for i := 0; i <= web.RecentCount; i++ {
|
||||
b := store.Bookmark{
|
||||
Key: fmt.Sprintf("asura:new%d", i), Site: "asura",
|
||||
SeriesID: fmt.Sprintf("new%d", i), Title: fmt.Sprintf("Waiting%d", i),
|
||||
Status: statusReading, LastChapterNum: 1, LatestChapter: "2",
|
||||
Status: store.StatusReading, LastChapterNum: 1, LatestChapter: "2",
|
||||
LatestChapterNum: floatPtr(2), UpdatedAt: time.Now().UnixMilli() + int64(i),
|
||||
}
|
||||
if _, err := store.Upsert(b); err != nil {
|
||||
if _, err := st.Upsert(b); err != nil {
|
||||
t.Fatalf("seed %s: %v", b.Key, err)
|
||||
}
|
||||
}
|
||||
if got := strings.Count(stripOf(t, srv, cfg, "all"), "recent-card"); got != recentCount {
|
||||
t.Fatalf("strip rendered %d cards, want %d", got, recentCount)
|
||||
if got := strings.Count(stripOf(t, srv, cfg, "all"), "recent-card"); got != web.RecentCount {
|
||||
t.Fatalf("strip rendered %d cards, want %d", got, web.RecentCount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,14 +690,14 @@ func postStatus(t *testing.T, srv http.Handler, cfg Config, key, status string)
|
||||
|
||||
func TestUIStatusSetsBucket(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, store)
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, st)
|
||||
|
||||
for _, want := range []string{statusArchived, statusFinished, statusReading} {
|
||||
for _, want := range []string{store.StatusArchived, store.StatusFinished, store.StatusReading} {
|
||||
if rr := postStatus(t, srv, cfg, "asura:reading", want); rr.Code != http.StatusOK {
|
||||
t.Fatalf("set %s: status = %d, body %s", want, rr.Code, rr.Body.String())
|
||||
}
|
||||
b, ok, err := store.Get("asura:reading")
|
||||
b, ok, err := st.Get("asura:reading")
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("Get: ok=%v err=%v", ok, err)
|
||||
}
|
||||
@@ -705,21 +709,21 @@ func TestUIStatusSetsBucket(t *testing.T) {
|
||||
|
||||
func TestUIStatusRejectsUnknownValue(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, store)
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, st)
|
||||
|
||||
if rr := postStatus(t, srv, cfg, "asura:reading", "dropped"); rr.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want 400", rr.Code)
|
||||
}
|
||||
b, _, _ := store.Get("asura:reading")
|
||||
if b.Status != statusReading {
|
||||
b, _, _ := st.Get("asura:reading")
|
||||
if b.Status != store.StatusReading {
|
||||
t.Fatalf("stored status = %q, want it untouched", b.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIStatusRequiresSession(t *testing.T) {
|
||||
srv, store := newWebTestServer(t, webConfig())
|
||||
seedStatusRows(t, store)
|
||||
srv, st := newWebTestServer(t, webConfig())
|
||||
seedStatusRows(t, st)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/ui/bookmarks/asura:reading/status",
|
||||
strings.NewReader("status=archived"))
|
||||
@@ -734,15 +738,15 @@ func TestUIStatusRequiresSession(t *testing.T) {
|
||||
|
||||
func TestUIStatusDoesNotReorderList(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, store)
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, st)
|
||||
|
||||
before, _, _ := store.Get("asura:reading")
|
||||
before, _, _ := st.Get("asura:reading")
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
if rr := postStatus(t, srv, cfg, "asura:reading", statusArchived); rr.Code != http.StatusOK {
|
||||
if rr := postStatus(t, srv, cfg, "asura:reading", store.StatusArchived); rr.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d", rr.Code)
|
||||
}
|
||||
after, _, _ := store.Get("asura:reading")
|
||||
after, _, _ := st.Get("asura:reading")
|
||||
if after.UpdatedAt != before.UpdatedAt {
|
||||
t.Fatalf("UpdatedAt moved %d -> %d", before.UpdatedAt, after.UpdatedAt)
|
||||
}
|
||||
@@ -750,8 +754,8 @@ func TestUIStatusDoesNotReorderList(t *testing.T) {
|
||||
|
||||
func TestCardShowsStatusControls(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, store)
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, st)
|
||||
|
||||
cases := []struct {
|
||||
tab string
|
||||
@@ -788,8 +792,8 @@ func TestCardShowsStatusControls(t *testing.T) {
|
||||
|
||||
func TestAppRendersNewTabs(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, store)
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seedStatusRows(t, st)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.AddCookie(sessionCookie(t, cfg))
|
||||
@@ -807,10 +811,10 @@ func TestAppRendersNewTabs(t *testing.T) {
|
||||
// outside the swapped card, so nothing else would correct them.
|
||||
func TestMutationRefreshesChromeOutOfBand(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
seed(t, store, Bookmark{
|
||||
srv, st := newWebTestServer(t, cfg)
|
||||
seed(t, st, store.Bookmark{
|
||||
Key: "asura:solo", Site: "asura", SeriesID: "solo", Title: "Solo Leveling",
|
||||
Status: statusReading, LastChapterNum: 10, LatestChapter: "Chapter 11",
|
||||
Status: store.StatusReading, LastChapterNum: 10, LatestChapter: "Chapter 11",
|
||||
LatestChapterNum: floatPtr(11), UpdatedAt: time.Now().UnixMilli(),
|
||||
})
|
||||
|
||||
@@ -820,7 +824,7 @@ func TestMutationRefreshesChromeOutOfBand(t *testing.T) {
|
||||
}
|
||||
|
||||
req := uiRequest(t, cfg, http.MethodPost, "/ui/bookmarks/asura:solo/status",
|
||||
url.Values{"status": {statusArchived}})
|
||||
url.Values{"status": {store.StatusArchived}})
|
||||
req.Header.Set("HX-Current-URL", "http://localhost/?tab=all")
|
||||
rr := httptest.NewRecorder()
|
||||
srv.ServeHTTP(rr, req)
|
||||
|
||||
Reference in New Issue
Block a user