mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-08-28 17:01:02 +00:00
Minor: Additional functions (#25)
* several functions. fixes #1 and #2 * multi selection functionality
This commit is contained in:
@@ -43,6 +43,7 @@ add_executable(ccm_core_tests
|
||||
card_sorter_tests.cpp
|
||||
card_filter_tests.cpp
|
||||
ascii_utils_tests.cpp
|
||||
card_lookup_detect_tests.cpp
|
||||
http_get_mapping_tests.cpp
|
||||
cpr_http_client_tests.cpp
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "ccm/util/CardLookupDetect.hpp"
|
||||
|
||||
using namespace ccm;
|
||||
|
||||
TEST_SUITE("preferDetectBySetNo") {
|
||||
TEST_CASE("only set number filled uses reverse lookup") {
|
||||
CHECK(preferDetectBySetNo(true, false, CardLookupEditField::None));
|
||||
CHECK(preferDetectBySetNo(true, false, CardLookupEditField::Name));
|
||||
CHECK(preferDetectBySetNo(true, false, CardLookupEditField::SetNo));
|
||||
}
|
||||
|
||||
TEST_CASE("only name filled uses name lookup") {
|
||||
CHECK_FALSE(preferDetectBySetNo(false, true, CardLookupEditField::None));
|
||||
CHECK_FALSE(preferDetectBySetNo(false, true, CardLookupEditField::Name));
|
||||
CHECK_FALSE(preferDetectBySetNo(false, true, CardLookupEditField::SetNo));
|
||||
}
|
||||
|
||||
TEST_CASE("both empty does not prefer reverse") {
|
||||
CHECK_FALSE(preferDetectBySetNo(true, true, CardLookupEditField::None));
|
||||
CHECK_FALSE(preferDetectBySetNo(true, true, CardLookupEditField::SetNo));
|
||||
}
|
||||
|
||||
TEST_CASE("both filled: last edited SetNo prefers reverse") {
|
||||
CHECK(preferDetectBySetNo(false, false, CardLookupEditField::SetNo));
|
||||
}
|
||||
|
||||
TEST_CASE("both filled: Name or None keeps name lookup") {
|
||||
CHECK_FALSE(preferDetectBySetNo(false, false, CardLookupEditField::Name));
|
||||
CHECK_FALSE(preferDetectBySetNo(false, false, CardLookupEditField::None));
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ public:
|
||||
std::string lastSetNo;
|
||||
std::string detectLastName;
|
||||
std::string detectLastSetId;
|
||||
std::string detectLastSetNo;
|
||||
AutoDetectedPrint detectedPrint{"LOB-001", "Ultra Rare"};
|
||||
bool allowAutoDetect{true};
|
||||
|
||||
@@ -67,6 +68,23 @@ public:
|
||||
return Result<std::vector<AutoDetectedPrint>>::ok(std::move(v));
|
||||
}
|
||||
|
||||
Result<AutoDetectedPrint> detectBySetNo(std::string_view setId,
|
||||
std::string_view setNo) override {
|
||||
detectLastSetId = std::string(setId);
|
||||
detectLastSetNo = std::string(setNo);
|
||||
return Result<AutoDetectedPrint>::ok(detectedPrint);
|
||||
}
|
||||
|
||||
Result<std::vector<AutoDetectedPrint>> detectVariantsBySetNo(
|
||||
std::string_view setId,
|
||||
std::string_view setNo) override {
|
||||
detectLastSetId = std::string(setId);
|
||||
detectLastSetNo = std::string(setNo);
|
||||
std::vector<AutoDetectedPrint> v;
|
||||
v.push_back(detectedPrint);
|
||||
return Result<std::vector<AutoDetectedPrint>>::ok(std::move(v));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool supportsAutoDetectPrint() const noexcept override {
|
||||
return allowAutoDetect;
|
||||
}
|
||||
@@ -992,3 +1010,41 @@ TEST_SUITE("CardPreviewService::detectPrintVariants") {
|
||||
std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("CardPreviewService::detectVariantsBySetNo") {
|
||||
TEST_CASE("routes set-scoped reverse lookup to registered source") {
|
||||
FakeSource source;
|
||||
source.detectedPrint = AutoDetectedPrint{"4", "Rare", "Pikachu"};
|
||||
FakeGameModule module;
|
||||
module.gameId = Game::Pokemon;
|
||||
module.preview = &source;
|
||||
|
||||
FixedHttpClient http;
|
||||
CardPreviewService svc{http};
|
||||
svc.registerModule(module);
|
||||
|
||||
const auto out = svc.detectVariantsBySetNo(Game::Pokemon, "base1", "4");
|
||||
REQUIRE(out.isOk());
|
||||
REQUIRE(out.value().size() == 1);
|
||||
CHECK(out.value()[0].name == "Pikachu");
|
||||
CHECK(out.value()[0].setNo == "4");
|
||||
CHECK(source.detectLastSetId == "base1");
|
||||
CHECK(source.detectLastSetNo == "4");
|
||||
}
|
||||
|
||||
TEST_CASE("returns error when game does not enable auto-detect") {
|
||||
FakeSource source;
|
||||
source.allowAutoDetect = false;
|
||||
FakeGameModule module;
|
||||
module.gameId = Game::Magic;
|
||||
module.preview = &source;
|
||||
|
||||
FixedHttpClient http;
|
||||
CardPreviewService svc{http};
|
||||
svc.registerModule(module);
|
||||
|
||||
const auto out = svc.detectVariantsBySetNo(Game::Magic, "lea", "1");
|
||||
CHECK(out.isErr());
|
||||
CHECK(out.error().find("not enabled") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,3 +177,56 @@ TEST_SUITE("DigiBattle99CardPreviewSource::detectPrintVariants") {
|
||||
CHECK(out.value()[1].setNo == "ST-126");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("DigiBattle99CardPreviewSource::detectVariantsBySetNo") {
|
||||
TEST_CASE("card id search fills name within pack") {
|
||||
FixedHttpClient http;
|
||||
http.body = R"([
|
||||
{"name":"Agumon","id":"ST-01","set_name":["Series 1 Starter Set"]}
|
||||
])";
|
||||
DigiBattle99CardPreviewSource src{http};
|
||||
const auto out = src.detectVariantsBySetNo("Series 1 Starter Set", "st-01");
|
||||
REQUIRE(out.isOk());
|
||||
REQUIRE(out.value().size() == 1);
|
||||
CHECK(out.value()[0].name == "Agumon");
|
||||
CHECK(out.value()[0].setNo == "ST-01");
|
||||
CHECK(http.lastUrl.find("card=ST-01") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("digits-only 1 matches ST-01 not ST-11 from fuzzy API hits") {
|
||||
FixedHttpClient http;
|
||||
http.body = R"([
|
||||
{"name":"Patamon","id":"ST-11","set_name":["Series 1 Starter Set"]},
|
||||
{"name":"Agumon","id":"ST-01","set_name":["Series 1 Starter Set"]},
|
||||
{"name":"Other","id":"BO-1","set_name":["Booster 1"]}
|
||||
])";
|
||||
DigiBattle99CardPreviewSource src{http};
|
||||
const auto out = src.detectVariantsBySetNo("Series 1 Starter Set", "1");
|
||||
REQUIRE(out.isOk());
|
||||
REQUIRE(out.value().size() == 1);
|
||||
CHECK(out.value()[0].name == "Agumon");
|
||||
CHECK(out.value()[0].setNo == "ST-01");
|
||||
}
|
||||
|
||||
TEST_CASE("digits-only 11 matches ST-11 not ST-01") {
|
||||
FixedHttpClient http;
|
||||
http.body = R"([
|
||||
{"name":"Agumon","id":"ST-01","set_name":["Series 1 Starter Set"]},
|
||||
{"name":"Patamon","id":"ST-11","set_name":["Series 1 Starter Set"]}
|
||||
])";
|
||||
DigiBattle99CardPreviewSource src{http};
|
||||
const auto out = src.detectVariantsBySetNo("Series 1 Starter Set", "11");
|
||||
REQUIRE(out.isOk());
|
||||
REQUIRE(out.value().size() == 1);
|
||||
CHECK(out.value()[0].name == "Patamon");
|
||||
CHECK(out.value()[0].setNo == "ST-11");
|
||||
}
|
||||
|
||||
TEST_CASE("empty pack is rejected") {
|
||||
FixedHttpClient http;
|
||||
DigiBattle99CardPreviewSource src{http};
|
||||
const auto out = src.detectVariantsBySetNo("", "ST-01");
|
||||
REQUIRE(out.isErr());
|
||||
CHECK(out.error().find("set") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,20 +404,32 @@ TEST_SUITE("YuGiOhSetCatalog JSON") {
|
||||
YuGiOhSetCatalogPack pack;
|
||||
pack.setId = "LOB";
|
||||
pack.setName = "Legend of Blue Eyes White Dragon";
|
||||
pack.cards.push_back(YuGiOhCatalogCard{"LOB-001", "Blue-Eyes White Dragon"});
|
||||
pack.cards.push_back(YuGiOhCatalogCard{"LOB-EN005", "Dark Magician"});
|
||||
pack.cards.push_back(YuGiOhCatalogCard{"LOB-001", "Blue-Eyes White Dragon", "Ultra Rare"});
|
||||
pack.cards.push_back(YuGiOhCatalogCard{"LOB-EN005", "Dark Magician", "Ultra Rare"});
|
||||
catalog.packs.push_back(std::move(pack));
|
||||
|
||||
nlohmann::json j = catalog;
|
||||
CHECK(j.at("packs").is_array());
|
||||
CHECK(j.at("packs").at(0).at("id") == "LOB");
|
||||
CHECK(j.at("packs").at(0).at("cards").at(0).at("setNo") == "LOB-001");
|
||||
CHECK(j.at("packs").at(0).at("cards").at(0).at("rarity") == "Ultra Rare");
|
||||
|
||||
const YuGiOhSetCatalog back = j.get<YuGiOhSetCatalog>();
|
||||
CHECK(back == catalog);
|
||||
CHECK(back.findPack("LOB") != nullptr);
|
||||
CHECK(back.findPack("missing") == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("legacy catalog JSON without rarity still loads") {
|
||||
const auto j = nlohmann::json::parse(R"({
|
||||
"packs":[{"id":"LOB","name":"Legend of Blue Eyes White Dragon",
|
||||
"cards":[{"setNo":"LOB-001","name":"Blue-Eyes White Dragon"}]}]
|
||||
})");
|
||||
const YuGiOhSetCatalog back = j.get<YuGiOhSetCatalog>();
|
||||
REQUIRE(back.packs.size() == 1);
|
||||
REQUIRE(back.packs[0].cards.size() == 1);
|
||||
CHECK(back.packs[0].cards[0].rarity.empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("PokemonSetCatalog JSON") {
|
||||
|
||||
@@ -28,6 +28,13 @@ TEST_SUITE("FsNames::formatTextForFs") {
|
||||
TEST_CASE("idempotent on already-clean strings") {
|
||||
CHECK(formatTextForFs("AlreadyClean") == "AlreadyClean");
|
||||
}
|
||||
|
||||
TEST_CASE("male and female signs become male/female") {
|
||||
CHECK(formatTextForFs("\xE2\x99\x82") == "male");
|
||||
CHECK(formatTextForFs("\xE2\x99\x80") == "female");
|
||||
CHECK(formatTextForFs("Nidoran \xE2\x99\x82") == "Nidoranmale");
|
||||
CHECK(formatTextForFs("Nidoran \xE2\x99\x80") == "Nidoranfemale");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("FsNames::parseIndexFromFilename") {
|
||||
|
||||
@@ -591,3 +591,58 @@ TEST_SUITE("JapanesePokemonCardPreviewSource::detectPrintVariants catalog-only")
|
||||
CHECK(shining.value()[0].setNo == "013");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("JapanesePokemonCardPreviewSource::detectVariantsBySetNoFromCatalog") {
|
||||
TEST_CASE("resolves English name from setId + localId") {
|
||||
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
|
||||
"sets": {},
|
||||
"prints": [
|
||||
{"set_id":"PMCG1","local_id":"001","name_en":"Bulbasaur","name_ja":"フシギダネ"}
|
||||
]
|
||||
})");
|
||||
REQUIRE(catalog.isOk());
|
||||
const auto out = JapanesePokemonCardPreviewSource::detectVariantsBySetNoFromCatalog(
|
||||
"PMCG1", "001", catalog.value());
|
||||
REQUIRE(out.isOk());
|
||||
REQUIRE(out.value().size() == 1);
|
||||
CHECK(out.value()[0].name == "Bulbasaur");
|
||||
CHECK(out.value()[0].setNo == "001");
|
||||
}
|
||||
|
||||
TEST_CASE("leading-zero-insensitive localId still resolves") {
|
||||
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
|
||||
"sets": {},
|
||||
"prints": [
|
||||
{"set_id":"PMCG1","local_id":"001","name_en":"Bulbasaur","name_ja":"フシギダネ"},
|
||||
{"set_id":"PMCG1","local_id":"011","name_en":"Weedle","name_ja":"ビードル"}
|
||||
]
|
||||
})");
|
||||
REQUIRE(catalog.isOk());
|
||||
const auto byOne = JapanesePokemonCardPreviewSource::detectVariantsBySetNoFromCatalog(
|
||||
"PMCG1", "1", catalog.value());
|
||||
REQUIRE(byOne.isOk());
|
||||
REQUIRE(byOne.value().size() == 1);
|
||||
CHECK(byOne.value()[0].name == "Bulbasaur");
|
||||
CHECK(byOne.value()[0].setNo == "001");
|
||||
|
||||
const auto byEleven =
|
||||
JapanesePokemonCardPreviewSource::detectVariantsBySetNoFromCatalog(
|
||||
"PMCG1", "11", catalog.value());
|
||||
REQUIRE(byEleven.isOk());
|
||||
REQUIRE(byEleven.value().size() == 1);
|
||||
CHECK(byEleven.value()[0].name == "Weedle");
|
||||
}
|
||||
|
||||
TEST_CASE("unknown localId is an error") {
|
||||
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
|
||||
"sets": {},
|
||||
"prints": [
|
||||
{"set_id":"PMCG1","local_id":"001","name_en":"Bulbasaur","name_ja":"フシギダネ"}
|
||||
]
|
||||
})");
|
||||
REQUIRE(catalog.isOk());
|
||||
const auto out = JapanesePokemonCardPreviewSource::detectVariantsBySetNoFromCatalog(
|
||||
"PMCG1", "999", catalog.value());
|
||||
REQUIRE(out.isErr());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,3 +323,78 @@ TEST_SUITE("PokemonCardPreviewSource::detectPrintVariants") {
|
||||
CHECK(http.calls == 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("PokemonCardPreviewSource::detectVariantsBySetNo") {
|
||||
TEST_CASE("card-by-id fills name from TCGdex response") {
|
||||
FixedHttpClient http;
|
||||
http.body = R"({
|
||||
"id":"base1-4",
|
||||
"localId":"4",
|
||||
"name":"Charmander",
|
||||
"rarity":"Common",
|
||||
"image":"https://assets.tcgdex.net/en/base/base1/4"
|
||||
})";
|
||||
PokemonCardPreviewSource src{http};
|
||||
const auto out = src.detectVariantsBySetNo("base1", "4");
|
||||
REQUIRE(out.isOk());
|
||||
REQUIRE(out.value().size() == 1);
|
||||
CHECK(out.value()[0].name == "Charmander");
|
||||
CHECK(out.value()[0].setNo == "4");
|
||||
CHECK(out.value()[0].rarity == "Common");
|
||||
CHECK(http.lastUrl.find("/v2/en/cards/base1-4") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("search fallback rejects localIds that only share a digit prefix") {
|
||||
struct ScriptedHttp : IHttpClient {
|
||||
int n = 0;
|
||||
Result<std::string> get(std::string_view) override {
|
||||
++n;
|
||||
if (n == 1) {
|
||||
return Result<std::string>::err("not found");
|
||||
}
|
||||
// Fuzzy search returns both "14" and "4"; only "4" may be kept.
|
||||
return Result<std::string>::ok(R"([
|
||||
{"id":"base1-14","localId":"14","name":"Wrong","rarity":"Common"},
|
||||
{"id":"base1-4","localId":"4","name":"Charmander","rarity":"Common"}
|
||||
])");
|
||||
}
|
||||
} http;
|
||||
PokemonCardPreviewSource src{http};
|
||||
const auto out = src.detectVariantsBySetNo("base1", "4");
|
||||
REQUIRE(out.isOk());
|
||||
REQUIRE(out.value().size() == 1);
|
||||
CHECK(out.value()[0].name == "Charmander");
|
||||
CHECK(out.value()[0].setNo == "4");
|
||||
}
|
||||
|
||||
TEST_CASE("card-by-id response with mismatched localId falls through to search") {
|
||||
struct ScriptedHttp : IHttpClient {
|
||||
int n = 0;
|
||||
Result<std::string> get(std::string_view) override {
|
||||
++n;
|
||||
if (n == 1) {
|
||||
return Result<std::string>::ok(R"({
|
||||
"id":"base1-14","localId":"14","name":"Wrong","rarity":"Rare"
|
||||
})");
|
||||
}
|
||||
return Result<std::string>::ok(R"([
|
||||
{"id":"base1-4","localId":"4","name":"Charmander","rarity":"Common"}
|
||||
])");
|
||||
}
|
||||
} http;
|
||||
PokemonCardPreviewSource src{http};
|
||||
const auto out = src.detectVariantsBySetNo("base1", "4");
|
||||
REQUIRE(out.isOk());
|
||||
REQUIRE(out.value().size() == 1);
|
||||
CHECK(out.value()[0].name == "Charmander");
|
||||
CHECK(out.value()[0].setNo == "4");
|
||||
}
|
||||
|
||||
TEST_CASE("empty set id is rejected") {
|
||||
FixedHttpClient http;
|
||||
PokemonCardPreviewSource src{http};
|
||||
const auto out = src.detectVariantsBySetNo("", "4");
|
||||
REQUIRE(out.isErr());
|
||||
CHECK(out.error().find("set") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "ccm/domain/YuGiOhSetCatalog.hpp"
|
||||
#include "ccm/games/yugioh/YuGiOhCardPreviewSource.hpp"
|
||||
#include "ccm/ports/IHttpClient.hpp"
|
||||
#include "ccm/util/YuGiOhPrintingSlot.hpp"
|
||||
@@ -86,6 +87,15 @@ TEST_SUITE("ygoPrintingSlotsMatch") {
|
||||
CHECK(ygoPrintingSlotsMatch("RA04-001", "RA04-EN001"));
|
||||
}
|
||||
|
||||
TEST_CASE("matches collector numbers ignoring leading zeros") {
|
||||
CHECK(ygoPrintingSlotsMatch("LOB-5", "LOB-005"));
|
||||
CHECK(ygoPrintingSlotsMatch("LOB-EN005", "LOB-5"));
|
||||
CHECK(ygoCollectorDigitsEqual("005", "5"));
|
||||
CHECK(ygoCollectorDigitsEqual("LOB-EN005", "5"));
|
||||
CHECK(ygoCollectorDigitsFromInput("005") == "005");
|
||||
CHECK(ygoCollectorDigitsFromInput("LOB-EN005") == "005");
|
||||
}
|
||||
|
||||
TEST_CASE("detects European alternate numbering suffix E+digit vs EN/DE") {
|
||||
CHECK(ygoLikelyEuropeanRegionalSetCode("LOB-E003"));
|
||||
CHECK_FALSE(ygoLikelyEuropeanRegionalSetCode("LOB-EN005"));
|
||||
@@ -935,3 +945,106 @@ TEST_SUITE("YuGiOhCardPreviewSource::detectFirstPrint") {
|
||||
CHECK(out.error() == "offline");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("YuGiOhCardPreviewSource::detectVariantsBySetNoFromCatalog") {
|
||||
TEST_CASE("matches digits within pack to card name") {
|
||||
YuGiOhSetCatalog catalog;
|
||||
YuGiOhSetCatalogPack pack;
|
||||
pack.setId = "LOB";
|
||||
pack.setName = "Legend of Blue Eyes White Dragon";
|
||||
pack.cards.push_back(YuGiOhCatalogCard{"LOB-005", "Dark Magician", "Ultra Rare"});
|
||||
pack.cards.push_back(YuGiOhCatalogCard{"LOB-EN001", "Blue-Eyes White Dragon", "Ultra Rare"});
|
||||
catalog.packs.push_back(std::move(pack));
|
||||
|
||||
const auto byDigits =
|
||||
YuGiOhCardPreviewSource::detectVariantsBySetNoFromCatalog(catalog, "LOB", "005");
|
||||
REQUIRE(byDigits.isOk());
|
||||
REQUIRE(byDigits.value().size() == 1);
|
||||
CHECK(byDigits.value()[0].name == "Dark Magician");
|
||||
CHECK(byDigits.value()[0].setNo == "LOB-005");
|
||||
CHECK(byDigits.value()[0].rarity == "Ultra Rare");
|
||||
|
||||
const auto byUnpadded =
|
||||
YuGiOhCardPreviewSource::detectVariantsBySetNoFromCatalog(catalog, "LOB", "5");
|
||||
REQUIRE(byUnpadded.isOk());
|
||||
REQUIRE(byUnpadded.value().size() == 1);
|
||||
CHECK(byUnpadded.value()[0].name == "Dark Magician");
|
||||
|
||||
const auto byRegionCode =
|
||||
YuGiOhCardPreviewSource::detectVariantsBySetNoFromCatalog(catalog, "LOB", "LOB-001");
|
||||
REQUIRE(byRegionCode.isOk());
|
||||
REQUIRE(byRegionCode.value().size() == 1);
|
||||
CHECK(byRegionCode.value()[0].name == "Blue-Eyes White Dragon");
|
||||
}
|
||||
|
||||
TEST_CASE("digits-only 1 does not match collector 011") {
|
||||
YuGiOhSetCatalog catalog;
|
||||
YuGiOhSetCatalogPack pack;
|
||||
pack.setId = "LOB";
|
||||
pack.cards.push_back(YuGiOhCatalogCard{"LOB-005", "Dark Magician"});
|
||||
pack.cards.push_back(YuGiOhCatalogCard{"LOB-011", "Hitotsu-Me Giant"});
|
||||
catalog.packs.push_back(std::move(pack));
|
||||
|
||||
CHECK(YuGiOhCardPreviewSource::detectVariantsBySetNoFromCatalog(catalog, "LOB", "1")
|
||||
.isErr());
|
||||
|
||||
const auto byEleven =
|
||||
YuGiOhCardPreviewSource::detectVariantsBySetNoFromCatalog(catalog, "LOB", "11");
|
||||
REQUIRE(byEleven.isOk());
|
||||
REQUIRE(byEleven.value().size() == 1);
|
||||
CHECK(byEleven.value()[0].name == "Hitotsu-Me Giant");
|
||||
}
|
||||
|
||||
TEST_CASE("unknown pack or number returns error") {
|
||||
YuGiOhSetCatalog catalog;
|
||||
YuGiOhSetCatalogPack pack;
|
||||
pack.setId = "LOB";
|
||||
pack.cards.push_back(YuGiOhCatalogCard{"LOB-005", "Dark Magician"});
|
||||
catalog.packs.push_back(std::move(pack));
|
||||
|
||||
CHECK(YuGiOhCardPreviewSource::detectVariantsBySetNoFromCatalog(catalog, "SDK", "001")
|
||||
.isErr());
|
||||
CHECK(YuGiOhCardPreviewSource::detectVariantsBySetNoFromCatalog(catalog, "LOB", "999")
|
||||
.isErr());
|
||||
}
|
||||
|
||||
TEST_CASE("detectVariantsBySetNo falls back to cardset HTTP without catalog") {
|
||||
FixedHttpClient http;
|
||||
http.body = R"({
|
||||
"data":[
|
||||
{"name":"Dark Magician",
|
||||
"card_sets":[
|
||||
{"set_name":"Legend of Blue Eyes White Dragon","set_code":"LOB-EN005","set_rarity":"Ultra Rare"}
|
||||
]}
|
||||
]
|
||||
})";
|
||||
YuGiOhCardPreviewSource src{http};
|
||||
const auto out = src.detectVariantsBySetNo("Legend of Blue Eyes White Dragon", "5");
|
||||
REQUIRE(out.isOk());
|
||||
REQUIRE(out.value().size() == 1);
|
||||
CHECK(out.value()[0].name == "Dark Magician");
|
||||
CHECK(out.value()[0].setNo == "LOB-EN005");
|
||||
CHECK(out.value()[0].rarity == "Ultra Rare");
|
||||
CHECK(http.lastUrl.find("cardset=") != std::string::npos);
|
||||
CHECK(http.lastUrl.find("fname=") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cardset reverse keeps distinct rarities for the same set code") {
|
||||
const std::string body = R"({
|
||||
"data":[
|
||||
{"name":"Elemental HERO Bubbleman",
|
||||
"card_sets":[
|
||||
{"set_name":"Soul of the Duelist","set_code":"SOD-EN015","set_rarity":"Ultra Rare"},
|
||||
{"set_name":"Soul of the Duelist","set_code":"SOD-EN015","set_rarity":"Ultimate Rare"}
|
||||
]}
|
||||
]
|
||||
})";
|
||||
const auto out = YuGiOhCardPreviewSource::detectVariantsBySetNoFromCardset(
|
||||
body, "Soul of the Duelist", "15");
|
||||
REQUIRE(out.isOk());
|
||||
REQUIRE(out.value().size() == 2);
|
||||
CHECK(out.value()[0].name == "Elemental HERO Bubbleman");
|
||||
CHECK(out.value()[0].rarity == "Ultra Rare");
|
||||
CHECK(out.value()[1].rarity == "Ultimate Rare");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,11 +213,16 @@ TEST_SUITE("YuGiOhSetSource::parseCatalog") {
|
||||
}
|
||||
CHECK(sawBe);
|
||||
CHECK(sawDm);
|
||||
for (const auto& c : lob->cards) {
|
||||
if (c.name == "Blue-Eyes White Dragon") CHECK(c.rarity == "Ultra Rare");
|
||||
if (c.name == "Dark Magician") CHECK(c.rarity == "Ultra Rare");
|
||||
}
|
||||
|
||||
const auto* mrd = out.value().findPack("MRD");
|
||||
REQUIRE(mrd != nullptr);
|
||||
REQUIRE(mrd->cards.size() == 1);
|
||||
CHECK(mrd->cards[0].setNo == "MRD-010");
|
||||
CHECK(mrd->cards[0].rarity == "Ultra Rare");
|
||||
}
|
||||
|
||||
TEST_CASE("missing data array returns error") {
|
||||
|
||||
@@ -124,6 +124,34 @@ TEST_SUITE("YuGiOhBandaiCardPreviewSource helpers") {
|
||||
CHECK(out.value()[0].setId == "ban1");
|
||||
}
|
||||
|
||||
TEST_CASE("parseAskResponse drops results whose Bandai number does not match") {
|
||||
const std::string body = R"JSON({
|
||||
"query": {
|
||||
"results": {
|
||||
"Card Eleven (Bandai)": {
|
||||
"printouts": {
|
||||
"English name": ["Card Eleven"],
|
||||
"Bandai number": [11],
|
||||
"Rarity": [{"fulltext": "Common"}]
|
||||
}
|
||||
},
|
||||
"Card One (Bandai)": {
|
||||
"printouts": {
|
||||
"English name": ["Card One"],
|
||||
"Bandai number": [1],
|
||||
"Rarity": [{"fulltext": "Common"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})JSON";
|
||||
auto out = YuGiOhBandaiCardPreviewSource::parseAskResponse(body, "ban1", "1");
|
||||
REQUIRE(out);
|
||||
REQUIRE(out.value().size() == 1);
|
||||
CHECK(out.value()[0].name == "Card One");
|
||||
CHECK(out.value()[0].setNo == "1");
|
||||
}
|
||||
|
||||
TEST_CASE("fetchImageUrl uses pageimages URL") {
|
||||
FixedHttpClient http;
|
||||
http.body = R"JSON({
|
||||
@@ -182,7 +210,7 @@ TEST_SUITE("YuGiOhBandaiCardPreviewSource helpers") {
|
||||
}
|
||||
})JSON";
|
||||
YuGiOhBandaiCardPreviewSource src(http);
|
||||
auto out = src.detectBySetNo("014");
|
||||
auto out = src.detectBySetNo("ban1", "014");
|
||||
REQUIRE(out);
|
||||
CHECK(out.value().name == "Dark Magician");
|
||||
CHECK(http.lastUrl.find("action=ask") != std::string::npos);
|
||||
@@ -196,7 +224,7 @@ TEST_SUITE("YuGiOhBandaiCardPreviewSource helpers") {
|
||||
}
|
||||
})JSON";
|
||||
YuGiOhBandaiCardPreviewSource src(http);
|
||||
auto out = src.detectBySetNo("ta2");
|
||||
auto out = src.detectBySetNo("banpromo-ta", "ta2");
|
||||
REQUIRE(out);
|
||||
CHECK(out.value().name == "Blue-Eyes White Dragon's 3-Body Connection");
|
||||
CHECK(out.value().setNo == "TA2");
|
||||
@@ -206,6 +234,27 @@ TEST_SUITE("YuGiOhBandaiCardPreviewSource helpers") {
|
||||
CHECK(http.lastUrl.find("Promotional") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("detectBySetNo filters out prints from other sets") {
|
||||
FixedHttpClient http;
|
||||
http.body = R"JSON({
|
||||
"query": {
|
||||
"results": {
|
||||
"Dark Magician (Bandai)": {
|
||||
"printouts": {
|
||||
"English name": ["Dark Magician"],
|
||||
"Bandai number": [14],
|
||||
"Rarity": [{"fulltext": "Rare"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})JSON";
|
||||
YuGiOhBandaiCardPreviewSource src(http);
|
||||
auto out = src.detectBySetNo("bansealdass", "14");
|
||||
CHECK_FALSE(out);
|
||||
CHECK(out.error().find("selected set") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("isAlphanumericPromoNumber detects Jump and Toei codes") {
|
||||
CHECK(YuGiOhBandaiCardPreviewSource::isAlphanumericPromoNumber("TA2"));
|
||||
CHECK(YuGiOhBandaiCardPreviewSource::isAlphanumericPromoNumber("j1"));
|
||||
|
||||
Reference in New Issue
Block a user