fix: enforce case-insensitive genre uniqueness
genres had only case-sensitive name UNIQUE while authors/tags use UNIQUE (lower(name)); sync_genres could race two case variants into duplicate rows (audit DB-3). Migration 0038 pre-dedups then adds UNIQUE (lower(name)); sync_genres upserts ON CONFLICT (lower(name)). Tested. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -674,6 +674,23 @@ async fn arbitrary_genres_from_source_get_inserted(pool: PgPool) {
|
||||
assert_eq!(webtoons_count.0, 1, "case-insensitive lookup reuses the existing row");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn genres_reject_case_variant_duplicates_at_the_db(pool: PgPool) {
|
||||
// The sequential pre-check in sync_genres dedups the common case, but two
|
||||
// concurrent inserts could each miss it. The lower(name) unique index (0038)
|
||||
// is the race backstop: a case variant of an existing genre must be rejected
|
||||
// by the DB itself. "Action" is seeded by 0009.
|
||||
let dup = sqlx::query("INSERT INTO genres (name) VALUES ('action')")
|
||||
.execute(&pool)
|
||||
.await;
|
||||
let err = dup.expect_err("a case-variant of a seeded genre must violate the unique index");
|
||||
let msg = err.to_string();
|
||||
assert!(
|
||||
msg.contains("genres_name_lower_uniq") || msg.contains("unique") || msg.contains("duplicate"),
|
||||
"expected a unique-violation, got: {msg}"
|
||||
);
|
||||
}
|
||||
|
||||
/// User-attached tags (rows with non-NULL `added_by` in `manga_tags`)
|
||||
/// must survive a crawler upsert. The crawler owns source-attached tags
|
||||
/// (added_by IS NULL); user attachments are owned by the user who made
|
||||
|
||||
Reference in New Issue
Block a user