198 lines
7.3 KiB
C++
198 lines
7.3 KiB
C++
#include "testenv.h"
|
|
#include "fixtures.h"
|
|
#include "nettest.h"
|
|
|
|
#include "launcherpaths.h"
|
|
#include "localization.h"
|
|
#include "seasonalbuildservice.h"
|
|
|
|
#include <QJsonArray>
|
|
#include <QSignalSpy>
|
|
|
|
namespace {
|
|
const QUrl kBase(QStringLiteral("https://launcher-builds.galeonworx.com"));
|
|
const QString kIndex = QStringLiteral("https://launcher-builds.galeonworx.com/index.json");
|
|
}
|
|
|
|
class TestSeasonalBuildService : 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 withoutBaseUrlFails()
|
|
{
|
|
SeasonalBuildService service;
|
|
CallResult r;
|
|
service.ensureLoaded(r.callback());
|
|
QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 5000);
|
|
QVERIFY(!r.ok);
|
|
QCOMPARE(r.warning, Loc::text("seasonal.error.noServerUrl"));
|
|
QCOMPARE(service.lastError(), Loc::text("seasonal.error.noServerUrl"));
|
|
QVERIFY(m_net.requests().isEmpty());
|
|
}
|
|
|
|
void parsesValidatesAndSorts()
|
|
{
|
|
m_net.route(kIndex, FakeResponse::json(Fixtures::read("seasonal/index.json")));
|
|
SeasonalBuildService service;
|
|
service.setBaseUrl(kBase);
|
|
QCOMPARE(service.baseUrl(), kBase);
|
|
CallResult r;
|
|
service.ensureLoaded(r.callback());
|
|
QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000);
|
|
QVERIFY2(r.ok, qPrintable(r.warning));
|
|
|
|
QStringList ids;
|
|
for (const SeasonalBuildEntry &entry : service.builds())
|
|
ids << entry.id;
|
|
// Открытые сезоны первыми (новые выше), закрытые — после; всё невалидное отброшено.
|
|
QCOMPARE(ids, QStringList({"season-5", "vanilla-plus", "season-4"}));
|
|
|
|
const auto s5 = service.find("season-5");
|
|
QVERIFY(s5.has_value());
|
|
QVERIFY(s5->isValid());
|
|
QCOMPARE(s5->name, QStringLiteral("Сезон 5: Пустоши"));
|
|
QCOMPARE(s5->revision, 3);
|
|
QCOMPARE(s5->minecraftVersion, QStringLiteral("1.21.1"));
|
|
QCOMPARE(s5->loader, QStringLiteral("fabric"));
|
|
QCOMPARE(s5->loaderVersion, QStringLiteral("0.16.10"));
|
|
QCOMPARE(s5->javaMajor, 21);
|
|
QCOMPARE(s5->modCount, 120);
|
|
QCOMPARE(s5->description, QStringLiteral("Пустоши")); // обрезаны пробелы
|
|
QCOMPARE(s5->seasonStart, QDate(2025, 7, 1));
|
|
QVERIFY(!s5->seasonEnd.isValid());
|
|
QCOMPARE(s5->archiveSize, qint64(123456));
|
|
QCOMPARE(s5->archiveSha256, QString("abcdef" + QString(58, u'0'))); // к нижнему регистру
|
|
QCOMPARE(s5->archiveUrl, QUrl("https://launcher-builds.galeonworx.com/packs/s5.zip"));
|
|
QCOMPARE(service.find("season-4")->seasonEnd, QDate(2025, 6, 1));
|
|
QVERIFY(service.find("vanilla-plus")->loader.isEmpty());
|
|
|
|
QVERIFY(QFile::exists(LauncherPaths::seasonalCatalogFile()));
|
|
}
|
|
|
|
void baseUrlPathIsKept()
|
|
{
|
|
m_net.route("https://launcher-builds.galeonworx.com/beta/index.json",
|
|
FakeResponse::json(QJsonObject{{"formatVersion", 1}, {"builds", QJsonArray{}}}));
|
|
SeasonalBuildService service;
|
|
service.setBaseUrl(QUrl("https://launcher-builds.galeonworx.com/beta"));
|
|
CallResult r;
|
|
service.ensureLoaded(r.callback());
|
|
QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000);
|
|
QCOMPARE(m_net.hits("https://launcher-builds.galeonworx.com/beta/index.json"), 1);
|
|
QVERIFY(!r.ok);
|
|
QCOMPARE(r.warning, Loc::text("seasonal.error.catalogEmpty"));
|
|
}
|
|
|
|
void formatVersionChecks_data()
|
|
{
|
|
QTest::addColumn<int>("format");
|
|
QTest::addColumn<QString>("error");
|
|
QTest::newRow("too new") << 2 << Loc::text("seasonal.error.formatTooNew").arg(2);
|
|
QTest::newRow("unknown") << 0 << Loc::text("seasonal.error.formatUnknown").arg(0);
|
|
}
|
|
|
|
void formatVersionChecks()
|
|
{
|
|
QFETCH(int, format);
|
|
QFETCH(QString, error);
|
|
QJsonObject catalog = Fixtures::readJson("seasonal/index.json");
|
|
catalog["formatVersion"] = format;
|
|
m_net.route(kIndex, FakeResponse::json(catalog));
|
|
SeasonalBuildService service;
|
|
service.setBaseUrl(kBase);
|
|
CallResult r;
|
|
service.ensureLoaded(r.callback());
|
|
QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000);
|
|
QVERIFY(!r.ok);
|
|
QCOMPARE(r.warning, error);
|
|
QCOMPARE(service.lastError(), error);
|
|
}
|
|
|
|
void cacheIsBoundToBaseUrl()
|
|
{
|
|
m_net.route(kIndex, FakeResponse::json(Fixtures::read("seasonal/index.json")));
|
|
{
|
|
SeasonalBuildService service;
|
|
service.setBaseUrl(kBase);
|
|
CallResult r;
|
|
service.ensureLoaded(r.callback());
|
|
QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000);
|
|
}
|
|
// Тот же адрес — свежий кеш, без сети.
|
|
{
|
|
SeasonalBuildService service;
|
|
service.setBaseUrl(kBase);
|
|
CallResult r;
|
|
service.ensureLoaded(r.callback());
|
|
QVERIFY(service.hasData());
|
|
QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 5000);
|
|
QCOMPARE(m_net.hits(kIndex), 1);
|
|
}
|
|
// Другой адрес — чужой кеш не подхватывается.
|
|
SeasonalBuildService other;
|
|
other.setBaseUrl(QUrl("https://mirror.example"));
|
|
m_net.route("https://mirror.example/index.json", FakeResponse::error(503));
|
|
CallResult r;
|
|
other.ensureLoaded(r.callback());
|
|
QVERIFY(!other.hasData());
|
|
QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000);
|
|
QVERIFY(!r.ok);
|
|
QVERIFY(r.warning.startsWith(Loc::text("seasonal.error.catalogFetchFailed").section(u'%', 0, 0)));
|
|
}
|
|
|
|
void setBaseUrlResetsData()
|
|
{
|
|
m_net.route(kIndex, FakeResponse::json(Fixtures::read("seasonal/index.json")));
|
|
SeasonalBuildService service;
|
|
service.setBaseUrl(kBase);
|
|
CallResult r;
|
|
service.ensureLoaded(r.callback());
|
|
QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000);
|
|
QVERIFY(service.hasData());
|
|
|
|
QSignalSpy changed(&service, &SeasonalBuildService::buildsChanged);
|
|
service.setBaseUrl(kBase); // тот же адрес — ничего не происходит
|
|
QCOMPARE(changed.count(), 0);
|
|
service.setBaseUrl(QUrl("https://mirror.example"));
|
|
QCOMPARE(changed.count(), 1);
|
|
QVERIFY(!service.hasData());
|
|
QVERIFY(!service.find("season-5").has_value());
|
|
}
|
|
|
|
void offlineWithCacheKeepsBuilds()
|
|
{
|
|
m_net.route(kIndex, FakeResponse::json(Fixtures::read("seasonal/index.json")));
|
|
{
|
|
SeasonalBuildService service;
|
|
service.setBaseUrl(kBase);
|
|
CallResult r;
|
|
service.ensureLoaded(r.callback());
|
|
QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000);
|
|
}
|
|
m_net.route(kIndex, FakeResponse::error(502));
|
|
SeasonalBuildService service;
|
|
service.setBaseUrl(kBase);
|
|
CallResult r;
|
|
service.ensureLoaded(r.callback(), /*forceRefresh=*/true);
|
|
QTRY_COMPARE_WITH_TIMEOUT(r.calls, 1, 15000);
|
|
QVERIFY(r.ok);
|
|
QVERIFY(!r.warning.isEmpty());
|
|
QCOMPARE(service.builds().size(), 3);
|
|
QCOMPARE(service.lastError(), r.warning);
|
|
}
|
|
};
|
|
|
|
BILAUNCHER_TEST_MAIN(TestSeasonalBuildService)
|
|
#include "tst_seasonalbuildservice.moc"
|