Files
Qt_DesktopPet/internal/qa/tests/weather/WeatherTests.cpp
T

226 lines
10 KiB
C++

#include "TestHarness.h"
#include "src/weather/WeatherParser.h"
#include "src/weather/WeatherLocationResolver.h"
#include "src/weather/WeatherSummaryFormatter.h"
namespace
{
WeatherLocationCandidate candidate(
const QString &city,
const QString &admin,
const QString &country,
const QString &countryCode,
const QString &featureCode,
qint64 population,
double latitude,
double longitude)
{
WeatherLocationCandidate value;
value.cityName = city;
value.adminName = admin;
value.countryName = country;
value.countryCode = countryCode;
value.featureCode = featureCode;
value.population = population;
value.latitude = latitude;
value.longitude = longitude;
return value;
}
}
QA_TEST(WeatherParser, ExtractsExplicitCitiesAndDates)
{
const WeatherParser parser;
const WeatherQuery current = parser.parse(QStringLiteral("西安天气怎么样"));
const WeatherQuery tomorrow = parser.parse(QStringLiteral("明天西安天气怎么样"));
const WeatherQuery afterTomorrow = parser.parse(QStringLiteral("后天纽约天气"));
QA_EXPECT_EQ(current.cityName, QStringLiteral("西安"));
QA_EXPECT_EQ(current.kind, WeatherQueryKind::Current);
QA_EXPECT_EQ(tomorrow.cityName, QStringLiteral("西安"));
QA_EXPECT_EQ(tomorrow.dateOffset, 1);
QA_EXPECT_EQ(afterTomorrow.cityName, QStringLiteral("纽约"));
QA_EXPECT_EQ(afterTomorrow.dateOffset, 2);
}
QA_TEST(WeatherParser, ParsesRangeAndDefaultCityRequest)
{
const WeatherParser parser;
const WeatherQuery range = parser.parse(QStringLiteral("未来三天北京天气"));
const WeatherQuery defaultCity = parser.parse(QStringLiteral("今天天气怎么样"));
QA_EXPECT_EQ(range.kind, WeatherQueryKind::Range);
QA_EXPECT_EQ(range.forecastDays, 3);
QA_EXPECT_EQ(range.cityName, QStringLiteral("北京"));
QA_EXPECT(defaultCity.cityName.isEmpty());
}
QA_TEST(WeatherParser, RejectsUnsupportedQueries)
{
const WeatherParser parser;
QA_EXPECT(!parser.parse(QStringLiteral("西安空气质量")).unsupportedReason.isEmpty());
QA_EXPECT(!parser.parse(QStringLiteral("北京天气预警")).unsupportedReason.isEmpty());
QA_EXPECT(!parser.parse(QStringLiteral("明天穿什么")).unsupportedReason.isEmpty());
}
QA_TEST(WeatherLocationResolver, ResolvesChineseInternationalCities)
{
const WeatherLocationSearch newYork = WeatherLocationResolver::resolveSearch(QStringLiteral("纽约"));
const WeatherLocationSearch tokyo = WeatherLocationResolver::resolveSearch(QStringLiteral("东京"));
const WeatherLocationSearch japanTokyo = WeatherLocationResolver::resolveSearch(QStringLiteral("日本东京"));
const WeatherLocationSearch qualifiedTokyo = WeatherLocationResolver::resolveSearch(QStringLiteral("东京,日本"));
QA_EXPECT_EQ(newYork.queryName, QStringLiteral("New York"));
QA_EXPECT_EQ(newYork.countryCode, QStringLiteral("US"));
QA_EXPECT_EQ(tokyo.queryName, QStringLiteral("Tokyo"));
QA_EXPECT_EQ(tokyo.countryCode, QStringLiteral("JP"));
QA_EXPECT_EQ(japanTokyo.queryName, QStringLiteral("Tokyo"));
QA_EXPECT_EQ(japanTokyo.countryCode, QStringLiteral("JP"));
QA_EXPECT_EQ(qualifiedTokyo.queryName, QStringLiteral("Tokyo"));
QA_EXPECT_EQ(qualifiedTokyo.countryCode, QStringLiteral("JP"));
}
QA_TEST(WeatherLocationResolver, PreservesDomesticCityAndCountryQualifier)
{
const WeatherLocationSearch xian = WeatherLocationResolver::resolveSearch(QStringLiteral("西安市"));
const WeatherLocationSearch chinaXian = WeatherLocationResolver::resolveSearch(QStringLiteral("中国西安"));
QA_EXPECT_EQ(xian.queryName, QStringLiteral("西安"));
QA_EXPECT(xian.countryCode.isEmpty());
QA_EXPECT_EQ(chinaXian.queryName, QStringLiteral("西安"));
QA_EXPECT_EQ(chinaXian.countryCode, QStringLiteral("CN"));
}
QA_TEST(WeatherLocationResolver, SuppressesLowValueAlternatives)
{
const QVector<WeatherLocationCandidate> candidates = {
candidate(QStringLiteral("西安"), QStringLiteral("陕西"), QStringLiteral("中国"), QStringLiteral("CN"), QStringLiteral("PPLA"), 6500000, 34.26, 108.93),
candidate(QStringLiteral("西安"), QStringLiteral("湖南"), QStringLiteral("中国"), QStringLiteral("CN"), QStringLiteral("PPL"), 1200, 27.10, 111.20),
candidate(QStringLiteral("西安"), QStringLiteral("广东"), QStringLiteral("中国"), QStringLiteral("CN"), QStringLiteral("PPL"), -1, 23.10, 113.20),
};
const QVector<WeatherLocationCandidate> selected = WeatherLocationResolver::selectRelevantCandidates(
candidates,
WeatherLocationResolver::resolveSearch(QStringLiteral("西安")));
QA_EXPECT_EQ(selected.size(), 1);
QA_EXPECT(!WeatherLocationResolver::hasAmbiguity(selected));
}
QA_TEST(WeatherLocationResolver, KeepsPlausibleAmbiguity)
{
const QVector<WeatherLocationCandidate> candidates = {
candidate(QStringLiteral("Springfield"), QStringLiteral("Missouri"), QStringLiteral("United States"), QStringLiteral("US"), QStringLiteral("PPLA2"), 170000, 37.21, -93.29),
candidate(QStringLiteral("Springfield"), QStringLiteral("Illinois"), QStringLiteral("United States"), QStringLiteral("US"), QStringLiteral("PPLA"), 114000, 39.80, -89.64),
candidate(QStringLiteral("Springfield"), QStringLiteral("Georgia"), QStringLiteral("United States"), QStringLiteral("US"), QStringLiteral("PPL"), 2700, 32.37, -81.31),
};
const QVector<WeatherLocationCandidate> selected = WeatherLocationResolver::selectRelevantCandidates(
candidates,
WeatherLocationResolver::resolveSearch(QStringLiteral("Springfield")));
QA_EXPECT_EQ(selected.size(), 2);
QA_EXPECT(WeatherLocationResolver::hasAmbiguity(selected));
}
QA_TEST(WeatherLocationResolver, AppliesExplicitCountryFilter)
{
const QVector<WeatherLocationCandidate> candidates = {
candidate(QStringLiteral("Tokyo"), QStringLiteral("Tokyo"), QStringLiteral("Japan"), QStringLiteral("JP"), QStringLiteral("PPLC"), 8300000, 35.68, 139.69),
candidate(QStringLiteral("Tokyo"), QStringLiteral("Texas"), QStringLiteral("United States"), QStringLiteral("US"), QStringLiteral("PPL"), 1000, 33.90, -102.50),
};
const QVector<WeatherLocationCandidate> selected = WeatherLocationResolver::selectRelevantCandidates(
candidates,
WeatherLocationResolver::resolveSearch(QStringLiteral("日本东京")));
QA_EXPECT_EQ(selected.size(), 1);
QA_EXPECT_EQ(selected.first().countryCode, QStringLiteral("JP"));
}
QA_TEST(WeatherLocationResolver, ParsesBothIpLocationSchemas)
{
const QByteArray ipApiPayload = QByteArrayLiteral(
R"({"city":"Xi'an","region":"Shaanxi","country_name":"China","latitude":34.26,"longitude":108.94,"timezone":"Asia/Shanghai"})");
const QByteArray ipWhoIsPayload = QByteArrayLiteral(
R"({"success":true,"city":"Tokyo","region":"Tokyo","country":"Japan","latitude":35.68,"longitude":139.69,"timezone":{"id":"Asia/Tokyo"}})");
WeatherLocation ipApiLocation;
WeatherLocation ipWhoIsLocation;
QA_EXPECT(WeatherLocationResolver::parseIpLocationResponse(
ipApiPayload,
WeatherIpLocationProvider::IpApiCo,
&ipApiLocation));
QA_EXPECT(WeatherLocationResolver::parseIpLocationResponse(
ipWhoIsPayload,
WeatherIpLocationProvider::IpWhoIs,
&ipWhoIsLocation));
QA_EXPECT_EQ(ipApiLocation.cityName, QStringLiteral("Xi'an"));
QA_EXPECT_EQ(ipApiLocation.countryName, QStringLiteral("China"));
QA_EXPECT_EQ(ipApiLocation.timezone, QStringLiteral("Asia/Shanghai"));
QA_EXPECT_EQ(ipWhoIsLocation.cityName, QStringLiteral("Tokyo"));
QA_EXPECT_EQ(ipWhoIsLocation.countryName, QStringLiteral("Japan"));
QA_EXPECT_EQ(ipWhoIsLocation.timezone, QStringLiteral("Asia/Tokyo"));
}
QA_TEST(WeatherLocationResolver, RejectsBrokenIpLocationResponses)
{
WeatherLocation location;
QA_EXPECT(!WeatherLocationResolver::parseIpLocationResponse(
QByteArrayLiteral("{broken"),
WeatherIpLocationProvider::IpApiCo,
&location));
QA_EXPECT(!WeatherLocationResolver::parseIpLocationResponse(
QByteArrayLiteral(R"({"success":false,"message":"rate limited"})"),
WeatherIpLocationProvider::IpWhoIs,
&location));
QA_EXPECT(!WeatherLocationResolver::parseIpLocationResponse(
QByteArrayLiteral(R"({"city":"Tokyo","latitude":35.68})"),
WeatherIpLocationProvider::IpWhoIs,
&location));
}
QA_TEST(WeatherFormatter, MapsWeatherCodesAndWindDirections)
{
QA_EXPECT_EQ(WeatherSummaryFormatter::weatherCodeText(0), QStringLiteral("晴"));
QA_EXPECT_EQ(WeatherSummaryFormatter::weatherCodeText(95), QStringLiteral("雷暴"));
QA_EXPECT_EQ(WeatherSummaryFormatter::weatherCodeText(-1), QStringLiteral("未知天气"));
QA_EXPECT_EQ(WeatherSummaryFormatter::windDirectionText(0), QStringLiteral("北风"));
QA_EXPECT_EQ(WeatherSummaryFormatter::windDirectionText(90), QStringLiteral("东风"));
QA_EXPECT_EQ(WeatherSummaryFormatter::windDirectionText(225), QStringLiteral("西南风"));
}
QA_TEST(WeatherFormatter, FormatsCurrentWeatherWithSource)
{
WeatherReport report;
report.query.kind = WeatherQueryKind::Current;
report.location.cityName = QStringLiteral("西安");
report.location.countryName = QStringLiteral("中国");
report.location.source = WeatherLocationSource::SettingsDefault;
report.current.valid = true;
report.current.weatherCode = 0;
report.current.hasTemperature = true;
report.current.temperatureC = 28.5;
const QString message = WeatherSummaryFormatter().format(report);
QA_EXPECT(message.contains(QStringLiteral("使用设置页默认城市")));
QA_EXPECT(message.contains(QStringLiteral("西安,中国当前天气:晴")));
QA_EXPECT(message.contains(QStringLiteral("28.5℃")));
}
QA_TEST(WeatherFormatter, FormatsAmbiguousLocationAndMissingData)
{
WeatherReport report;
report.query.kind = WeatherQueryKind::Current;
report.location.cityName = QStringLiteral("Springfield");
report.location.countryName = QStringLiteral("United States");
report.hasLocationAmbiguity = true;
report.locationCandidates = {
{QStringLiteral("Springfield"), QStringLiteral("Illinois"), QStringLiteral("United States"), {}, 0.0, 0.0},
{QStringLiteral("Springfield"), QStringLiteral("Missouri"), QStringLiteral("United States"), {}, 0.0, 0.0},
};
const QString message = WeatherSummaryFormatter().format(report);
QA_EXPECT(message.contains(QStringLiteral("可能存在同名城市")));
QA_EXPECT(message.contains(QStringLiteral("Missouri")));
QA_EXPECT(message.contains(QStringLiteral("缺少当前天气")));
}