Files
Card-Collection-Manager-3/tests/japanese_pokemon_card_preview_source_tests.cpp
T
Sebastian Dine d3b4762b76 Minor: Additional functions (#25)
* several functions. fixes #1 and #2

* multi selection functionality
2026-08-03 08:35:38 +02:00

649 lines
28 KiB
C++

#include <doctest/doctest.h>
#include "ccm/games/pokemonjp/JapanesePokemonCardPreviewSource.hpp"
#include "ccm/games/pokemonjp/JapanesePokemonEnCatalog.hpp"
#include "ccm/ports/IHttpClient.hpp"
#include <string>
#include <unordered_map>
using namespace ccm;
namespace {
class RoutingHttpClient final : public IHttpClient {
public:
std::unordered_map<std::string, std::string> bodies;
std::string lastUrl;
bool ok = true;
Result<std::string> get(std::string_view url) override {
lastUrl = std::string(url);
if (!ok) return Result<std::string>::err("offline");
const auto it = bodies.find(lastUrl);
if (it == bodies.end()) return Result<std::string>::err("unknown url: " + lastUrl);
return Result<std::string>::ok(it->second);
}
};
JapanesePokemonEnCatalog sampleCatalog() {
auto c = JapanesePokemonEnCatalog::parse(R"({
"sets": {
"SV1a": {"name_en":"Triplet Beat","name_ja":"トリプレットビート"}
},
"prints": [
{"set_id":"SV1a","local_id":"001","name_en":"Tropius","name_ja":"トロピウス","name_en_source":"bulbapedia"}
]
})");
REQUIRE(c.isOk());
return std::move(c).value();
}
} // namespace
TEST_SUITE("JapanesePokemonCardPreviewSource helpers") {
TEST_CASE("normalizeLocalId strips slash and whitespace") {
CHECK(JapanesePokemonCardPreviewSource::normalizeLocalId(" 001/102 ") == "001");
CHECK(JapanesePokemonCardPreviewSource::normalizeLocalId("4/102") == "4");
}
TEST_CASE("imageUrlFromBase appends high.png") {
CHECK(JapanesePokemonCardPreviewSource::imageUrlFromBase(
"https://assets.tcgdex.net/ja/SV/SV1a/001") ==
"https://assets.tcgdex.net/ja/SV/SV1a/001/high.png");
}
TEST_CASE("buildCardUrl encodes set-local id") {
CHECK(JapanesePokemonCardPreviewSource::buildCardUrl("SV1a", "001") ==
"https://api.tcgdex.net/v2/ja/cards/SV1a-001");
}
}
TEST_SUITE("JapanesePokemonCardPreviewSource::parseSetCards") {
TEST_CASE("parses localId name and image") {
const std::string json = R"({
"id":"SV1a",
"cards":[
{"id":"SV1a-001","localId":"001","name":"トロピウス",
"image":"https://assets.tcgdex.net/ja/SV/SV1a/001","rarity":"Common"}
]
})";
const auto out = JapanesePokemonCardPreviewSource::parseSetCards(json);
REQUIRE(out.isOk());
REQUIRE(out.value().size() == 1);
CHECK(out.value()[0].localId == "001");
CHECK(out.value()[0].nameJa == "トロピウス");
CHECK(out.value()[0].imageBase == "https://assets.tcgdex.net/ja/SV/SV1a/001");
}
TEST_CASE("missing cards array is Transient") {
const auto out = JapanesePokemonCardPreviewSource::parseSetCards(R"({"id":"X"})");
REQUIRE(out.isErr());
CHECK(out.error().kind == PreviewLookupError::Kind::Transient);
}
}
TEST_SUITE("JapanesePokemonCardPreviewSource::parseCardImageUrl") {
TEST_CASE("returns high.png URL") {
const auto out = JapanesePokemonCardPreviewSource::parseCardImageUrl(
R"({"id":"SV1a-001","image":"https://assets.tcgdex.net/ja/SV/SV1a/001"})");
REQUIRE(out.isOk());
CHECK(out.value() == "https://assets.tcgdex.net/ja/SV/SV1a/001/high.png");
}
TEST_CASE("null image is NotFound") {
const auto out = JapanesePokemonCardPreviewSource::parseCardImageUrl(
R"({"id":"PMCG1-001","image":null})");
REQUIRE(out.isErr());
CHECK(out.error().kind == PreviewLookupError::Kind::NotFound);
}
TEST_CASE("malformed JSON is Transient") {
const auto out = JapanesePokemonCardPreviewSource::parseCardImageUrl("{bad");
REQUIRE(out.isErr());
CHECK(out.error().kind == PreviewLookupError::Kind::Transient);
}
}
TEST_SUITE("JapanesePokemonCardPreviewSource::parsePrintVariants") {
TEST_CASE("matches English catalog name") {
const std::string body = R"({
"id":"SV1a",
"cards":[
{"localId":"001","name":"トロピウス","rarity":"Common"},
{"localId":"002","name":"other","rarity":"Common"}
]
})";
const auto catalog = sampleCatalog();
const auto out = JapanesePokemonCardPreviewSource::parsePrintVariants(
body, "SV1a", "Tropius", catalog);
REQUIRE(out.isOk());
REQUIRE(out.value().size() == 1);
CHECK(out.value()[0].setNo == "001");
}
TEST_CASE("matches Japanese name without catalog") {
const std::string body = R"({
"id":"SV1a",
"cards":[{"localId":"001","name":"トロピウス"}]
})";
JapanesePokemonEnCatalog empty;
const auto out = JapanesePokemonCardPreviewSource::parsePrintVariants(
body, "SV1a", "トロピウス", empty);
REQUIRE(out.isOk());
CHECK(out.value().front().setNo == "001");
}
TEST_CASE("rejects stale catalog localId when name_ja disagrees with TCGdex") {
// Historical seed bug: Charmander mapped to 001 (actually Bulbasaur).
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
"sets": {},
"prints": [
{"set_id":"PMCG1","local_id":"001","name_en":"Charmander","name_ja":"ヒトカゲ"}
]
})");
REQUIRE(catalog.isOk());
const std::string body = R"({
"id":"PMCG1",
"cards":[
{"localId":"001","name":"フシギダネ","rarity":"Common"},
{"localId":"014","name":"ヒトカゲ","rarity":"Common"}
]
})";
const auto out = JapanesePokemonCardPreviewSource::parsePrintVariants(
body, "PMCG1", "Charmander", catalog.value());
REQUIRE(out.isErr());
}
TEST_CASE("accepts corrected catalog localId for Charmander") {
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
"sets": {},
"prints": [
{"set_id":"PMCG1","local_id":"014","name_en":"Charmander","name_ja":"ヒトカゲ"}
]
})");
REQUIRE(catalog.isOk());
const std::string body = R"({
"id":"PMCG1",
"cards":[
{"localId":"001","name":"フシギダネ"},
{"localId":"014","name":"ヒトカゲ","rarity":"Common"}
]
})";
const auto out = JapanesePokemonCardPreviewSource::parsePrintVariants(
body, "PMCG1", "Charmander", catalog.value());
REQUIRE(out.isOk());
REQUIRE(out.value().size() == 1);
CHECK(out.value()[0].setNo == "014");
}
TEST_CASE("English Blastoise and Mewtwo resolve Expansion Pack localIds") {
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
"sets": {},
"prints": [
{"set_id":"PMCG1","local_id":"032","name_en":"Blastoise","name_ja":"カメックス"},
{"set_id":"PMCG1","local_id":"050","name_en":"Mewtwo","name_ja":"ミュウツー"}
]
})");
REQUIRE(catalog.isOk());
const std::string body = R"({
"id":"PMCG1",
"cards":[
{"localId":"032","name":"カメックス","rarity":"Holo Rare"},
{"localId":"050","name":"ミュウツー","rarity":"Holo Rare"}
]
})";
auto blast = JapanesePokemonCardPreviewSource::parsePrintVariants(
body, "PMCG1", "Blastoise", catalog.value());
REQUIRE(blast.isOk());
REQUIRE(blast.value().size() == 1);
CHECK(blast.value()[0].setNo == "032");
auto mew = JapanesePokemonCardPreviewSource::parsePrintVariants(
body, "PMCG1", "Mewtwo", catalog.value());
REQUIRE(mew.isOk());
REQUIRE(mew.value().size() == 1);
CHECK(mew.value()[0].setNo == "050");
}
TEST_CASE("English Switch resolves Expansion Pack localId 073") {
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
"sets": {},
"prints": [
{"set_id":"PMCG1","local_id":"073","name_en":"Switch","name_ja":"ポケモンいれかえ"}
]
})");
REQUIRE(catalog.isOk());
const std::string body = R"({
"id":"PMCG1",
"cards":[
{"localId":"071","name":"きずぐすり","rarity":"Common"},
{"localId":"073","name":"ポケモンいれかえ","rarity":"Common"}
]
})";
const auto out = JapanesePokemonCardPreviewSource::parsePrintVariants(
body, "PMCG1", "Switch", catalog.value());
REQUIRE(out.isOk());
REQUIRE(out.value().size() == 1);
CHECK(out.value()[0].setNo == "073");
}
}
TEST_SUITE("JapanesePokemonCardPreviewSource::fetchImageUrl") {
TEST_CASE("resolves via card endpoint when localId present") {
RoutingHttpClient http;
http.bodies[JapanesePokemonCardPreviewSource::buildCardUrl("SV1a", "001")] =
R"({"id":"SV1a-001","image":"https://assets.tcgdex.net/ja/SV/SV1a/001"})";
auto catalog = sampleCatalog();
JapanesePokemonCardPreviewSource src{http, catalog};
const auto out = src.fetchImageUrl("Tropius", "SV1a", "001");
REQUIRE(out.isOk());
CHECK(out.value() == "https://assets.tcgdex.net/ja/SV/SV1a/001/high.png");
}
TEST_CASE("resolves via set detail when card has no image but set row does") {
RoutingHttpClient http;
http.bodies[JapanesePokemonCardPreviewSource::buildCardUrl("SV1a", "001")] =
R"({"id":"SV1a-001","image":null})";
http.bodies[JapanesePokemonCardPreviewSource::buildSetDetailUrl("SV1a")] = R"({
"id":"SV1a",
"cards":[{"localId":"001","name":"トロピウス",
"image":"https://assets.tcgdex.net/ja/SV/SV1a/001"}]
})";
auto catalog = sampleCatalog();
JapanesePokemonCardPreviewSource src{http, catalog};
const auto out = src.fetchImageUrl("Tropius", "SV1a", "001");
REQUIRE(out.isOk());
CHECK(out.value().find("/high.png") != std::string::npos);
}
TEST_CASE("stale catalog does not bind English name to wrong localId image") {
RoutingHttpClient http;
http.bodies[JapanesePokemonCardPreviewSource::buildSetDetailUrl("PMCG1")] = R"({
"id":"PMCG1",
"cards":[
{"localId":"001","name":"フシギダネ",
"image":"https://assets.tcgdex.net/ja/PMCG/PMCG1/001"},
{"localId":"014","name":"ヒトカゲ",
"image":"https://assets.tcgdex.net/ja/PMCG/PMCG1/014"}
]
})";
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
"sets": {},
"prints": [
{"set_id":"PMCG1","local_id":"001","name_en":"Charmander","name_ja":"ヒトカゲ"}
]
})");
REQUIRE(catalog.isOk());
JapanesePokemonCardPreviewSource src{http, catalog.value()};
// Empty setNo forces name match; stale catalog must not pick Bulbasaur's art.
const auto out = src.fetchImageUrl("Charmander", "PMCG1", "");
REQUIRE(out.isErr());
CHECK(out.error().kind == PreviewLookupError::Kind::NotFound);
}
TEST_CASE("English name resolves correct localId image via catalog") {
RoutingHttpClient http;
http.bodies[JapanesePokemonCardPreviewSource::buildSetDetailUrl("PMCG1")] = R"({
"id":"PMCG1",
"cards":[
{"localId":"001","name":"フシギダネ",
"image":"https://assets.tcgdex.net/ja/PMCG/PMCG1/001"},
{"localId":"014","name":"ヒトカゲ",
"image":"https://assets.tcgdex.net/ja/PMCG/PMCG1/014"}
]
})";
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
"sets": {},
"prints": [
{"set_id":"PMCG1","local_id":"014","name_en":"Charmander","name_ja":"ヒトカゲ"}
]
})");
REQUIRE(catalog.isOk());
JapanesePokemonCardPreviewSource src{http, catalog.value()};
const auto out = src.fetchImageUrl("Charmander", "PMCG1", "");
REQUIRE(out.isOk());
CHECK(out.value() == "https://assets.tcgdex.net/ja/PMCG/PMCG1/014/high.png");
}
TEST_CASE("null image on set-specific card is NotFound without other-printing fallback") {
RoutingHttpClient http;
http.bodies[JapanesePokemonCardPreviewSource::buildCardUrl("PMCG1", "021")] =
R"({"id":"PMCG1-021","name":"リザードン","image":null})";
http.bodies[JapanesePokemonCardPreviewSource::buildSetDetailUrl("PMCG1")] = R"({
"id":"PMCG1",
"cards":[{"localId":"021","name":"リザードン"}]
})";
JapanesePokemonEnCatalog empty;
JapanesePokemonCardPreviewSource src{http, empty};
const auto out = src.fetchImageUrl("Charizard", "PMCG1", "021");
REQUIRE(out.isErr());
CHECK(out.error().kind == PreviewLookupError::Kind::NotFound);
}
TEST_CASE("catalog tcgplayer_id gap-fills when TCGdex image is null") {
RoutingHttpClient http;
http.bodies[JapanesePokemonCardPreviewSource::buildCardUrl("PMCG1", "021")] =
R"({"id":"PMCG1-021","name":"リザードン","image":null})";
http.bodies[JapanesePokemonCardPreviewSource::buildSetDetailUrl("PMCG1")] = R"({
"id":"PMCG1",
"cards":[{"localId":"021","name":"リザードン"}]
})";
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
"sets": {},
"prints": [
{"set_id":"PMCG1","local_id":"021","name_en":"Charizard",
"name_ja":"リザードン","tcgplayer_id":"575604"}
]
})");
REQUIRE(catalog.isOk());
JapanesePokemonCardPreviewSource src{http, catalog.value()};
const auto out = src.fetchImageUrl("Charizard", "PMCG1", "021");
REQUIRE(out.isOk());
CHECK(out.value() ==
"https://product-images.tcgplayer.com/fit-in/437x437/575604.jpg");
}
TEST_CASE("HTTP failure is Transient") {
RoutingHttpClient http;
http.ok = false;
JapanesePokemonEnCatalog empty;
JapanesePokemonCardPreviewSource src{http, empty};
const auto out = src.fetchImageUrl("Tropius", "SV1a", "001");
REQUIRE(out.isErr());
CHECK(out.error().kind == PreviewLookupError::Kind::Transient);
}
TEST_CASE("catalog-only theme deck resolves preview from tcgplayer_id") {
RoutingHttpClient http;
// No TCGdex bodies: card + set detail both miss.
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
"sets": {"TamamushiCG":{"name_en":"Tamamushi City Gym"}},
"prints": [
{"set_id":"TamamushiCG","local_id":"021","name_en":"Celadon City Gym",
"name_ja":"タマムシシティジム","tcgplayer_id":"12345"}
]
})");
REQUIRE(catalog.isOk());
JapanesePokemonCardPreviewSource src{http, catalog.value()};
const auto out = src.fetchImageUrl("Celadon City Gym", "TamamushiCG", "021");
REQUIRE(out.isOk());
CHECK(out.value() ==
"https://product-images.tcgplayer.com/fit-in/437x437/12345.jpg");
}
TEST_CASE("Tamamushi City Gym Erika uses catalog tcgplayer gap-fill") {
RoutingHttpClient http;
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
"sets": {"TamamushiCG":{"name_en":"Tamamushi City Gym"}},
"prints": [
{"set_id":"TamamushiCG","local_id":"016","name_en":"Erika",
"name_ja":"エリカ","name_en_source":"trainer-table",
"tcgplayer_id":"576776"}
]
})");
REQUIRE(catalog.isOk());
JapanesePokemonCardPreviewSource src{http, catalog.value()};
const auto out = src.fetchImageUrl("Erika", "TamamushiCG", "016");
REQUIRE(out.isOk());
CHECK(out.value() ==
"https://product-images.tcgplayer.com/fit-in/437x437/576776.jpg");
}
TEST_CASE("neo catalog image_url gap-fills when TCGdex image is null") {
RoutingHttpClient http;
http.bodies[JapanesePokemonCardPreviewSource::buildCardUrl("neo4", "106")] =
R"({"id":"neo4-106","name":"ラッキースタジアム","image":null})";
http.bodies[JapanesePokemonCardPreviewSource::buildSetDetailUrl("neo4")] = R"({
"id":"neo4",
"cards":[{"localId":"106","name":"ラッキースタジアム"}]
})";
const auto catalog = JapanesePokemonEnCatalog::parse(R"({
"sets": {},
"prints": [
{"set_id":"neo4","local_id":"106","name_en":"Shining Celebi",
"name_ja":"輝くセレビ","name_en_source":"species-table-variant",
"image_url":"https://images.pokemontcg.io/neo4/106_hires.png"}
]
})");
REQUIRE(catalog.isOk());
JapanesePokemonCardPreviewSource src{http, catalog.value()};
const auto out = src.fetchImageUrl("Shining Celebi", "neo4", "106");
REQUIRE(out.isOk());
CHECK(out.value() == "https://images.pokemontcg.io/neo4/106_hires.png");
}
}
TEST_SUITE("JapanesePokemonCardPreviewSource::detectPrintVariants catalog-only") {
TEST_CASE("English trainer name resolves when TCGdex set detail is unavailable") {
RoutingHttpClient http;
const auto catalog = JapanesePokemonEnCatalog::parse(R"json({
"sets": {"TamamushiCG":{"name_en":"Tamamushi City Gym"}},
"prints": [
{"set_id":"TamamushiCG","local_id":"021","name_en":"Celadon City Gym",
"name_ja":"タマムシシティジム","name_en_source":"trainer-table",
"image_url":"https://example.com/celadon.jpg"},
{"set_id":"TamamushiCG","local_id":"001","name_en":"Erika's Oddish",
"name_ja":"エリカのナゾノクサ","name_en_source":"manual",
"image_url":"https://example.com/oddish.jpg"}
]
})json");
REQUIRE(catalog.isOk());
JapanesePokemonCardPreviewSource src{http, catalog.value()};
const auto out = src.detectPrintVariants("Celadon City Gym", "TamamushiCG");
REQUIRE(out.isOk());
REQUIRE(out.value().size() == 1);
CHECK(out.value()[0].setNo == "021");
}
TEST_CASE("detectPrintVariantsFromCatalog includes UnnumberedPromo prints without image_url") {
const auto catalog = JapanesePokemonEnCatalog::parse(R"json({
"sets": {},
"prints": [
{"set_id":"UnnumberedPromo","local_id":"007","name_en":"Mewtwo (CoroCoro promo)",
"name_ja":"ミュウツー","name_en_source":"manual"},
{"set_id":"UnnumberedPromo","local_id":"008","name_en":"Mewtwo (Fan Book promo)",
"name_ja":"ミュウツー","name_en_source":"manual"},
{"set_id":"UnnumberedPromo","local_id":"030","name_en":"Mewtwo (WHF Special Sheet promo)",
"name_ja":"ミュウツー","name_en_source":"manual",
"image_url":"https://archives.bulbagarden.net/media/upload/w/w/MewtwoWHF.jpg"},
{"set_id":"UnnumberedPromo","local_id":"045","name_en":"Mewtwo Strikes Back (Jumbo)",
"name_ja":"","name_en_source":"manual"}
]
})json");
REQUIRE(catalog.isOk());
const auto mew = JapanesePokemonCardPreviewSource::detectPrintVariantsFromCatalog(
"UnnumberedPromo", "Mewtwo", catalog.value());
REQUIRE(mew.isOk());
REQUIRE(mew.value().size() == 4);
// Imaged prints first, then empty-URL identity rows.
CHECK(mew.value()[0].setNo == "030");
CHECK(mew.value()[1].setNo == "007");
CHECK(mew.value()[2].setNo == "008");
CHECK(mew.value()[3].setNo == "045");
RoutingHttpClient http;
JapanesePokemonCardPreviewSource src{http, catalog.value()};
const auto img = src.fetchImageUrl("Mewtwo", "UnnumberedPromo", "030");
REQUIRE(img.isOk());
CHECK(img.value() ==
"https://archives.bulbagarden.net/media/upload/w/w/MewtwoWHF.jpg");
}
TEST_CASE("fetchImageUrl does not borrow sibling UnnumberedPromo image") {
RoutingHttpClient http;
const auto catalog = JapanesePokemonEnCatalog::parse(R"json({
"sets": {},
"prints": [
{"set_id":"UnnumberedPromo","local_id":"007","name_en":"Mewtwo (CoroCoro promo)",
"name_ja":"ミュウツー","name_en_source":"manual"},
{"set_id":"UnnumberedPromo","local_id":"030","name_en":"Mewtwo (WHF Special Sheet promo)",
"name_ja":"ミュウツー","name_en_source":"manual",
"image_url":"https://archives.bulbagarden.net/media/upload/w/w/MewtwoWHF.jpg"}
]
})json");
REQUIRE(catalog.isOk());
JapanesePokemonCardPreviewSource src{http, catalog.value()};
const auto empty = src.fetchImageUrl("Mewtwo", "UnnumberedPromo", "007");
REQUIRE(empty.isErr());
CHECK(empty.error().kind == PreviewLookupError::Kind::NotFound);
const auto whf = src.fetchImageUrl("Mewtwo", "UnnumberedPromo", "030");
REQUIRE(whf.isOk());
CHECK(whf.value() ==
"https://archives.bulbagarden.net/media/upload/w/w/MewtwoWHF.jpg");
}
TEST_CASE("detectPrintVariantsFromCatalog dedupes shared preview URLs") {
const auto catalog = JapanesePokemonEnCatalog::parse(R"json({
"sets": {},
"prints": [
{"set_id":"UnnumberedPromo","local_id":"030","name_en":"Mewtwo (WHF Special Sheet promo)",
"image_url":"https://archives.bulbagarden.net/media/upload/w/w/same.jpg"},
{"set_id":"UnnumberedPromo","local_id":"073","name_en":"Mewtwo (Song Best Collection promo)",
"image_url":"https://archives.bulbagarden.net/media/upload/w/w/same.jpg"},
{"set_id":"UnnumberedPromo","local_id":"197","name_en":"Mewtwo (Wizards Promo 12)",
"image_url":"https://archives.bulbagarden.net/media/upload/w/w/same.jpg"}
]
})json");
REQUIRE(catalog.isOk());
const auto mew = JapanesePokemonCardPreviewSource::detectPrintVariantsFromCatalog(
"UnnumberedPromo", "Mewtwo", catalog.value());
REQUIRE(mew.isOk());
REQUIRE(mew.value().size() == 1);
CHECK(mew.value()[0].setNo == "030");
}
TEST_CASE("detectPrintVariantsFromCatalog matches owner Pokemon English title") {
const auto catalog = JapanesePokemonEnCatalog::parse(R"json({
"sets": {},
"prints": [
{"set_id":"TamamushiCG","local_id":"001","name_en":"Erika's Oddish",
"name_ja":"エリカのナゾノクサ",
"image_url":"https://example.com/oddish.jpg"}
]
})json");
REQUIRE(catalog.isOk());
const auto out = JapanesePokemonCardPreviewSource::detectPrintVariantsFromCatalog(
"TamamushiCG", "Erika's Oddish", catalog.value());
REQUIRE(out.isOk());
REQUIRE(out.value().size() == 1);
CHECK(out.value()[0].setNo == "001");
}
TEST_CASE("detectPrintVariantsFromCatalog matches Dark Rocket and Owner PMCG titles") {
const auto catalog = JapanesePokemonEnCatalog::parse(R"json({
"sets": {},
"prints": [
{"set_id":"PMCG4","local_id":"017","name_en":"Dark Charizard",
"name_ja":"わるいリザードン","name_en_source":"species-table-variant",
"tcgplayer_id":"575744"},
{"set_id":"PMCG6","local_id":"042","name_en":"Rocket's Zapdos",
"name_ja":"R団のサンダー","name_en_source":"species-table-variant",
"tcgplayer_id":"1"},
{"set_id":"PMCG5","local_id":"002","name_en":"Erika's Oddish",
"name_ja":"エリカのナゾノクサ","name_en_source":"species-table-variant",
"tcgplayer_id":"2"}
]
})json");
REQUIRE(catalog.isOk());
const auto dark = JapanesePokemonCardPreviewSource::detectPrintVariantsFromCatalog(
"PMCG4", "Dark Charizard", catalog.value());
REQUIRE(dark.isOk());
REQUIRE(dark.value().size() == 1);
CHECK(dark.value()[0].setNo == "017");
const auto rocket = JapanesePokemonCardPreviewSource::detectPrintVariantsFromCatalog(
"PMCG6", "Rocket's Zapdos", catalog.value());
REQUIRE(rocket.isOk());
REQUIRE(rocket.value().size() == 1);
CHECK(rocket.value()[0].setNo == "042");
const auto owner = JapanesePokemonCardPreviewSource::detectPrintVariantsFromCatalog(
"PMCG5", "Erika's Oddish", catalog.value());
REQUIRE(owner.isOk());
REQUIRE(owner.value().size() == 1);
CHECK(owner.value()[0].setNo == "002");
}
TEST_CASE("detectPrintVariantsFromCatalog matches Light and Shining neo titles") {
const auto catalog = JapanesePokemonEnCatalog::parse(R"json({
"sets": {},
"prints": [
{"set_id":"neo4","local_id":"004","name_en":"Light Sunflora",
"name_ja":"軽いサンフロラ","name_en_source":"species-table-variant",
"image_url":"https://example.com/sunflora.png"},
{"set_id":"neo4","local_id":"013","name_en":"Shining Celebi",
"name_ja":"輝くセレビ","name_en_source":"species-table-variant",
"image_url":"https://example.com/celebi.png"}
]
})json");
REQUIRE(catalog.isOk());
const auto light = JapanesePokemonCardPreviewSource::detectPrintVariantsFromCatalog(
"neo4", "Light Sunflora", catalog.value());
REQUIRE(light.isOk());
REQUIRE(light.value().size() == 1);
CHECK(light.value()[0].setNo == "004");
const auto shining = JapanesePokemonCardPreviewSource::detectPrintVariantsFromCatalog(
"neo4", "Shining Celebi", catalog.value());
REQUIRE(shining.isOk());
REQUIRE(shining.value().size() == 1);
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());
}
}