diff --git a/tests/carddatabase/carddatabase_test.cpp b/tests/carddatabase/carddatabase_test.cpp index 592f1e257..7fce085aa 100644 --- a/tests/carddatabase/carddatabase_test.cpp +++ b/tests/carddatabase/carddatabase_test.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,98 @@ TEST(CardDatabaseTest, LoadXml) ASSERT_EQ(NotLoaded, db->getLoadStatus()) << "Incorrect status after clear"; } +TEST(CardDatabaseTest, QuerierCachesInvalidateOnCardAddedAndReset) +{ + // Same construction as LoadXml: a real database over the checked-in fixture. + CardDatabase db(nullptr, new NoopCardPreferenceProvider(), new TestCardDatabasePathProvider(), + new NoopCardSetPriorityController()); + db.loadCardDatabases(); + + ASSERT_EQ(9, db.getCardList().size()) << "fixture card count changed"; + ASSERT_EQ(5, db.getSetList().size()) << "fixture set count changed"; + + // The fixture has no format-* property, so its formats cache would start empty and could not + // prove invalidation. A primer card gives all three caches a non-empty pre-mutation state. + CardInfo::UiAttributes attributes = {.tableRow = 1}; + QHash primerProperties; + primerProperties["maintype"] = "Creature"; + primerProperties["type"] = "Creature — Cat"; + primerProperties["format-vintage"] = "legal"; + db.addCard(CardInfo::newInstance("Cache Primer", "", false, primerProperties, {}, {}, {}, attributes)); + + // Prime the three lazily rebuilt caches. Each only recomputes while its map is empty, so + // these reads pin the pre-mutation snapshot. Fixture counts: cards.xml adds Cat, Dog, Doctor + // (Creature), Not Dead and Truth (Instant); tokens.xml adds Kitten and Puppy (Token); + // customset1.xml adds Sparrow and Crow (Creature). + const QMap primedMain = db.query().getAllMainCardTypesWithCount(); + EXPECT_EQ(6, primedMain.value("Creature")); + EXPECT_EQ(2, primedMain.value("Instant")); + EXPECT_EQ(2, primedMain.value("Token")); + EXPECT_EQ(3, primedMain.size()); + + const QMap primedSub = db.query().getAllSubCardTypesWithCount(); + EXPECT_EQ(2, primedSub.value("Cat")) << "the primer shares the fixture's Cat subtype"; + EXPECT_EQ(1, primedSub.value("Dog")); + EXPECT_EQ(1, primedSub.value("Human")); + EXPECT_EQ(1, primedSub.value("Time")); + EXPECT_EQ(1, primedSub.value("Lord")); + EXPECT_EQ(1, primedSub.value("Doctor")); + EXPECT_EQ(6, primedSub.size()); + + const QMap primedFormats = db.query().getAllFormatsWithCount(); + EXPECT_EQ(1, primedFormats.value("vintage")); + EXPECT_EQ(1, primedFormats.size()); + + // cardAdded must invalidate the primed caches; without the connection the maps stay + // non-empty and the probe's values never appear. + QHash probeProperties; + probeProperties["maintype"] = "Enchantment"; + probeProperties["type"] = "Enchantment — Aura"; + probeProperties["format-probelegacy"] = "legal"; + db.addCard(CardInfo::newInstance("Cache Probe", "", false, probeProperties, {}, {}, {}, attributes)); + + const QMap afterAddMain = db.query().getAllMainCardTypesWithCount(); + EXPECT_EQ(1, afterAddMain.value("Enchantment")) << "cardAdded must invalidate the main-type cache"; + EXPECT_EQ(6, afterAddMain.value("Creature")) << "the rebuilt cache must still hold the earlier cards"; + EXPECT_EQ(4, afterAddMain.size()); + + const QMap afterAddSub = db.query().getAllSubCardTypesWithCount(); + EXPECT_EQ(1, afterAddSub.value("Aura")) << "cardAdded must invalidate the subtype cache"; + EXPECT_EQ(2, afterAddSub.value("Cat")) << "the rebuilt cache must still hold the earlier cards"; + EXPECT_EQ(7, afterAddSub.size()); + + const QMap afterAddFormats = db.query().getAllFormatsWithCount(); + EXPECT_EQ(1, afterAddFormats.value("probelegacy")) << "cardAdded must invalidate the format cache"; + EXPECT_EQ(1, afterAddFormats.value("vintage")) << "the rebuilt cache must still hold the earlier cards"; + EXPECT_EQ(2, afterAddFormats.size()); + + // Dropping every card must leave the caches rebuilt from an empty database. + db.clear(); + EXPECT_TRUE(db.query().getAllMainCardTypesWithCount().isEmpty()) << "main types after clear()"; + EXPECT_TRUE(db.query().getAllSubCardTypesWithCount().isEmpty()) << "subtypes after clear()"; + EXPECT_TRUE(db.query().getAllFormatsWithCount().isEmpty()) << "formats after clear()"; + + // cardDatabaseReset is emitted by swapInDatabaseData() (the snapshot swap), not by clear(), + // so re-prime over one card and swap in an empty snapshot: only the reset connection can + // invalidate the caches there. + QHash resetProperties; + resetProperties["maintype"] = "Battle"; + resetProperties["type"] = "Battle — Siege"; + resetProperties["format-pauper"] = "legal"; + db.addCard(CardInfo::newInstance("Reset Probe", "", false, resetProperties, {}, {}, {}, attributes)); + ASSERT_EQ(1, db.query().getAllMainCardTypesWithCount().value("Battle")); + ASSERT_EQ(1, db.query().getAllSubCardTypesWithCount().value("Siege")); + ASSERT_EQ(1, db.query().getAllFormatsWithCount().value("pauper")); + + db.swapInDatabaseData(CardDatabaseData{}); + + EXPECT_TRUE(db.query().getAllMainCardTypesWithCount().isEmpty()) + << "cardDatabaseReset must invalidate the main-type cache"; + EXPECT_TRUE(db.query().getAllSubCardTypesWithCount().isEmpty()) + << "cardDatabaseReset must invalidate the subtype cache"; + EXPECT_TRUE(db.query().getAllFormatsWithCount().isEmpty()) << "cardDatabaseReset must invalidate the format cache"; +} + TEST(CardDatabaseTest, LazyPropertiesHashStreamsBlobAccessors) { QHash props;