Commit 22a3d8af authored by Volker Krause's avatar Volker Krause
Browse files

Validate journey access/egress mode settings before executing queries

There's a number of constraints that need to be considered here, so better
do this centrally rather than having all backends deal with invalid
combinations.
parent 819942fa
Pipeline #84975 passed with stage
in 1 minute and 14 seconds
......@@ -18,6 +18,7 @@ ecm_add_test(locationtest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(linetest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(departuretest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(journeytest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(journeyrequesttest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(platformtest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(notestest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(backendtest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
......
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <KPublicTransport/JourneyRequest>
#include <QTest>
#define s(x) QStringLiteral(x)
using namespace KPublicTransport;
namespace KPublicTransport {
class JourneyRequestTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testAccessEgressModeValidation()
{
JourneyRequest req;
// there always has to be at least one access/egress mode
req.setAccessModes(std::vector<IndividualTransport>{});
req.setEgressModes(std::vector<IndividualTransport>{});
req.validate();
QCOMPARE(req.accessModes().size(), 1);
QCOMPARE(req.accessModes()[0], IndividualTransport{IndividualTransport::Walk});
QCOMPARE(req.egressModes().size(), 1);
QCOMPARE(req.egressModes()[0], IndividualTransport{IndividualTransport::Walk});
// parking is not a valid qualifier for egress
req.setEgressModes(std::vector<IndividualTransport>{{IndividualTransport::Bike, IndividualTransport::Park}});
req.validate();
QCOMPARE(req.egressModes()[0], IndividualTransport{IndividualTransport::Walk});
// when taking a bike on public transport, we also need to take it off again
req.setAccessModes(std::vector<IndividualTransport>{{IndividualTransport::Bike}});
req.validate();
QCOMPARE(req.egressModes().size(), 2);
QCOMPARE(req.egressModes()[1], IndividualTransport{IndividualTransport::Bike});
// cars can't be taken on a train
req.setAccessModes(std::vector<IndividualTransport>{{IndividualTransport::Car}});
req.validate();
QCOMPARE(req.accessModes().size(), 1);
QCOMPARE(req.accessModes()[0], IndividualTransport{IndividualTransport::Walk});
// ... and that also means we wont have a bike to take of the train again
QCOMPARE(req.egressModes().size(), 1);
QCOMPARE(req.egressModes()[0], IndividualTransport{IndividualTransport::Walk});
}
};
}
QTEST_GUILESS_MAIN(JourneyRequestTest)
#include "journeyrequesttest.moc"
......@@ -228,4 +228,44 @@ QString JourneyRequest::cacheKey() const
return QString::fromUtf8(hash.result().toHex());
}
static bool hasTakeBikeMode(const std::vector<IndividualTransport> &modes)
{
return std::any_of(modes.begin(), modes.end(), [](const auto &it) {
return it.mode() == IndividualTransport::Bike && it.qualifier() == IndividualTransport::None;
});
}
void JourneyRequest::validate() const
{
// remove invalid access/egress modes
d->accessModes.erase(std::remove_if(d->accessModes.begin(), d->accessModes.end(), [](const auto &it) {
return (it.mode() == IndividualTransport::Car && it.qualifier() == IndividualTransport::None)
|| it.qualifier() == IndividualTransport::Pickup;
}), d->accessModes.end());
d->egressModes.erase(std::remove_if(d->egressModes.begin(), d->egressModes.end(), [](const auto &it) {
return (it.mode() == IndividualTransport::Car && it.qualifier() == IndividualTransport::None)
|| it.qualifier() == IndividualTransport::Dropoff
|| it.qualifier() == IndividualTransport::Park;
}), d->egressModes.end());
// taking a bike on public transport needs to be symmetric
const auto hasTakeBikeAccess = hasTakeBikeMode(d->accessModes);
const auto hasTakeBikeEgress = hasTakeBikeMode(d->egressModes);
if (hasTakeBikeAccess && !hasTakeBikeEgress) {
d->egressModes.push_back({ IndividualTransport::Bike });
} else if (!hasTakeBikeAccess && hasTakeBikeEgress) {
d->egressModes.erase(std::remove_if(d->egressModes.begin(), d->egressModes.end(), [](const auto &it) {
return it.mode() == IndividualTransport::Bike && it.qualifier() == IndividualTransport::None;
}), d->egressModes.end());
}
// access/egress modes must not be empty
if (d->accessModes.empty()) {
d->accessModes = {{IndividualTransport::Walk}};
}
if (d->egressModes.empty()) {
d->egressModes = {{IndividualTransport::Walk}};
}
}
#include "moc_journeyrequest.cpp"
......@@ -139,6 +139,7 @@ private:
friend class JourneyReply;
friend class JourneyReplyPrivate;
friend class Manager;
friend class JourneyRequestTest;
Q_DECL_HIDDEN QVariantList accessModesVariant() const;
Q_DECL_HIDDEN void setAccessModes(const QVariantList &accessModesVariant);
......@@ -149,6 +150,9 @@ private:
Q_DECL_HIDDEN const std::vector<RequestContext>& contexts() const;
Q_DECL_HIDDEN void setContext(const AbstractBackend *backend, RequestContext &&context);
Q_DECL_HIDDEN void purgeLoops(const JourneyRequest &baseRequest);
/** Check that the given request parameters are semantically sane, and fix that if needed. */
void validate() const;
};
}
......
......@@ -501,6 +501,7 @@ JourneyReply* Manager::queryJourney(const JourneyRequest &req) const
int pendingOps = 0;
// validate input
req.validate();
if (!req.isValid()) {
reply->addError(Reply::InvalidRequest, {});
reply->setPendingOps(pendingOps);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment