mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 09:05:10 -07:00
Merge branch 'master' into tooomm-patch-33
This commit is contained in:
commit
e35c63a592
171 changed files with 4899 additions and 701 deletions
|
|
@ -24,6 +24,19 @@ target_include_directories(lag_monitor_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatri
|
|||
add_test(NAME latency_tracker_test COMMAND latency_tracker_test)
|
||||
add_executable(latency_tracker_test latency_tracker_test.cpp)
|
||||
|
||||
add_test(NAME loader_local_matching_test COMMAND loader_local_matching_test)
|
||||
add_executable(
|
||||
${VERSION_STRING_CPP}
|
||||
loader_local_matching_test
|
||||
loader_local_matching_test.cpp
|
||||
${CMAKE_SOURCE_DIR}/cockatrice/src/client/network/update/client/release_channel.cpp
|
||||
${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/cache_settings.cpp
|
||||
${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/card_counter_settings.cpp
|
||||
${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/shortcuts_settings.cpp
|
||||
${CMAKE_SOURCE_DIR}/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp
|
||||
)
|
||||
target_include_directories(loader_local_matching_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src)
|
||||
|
||||
add_test(NAME metrics_registry_test COMMAND metrics_registry_test)
|
||||
add_executable(metrics_registry_test ../servatrice/src/metrics_registry.cpp metrics_registry_test.cpp)
|
||||
|
||||
|
|
@ -42,6 +55,9 @@ add_executable(server_counter_test server_counter_test.cpp)
|
|||
add_test(NAME server_developer_role_test COMMAND server_developer_role_test)
|
||||
add_executable(server_developer_role_test server_developer_role_test.cpp)
|
||||
|
||||
add_test(NAME server_game_join_test COMMAND server_game_join_test)
|
||||
add_executable(server_game_join_test server_game_join_test.cpp)
|
||||
|
||||
add_test(NAME server_rate_limiter_test COMMAND server_rate_limiter_test)
|
||||
add_executable(server_rate_limiter_test server_rate_limiter_test.cpp)
|
||||
|
||||
|
|
@ -78,20 +94,22 @@ if(NOT GTEST_FOUND)
|
|||
set(GTEST_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/gtest-src/include")
|
||||
set(GTEST_BOTH_LIBRARIES gtest)
|
||||
add_dependencies(dummy_test gtest)
|
||||
add_dependencies(expression_test gtest)
|
||||
add_dependencies(clamped_arithmetic_test gtest)
|
||||
add_dependencies(test_age_formatting gtest)
|
||||
add_dependencies(password_hash_test gtest)
|
||||
add_dependencies(playmat_resolver_test gtest)
|
||||
add_dependencies(deck_hash_performance_test gtest)
|
||||
add_dependencies(server_card_counter_test gtest)
|
||||
add_dependencies(server_counter_test gtest)
|
||||
add_dependencies(server_rate_limiter_test gtest)
|
||||
add_dependencies(server_developer_role_test gtest)
|
||||
add_dependencies(warning_categories_test gtest)
|
||||
add_dependencies(expression_test gtest)
|
||||
add_dependencies(lag_monitor_test gtest)
|
||||
add_dependencies(latency_tracker_test gtest)
|
||||
add_dependencies(loader_local_matching_test gtest)
|
||||
add_dependencies(metrics_registry_test gtest)
|
||||
add_dependencies(password_hash_test gtest)
|
||||
add_dependencies(playmat_resolver_test gtest)
|
||||
add_dependencies(server_card_counter_test gtest)
|
||||
add_dependencies(server_counter_test gtest)
|
||||
add_dependencies(server_developer_role_test gtest)
|
||||
add_dependencies(server_game_join_test gtest)
|
||||
add_dependencies(server_rate_limiter_test gtest)
|
||||
add_dependencies(test_age_formatting gtest)
|
||||
add_dependencies(warning_categories_test gtest)
|
||||
endif()
|
||||
|
||||
include_directories(${GTEST_INCLUDE_DIRS})
|
||||
|
|
@ -127,6 +145,10 @@ target_link_libraries(
|
|||
server_developer_role_test libcockatrice_network libcockatrice_rng Threads::Threads ${GTEST_BOTH_LIBRARIES}
|
||||
${TEST_QT_MODULES}
|
||||
)
|
||||
target_link_libraries(
|
||||
server_game_join_test libcockatrice_network_server_remote libcockatrice_rng Threads::Threads ${GTEST_BOTH_LIBRARIES}
|
||||
${TEST_QT_MODULES}
|
||||
)
|
||||
target_link_libraries(
|
||||
warning_categories_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
||||
)
|
||||
|
|
@ -136,9 +158,12 @@ target_link_libraries(
|
|||
)
|
||||
target_include_directories(metrics_registry_test PRIVATE ${CMAKE_SOURCE_DIR}/servatrice/src)
|
||||
target_link_libraries(metrics_registry_test ${TEST_QT_MODULES} Threads::Threads ${GTEST_BOTH_LIBRARIES})
|
||||
target_link_libraries(
|
||||
loader_local_matching_test libcockatrice_settings Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
||||
)
|
||||
|
||||
add_subdirectory(card_zone_algorithms)
|
||||
add_subdirectory(carddatabase)
|
||||
add_subdirectory(card_zone_algorithms)
|
||||
add_subdirectory(deck_list_model)
|
||||
add_subdirectory(deck_list_zones)
|
||||
add_subdirectory(loading_from_clipboard)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,14 @@
|
|||
#include "test_card_database_path_provider.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <QTemporaryDir>
|
||||
#include <libcockatrice/card/card_info.h>
|
||||
#include <libcockatrice/card/database/card_database_data.h>
|
||||
#include <libcockatrice/card/database/parser/cockatrice_xml_4.h>
|
||||
#include <libcockatrice/card/lazy_properties_hash.h>
|
||||
#include <libcockatrice/card/printing/printing_info.h>
|
||||
#include <libcockatrice/interfaces/noop_card_preference_provider.h>
|
||||
#include <libcockatrice/interfaces/noop_card_set_priority_controller.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
|
@ -33,6 +38,49 @@ TEST(CardDatabaseTest, LoadXml)
|
|||
ASSERT_EQ(0, db->query()->getAllMainCardTypes().size()) << "Types not empty after clear";
|
||||
ASSERT_EQ(NotLoaded, db->getLoadStatus()) << "Incorrect status after clear";
|
||||
}
|
||||
|
||||
TEST(CardDatabaseTest, Xml4LocalizedDataRoundTrip)
|
||||
{
|
||||
NoopCardSetPriorityController controller;
|
||||
CardSetPtr set =
|
||||
CardSet::newInstance(&controller, "TST", "Test Set", "expansion", QDate(), CardSet::PriorityPrimary);
|
||||
|
||||
QHash<QString, QString> props;
|
||||
props["manacost"] = "1R";
|
||||
PrintingInfo printing(set, LazyPropertiesHash(props));
|
||||
SetToPrintingsMap setsInfo;
|
||||
setsInfo["TST"].append(printing);
|
||||
|
||||
CardInfo::UiAttributes attributes = {.tableRow = 1};
|
||||
CardInfoPtr card =
|
||||
CardInfo::newInstance("Lightning Bolt", "Deal 3 damage.", false, {}, {}, {}, setsInfo, attributes);
|
||||
card->setLocalizedName("de", "Blitzschlag");
|
||||
card->setLocalizedText("de", "Blitzschlag fügt 3 Schadenspunkte zu.");
|
||||
|
||||
SetNameMap sets;
|
||||
sets.insert("TST", set);
|
||||
CardNameMap cards;
|
||||
cards.insert("Lightning Bolt", card);
|
||||
|
||||
QTemporaryDir tempDir;
|
||||
const QString fileName = tempDir.filePath("cards.xml");
|
||||
NoopCardPreferenceProvider prefProvider;
|
||||
CockatriceXml4Parser writer(&prefProvider, &controller);
|
||||
ASSERT_TRUE(writer.saveToFile({}, sets, cards, fileName));
|
||||
|
||||
CardDatabaseData data;
|
||||
CockatriceXml4Parser parser(&prefProvider, &controller);
|
||||
QFile file(fileName);
|
||||
ASSERT_TRUE(file.open(QIODevice::ReadOnly));
|
||||
parser.parseFileInto(file, data);
|
||||
|
||||
CardInfoPtr loaded = data.cards.value("Lightning Bolt");
|
||||
ASSERT_FALSE(loaded.isNull());
|
||||
ASSERT_EQ(loaded->getName(), "Lightning Bolt");
|
||||
ASSERT_EQ(loaded->getLocalizedName("de"), "Blitzschlag");
|
||||
ASSERT_EQ(loaded->getLocalizedText("de"), "Blitzschlag fügt 3 Schadenspunkte zu.");
|
||||
ASSERT_EQ(loaded->getLocalizedText("fr"), "Deal 3 damage.");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv)
|
||||
|
|
|
|||
187
tests/loader_local_matching_test.cpp
Normal file
187
tests/loader_local_matching_test.cpp
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
#include "client/settings/cache_settings.h"
|
||||
#include "interface/card_picture_loader/card_picture_loader_local.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <QColor>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QImage>
|
||||
#include <QImageWriter>
|
||||
#include <QLoggingCategory>
|
||||
#include <QStandardPaths>
|
||||
#include <QTemporaryDir>
|
||||
#include <libcockatrice/card/lazy_properties_hash.h>
|
||||
#include <libcockatrice/card/printing/exact_card.h>
|
||||
#include <libcockatrice/card/set/card_set.h>
|
||||
#include <libcockatrice/interfaces/noop_card_set_priority_controller.h>
|
||||
#include <libcockatrice/settings/paths_settings.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Builds an ExactCard with the requested identity fields.
|
||||
*
|
||||
* Mirrors how the client constructs cards: the set short name feeds tryLoad()'s
|
||||
* setName, and the "num" printing property feeds the collector number.
|
||||
*/
|
||||
ExactCard cardFor(const QString &name, const QString &setShortName, const QString &collectorNumber)
|
||||
{
|
||||
CardSetPtr set;
|
||||
if (!setShortName.isEmpty()) {
|
||||
set = CardSet::newInstance(new NoopCardSetPriorityController(), setShortName, setShortName);
|
||||
}
|
||||
|
||||
LazyPropertiesHash properties;
|
||||
if (!collectorNumber.isEmpty()) {
|
||||
properties.insert("num", collectorNumber);
|
||||
}
|
||||
|
||||
return ExactCard(CardInfo::newInstance(name), PrintingInfo(set, properties));
|
||||
}
|
||||
|
||||
class LocalMatcherTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
QTemporaryDir tempDir; ///< Sandboxed "pics" root for every test.
|
||||
CardPictureLoaderLocal *loader = nullptr; ///< Constructed per test against the sandboxed paths.
|
||||
|
||||
QString picsPath() const
|
||||
{
|
||||
return tempDir.path() + "/pics";
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
// The loader ctor snapshots the global picture paths once, so point them at the
|
||||
// sandbox before constructing it.
|
||||
SettingsCache::instance().paths().setPicsPath(picsPath());
|
||||
SettingsCache::instance().paths().setCustomPicsPath(picsPath() + "/CUSTOM/");
|
||||
|
||||
loader = new CardPictureLoaderLocal(nullptr);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
delete loader;
|
||||
loader = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes a valid 1x1 PNG under the sandboxed pics path.
|
||||
*/
|
||||
void writePngUnderPics(const QString &relativePath, const QColor &color = Qt::red)
|
||||
{
|
||||
const QString fullPath = picsPath() + "/" + relativePath;
|
||||
ASSERT_TRUE(QDir().mkpath(QFileInfo(fullPath).absolutePath()));
|
||||
|
||||
QImage image(1, 1, QImage::Format_RGB32);
|
||||
image.fill(color);
|
||||
|
||||
QImageWriter writer(fullPath, "PNG");
|
||||
ASSERT_TRUE(writer.write(image));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(LocalMatcherTest, ExactMatchBareFileWinsOverSuffixedCompanion)
|
||||
{
|
||||
writePngUnderPics("downloadedPics/TestCard.png");
|
||||
writePngUnderPics("downloadedPics/TestCard (1).png");
|
||||
|
||||
const QImage image = loader->tryLoad(cardFor("TestCard", "", ""));
|
||||
|
||||
EXPECT_FALSE(image.isNull()) << "The bare TestCard.png must be picked over its suffixed companion";
|
||||
}
|
||||
|
||||
TEST_F(LocalMatcherTest, SuffixedFileWithoutExactMatchIsNotLoaded)
|
||||
{
|
||||
// The pre-refactor prefix match would have accepted "TestCard (1).png" for "TestCard".
|
||||
writePngUnderPics("downloadedPics/TestCard (1).png");
|
||||
|
||||
const QImage image = loader->tryLoad(cardFor("TestCard", "", ""));
|
||||
|
||||
EXPECT_TRUE(image.isNull()) << "A suffixed file must not satisfy an exact card-name lookup";
|
||||
}
|
||||
|
||||
TEST_F(LocalMatcherTest, SetFolderLookupIgnoresSuffixedFiles)
|
||||
{
|
||||
writePngUnderPics("M10/TestCard (1).png");
|
||||
|
||||
const QImage image = loader->tryLoad(cardFor("TestCard", "M10", ""));
|
||||
|
||||
EXPECT_TRUE(image.isNull()) << "Set-folder lookups must also require an exact name match";
|
||||
}
|
||||
|
||||
TEST_F(LocalMatcherTest, SetFolderLookupStillResolvesExactFile)
|
||||
{
|
||||
writePngUnderPics("M10/TestCard.png");
|
||||
writePngUnderPics("M10/TestCard (1).png");
|
||||
|
||||
const QImage image = loader->tryLoad(cardFor("TestCard", "M10", ""));
|
||||
|
||||
EXPECT_FALSE(image.isNull()) << "The exact file in the set folder must still resolve";
|
||||
}
|
||||
|
||||
TEST_F(LocalMatcherTest, RootDownloadedPicsFallbackResolvesSchemeFilename)
|
||||
{
|
||||
// Non-set-folder export schemes (Name_Set_Collector) write straight into downloadedPics/.
|
||||
writePngUnderPics("downloadedPics/TestCard_M10_1.png");
|
||||
|
||||
const QImage image = loader->tryLoad(cardFor("TestCard", "M10", "1"));
|
||||
|
||||
EXPECT_FALSE(image.isNull()) << "downloadedPics/TestCard_M10_1.png must resolve via the root fallback";
|
||||
}
|
||||
|
||||
TEST_F(LocalMatcherTest, RootFallbackResolvesDashSeparatedVariant)
|
||||
{
|
||||
writePngUnderPics("downloadedPics/TestCard-M10-1.png");
|
||||
|
||||
const QImage image = loader->tryLoad(cardFor("TestCard", "M10", "1"));
|
||||
|
||||
EXPECT_FALSE(image.isNull()) << "The dash-separated import variant must resolve via the root fallback";
|
||||
}
|
||||
|
||||
TEST_F(LocalMatcherTest, DownloadedPicsSetSubfolderStillResolves)
|
||||
{
|
||||
writePngUnderPics("downloadedPics/M10/TestCard_M10_1.png");
|
||||
|
||||
const QImage image = loader->tryLoad(cardFor("TestCard", "M10", "1"));
|
||||
|
||||
EXPECT_FALSE(image.isNull()) << "The set-subfolder export scheme must keep resolving";
|
||||
}
|
||||
|
||||
TEST_F(LocalMatcherTest, SetFolderCandidateTakesPrecedenceOverRootFallback)
|
||||
{
|
||||
writePngUnderPics("M10/TestCard_M10_1.png", Qt::red);
|
||||
writePngUnderPics("downloadedPics/TestCard_M10_1.png", Qt::blue);
|
||||
|
||||
const QImage image = loader->tryLoad(cardFor("TestCard", "M10", "1"));
|
||||
|
||||
ASSERT_FALSE(image.isNull());
|
||||
EXPECT_EQ(image.pixelColor(0, 0), QColor(Qt::red)) << "The set-folder candidate must be preferred";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Redirect SettingsCache reads/writes (app-data location) away from the real user profile.
|
||||
QStandardPaths::setTestModeEnabled(true);
|
||||
|
||||
// Some CI containers run as a uid without a passwd entry (e.g. GitHub's docker
|
||||
// runner), so HOME resolves to "/" and the test-mode qttest data dir cannot be
|
||||
// created. SettingsCache's QSettings then silently drops every write, reads come
|
||||
// back empty, and the paths the loader searches are "". Give the test a writable
|
||||
// HOME for the duration of the run so settings behave like on a normal machine.
|
||||
QTemporaryDir home;
|
||||
if (home.isValid()) {
|
||||
qputenv("HOME", home.path().toLocal8Bit());
|
||||
}
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
QLoggingCategory::setFilterRules("card_picture_loader.*=false\nsettings_cache.*=false");
|
||||
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
|
@ -72,6 +72,18 @@ protected:
|
|||
return card;
|
||||
}
|
||||
|
||||
// Helper: build a single MTGJSON foreignData entry
|
||||
QJsonObject makeForeignEntry(const QString &language, const QString &name, const QString &text)
|
||||
{
|
||||
QJsonObject entry;
|
||||
entry["language"] = language;
|
||||
entry["name"] = name;
|
||||
if (!text.isEmpty()) {
|
||||
entry["text"] = text;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
NoopCardSetPriorityController *controller;
|
||||
OracleImporter *importer;
|
||||
CardSetPtr set;
|
||||
|
|
@ -872,6 +884,243 @@ TEST_F(OracleImporterTest, DisablingProgressReportingSuppressesScanEmissions)
|
|||
ASSERT_GT(emissions, 0);
|
||||
}
|
||||
|
||||
// Localized card text tests
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(OracleImporterTest, ImportsLocalizedTextForRequestedLanguage)
|
||||
{
|
||||
QJsonObject card = makeCard("Lightning Bolt");
|
||||
card["foreignData"] =
|
||||
QJsonArray{makeForeignEntry("German", "Blitzschlag", "Blitzschlag fügt 3 Schadenspunkte zu.")};
|
||||
QJsonArray cards{card};
|
||||
|
||||
importer->setCardLang("de");
|
||||
importer->importCardsFromSet(set, cards);
|
||||
importer->applyLocalizedData();
|
||||
|
||||
auto result = importer->getCardList().value("Lightning Bolt");
|
||||
ASSERT_FALSE(result.isNull());
|
||||
ASSERT_EQ(result->getLocalizedName("de"), "Blitzschlag");
|
||||
ASSERT_EQ(result->getLocalizedText("de"), "Blitzschlag fügt 3 Schadenspunkte zu.");
|
||||
// English identity untouched
|
||||
ASSERT_EQ(result->getName(), "Lightning Bolt");
|
||||
ASSERT_EQ(result->getText(), "Rules text.");
|
||||
}
|
||||
|
||||
TEST_F(OracleImporterTest, ImportsLocalizedNameAndTextForMultiFaceCards)
|
||||
{
|
||||
// MTGJSON reports multi-face cards (adventure/split/aftermath/prepare) as one
|
||||
// card object per face; every face carries the joined name but only its own
|
||||
// face's rules text in foreignData. The importer joins the per-face texts with
|
||||
// the same separator as the English merge.
|
||||
QJsonObject front = makeCard("Disruptive Stormbrood // Petty Revenge");
|
||||
front["layout"] = "adventure";
|
||||
front["faceName"] = "Disruptive Stormbrood";
|
||||
front["side"] = "a";
|
||||
front["foreignData"] = QJsonArray{
|
||||
makeForeignEntry("German", "Disruptive Stormbrood // Kleinliche Rache",
|
||||
"Fliegend\nWenn diese Kreatur ins Spiel kommt, zerstöre bis zu ein Artefakt oder eine "
|
||||
"Verzauberung deiner Wahl.")};
|
||||
QJsonObject back = makeCard("Disruptive Stormbrood // Petty Revenge");
|
||||
back["layout"] = "adventure";
|
||||
back["faceName"] = "Petty Revenge";
|
||||
back["side"] = "b";
|
||||
back["text"] = "Destroy target creature.";
|
||||
back["foreignData"] = QJsonArray{makeForeignEntry("German", "Disruptive Stormbrood // Kleinliche Rache",
|
||||
"Zerstöre eine Kreatur deiner Wahl mit Stärke 3 oder weniger.")};
|
||||
QJsonArray cards{front, back};
|
||||
|
||||
importer->setCardLang("de");
|
||||
importer->importCardsFromSet(set, cards);
|
||||
importer->applyLocalizedData();
|
||||
|
||||
auto result = importer->getCardList().value("Disruptive Stormbrood // Petty Revenge");
|
||||
ASSERT_FALSE(result.isNull());
|
||||
ASSERT_EQ(result->getLocalizedName("de"), "Disruptive Stormbrood // Kleinliche Rache");
|
||||
ASSERT_EQ(result->getLocalizedText("de"),
|
||||
"Fliegend\nWenn diese Kreatur ins Spiel kommt, zerstöre bis zu ein Artefakt oder eine Verzauberung "
|
||||
"deiner Wahl.\n\n---\n\nZerstöre eine Kreatur deiner Wahl mit Stärke 3 oder weniger.");
|
||||
// English identity untouched
|
||||
ASSERT_EQ(result->getName(), "Disruptive Stormbrood // Petty Revenge");
|
||||
ASSERT_EQ(result->getText(), "Rules text.\n\n---\n\nDestroy target creature.");
|
||||
}
|
||||
|
||||
TEST_F(OracleImporterTest, MultiFaceCardsWithoutCompleteForeignTextKeepEnglishText)
|
||||
{
|
||||
// Both faces must carry a foreignData text for the joined text; otherwise the
|
||||
// rules text stays English while the localized name (from a later complete
|
||||
// printing) is still applied.
|
||||
QJsonObject front = makeCard("Wear // Tear");
|
||||
front["layout"] = "split";
|
||||
front["faceName"] = "Wear";
|
||||
front["side"] = "a";
|
||||
front["foreignData"] = QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Verschleiß-Text.")};
|
||||
QJsonObject back = makeCard("Wear // Tear");
|
||||
back["layout"] = "split";
|
||||
back["faceName"] = "Tear";
|
||||
back["side"] = "b";
|
||||
back["text"] = "Tear rules text.";
|
||||
back["foreignData"] = QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "")};
|
||||
QJsonArray cards{front, back};
|
||||
|
||||
importer->setCardLang("de");
|
||||
importer->importCardsFromSet(set, cards);
|
||||
importer->applyLocalizedData();
|
||||
|
||||
auto result = importer->getCardList().value("Wear // Tear");
|
||||
ASSERT_FALSE(result.isNull());
|
||||
// The joined name is still applied.
|
||||
ASSERT_EQ(result->getLocalizedName("de"), "Verschleiß // Zerrreißung");
|
||||
// The incomplete text must not become the card's localized text.
|
||||
ASSERT_TRUE(result->getLocalizedTexts().isEmpty());
|
||||
ASSERT_EQ(result->getText(), "Rules text.\n\n---\n\nTear rules text.");
|
||||
}
|
||||
|
||||
TEST_F(OracleImporterTest, DefaultLanguageSkipsForeignData)
|
||||
{
|
||||
QJsonObject card = makeCard("Lightning Bolt");
|
||||
card["foreignData"] =
|
||||
QJsonArray{makeForeignEntry("German", "Blitzschlag", "Blitzschlag fügt 3 Schadenspunkte zu.")};
|
||||
QJsonArray cards{card};
|
||||
|
||||
// cardLang defaults to "en" — foreignData must never be imported
|
||||
importer->importCardsFromSet(set, cards);
|
||||
importer->applyLocalizedData();
|
||||
|
||||
auto result = importer->getCardList().value("Lightning Bolt");
|
||||
ASSERT_FALSE(result.isNull());
|
||||
ASSERT_TRUE(result->getLocalizedNames().isEmpty());
|
||||
ASSERT_TRUE(result->getLocalizedTexts().isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(OracleImporterTest, UnsupportedLanguageSkipsForeignData)
|
||||
{
|
||||
QJsonObject card = makeCard("Lightning Bolt");
|
||||
card["foreignData"] = QJsonArray{makeForeignEntry("xx", "Kochanie", "Grzmot uderza.")};
|
||||
QJsonArray cards{card};
|
||||
|
||||
importer->setCardLang("xx");
|
||||
importer->importCardsFromSet(set, cards);
|
||||
importer->applyLocalizedData();
|
||||
|
||||
auto result = importer->getCardList().value("Lightning Bolt");
|
||||
ASSERT_FALSE(result.isNull());
|
||||
ASSERT_TRUE(result->getLocalizedNames().isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(OracleImporterTest, NonMatchingLanguageNotCollected)
|
||||
{
|
||||
QJsonObject card = makeCard("Lightning Bolt");
|
||||
card["foreignData"] = QJsonArray{makeForeignEntry("French", "Éclair", "L'Éclair inflige 3 blessures.")};
|
||||
QJsonArray cards{card};
|
||||
|
||||
importer->setCardLang("de");
|
||||
importer->importCardsFromSet(set, cards);
|
||||
importer->applyLocalizedData();
|
||||
|
||||
auto result = importer->getCardList().value("Lightning Bolt");
|
||||
ASSERT_FALSE(result.isNull());
|
||||
ASSERT_TRUE(result->getLocalizedNames().isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(OracleImporterTest, HigherPrioritySetWinsForReprint)
|
||||
{
|
||||
// First printing in a reprint set, then another in a (more authoritative)
|
||||
// core set: the core set's German text must win even though it was seen later.
|
||||
QJsonObject reprintCard = makeCard("Lightning Bolt");
|
||||
reprintCard["foreignData"] = QJsonArray{makeForeignEntry("German", "Blitzschlag", "Älterer deutscher Text.")};
|
||||
CardSetPtr reprintSet =
|
||||
CardSet::newInstance(controller, "TS2", "Second Set", QString(), QDate(), CardSet::PriorityReprint);
|
||||
importer->setCardLang("de");
|
||||
importer->importCardsFromSet(reprintSet, QJsonArray{reprintCard});
|
||||
|
||||
QJsonObject primaryCard = makeCard("Lightning Bolt");
|
||||
primaryCard["foreignData"] =
|
||||
QJsonArray{makeForeignEntry("German", "Blitzschlag", "Blitzschlag fügt 3 Schadenspunkte zu.")};
|
||||
CardSetPtr primarySet =
|
||||
CardSet::newInstance(controller, "TS3", "Third Set", QString(), QDate(), CardSet::PriorityPrimary);
|
||||
importer->importCardsFromSet(primarySet, QJsonArray{primaryCard});
|
||||
importer->applyLocalizedData();
|
||||
|
||||
auto result = importer->getCardList().value("Lightning Bolt");
|
||||
ASSERT_FALSE(result.isNull());
|
||||
ASSERT_EQ(result->getLocalizedName("de"), "Blitzschlag");
|
||||
ASSERT_EQ(result->getLocalizedText("de"), "Blitzschlag fügt 3 Schadenspunkte zu.");
|
||||
ASSERT_EQ(importer->getCardList().size(), 1);
|
||||
}
|
||||
|
||||
TEST_F(OracleImporterTest, HigherPrioritySplitSetWinsForReprint)
|
||||
{
|
||||
// Split cards print each face as its own card object; the joined text is
|
||||
// collected per set with the same priority policy as single-face cards, so a
|
||||
// reprint set's German text must yield to the core set's even when reprints
|
||||
// are imported first.
|
||||
QJsonObject reprintFront = makeCard("Wear // Tear");
|
||||
reprintFront["layout"] = "split";
|
||||
reprintFront["faceName"] = "Wear";
|
||||
reprintFront["side"] = "a";
|
||||
reprintFront["foreignData"] =
|
||||
QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Wear alter Text.")};
|
||||
QJsonObject reprintBack = makeCard("Wear // Tear");
|
||||
reprintBack["layout"] = "split";
|
||||
reprintBack["faceName"] = "Tear";
|
||||
reprintBack["side"] = "b";
|
||||
reprintBack["foreignData"] =
|
||||
QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Tear alter Text.")};
|
||||
CardSetPtr reprintSet =
|
||||
CardSet::newInstance(controller, "TS2", "Second Set", QString(), QDate(), CardSet::PriorityReprint);
|
||||
importer->setCardLang("de");
|
||||
importer->importCardsFromSet(reprintSet, QJsonArray{reprintFront, reprintBack});
|
||||
|
||||
QJsonObject primaryFront = makeCard("Wear // Tear");
|
||||
primaryFront["layout"] = "split";
|
||||
primaryFront["faceName"] = "Wear";
|
||||
primaryFront["side"] = "a";
|
||||
primaryFront["foreignData"] =
|
||||
QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Wear neuer Text.")};
|
||||
QJsonObject primaryBack = makeCard("Wear // Tear");
|
||||
primaryBack["layout"] = "split";
|
||||
primaryBack["faceName"] = "Tear";
|
||||
primaryBack["side"] = "b";
|
||||
primaryBack["foreignData"] =
|
||||
QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Tear neuer Text.")};
|
||||
CardSetPtr primarySet =
|
||||
CardSet::newInstance(controller, "TS3", "Third Set", QString(), QDate(), CardSet::PriorityPrimary);
|
||||
importer->importCardsFromSet(primarySet, QJsonArray{primaryFront, primaryBack});
|
||||
importer->applyLocalizedData();
|
||||
|
||||
auto result = importer->getCardList().value("Wear // Tear");
|
||||
ASSERT_FALSE(result.isNull());
|
||||
ASSERT_EQ(result->getLocalizedName("de"), "Verschleiß // Zerrreißung");
|
||||
ASSERT_EQ(result->getLocalizedText("de"), "Wear neuer Text.\n\n---\n\nTear neuer Text.");
|
||||
ASSERT_EQ(importer->getCardList().size(), 1);
|
||||
}
|
||||
|
||||
TEST_F(OracleImporterTest, StartImportAppliesLocalizedData)
|
||||
{
|
||||
QJsonObject card = makeCard("Lightning Bolt");
|
||||
card["foreignData"] =
|
||||
QJsonArray{makeForeignEntry("Portuguese (Brazil)", "Raio", "Raio causa 3 de dano a qualquer alvo.")};
|
||||
QJsonObject dataSet;
|
||||
dataSet["code"] = "tst";
|
||||
dataSet["name"] = "Test Set";
|
||||
dataSet["type"] = "expansion";
|
||||
dataSet["releaseDate"] = "2024-01-01";
|
||||
dataSet["cards"] = QJsonArray{card};
|
||||
|
||||
QJsonObject root;
|
||||
root["data"] = QJsonObject{{"TST", dataSet}};
|
||||
|
||||
importer->setCardLang("pt");
|
||||
ASSERT_TRUE(importer->readSetsFromByteArray(QJsonDocument(root).toJson(QJsonDocument::Compact)));
|
||||
ASSERT_EQ(importer->startImport(), 1);
|
||||
|
||||
auto result = importer->getCardList().value("Lightning Bolt");
|
||||
ASSERT_FALSE(result.isNull());
|
||||
ASSERT_EQ(result->getLocalizedName("pt"), "Raio");
|
||||
ASSERT_EQ(result->getLocalizedText("pt"), "Raio causa 3 de dano a qualquer alvo.");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
|
|
|||
174
tests/server_game_join_test.cpp
Normal file
174
tests/server_game_join_test.cpp
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/** @file server_game_join_test.cpp
|
||||
* @brief Tests for the moderator/judge game-entry restriction override in Server_Game::checkJoin.
|
||||
* @ingroup Tests
|
||||
*/
|
||||
|
||||
#include "game/server_game.h"
|
||||
#include "server.h"
|
||||
#include "server_database_interface.h"
|
||||
#include "server_room.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||
#include <libcockatrice/rng/rng_abstract.h>
|
||||
|
||||
RNG_Abstract *rng = nullptr; // referenced by the server_remote library
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class MockDatabaseInterface : public Server_DatabaseInterface
|
||||
{
|
||||
public:
|
||||
AuthenticationResult checkUserPassword(Server_ProtocolHandler *,
|
||||
const QString &,
|
||||
const QString &,
|
||||
const QString &,
|
||||
QString &,
|
||||
int &,
|
||||
bool) override
|
||||
{
|
||||
return NotLoggedIn;
|
||||
}
|
||||
int getNextReplayId() override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
int getNextGameId() override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
int getActiveUserCount(QString) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
ServerInfo_User getUserData(const QString &, bool) override
|
||||
{
|
||||
return ServerInfo_User();
|
||||
}
|
||||
};
|
||||
|
||||
class FakeServer : public Server
|
||||
{
|
||||
public:
|
||||
FakeServer()
|
||||
{
|
||||
setDatabaseInterface(new MockDatabaseInterface());
|
||||
}
|
||||
};
|
||||
|
||||
class GameJoinOverrideTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
FakeServer server;
|
||||
Server_Room room{0, 0, "", "", "", "", false, "", {}, &server};
|
||||
ServerInfo_User creator;
|
||||
ServerInfo_User plainUser;
|
||||
ServerInfo_User unregisteredJudge;
|
||||
ServerInfo_User moderator;
|
||||
ServerInfo_User judge;
|
||||
Server_Game *game = nullptr;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
creator.set_name("creator");
|
||||
creator.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered);
|
||||
plainUser.set_name("plain-user");
|
||||
plainUser.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered);
|
||||
unregisteredJudge.set_name("unregistered-judge");
|
||||
unregisteredJudge.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsJudge);
|
||||
moderator.set_name("moderator");
|
||||
moderator.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered |
|
||||
ServerInfo_User::IsModerator);
|
||||
judge.set_name("judge");
|
||||
judge.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered | ServerInfo_User::IsJudge);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
delete game;
|
||||
}
|
||||
|
||||
Server_Game *makeGame(bool passwordProtected, bool onlyRegistered, bool onlyBuddies, bool spectatorsAllowed)
|
||||
{
|
||||
GameConfig config{.creatorInfo = creator,
|
||||
.gameId = 1,
|
||||
.description = QString(),
|
||||
.password = passwordProtected ? "secret" : QString(),
|
||||
.maxPlayers = 2,
|
||||
.gameTypes = QList<int>(),
|
||||
.onlyBuddies = onlyBuddies,
|
||||
.onlyRegistered = onlyRegistered,
|
||||
.spectatorsAllowed = spectatorsAllowed,
|
||||
.spectatorsNeedPassword = true,
|
||||
.spectatorsCanTalk = false,
|
||||
.spectatorsSeeEverything = false,
|
||||
.startingLifeTotal = 20,
|
||||
.shareDecklistsOnLoad = false};
|
||||
return new Server_Game(config, &room);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(GameJoinOverrideTest, StaffBypassPasswordRestriction)
|
||||
{
|
||||
game = makeGame(true, false, false, true);
|
||||
|
||||
// A plain user cannot override the password even with the override flag set.
|
||||
EXPECT_EQ(game->checkJoin(&plainUser, "wrong", false, true, false), Response::RespWrongPassword);
|
||||
// Moderators and judges may enter any game regardless of the password.
|
||||
EXPECT_EQ(game->checkJoin(&moderator, "wrong", false, true, false), Response::RespOk);
|
||||
EXPECT_EQ(game->checkJoin(&judge, "wrong", false, true, false), Response::RespOk);
|
||||
// Without the override flag judges are still subject to the password.
|
||||
EXPECT_EQ(game->checkJoin(&judge, "wrong", false, false, true), Response::RespWrongPassword);
|
||||
EXPECT_EQ(game->checkJoin(&judge, "secret", false, false, true), Response::RespOk);
|
||||
}
|
||||
|
||||
TEST_F(GameJoinOverrideTest, StaffBypassRegisteredOnlyRestriction)
|
||||
{
|
||||
game = makeGame(false, true, false, true);
|
||||
|
||||
// Without the override flag the only-registered restriction still applies.
|
||||
EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), false, false, false), Response::RespUserLevelTooLow);
|
||||
// An unregistered judge may enter when overriding restrictions.
|
||||
EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), false, true, false), Response::RespOk);
|
||||
}
|
||||
|
||||
TEST_F(GameJoinOverrideTest, StaffBypassBuddiesOnlyRestriction)
|
||||
{
|
||||
game = makeGame(false, false, true, true);
|
||||
|
||||
// A plain user who is not on the creator's buddy list gets rejected.
|
||||
EXPECT_EQ(game->checkJoin(&plainUser, QString(), false, true, false), Response::RespOnlyBuddies);
|
||||
// Moderators and judges bypass the buddies-only restriction.
|
||||
EXPECT_EQ(game->checkJoin(&moderator, QString(), false, true, false), Response::RespOk);
|
||||
EXPECT_EQ(game->checkJoin(&judge, QString(), false, true, false), Response::RespOk);
|
||||
}
|
||||
|
||||
TEST_F(GameJoinOverrideTest, StaffBypassSpectatorsNotAllowedRestriction)
|
||||
{
|
||||
game = makeGame(false, false, false, false);
|
||||
|
||||
// A plain user cannot spectate when the game disallows spectators.
|
||||
EXPECT_EQ(game->checkJoin(&plainUser, QString(), true, false, false), Response::RespSpectatorsNotAllowed);
|
||||
// Moderators and judges may spectate any game regardless of the password
|
||||
// and the spectator restriction.
|
||||
EXPECT_EQ(game->checkJoin(&moderator, "wrong", true, true, false), Response::RespOk);
|
||||
EXPECT_EQ(game->checkJoin(&judge, "wrong", true, true, false), Response::RespOk);
|
||||
}
|
||||
|
||||
TEST_F(GameJoinOverrideTest, JudgeOverrideDoesNotGrantJudgeJoinToPlainUser)
|
||||
{
|
||||
game = makeGame(false, false, false, true);
|
||||
|
||||
// joining with join_as_judge still requires the judge flag even when overriding.
|
||||
EXPECT_EQ(game->checkJoin(&plainUser, QString(), false, true, true), Response::RespUserLevelTooLow);
|
||||
EXPECT_EQ(game->checkJoin(&judge, QString(), false, true, true), Response::RespOk);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
|
@ -386,6 +386,19 @@ TEST_F(SettingsDefaultsTest, Appearance_HomeTabDisplayCardName_Default)
|
|||
ASSERT_EQ(s.getHomeTabDisplayCardName(), true);
|
||||
}
|
||||
|
||||
TEST_F(SettingsDefaultsTest, Appearance_HomeTabBackgroundDim_Default)
|
||||
{
|
||||
AppearanceSettings s(settingsPath, nullptr);
|
||||
ASSERT_EQ(s.getHomeTabBackgroundDim(), true);
|
||||
}
|
||||
|
||||
TEST_F(SettingsDefaultsTest, Appearance_HomeTabBackgroundDim_SetAndGet)
|
||||
{
|
||||
AppearanceSettings s(settingsPath, nullptr);
|
||||
s.setHomeTabBackgroundDim(false);
|
||||
ASSERT_EQ(s.getHomeTabBackgroundDim(), false);
|
||||
}
|
||||
|
||||
// --- InterfaceSettings ---
|
||||
|
||||
TEST_F(SettingsDefaultsTest, Interface_ShowStatusBar_Default)
|
||||
|
|
@ -549,6 +562,19 @@ TEST_F(SettingsDefaultsTest, CardsDisplay_ArrowDrawAnimation_Default)
|
|||
ASSERT_EQ(s.getArrowDrawAnimation(), true);
|
||||
}
|
||||
|
||||
TEST_F(SettingsDefaultsTest, CardsDisplay_CardLang_Default)
|
||||
{
|
||||
CardsDisplaySettings s(settingsPath, nullptr);
|
||||
ASSERT_EQ(s.getCardLang(), QString("en"));
|
||||
}
|
||||
|
||||
TEST_F(SettingsDefaultsTest, CardsDisplay_CardLang_SetAndGet)
|
||||
{
|
||||
CardsDisplaySettings s(settingsPath, nullptr);
|
||||
s.setCardLang("de");
|
||||
ASSERT_EQ(s.getCardLang(), QString("de"));
|
||||
}
|
||||
|
||||
// --- VisualDeckStorageSettings ---
|
||||
|
||||
TEST_F(SettingsDefaultsTest, VisualDeckStorage_SortingOrder_Default)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue