diff --git a/crates/mtgc-core/src/decks.rs b/crates/mtgc-core/src/decks.rs index b072d3a..18bc5bd 100644 --- a/crates/mtgc-core/src/decks.rs +++ b/crates/mtgc-core/src/decks.rs @@ -48,6 +48,36 @@ impl Catalog { Ok(()) } + /// Rename a deck. Fails if another deck already has that name (`name` is + /// UNIQUE). + pub fn rename_deck(&self, id: i64, name: &str) -> Result<()> { + self.conn + .execute("UPDATE decks SET name = ?1 WHERE id = ?2", params![name, id])?; + Ok(()) + } + + /// Duplicate a deck under a new name: same format/commander/notes, plus a + /// copy of every deck_cards row (quantity + commander flag). Fails if + /// `new_name` is already taken. + pub fn duplicate_deck(&self, id: i64, new_name: &str) -> Result { + let src = self + .get_deck(id)? + .ok_or_else(|| crate::error::CoreError::Other(format!("deck {id} not found")))?; + self.conn.execute( + "INSERT INTO decks(name, format, commander_oracle_id, notes) + VALUES (?1, ?2, ?3, ?4)", + params![new_name, src.format, src.commander_oracle_id, src.notes], + )?; + let new_id = self.conn.last_insert_rowid(); + self.conn.execute( + "INSERT INTO deck_cards(deck_id, oracle_id, quantity, is_commander) + SELECT ?1, oracle_id, quantity, is_commander FROM deck_cards WHERE deck_id = ?2", + params![new_id, id], + )?; + self.get_deck(new_id) + .map(|d| d.expect("just-inserted deck must exist")) + } + /// Set how many copies of a card belong to a deck. `quantity <= 0` removes /// the card from the deck. Upserts on (deck_id, oracle_id). pub fn set_deck_card( @@ -286,4 +316,57 @@ mod tests { cat.delete_deck(d.id).unwrap(); assert!(cat.card_decks("lightning-bolt").unwrap().is_empty()); } + + #[test] + fn rename_deck_updates_name_and_rejects_duplicates() { + let fx = test_support::make(); + let cat = &fx.catalog; + let a = cat.create_deck("Deck A", None).unwrap(); + cat.create_deck("Deck B", None).unwrap(); + + cat.rename_deck(a.id, "Deck A Renamed").unwrap(); + assert_eq!(cat.get_deck(a.id).unwrap().unwrap().name, "Deck A Renamed"); + + // decks.name is UNIQUE. + assert!(cat.rename_deck(a.id, "Deck B").is_err()); + } + + #[test] + fn duplicate_deck_copies_metadata_and_cards() { + let fx = test_support::make(); + let cat = &fx.catalog; + cat.add_owned("llanowar-elves", 2).unwrap(); + + let src = cat.create_deck("Original", Some("llanowar-elves")).unwrap(); + cat.set_deck_format(src.id, "Commander").unwrap(); + cat.set_deck_card(src.id, "llanowar-elves", 2, true).unwrap(); + cat.set_deck_card(src.id, "lightning-bolt", 1, false).unwrap(); + + let copy = cat.duplicate_deck(src.id, "Original (copy)").unwrap(); + assert_eq!(copy.name, "Original (copy)"); + assert_eq!(copy.format, "Commander"); + assert_eq!(copy.commander_oracle_id.as_deref(), Some("llanowar-elves")); + + let mut contents: Vec<_> = cat + .deck_contents(copy.id) + .unwrap() + .into_iter() + .map(|e| (e.card.oracle_id, e.quantity, e.is_commander)) + .collect(); + contents.sort_by(|a, b| a.0.cmp(&b.0)); + assert_eq!( + contents, + vec![ + ("lightning-bolt".to_string(), 1, false), + ("llanowar-elves".to_string(), 2, true), + ] + ); + + // The original deck is untouched. + let original_contents = cat.deck_contents(src.id).unwrap(); + assert_eq!(original_contents.len(), 2); + + // Duplicating into an already-taken name fails (UNIQUE). + assert!(cat.duplicate_deck(src.id, "Original").is_err()); + } }