#include "testenv.h" #include "fixtures.h" #include "gamedirimport.h" #include #include #include #include class TestGameDirImport : public QObject { Q_OBJECT QString m_game; void writeVersion(const QString &id, const QJsonObject &json) { QVERIFY(Fixtures::writeJson(m_game + "/versions/" + id + "/" + id + ".json", json)); } void writeVanilla(const QString &id) { writeVersion(id, {{"id", id}, {"type", "release"}}); } void writeLoaderProfile(const QString &id, const QString &game, const QString &library) { QJsonArray libraries; if (!library.isEmpty()) libraries.append(QJsonObject{{"name", library}}); writeVersion(id, {{"id", id}, {"inheritsFrom", game}, {"libraries", libraries}}); } void writeProfiles(const QJsonObject &profiles) { QVERIFY(Fixtures::writeJson(m_game + "/launcher_profiles.json", {{"profiles", profiles}})); } static QJsonObject profile(const QString &versionId, const QString &lastUsed) { return {{"type", "custom"}, {"lastVersionId", versionId}, {"lastUsed", lastUsed}}; } // Мтайм ставим явно: файлы, записанные подряд, легко получают одну секунду. void touch(const QString &id, int secondsAgo) { QFile file(m_game + "/versions/" + id + "/" + id + ".json"); QVERIFY(file.open(QIODevice::ReadWrite)); QVERIFY(file.setFileTime(QDateTime::currentDateTime().addSecs(-secondsAgo), QFileDevice::FileModificationTime)); } private slots: void init() { m_game = TestEnv::freshDir(QStringLiteral("game")); } void missingOrEmptyFolderGivesNothing() { QVERIFY(!GameDirImport::detect(m_game + "/nope")); QVERIFY(!GameDirImport::detect(m_game)); QVERIFY(!GameDirImport::detect(QString())); } void sharedFoldersAndLauncherJunkAreNotABuild() { Fixtures::write(m_game + "/libraries/x/y.jar", "lib"); Fixtures::write(m_game + "/assets/indexes/5.json", "{}"); Fixtures::write(m_game + "/launcher_log.txt", "log"); Fixtures::write(m_game + "/webcache2/a", "a"); QVERIFY(Fixtures::writeJson(m_game + "/launcher_profiles.json", {{"profiles", QJsonObject{}}})); QVERIFY(QDir().mkpath(m_game + "/saves")); // пустая папка — не признак QVERIFY(!GameDirImport::detect(m_game)); } void contentWithoutVersionGivesEmptyBuild() { Fixtures::write(m_game + "/saves/World/level.dat", "w"); const auto found = GameDirImport::detect(m_game); QVERIFY(found); QCOMPARE(found->source, QStringLiteral("content")); QVERIFY(found->minecraftVersion.isEmpty()); QVERIFY(found->resolvedVersionId.isEmpty()); QVERIFY(found->loader.isEmpty()); } void lastUsedProfileWins() { writeVanilla("1.20.1"); writeVanilla("1.21.1"); writeVanilla("1.19.4"); touch("1.19.4", 0); // самая свежая по мтайму, но профили важнее writeProfiles({{"a", profile("1.20.1", "2026-01-01T10:00:00.000Z")}, {"b", profile("1.21.1", "2026-03-01T10:00:00.000Z")}, // Версии нет на диске — пропускается, даже будучи последней. {"c", profile("1.8.9", "2026-05-01T10:00:00.000Z")}, // latest-release без lastVersionId не разрешить без манифеста. {"d", QJsonObject{{"type", "latest-release"}, {"lastUsed", "2026-06-01T10:00:00Z"}}}}); const auto found = GameDirImport::detect(m_game); QVERIFY(found); QCOMPARE(found->source, QStringLiteral("launcher_profiles")); QCOMPARE(found->minecraftVersion, QStringLiteral("1.21.1")); QVERIFY(found->resolvedVersionId.isEmpty()); QVERIFY(found->loader.isEmpty()); } void withoutProfilesNewestVersionWins() { writeVanilla("1.20.1"); writeVanilla("1.21.1"); touch("1.20.1", 0); touch("1.21.1", 3600); Fixtures::write(m_game + "/launcher_profiles.json", "{ broken"); const auto found = GameDirImport::detect(m_game); QVERIFY(found); QCOMPARE(found->source, QStringLiteral("versions")); QCOMPARE(found->minecraftVersion, QStringLiteral("1.20.1")); } void brokenVersionJsonIsSkipped() { writeVanilla("1.20.1"); Fixtures::write(m_game + "/versions/1.21.1/1.21.1.json", "not json"); touch("1.20.1", 3600); touch("1.21.1", 0); const auto found = GameDirImport::detect(m_game); QVERIFY(found); QCOMPARE(found->minecraftVersion, QStringLiteral("1.20.1")); } void modsPreferLoaderProfile() { writeVanilla("1.20.1"); writeLoaderProfile("fabric-loader-0.15.0-1.20.1", "1.20.1", "net.fabricmc:fabric-loader:0.15.0"); // Последней запускали ванилу, но моды лежат — значит, играли с Fabric. writeProfiles({{"vanilla", profile("1.20.1", "2026-03-01T10:00:00.000Z")}}); Fixtures::write(m_game + "/mods/sodium.jar", "jar"); const auto found = GameDirImport::detect(m_game); QVERIFY(found); QCOMPARE(found->source, QStringLiteral("versions")); QCOMPARE(found->minecraftVersion, QStringLiteral("1.20.1")); QCOMPARE(found->resolvedVersionId, QStringLiteral("fabric-loader-0.15.0-1.20.1")); QCOMPARE(found->loader, QStringLiteral("fabric")); QCOMPARE(found->loaderVersion, QStringLiteral("0.15.0")); // Без модов тот же набор даёт ванилу из профилей. QVERIFY(QFile::remove(m_game + "/mods/sodium.jar")); QCOMPARE(GameDirImport::detect(m_game)->resolvedVersionId, QString()); } void fabricFixtureIsRecognised() { const QJsonObject json = Fixtures::readJson("versions/fabric-loader-0.15.0-1.20.1.json"); const auto loader = GameDirImport::loaderOf(json); QCOMPARE(loader.key, QStringLiteral("fabric")); QCOMPARE(loader.version, QStringLiteral("0.15.0")); } void loaderFromLibraries_data() { QTest::addColumn("game"); QTest::addColumn("library"); QTest::addColumn("key"); QTest::addColumn("version"); QTest::newRow("quilt") << "1.20.1" << "org.quiltmc:quilt-loader:0.26.0" << "quilt" << "0.26.0"; QTest::newRow("forge-1.20.1") << "1.20.1" << "net.minecraftforge:forge:1.20.1-47.2.0:universal" << "forge" << "47.2.0"; QTest::newRow("forge-1.12.2") << "1.12.2" << "net.minecraftforge:forge:1.12.2-14.23.5.2860" << "forge" << "14.23.5.2860"; // Как в каталоге модлоадеров: хвост "-1.7.10" остаётся. QTest::newRow("forge-1.7.10") << "1.7.10" << "net.minecraftforge:forge:1.7.10-10.13.4.1614-1.7.10" << "forge" << "10.13.4.1614-1.7.10"; QTest::newRow("neoforge") << "1.21.1" << "net.neoforged:neoforge:21.1.77:universal" << "neoforge" << "21.1.77"; QTest::newRow("neoforge-1.20.1") << "1.20.1" << "net.neoforged:forge:1.20.1-47.1.106:universal" << "neoforge" << "47.1.106"; QTest::newRow("optifine") << "1.20.1" << "optifine:OptiFine:1.20.1_HD_U_I6" << "" << ""; } void loaderFromLibraries() { QFETCH(QString, game); QFETCH(QString, library); QFETCH(QString, key); QFETCH(QString, version); const QJsonObject json{{"id", "custom"}, {"inheritsFrom", game}, {"libraries", QJsonArray{QJsonObject{{"name", "com.google:guava:31.0"}}, QJsonObject{{"name", library}}}}}; const auto loader = GameDirImport::loaderOf(json); QCOMPARE(loader.key, key); QCOMPARE(loader.version, version); } void loaderFromIdWhenLibrariesSilent_data() { QTest::addColumn("id"); QTest::addColumn("game"); QTest::addColumn("key"); QTest::addColumn("version"); QTest::newRow("fabric") << "fabric-loader-0.16.10-1.21.1" << "1.21.1" << "fabric" << "0.16.10"; QTest::newRow("quilt") << "quilt-loader-0.26.0-1.20.1" << "1.20.1" << "quilt" << "0.26.0"; QTest::newRow("forge") << "1.20.1-forge-47.2.0" << "1.20.1" << "forge" << "47.2.0"; QTest::newRow("neoforge") << "neoforge-21.1.77" << "1.21.1" << "neoforge" << "21.1.77"; QTest::newRow("unknown") << "1.20.1-OptiFine_HD_U_I6" << "1.20.1" << "" << ""; } void loaderFromIdWhenLibrariesSilent() { QFETCH(QString, id); QFETCH(QString, game); QFETCH(QString, key); QFETCH(QString, version); const auto loader = GameDirImport::loaderOf({{"id", id}, {"inheritsFrom", game}}); QCOMPARE(loader.key, key); QCOMPARE(loader.version, version); } void unknownInheritorKeepsResolvedId() { writeVanilla("1.20.1"); writeLoaderProfile("1.20.1-OptiFine_HD_U_I6", "1.20.1", "optifine:OptiFine:1.20.1_HD_U_I6"); writeProfiles({{"of", profile("1.20.1-OptiFine_HD_U_I6", "2026-03-01T10:00:00Z")}}); const auto found = GameDirImport::detect(m_game); QVERIFY(found); QCOMPARE(found->minecraftVersion, QStringLiteral("1.20.1")); QCOMPARE(found->resolvedVersionId, QStringLiteral("1.20.1-OptiFine_HD_U_I6")); QVERIFY(found->loader.isEmpty()); } void detectionDoesNotTouchGameDir() { writeVanilla("1.20.1"); writeLoaderProfile("fabric-loader-0.15.0-1.20.1", "1.20.1", "net.fabricmc:fabric-loader:0.15.0"); writeProfiles({{"f", profile("fabric-loader-0.15.0-1.20.1", "2026-03-01T10:00:00Z")}}); Fixtures::write(m_game + "/mods/a.jar", "a"); Fixtures::write(m_game + "/saves/W/level.dat", "w"); const QStringList before = Fixtures::listTree(m_game); QVERIFY(GameDirImport::detect(m_game)); QCOMPARE(Fixtures::listTree(m_game), before); } }; BILAUNCHER_TEST_MAIN(TestGameDirImport) #include "tst_gamedirimport.moc"