#include "testenv.h" #include "fixtures.h" #include "nettest.h" #include "launcherpaths.h" #include "localization.h" #include "versionmanifestservice.h" #include #include namespace { const QString kManifest = QStringLiteral("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"); } class TestVersionManifestService : public QObject { Q_OBJECT FakeInternet m_net; private slots: void initTestCase() { BILAUNCHER_START_FAKE_INTERNET(m_net); } void init() { TestEnv::resetLauncherRoot(); m_net.reset(); } void loadsSortsAndFilters() { m_net.route(kManifest, FakeResponse::json(Fixtures::read("mojang/version_manifest_v2.json"))); VersionManifestService service; QSignalSpy changed(&service, &VersionManifestService::versionsChanged); QSignalSpy refreshing(&service, &VersionManifestService::refreshingChanged); CallResult result; service.ensureLoaded(result.callback()); QVERIFY(service.isRefreshing()); QTRY_COMPARE_WITH_TIMEOUT(result.calls, 1, 15000); QVERIFY(result.ok); QVERIFY(result.warning.isEmpty()); QVERIFY(!service.isRefreshing()); QCOMPARE(changed.count(), 1); QCOMPARE(refreshing.count(), 2); // Новые первыми; без id и с http-ссылкой — выброшены. QStringList ids; for (const RemoteVersionEntry &entry : service.versions()) ids << entry.id; QCOMPARE(ids, QStringList({"25w33a", "1.21.8", "1.20.1", "b1.7.3"})); const auto entry = service.find("1.20.1"); QVERIFY(entry.has_value()); QCOMPARE(entry->type, QStringLiteral("release")); QCOMPARE(entry->sha1, QStringLiteral("aaaa")); QCOMPARE(entry->url, QUrl("https://piston-meta.mojang.com/v1/packages/aaaa/1.20.1.json")); QCOMPARE(entry->releaseTime, QDateTime(QDate(2023, 6, 12), QTime(13, 25, 51), QTimeZone::UTC)); QVERIFY(!service.find("insecure").has_value()); QVERIFY(service.fetchedAt().isValid()); // Кеш на диске записан. const QJsonObject cache = Fixtures::readJsonFile(LauncherPaths::versionManifestFile()); QCOMPARE(cache.value("versions").toArray().size(), 4); QVERIFY(!cache.value("fetchedAt").toString().isEmpty()); } void freshCacheAvoidsNetwork() { m_net.route(kManifest, FakeResponse::json(Fixtures::read("mojang/version_manifest_v2.json"))); { VersionManifestService first; CallResult r; first.ensureLoaded(r.callback()); QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000); } QCOMPARE(m_net.hits(kManifest), 1); // Новый экземпляр (как после перезапуска лаунчера) берёт свежий кеш. VersionManifestService second; CallResult r; second.ensureLoaded(r.callback()); QVERIFY(second.hasData()); // данные из кеша уже доступны синхронно QCOMPARE(r.calls, 0); // колбэк — только через цикл событий QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 5000); QVERIFY(r.ok); QCOMPARE(m_net.hits(kManifest), 1); // forceRefresh идёт в сеть даже при свежем кеше. CallResult forced; second.ensureLoaded(forced.callback(), true); QTRY_COMPARE_WITH_TIMEOUT(forced.calls, 1, 15000); QCOMPARE(m_net.hits(kManifest), 2); } void staleCacheIsRefreshed() { QJsonObject cache = Fixtures::readJson("mojang/version_manifest_v2.json"); cache["fetchedAt"] = QDateTime::currentDateTimeUtc().addDays(-2).toString(Qt::ISODate); Fixtures::writeJson(LauncherPaths::versionManifestFile(), cache); m_net.route(kManifest, FakeResponse::json(Fixtures::read("mojang/version_manifest_v2.json"))); VersionManifestService service; CallResult r; service.ensureLoaded(r.callback()); QVERIFY(service.hasData()); QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000); QCOMPARE(m_net.hits(kManifest), 1); } void networkErrorWithoutCacheFails() { m_net.route(kManifest, FakeResponse::error(503)); VersionManifestService service; CallResult r; service.ensureLoaded(r.callback()); QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000); QVERIFY(!r.ok); QVERIFY(r.warning.startsWith(Loc::text("version.error.manifestFetchFailed").section(u'%', 0, 0))); QVERIFY(!service.hasData()); } void networkErrorWithCacheSucceedsWithWarning() { QJsonObject cache = Fixtures::readJson("mojang/version_manifest_v2.json"); cache["fetchedAt"] = QDateTime::currentDateTimeUtc().addDays(-2).toString(Qt::ISODate); Fixtures::writeJson(LauncherPaths::versionManifestFile(), cache); m_net.route(kManifest, FakeResponse::error(500)); VersionManifestService service; CallResult r; service.ensureLoaded(r.callback()); QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000); QVERIFY(r.ok); // офлайн, но список есть QVERIFY(!r.warning.isEmpty()); QCOMPARE(service.versions().size(), 4); } void garbageResponseIsRejected() { m_net.route(kManifest, FakeResponse::json("{\"versions\": []}")); VersionManifestService service; CallResult r; service.ensureLoaded(r.callback()); QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000); QVERIFY(!r.ok); QCOMPARE(r.warning, Loc::text("version.error.manifestUnparsable")); QVERIFY(!QFile::exists(LauncherPaths::versionManifestFile())); } void concurrentCallersShareOneRequest() { FakeResponse slow = FakeResponse::json(Fixtures::read("mojang/version_manifest_v2.json")); slow.delayMs = 300; m_net.route(kManifest, slow); VersionManifestService service; CallResult a, b, c; service.ensureLoaded(a.callback()); service.ensureLoaded(b.callback()); service.ensureLoaded(c.callback()); QTRY_VERIFY_WITH_TIMEOUT(a.calls == 1 && b.calls == 1 && c.calls == 1, 15000); QVERIFY(a.ok && b.ok && c.ok); QCOMPARE(m_net.hits(kManifest), 1); } void corruptCacheIsIgnored() { Fixtures::write(LauncherPaths::versionManifestFile(), "\x00\x01garbage"); m_net.route(kManifest, FakeResponse::json(Fixtures::read("mojang/version_manifest_v2.json"))); VersionManifestService service; CallResult r; service.ensureLoaded(r.callback()); QVERIFY(!service.hasData()); QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000); QVERIFY(r.ok); QVERIFY(service.hasData()); } }; BILAUNCHER_TEST_MAIN(TestVersionManifestService) #include "tst_versionmanifestservice.moc"