mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-08-28 22:01:12 +00:00
d3b4762b76
* several functions. fixes #1 and #2 * multi selection functionality
649 lines
28 KiB
C++
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());
|
|
}
|
|
}
|