Files
BiLauncher/tests/net/tst_modloaderversionservice.cpp
galeon 5635dcc558
tests / package-smoke-linux (push) Skipped
tests / package-smoke-windows (push) Skipped
tests / macos (push) Successful in 1m22s
tests / linux (pull_request) Successful in 1m54s
tests / package-smoke-macos (push) Skipped
tests / linux (push) Successful in 2m43s
tests / windows (push) Successful in 4m13s
tests / windows (pull_request) Skipped
tests / macos (pull_request) Skipped
tests / package-smoke-linux (pull_request) Skipped
tests / package-smoke-windows (pull_request) Skipped
tests / package-smoke-macos (pull_request) Skipped
autotests fixes
Signed-off-by: galeon <dertiellis@gmail.com>
2026-11-01 21:07:03 +03:00

293 lines
13 KiB
C++

#include "testenv.h"
#include "fixtures.h"
#include "nettest.h"
#include "launcherpaths.h"
#include "localization.h"
#include "modloaderversionservice.h"
#include <QSignalSpy>
#include <QNetworkAccessManager>
#include <QNetworkReply>
namespace {
const QString kFabric = QStringLiteral("https://meta.fabricmc.net/v2/versions/loader/1.20.1");
const QString kQuilt = QStringLiteral("https://meta.quiltmc.org/v3/versions/loader/1.20.1");
const QString kForgeMeta = QStringLiteral("https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml");
const QString kForgePromos = QStringLiteral("https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json");
const QString kNeoMeta = QStringLiteral("https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml");
const QString kNeoLegacyMeta = QStringLiteral("https://maven.neoforged.net/releases/net/neoforged/forge/maven-metadata.xml");
FakeResponse xml(const QByteArray &body)
{
FakeResponse response = FakeResponse::bytes(body);
response.contentType = "application/xml";
return response;
}
QStringList loaderVersions(const QList<LoaderVersionEntry> &list)
{
QStringList out;
for (const LoaderVersionEntry &entry : list)
out << entry.loaderVersion;
return out;
}
} // namespace
class TestModLoaderVersionService : public QObject
{
Q_OBJECT
FakeInternet m_net;
void routeAll()
{
m_net.route(kFabric, FakeResponse::json(Fixtures::read("loaders/fabric-1.20.1.json")));
m_net.route(kQuilt, FakeResponse::json(Fixtures::read("loaders/quilt-1.20.1.json")));
m_net.route(kForgeMeta, xml(Fixtures::read("loaders/forge-maven-metadata.xml")));
m_net.route(kForgePromos, FakeResponse::json(Fixtures::read("loaders/forge-promotions_slim.json")));
m_net.route(kNeoMeta, xml(Fixtures::read("loaders/neoforge-maven-metadata.xml")));
m_net.route(kNeoLegacyMeta, xml(Fixtures::read("loaders/neoforge-legacy-maven-metadata.xml")));
}
static bool load(ModLoaderVersionService &service, ModLoader loader, const QString &game,
CallResult &r, bool force = false)
{
service.ensureLoaded(loader, game, r.callback(), force);
return QTest::qWaitFor([&] { return r.calls > 0; }, 15000);
}
bool loadNeoForgeInOrder(ModLoaderVersionService &service, CallResult &result,
const QString &first, const QString &second)
{
QStringList completed;
QObject observer;
auto *network = service.findChild<QNetworkAccessManager *>();
if (!network)
qFatal("ModLoaderVersionService has no network manager");
connect(network, &QNetworkAccessManager::finished, &observer, [&](QNetworkReply *reply) {
completed << reply->url().toString();
});
service.ensureLoaded(ModLoader::NeoForge, "1.20.1", result.callback());
if (!QTest::qWaitFor([&] { return completed.contains(first) && m_net.hits(second) == 1; }, 15000))
return false;
if (!m_net.releaseResponse(second))
return false;
return QTest::qWaitFor([&] { return result.calls == 1; }, 15000);
}
private slots:
void initTestCase() { BILAUNCHER_START_FAKE_INTERNET(m_net); }
void init()
{
TestEnv::resetLauncherRoot();
m_net.reset();
routeAll();
}
void fabric()
{
ModLoaderVersionService service;
QSignalSpy changed(&service, &ModLoaderVersionService::versionsChanged);
CallResult r;
QVERIFY(load(service, ModLoader::Fabric, "1.20.1", r));
QVERIFY2(r.ok, qPrintable(r.warning));
const QList<LoaderVersionEntry> list = service.versions(ModLoader::Fabric, "1.20.1");
// Новые первыми: 0.16.10 > 0.16.9 (числовое сравнение, не строковое); пустые версии выброшены.
QCOMPARE(loaderVersions(list), QStringList({"0.17.0-beta.1", "0.16.10", "0.16.9"}));
QCOMPARE(list.at(1).versionId, QStringLiteral("fabric-loader-0.16.10-1.20.1"));
QCOMPARE(list.at(1).gameVersion, QStringLiteral("1.20.1"));
QVERIFY(list.at(1).stable);
QVERIFY(!list.at(0).stable);
QVERIFY(list.at(1).installerUrl.isEmpty());
QCOMPARE(changed.count(), 1);
QCOMPARE(changed.constFirst().at(0).toString(), QStringLiteral("fabric"));
QCOMPARE(changed.constFirst().at(1).toString(), QStringLiteral("1.20.1"));
QVERIFY(service.find(ModLoader::Fabric, "1.20.1", "0.16.9").has_value());
QVERIFY(!service.find(ModLoader::Fabric, "1.20.1", "9.9.9").has_value());
QVERIFY(service.versions(ModLoader::Fabric, "1.19.2").isEmpty());
QVERIFY(QFile::exists(LauncherPaths::loaderCacheFile("fabric")));
}
void quiltStabilityHeuristic()
{
ModLoaderVersionService service;
CallResult r;
QVERIFY(load(service, ModLoader::Quilt, "1.20.1", r));
QVERIFY(r.ok);
const QList<LoaderVersionEntry> list = service.versions(ModLoader::Quilt, "1.20.1");
QCOMPARE(loaderVersions(list), QStringList({"0.26.4-beta.1", "0.26.3"}));
QCOMPARE(list.at(0).versionId, QStringLiteral("quilt-loader-0.26.4-beta.1-1.20.1"));
QVERIFY(!list.at(0).stable); // поля stable нет — решает «beta» в номере
QVERIFY(list.at(1).stable);
}
void forgeMergesPromotions()
{
ModLoaderVersionService service;
CallResult r;
QVERIFY(load(service, ModLoader::Forge, "1.20.1", r));
QVERIFY2(r.ok, qPrintable(r.warning));
QCOMPARE(m_net.hits(kForgeMeta), 1);
QCOMPARE(m_net.hits(kForgePromos), 1);
const QList<LoaderVersionEntry> v1201 = service.versions(ModLoader::Forge, "1.20.1");
QCOMPARE(loaderVersions(v1201), QStringList({"47.4.1", "47.4.0", "47.3.0"}));
for (const LoaderVersionEntry &entry : v1201)
QCOMPARE(entry.recommended, entry.loaderVersion == QLatin1String("47.4.0"));
QCOMPARE(v1201.at(1).installerUrl,
QUrl("https://maven.minecraftforge.net/net/minecraftforge/forge/1.20.1-47.4.0/forge-1.20.1-47.4.0-installer.jar"));
QVERIFY(v1201.at(1).versionId.isEmpty()); // id Forge узнаём только после установки
// Весь каталог Forge — одним запросом, по всем версиям игры.
const QList<LoaderVersionEntry> v1211 = service.versions(ModLoader::Forge, "1.21.1");
QCOMPARE(loaderVersions(v1211), QStringList({"52.0.10", "52.0.2-beta"}));
QVERIFY(!v1211.at(1).stable);
QCOMPARE(loaderVersions(service.versions(ModLoader::Forge, "1.7.10")),
QStringList{"10.13.4.1614-1.7.10"});
// Другая версия игры у Forge не требует нового запроса.
CallResult again;
QVERIFY(load(service, ModLoader::Forge, "1.21.1", again));
QCOMPARE(m_net.hits(kForgeMeta), 1);
}
void forgeWithoutPromotionsStillWorks()
{
m_net.route(kForgePromos, FakeResponse::error(500));
ModLoaderVersionService service;
CallResult r;
QVERIFY(load(service, ModLoader::Forge, "1.20.1", r));
QVERIFY(r.ok);
for (const LoaderVersionEntry &entry : service.versions(ModLoader::Forge, "1.20.1"))
QVERIFY(!entry.recommended);
}
void neoForgeBothSchemes()
{
ModLoaderVersionService service;
CallResult r;
QVERIFY(load(service, ModLoader::NeoForge, "1.21.1", r));
QVERIFY2(r.ok, qPrintable(r.warning));
QCOMPARE(loaderVersions(service.versions(ModLoader::NeoForge, "1.21.1")),
QStringList({"21.1.248", "21.1.200"}));
QCOMPARE(loaderVersions(service.versions(ModLoader::NeoForge, "1.21")), QStringList{"21.0.10"});
QCOMPARE(loaderVersions(service.versions(ModLoader::NeoForge, "1.20.4")),
QStringList({"20.4.237", "20.4.80-beta"}));
// Новая нумерация (год.выпуск.патч.сборка).
QCOMPARE(loaderVersions(service.versions(ModLoader::NeoForge, "26.1")), QStringList{"26.1.0.5"});
QCOMPARE(loaderVersions(service.versions(ModLoader::NeoForge, "26.1.1")), QStringList{"26.1.1.3"});
QCOMPARE(service.find(ModLoader::NeoForge, "1.21.1", "21.1.248")->installerUrl,
QUrl("https://maven.neoforged.net/releases/net/neoforged/neoforge/21.1.248/neoforge-21.1.248-installer.jar"));
}
void neoForgeLegacyArtifactArrivesLast()
{
// Ранние сборки под 1.20.1 — из старого артефакта net.neoforged:forge.
FakeResponse legacy = xml(Fixtures::read("loaders/neoforge-legacy-maven-metadata.xml"));
legacy.deferred = true;
m_net.route(kNeoLegacyMeta, legacy);
ModLoaderVersionService service;
CallResult r;
QVERIFY(loadNeoForgeInOrder(service, r, kNeoMeta, kNeoLegacyMeta));
QVERIFY(r.ok);
const QList<LoaderVersionEntry> versions = service.versions(ModLoader::NeoForge, "1.20.1");
QCOMPARE(loaderVersions(versions), QStringList({"47.1.106", "47.1.79"}));
QCOMPARE(versions.at(0).installerUrl,
QUrl("https://maven.neoforged.net/releases/net/neoforged/forge/1.20.1-47.1.106/forge-1.20.1-47.1.106-installer.jar"));
}
void neoForgeLegacyArtifactArrivesFirst()
{
// Основной ответ выпускается только после обработки legacy клиентом.
FakeResponse primary = xml(Fixtures::read("loaders/neoforge-maven-metadata.xml"));
primary.deferred = true;
m_net.route(kNeoMeta, primary);
ModLoaderVersionService service;
CallResult r;
QVERIFY(loadNeoForgeInOrder(service, r, kNeoLegacyMeta, kNeoMeta));
QVERIFY(r.ok);
QCOMPARE(service.versions(ModLoader::NeoForge, "1.21.1").size(), 2);
QCOMPARE(service.versions(ModLoader::NeoForge, "1.20.1").size(), 2);
}
void primaryFailureIsError()
{
m_net.route(kNeoMeta, FakeResponse::error(503));
ModLoaderVersionService service;
CallResult r;
QVERIFY(load(service, ModLoader::NeoForge, "1.21.1", r));
QVERIFY(!r.ok);
QVERIFY(r.warning.contains(loaderTitle(ModLoader::NeoForge)));
m_net.route(kForgeMeta, xml("<metadata><versioning><versions></versions></versioning></metadata>"));
CallResult empty;
QVERIFY(load(service, ModLoader::Forge, "1.20.1", empty));
QVERIFY(!empty.ok);
QCOMPARE(empty.warning, Loc::text("loader.error.versionsUnparsable").arg(loaderTitle(ModLoader::Forge)));
m_net.route(kFabric, FakeResponse::json("{\"not\": \"an array\"}"));
CallResult notArray;
QVERIFY(load(service, ModLoader::Fabric, "1.20.1", notArray));
QVERIFY(!notArray.ok);
QCOMPARE(notArray.warning, Loc::text("loader.error.versionsUnparsable").arg(loaderTitle(ModLoader::Fabric)));
}
void cachePerLoaderAndOfflineFallback()
{
{
ModLoaderVersionService service;
CallResult r;
QVERIFY(load(service, ModLoader::Fabric, "1.20.1", r));
}
QCOMPARE(m_net.hits(kFabric), 1);
ModLoaderVersionService service;
CallResult cached;
QVERIFY(load(service, ModLoader::Fabric, "1.20.1", cached));
QVERIFY(cached.ok);
QCOMPARE(m_net.hits(kFabric), 1); // свежий кеш
QCOMPARE(service.versions(ModLoader::Fabric, "1.20.1").size(), 3);
// Сеть легла, принудительное обновление: данные остаются, ok с предупреждением.
m_net.route(kFabric, FakeResponse::error(502));
CallResult offline;
QVERIFY(load(service, ModLoader::Fabric, "1.20.1", offline, true));
QVERIFY(offline.ok);
QVERIFY(!offline.warning.isEmpty());
QCOMPARE(service.versions(ModLoader::Fabric, "1.20.1").size(), 3);
}
void emptyGameVersionIsNoop()
{
ModLoaderVersionService service;
CallResult r;
QVERIFY(load(service, ModLoader::Fabric, " ", r));
QVERIFY(r.ok);
QVERIFY(m_net.requests().isEmpty());
}
void inFlightRequestsAreShared()
{
FakeResponse slow = FakeResponse::json(Fixtures::read("loaders/fabric-1.20.1.json"));
slow.delayMs = 300;
m_net.route(kFabric, slow);
ModLoaderVersionService service;
CallResult a, b;
service.ensureLoaded(ModLoader::Fabric, "1.20.1", a.callback());
QVERIFY(service.isRefreshing(ModLoader::Fabric, "1.20.1"));
QVERIFY(!service.isRefreshing(ModLoader::Fabric, "1.19.2"));
service.ensureLoaded(ModLoader::Fabric, "1.20.1", b.callback());
QTRY_VERIFY_WITH_TIMEOUT(a.calls == 1 && b.calls == 1, 15000);
QCOMPARE(m_net.hits(kFabric), 1);
QVERIFY(!service.isRefreshing(ModLoader::Fabric, "1.20.1"));
}
};
BILAUNCHER_TEST_MAIN(TestModLoaderVersionService)
#include "tst_modloaderversionservice.moc"