feat(history): report personal new-chapter count on read-progress list
Add `new_chapters_count` to each `GET /me/read-progress` item: how many chapters sit past the reader's last-read chapter, computed by a correlated subquery on chapter number. It is 0 when the reader is caught up, and 0 when the last-read chapter is unknown (manga-level progress or a deleted chapter) so we never over-claim. (manga_id, number) is non-unique, so this counts positions past the reader rather than distinct files. Backs the personal "new since last read" indicator on the upcoming Continue-reading shelf. Tests cover the mid-series, caught-up, and no-read-chapter cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -164,6 +164,84 @@ async fn list_is_per_user_only(pool: PgPool) {
|
||||
assert_eq!(body["items"], json!([]));
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn list_reports_new_chapters_since_last_read(pool: PgPool) {
|
||||
let h = common::harness(pool);
|
||||
let (_, cookie) = common::register_user(&h.app).await;
|
||||
let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await;
|
||||
// Three chapters exist; the reader is on chapter 1, so chapters 2 and
|
||||
// 3 are "new since last read".
|
||||
let ch1 = seed_chapter(&h.app, &cookie, manga_id, 1).await;
|
||||
let _ = seed_chapter(&h.app, &cookie, manga_id, 2).await;
|
||||
let _ = seed_chapter(&h.app, &cookie, manga_id, 3).await;
|
||||
let _ = upsert_progress(
|
||||
&h.app,
|
||||
&cookie,
|
||||
json!({ "manga_id": manga_id.to_string(), "chapter_id": ch1, "page": 1 }),
|
||||
)
|
||||
.await;
|
||||
|
||||
let resp = h
|
||||
.app
|
||||
.clone()
|
||||
.oneshot(common::get_with_cookie("/api/v1/me/read-progress", &cookie))
|
||||
.await
|
||||
.unwrap();
|
||||
let body = common::body_json(resp).await;
|
||||
assert_eq!(body["items"][0]["new_chapters_count"], 2);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn list_new_chapters_count_is_zero_at_latest_chapter(pool: PgPool) {
|
||||
let h = common::harness(pool);
|
||||
let (_, cookie) = common::register_user(&h.app).await;
|
||||
let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await;
|
||||
let _ = seed_chapter(&h.app, &cookie, manga_id, 1).await;
|
||||
let ch2 = seed_chapter(&h.app, &cookie, manga_id, 2).await;
|
||||
// Caught up to the last chapter → nothing newer.
|
||||
let _ = upsert_progress(
|
||||
&h.app,
|
||||
&cookie,
|
||||
json!({ "manga_id": manga_id.to_string(), "chapter_id": ch2, "page": 1 }),
|
||||
)
|
||||
.await;
|
||||
|
||||
let resp = h
|
||||
.app
|
||||
.clone()
|
||||
.oneshot(common::get_with_cookie("/api/v1/me/read-progress", &cookie))
|
||||
.await
|
||||
.unwrap();
|
||||
let body = common::body_json(resp).await;
|
||||
assert_eq!(body["items"][0]["new_chapters_count"], 0);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn list_new_chapters_count_is_zero_without_a_read_chapter(pool: PgPool) {
|
||||
let h = common::harness(pool);
|
||||
let (_, cookie) = common::register_user(&h.app).await;
|
||||
let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await;
|
||||
let _ = seed_chapter(&h.app, &cookie, manga_id, 1).await;
|
||||
let _ = seed_chapter(&h.app, &cookie, manga_id, 2).await;
|
||||
// Progress recorded without a chapter (manga-level) — we can't place
|
||||
// the reader among the chapters, so we don't claim any are "new".
|
||||
let _ = upsert_progress(
|
||||
&h.app,
|
||||
&cookie,
|
||||
json!({ "manga_id": manga_id.to_string(), "page": 1 }),
|
||||
)
|
||||
.await;
|
||||
|
||||
let resp = h
|
||||
.app
|
||||
.clone()
|
||||
.oneshot(common::get_with_cookie("/api/v1/me/read-progress", &cookie))
|
||||
.await
|
||||
.unwrap();
|
||||
let body = common::body_json(resp).await;
|
||||
assert_eq!(body["items"][0]["new_chapters_count"], 0);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn get_single_manga_returns_404_when_unread(pool: PgPool) {
|
||||
let h = common::harness(pool);
|
||||
|
||||
Reference in New Issue
Block a user