test(chapters): lock open-contribution upload contract + document intent
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,21 @@ async fn get_one(
|
|||||||
Ok(Json(chapter))
|
Ok(Json(chapter))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a chapter to a manga.
|
||||||
|
///
|
||||||
|
/// **Authorization is intentionally open**: any authenticated principal —
|
||||||
|
/// a browser session *or* a bot API token — may add a chapter to *any*
|
||||||
|
/// manga, including crawler-imported rows. This is by design: chapters are
|
||||||
|
/// treated as community contributions, unlike the manga record itself
|
||||||
|
/// (title/cover/metadata), whose edits gate through `require_can_edit`
|
||||||
|
/// (see [`crate::api::mangas`]). The `CurrentUser` binding still requires a
|
||||||
|
/// valid identity, so contributions are attributable, just not owner-scoped.
|
||||||
|
///
|
||||||
|
/// This contract is locked by `tests/api_chapters.rs`
|
||||||
|
/// (`non_owner_can_upload_chapter`); changing it to owner-only is a
|
||||||
|
/// deliberate decision, not a drive-by tightening. Until a richer
|
||||||
|
/// contributor/moderation model lands this is acknowledged, intended
|
||||||
|
/// behaviour — see the auth note in CLAUDE.md.
|
||||||
async fn create(
|
async fn create(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
CurrentUser(user): CurrentUser,
|
CurrentUser(user): CurrentUser,
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ use uuid::Uuid;
|
|||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use serde_json as _;
|
use serde_json as _;
|
||||||
|
|
||||||
|
use common::MultipartBuilder;
|
||||||
|
|
||||||
async fn seed_manga(h: &common::Harness, cookie: &str, title: &str) -> Uuid {
|
async fn seed_manga(h: &common::Harness, cookie: &str, title: &str) -> Uuid {
|
||||||
common::seed_manga_via_api(&h.app, cookie, title).await
|
common::seed_manga_via_api(&h.app, cookie, title).await
|
||||||
}
|
}
|
||||||
@@ -272,3 +274,44 @@ async fn list_pages_returns_404_for_unknown_chapter(pool: PgPool) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(migrations = "./migrations")]
|
||||||
|
async fn non_owner_can_upload_chapter(pool: PgPool) {
|
||||||
|
// Contract lock: chapter upload is INTENTIONALLY open to any
|
||||||
|
// authenticated user, not just the manga's creator. A user who did not
|
||||||
|
// create the manga can still contribute a chapter (community
|
||||||
|
// contributions), unlike manga-record edits which gate on ownership.
|
||||||
|
// If this ever needs to become owner-only, that's a deliberate change —
|
||||||
|
// this test (and the doc comment on `api::chapters::create`) should be
|
||||||
|
// updated together, not silently broken.
|
||||||
|
let h = common::harness(pool);
|
||||||
|
|
||||||
|
// Owner creates the manga.
|
||||||
|
let (_owner, owner_cookie) = common::register_user(&h.app).await;
|
||||||
|
let manga_id = seed_manga(&h, &owner_cookie, "Berserk").await;
|
||||||
|
|
||||||
|
// A different, non-owner user uploads a chapter to it.
|
||||||
|
let (_other, other_cookie) = common::register_user(&h.app).await;
|
||||||
|
let resp = h
|
||||||
|
.app
|
||||||
|
.clone()
|
||||||
|
.oneshot(common::post_multipart_with_cookie(
|
||||||
|
&format!("/api/v1/mangas/{manga_id}/chapters"),
|
||||||
|
MultipartBuilder::new()
|
||||||
|
.add_json("metadata", json!({ "number": 1, "title": "Contributed" }))
|
||||||
|
.add_file("page", "1.png", "image/png", &common::fake_png_bytes()),
|
||||||
|
&other_cookie,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resp.status(),
|
||||||
|
StatusCode::CREATED,
|
||||||
|
"a non-owner authenticated user must be able to upload a chapter"
|
||||||
|
);
|
||||||
|
let body = common::body_json(resp).await;
|
||||||
|
assert_eq!(body["number"], 1);
|
||||||
|
assert_eq!(body["title"], "Contributed");
|
||||||
|
assert_eq!(body["page_count"], 1);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user