fix: omit status on non-archive writes, skip finished in refresh and scan

This commit is contained in:
2026-07-27 13:35:46 +07:00
parent 896792d504
commit 7a16abd6a3
4 changed files with 83 additions and 6 deletions
+36
View File
@@ -160,3 +160,39 @@ func TestPutStatusValidation(t *testing.T) {
})
}
}
// A PUT that omits the status field entirely (what a userscript build
// predating the column sends) is the actual preserve path — the "" case
// above only exercises the fresh-INSERT default and never touches an
// existing bucket. This must both keep the archived bucket and still apply
// the chapter progress carried in the same request.
func TestPutOmittedStatusPreservesArchivedAndAppliesProgress(t *testing.T) {
srv := newTestServer(t)
seed := httptest.NewRequest(http.MethodPut, "/bookmarks/asura:solo",
strings.NewReader(`{"title":"Solo","status":"archived"}`))
rr := httptest.NewRecorder()
srv.ServeHTTP(rr, auth(seed))
if rr.Code != http.StatusOK {
t.Fatalf("seed status = %d, want 200 (body %s)", rr.Code, rr.Body.String())
}
req := httptest.NewRequest(http.MethodPut, "/bookmarks/asura:solo",
strings.NewReader(`{"title":"Solo","last_chapter":"12","last_chapter_num":12}`))
rr = httptest.NewRecorder()
srv.ServeHTTP(rr, auth(req))
if rr.Code != http.StatusOK {
t.Fatalf("status = %d, want 200 (body %s)", rr.Code, rr.Body.String())
}
var got Bookmark
if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil {
t.Fatalf("decode: %v", err)
}
if got.Status != "archived" {
t.Fatalf("stored status = %q, want %q", got.Status, "archived")
}
if got.LastChapterNum != 12 {
t.Fatalf("stored last_chapter_num = %v, want 12", got.LastChapterNum)
}
}
+4 -3
View File
@@ -193,10 +193,11 @@ func scanBookmark(scan func(...any) error) (Bookmark, error) {
if latestChapterNum.Valid {
b.LatestChapterNum = &latestChapterNum.Float64
}
// A NULL or empty bucket would leave the row in no list at all, so it
// reads as the default rather than being passed through.
// A NULL, empty, or unrecognised bucket (e.g. a hand-edited row) would
// leave the row in no list at all, so anything outside the three known
// buckets reads as the default rather than being passed through.
b.Status = status.String
if b.Status == "" {
if b.Status != statusReading && b.Status != statusArchived && b.Status != statusFinished {
b.Status = statusReading
}
return b, nil
+33
View File
@@ -760,6 +760,39 @@ func TestUpsertEmptyStatusPreservesStored(t *testing.T) {
}
}
// Mirrors the poller's read-modify-write in latest.go: Get the current row,
// mutate only the latest-chapter fields, and Upsert the whole struct back.
// cur.Status comes from Get (never empty — see scanBookmark), so it must
// round-trip through Upsert unchanged rather than being reset.
func TestLatestPollRoundTripPreservesArchived(t *testing.T) {
store := newTestStore(t)
base := Bookmark{
Key: "asura:solo", Site: "asura", SeriesID: "solo",
Status: statusArchived, UpdatedAt: time.Now().UnixMilli(),
}
if _, err := store.Upsert(base); err != nil {
t.Fatalf("seed: %v", err)
}
cur, found, err := store.Get(base.Key)
if err != nil || !found {
t.Fatalf("Get: found=%v err=%v", found, err)
}
num := 7.0
cur.LatestChapter = "7"
cur.LatestChapterNum = &num
cur.UpdatedAt = time.Now().UnixMilli()
stored, err := store.Upsert(cur)
if err != nil {
t.Fatalf("Upsert: %v", err)
}
if stored.Status != statusArchived {
t.Fatalf("Status = %q, want it preserved as %q", stored.Status, statusArchived)
}
}
func TestUpsertReplacesStatusWhenGiven(t *testing.T) {
store := newTestStore(t)
base := Bookmark{