Revision control

++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include "../client/testapi.h"
++
++ #include <QtCore/qobject.h>
++ #include <QtCore/qprocess.h>
++ #include <QtNetwork/qnetworkrequestfactory.h>
++ #include <QtNetwork/qrestaccessmanager.h>
++ #include <QtTest/qtest.h>
++
++ #include <cstdio>
++
++ using namespace Qt::StringLiterals;
++
++ #define CALL_TEST_OPERATION(OPERATION, PARAM, EXPECTED_STRING) \
++ CALL_TEST_OPERATION_MULTI_PARAMS(OPERATION, EXPECTED_STRING, PARAM)
++
++ #define CALL_TEST_OPERATION_MULTI_PARAMS(OPERATION, EXPECTED_STRING, ...) \
++ { \
++ bool done = false; \
++ OPERATION(__VA_ARGS__, this, [&](const QRestReply &reply, const QString &summary) { \
++ if (!(done = reply.isSuccess())) \
++ qWarning() << "Error happened while issuing request : " << reply.errorString(); \
++ QCOMPARE(getStatusString(summary), EXPECTED_STRING); \
++ }); \
++ QCOMPARE(m_manager->m_operationPath, EXPECTED_STRING); \
++ QTRY_COMPARE_EQ(done, true); \
++ }
++
++ #define CALL_TEST_POST_OPERATION(OPERATION, PARAM, EXPECTED_STRING) \
++ { \
++ bool done = false; \
++ OPERATION(PARAM, this, [&](const QRestReply &reply, const QString &summary) { \
++ if (!(done = reply.isSuccess())) \
++ qWarning() << "Error happened while issuing request : " << reply.errorString(); \
++ QCOMPARE(getStatusString(summary), EXPECTED_STRING); \
++ QCOMPARE(getHeaderValue(summary), "application/x-www-form-urlencoded"); \
++ }); \
++ QCOMPARE(m_manager->m_operationPath, EXPECTED_STRING); \
++ QTRY_COMPARE_EQ(done, true); \
++ }
++
++ #define CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(OPERATION, PARAM) \
++ { \
++ bool done = false; \
++ OPERATION(PARAM, this, [&](const QRestReply &reply, const QString &summary) { \
++ Q_UNUSED(summary) \
++ done = reply.isSuccess(); \
++ QCOMPARE(reply.httpStatus(), 200); \
++ QCOMPARE(getHeaderValue(summary), "application/x-www-form-urlencoded"); \
++ }); \
++ QTRY_COMPARE_EQ(done, true); \
++ }
++
++ #define CALL_NOT_FOUND_TEST_OPERATION(OPERATION, PARAM) \
++ CALL_FAIL_TEST_OPERATION(OPERATION, PARAM, QString, 404, "404 page not found"_L1)
++
++ #define CALL_FAIL_TEST_OPERATION(OPERATION, PARAM, RESPONSE_TYPE, STATUS, ERROR_MESSAGE)\
++ { \
++ bool done = true; \
++ OPERATION(PARAM, this, [&](const QRestReply &reply, const RESPONSE_TYPE &summary) { \
++ Q_UNUSED(summary) \
++ done = reply.isSuccess(); \
++ QCOMPARE(reply.httpStatus(), STATUS); \
++ QCOMPARE(reply.networkReply()->readAll(), ERROR_MESSAGE); \
++ }); \
++ QTRY_COMPARE_EQ(done, false); \
++ }
++
++ #define CALL_TEST_NUMERIC_OPERATION(OPERATION, PARAM, EXPECTED_SUMMARY) \
++ { \
++ bool done = false; \
++ using RESPONSE_TYPE = decltype(EXPECTED_SUMMARY); \
++ OPERATION(PARAM, this, [&](const QRestReply &reply, const RESPONSE_TYPE &summary) { \
++ if (!(done = reply.isSuccess())) \
++ qWarning() << "Error happened while issuing request : " << reply.errorString(); \
++ QCOMPARE(summary.getStringValue(), EXPECTED_SUMMARY.getStringValue()); \
++ auto expectedVal = EXPECTED_SUMMARY.getValue(); \
++ if (!std::isnan(expectedVal) && !std::isinf(expectedVal)) \
++ QCOMPARE(summary.getValue(), expectedVal); \
++ }); \
++ QCOMPARE(m_manager->m_operationPath, EXPECTED_SUMMARY.getStringValue()); \
++ QTRY_COMPARE_EQ(done, true); \
++ }
++
++ class LoggingNetworkAccessManager : public QNetworkAccessManager
++ {
++ public:
++ LoggingNetworkAccessManager(QObject *parent = nullptr)
++ : QNetworkAccessManager(parent)
++ {}
++ ~LoggingNetworkAccessManager() override
++ {}
++
++ protected:
++ QNetworkReply *createRequest(QNetworkAccessManager::Operation op,
++ const QNetworkRequest &originalReq,
++ QIODevice *outgoingData = nullptr) override;
++
++ public:
++ QString m_operationPath;
++ };
++
++ QNetworkReply *LoggingNetworkAccessManager::createRequest(QNetworkAccessManager::Operation op,
++ const QNetworkRequest &originalReq,
++ QIODevice *outgoingData)
++ {
++ const QUrl fullUrl = originalReq.url();
++ // we only need the path and query parameters
++ m_operationPath = fullUrl.path(QUrl::FullyEncoded);
++ if (fullUrl.hasQuery())
++ m_operationPath += u'?' + fullUrl.query(QUrl::FullyEncoded);
++
++ return QNetworkAccessManager::createRequest(op, originalReq, outgoingData);
++ }
++
++ namespace QtOpenAPI {
++
++ static QProcess serverProcess;
++ void startServerProcess()
++ {
++ serverProcess.start(SERVER_PATH);
++ if (!serverProcess.waitForStarted()) {
++ qFatal() << "Couldn't start the server: " << serverProcess.errorString();
++ exit(EXIT_FAILURE);
++ }
++ // give the process some time to properly start up the server
++ QThread::currentThread()->msleep(1000);
++ }
++
++ static QJsonValue getObjectValue(const QString &summary, const QString &key)
++ {
++ QJsonDocument doc = QJsonDocument::fromJson(summary.toUtf8());
++ if (!doc.isNull() && doc.isObject()) {
++ QJsonObject obj = doc.object();
++ return obj.value(key);
++ }
++ return QJsonValue();
++ }
++
++ static QString getStatusString(const QString &summary)
++ {
++ const QJsonValue val = getObjectValue(summary, "status"_L1);
++ return val.toString();
++ }
++
++ static QString getHeaderValue(const QString &summary,
++ const QString &headerType = "Content-Type"_L1)
++ {
++ const QJsonValue val = getObjectValue(summary, "header"_L1);
++ if (!val.isNull()) {
++ const QStringList headers = val.toVariant().toMap().value(headerType).toStringList();
++ if (headers.size() > 0)
++ return headers.at(0);
++ }
++ return QString();
++ }
++
++ class OperationParameters : public TestApi {
++ Q_OBJECT
++
++ private Q_SLOTS:
++ void initTestCase()
++ {
++ if (serverProcess.state() != QProcess::ProcessState::Running)
++ startServerProcess();
++
++ m_manager = new LoggingNetworkAccessManager(this);
++ m_restManager = new QRestAccessManager(m_manager, this);
++ setRestAccessManager(m_restManager);
++ }
++ void pathStringParameters_data();
++ void pathStringParameters();
++ void pathIntParameters_data();
++ void pathIntParameters();
++ void pathFloatParameters_data();
++ void pathFloatParameters();
++ void pathDoubleParameters_data();
++ void pathDoubleParameters();
++ void pathArrayParameters_data();
++ void pathArrayParameters();
++ void pathAnyTypeParameters_data();
++ void pathAnyTypeParameters();
++ void pathObjectParameters_data();
++ void pathObjectParameters();
++ void pathStringMapParameters_data();
++ void pathStringMapParameters();
++ void pathModelMapParameters();
++ void multiplePathParameters();
++ void queryParameters();
++ void queryAnyTypeParameters_data();
++ void queryAnyTypeParameters();
++ void pathNACombinations();
++ void queryNACombinations();
++ void pathAndQueryUndefined();
++ void severalQueryParametersPerOPeration();
++ void pathAndQueryParameters();
++ void headerAnyTypeParameters_data();
++ void headerAnyTypeParameters();
++ void headerStringParameters_data();
++ void headerStringParameters();
++ void headerArrayParameters_data();
++ void headerArrayParameters();
++ void headerObjectParameters_data();
++ void headerObjectParameters();
++ void headerMapParameters_data();
++ void headerMapParameters();
++ void headerAdditionalCases_data();
++ void headerAdditionalCases();
++ void severalHeaderParameters();
++ void headerInvalidStyle();
++ void cookieAnyTypeParameters_data();
++ void cookieAnyTypeParameters();
++ void cookieStringParameters_data();
++ void cookieStringParameters();
++ void cookieArrayParameters_data();
++ void cookieArrayParameters();
++ void cookieObjectParameters_data();
++ void cookieObjectParameters();
++ void cookieMapParameters_data();
++ void cookieMapParameters();
++ void severalCookies();
++ void cookieInvalidStyle();
++ void cleanupTestCase();
++
++ private:
++ LoggingNetworkAccessManager *m_manager = nullptr;
++ QRestAccessManager *m_restManager = nullptr;
++ };
++
++ static void ignoreInvalidExplodeWarningMsg(const char *paramName, const char *style, bool explode)
++ {
++ Q_DECL_UNINITIALIZED
++ char buf[512];
++ const auto r = std::snprintf(buf, sizeof buf,
++ "Invalid combination for query parameter '%s': style=%s, explode=%s.%s"
++ "Using valid explode=%s instead.",
++ paramName,
++ style,
++ explode ? "true" : "false",
++ #ifdef Q_OS_WIN
++ "\r\n",
++ #else
++ "\n",
++ #endif
++ explode ? "false" : "true");
++ QCOMPARE_GT(r, 0);
++ QCOMPARE_LT(r, int{sizeof buf});
++ QTest::ignoreMessage(QtWarningMsg, buf);
++ }
++
++ // The latest implementation is done based on this information:
++ // NOTE! All QUERY and PATH params are encoded.
++ void OperationParameters::pathStringParameters_data()
++ {
++ QTest::addColumn<QString>("stringValue");
++ QTest::addColumn<QString>("expectedSimpleExplode");
++ QTest::addColumn<QString>("expectedSimpleNotExplode");
++ QTest::addColumn<QString>("expectedLabelExplode");
++ QTest::addColumn<QString>("expectedLabelNotExplode");
++ QTest::addColumn<QString>("expectedMatrixExplode");
++ QTest::addColumn<QString>("expectedMatrixNotExplode");
++ QTest::newRow("QString(Hello World!)") << QString("Hello World!")
++ << "/v2/path/string/simple-explode/Hello%20World%21"
++ << "/v2/path/string/simple-not-explode/Hello%20World%21"
++ << "/v2/path/string/label-explode/.Hello%20World%21"
++ << "/v2/path/string/label-not-explode/.Hello%20World%21"
++ << "/v2/path/string/matrix-explode/;stringParameter=Hello%20World%21"
++ << "/v2/path/string/matrix-not-explode/;stringParameter=Hello%20World%21";
++
++ QTest::newRow("QString(QwerTy12399.)") << QString("QwerTy12399.")
++ << "/v2/path/string/simple-explode/QwerTy12399."
++ << "/v2/path/string/simple-not-explode/QwerTy12399."
++ << "/v2/path/string/label-explode/.QwerTy12399."
++ << "/v2/path/string/label-not-explode/.QwerTy12399."
++ << "/v2/path/string/matrix-explode/;stringParameter=QwerTy12399."
++ << "/v2/path/string/matrix-not-explode/;stringParameter=QwerTy12399.";
++ // test sub-delims: *+,;=!$&'()
++ QTest::newRow("QString(sub-delims: *+,;=!$&'())") << QString("*+,;=!$&'()")
++ << "/v2/path/string/simple-explode/%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/string/simple-not-explode/%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/string/label-explode/.%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/string/label-not-explode/.%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/string/matrix-explode/;stringParameter=%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/string/matrix-not-explode/;stringParameter=%2A%2B%2C%3B%3D%21%24%26%27%28%29";
++
++ }
++
++ void OperationParameters::pathStringParameters()
++ {
++ QFETCH(QString, stringValue);
++ QFETCH(QString, expectedSimpleExplode);
++ QFETCH(QString, expectedSimpleNotExplode);
++ QFETCH(QString, expectedLabelExplode);
++ QFETCH(QString, expectedLabelNotExplode);
++ QFETCH(QString, expectedMatrixExplode);
++ QFETCH(QString, expectedMatrixNotExplode);
++
++ // style=simple, explode=true, type=string
++ CALL_TEST_OPERATION(simpleExplodeString, stringValue, expectedSimpleExplode);
++
++ // style=simple, explode=false, type=string
++ CALL_TEST_OPERATION(simpleNotExplodeString, stringValue, expectedSimpleNotExplode);
++
++ // style=label, explode=true, type=string
++ CALL_TEST_OPERATION(labelExplodeString, stringValue, expectedLabelExplode);
++
++ // style=label, explode=false, type=string
++ CALL_TEST_OPERATION(labelNotExplodeString, stringValue, expectedLabelNotExplode);
++
++ // style=matrix, explode=true, type=string
++ CALL_TEST_OPERATION(matrixExplodeString, stringValue, expectedMatrixExplode);
++
++ // style=matrix, explode=false, type=string
++ CALL_TEST_OPERATION(matrixNotExplodeString, stringValue, expectedMatrixNotExplode);
++ }
++
++ void OperationParameters::pathIntParameters_data()
++ {
++ QTest::addColumn<qint64>("intValue");
++ QTest::addColumn<QString>("expectedSimpleExplode");
++ QTest::addColumn<QString>("expectedSimpleNotExplode");
++ QTest::addColumn<QString>("expectedLabelExplode");
++ QTest::addColumn<QString>("expectedLabelNotExplode");
++ QTest::addColumn<QString>("expectedMatrixExplode");
++ QTest::addColumn<QString>("expectedMatrixNotExplode");
++
++ const std::array<qint64, 3> values = {22, std::numeric_limits<qint64>::max(),
++ std::numeric_limits<qint64>::min()};
++ for (auto int_val : values) {
++ const QString strVal = QString::number(int_val);
++ QTest::addRow("int %s", strVal.toLatin1().constData())
++ << int_val
++ << "/v2/path/int/simple-explode/"_L1 + strVal
++ << "/v2/path/int/simple-not-explode/"_L1 + strVal
++ << "/v2/path/int/label-explode/."_L1 + strVal
++ << "/v2/path/int/label-not-explode/."_L1 + strVal
++ << "/v2/path/int/matrix-explode/;intParameter="_L1 + strVal
++ << "/v2/path/int/matrix-not-explode/;intParameter="_L1 + strVal;
++ }
++ }
++
++ void OperationParameters::pathIntParameters()
++ {
++ QFETCH(qint64, intValue);
++ QFETCH(QString, expectedSimpleExplode);
++ QFETCH(QString, expectedSimpleNotExplode);
++ QFETCH(QString, expectedLabelExplode);
++ QFETCH(QString, expectedLabelNotExplode);
++ QFETCH(QString, expectedMatrixExplode);
++ QFETCH(QString, expectedMatrixNotExplode);
++
++ // style=simple, explode=true, type=integer, format=int64
++ CALL_TEST_OPERATION(simpleExplodeInt, intValue, expectedSimpleExplode);
++
++ // style=simple, explode=false, type=integer, format=int64
++ CALL_TEST_OPERATION(simpleNotExplodeInt, intValue, expectedSimpleNotExplode);
++
++ // style=label, explode=true, type=integer, format=int64
++ CALL_TEST_OPERATION(labelExplodeInt, intValue, expectedLabelExplode);
++
++ // style=label, explode=false, type=integer, format=int64
++ CALL_TEST_OPERATION(labelNotExplodeInt, intValue, expectedLabelNotExplode);
++
++ // style=matrix, explode=true, type=integer, format=int64
++ CALL_TEST_OPERATION(matrixExplodeInt, intValue, expectedMatrixExplode);
++
++ // style=matrix, explode=false, type=integer, format=int64
++ CALL_TEST_OPERATION(matrixNotExplodeInt, intValue, expectedMatrixNotExplode);
++ }
++
++ void OperationParameters::pathFloatParameters_data()
++ {
++ QTest::addColumn<float>("floatValue");
++ QTest::addColumn<QString>("expectedSimpleExplode");
++ QTest::addColumn<QString>("expectedSimpleNotExplode");
++ QTest::addColumn<QString>("expectedLabelExplode");
++ QTest::addColumn<QString>("expectedLabelNotExplode");
++ QTest::addColumn<QString>("expectedMatrixExplode");
++ QTest::addColumn<QString>("expectedMatrixNotExplode");
++
++ const std::array<float, 5> values = {22.012345f, 22.01236f,
++ std::numeric_limits<float>::quiet_NaN(),
++ std::numeric_limits<float>::infinity(),
++ -std::numeric_limits<float>::infinity()};
++ for (auto f_val : values) {
++ const QString strVal = QString::number(f_val, 'g', QLocale::FloatingPointShortest);
++ QTest::addRow("float %s", strVal.toLatin1().constData())
++ << f_val
++ << "/v2/path/float/simple-explode/"_L1 + strVal
++ << "/v2/path/float/simple-not-explode/"_L1 + strVal
++ << "/v2/path/float/label-explode/."_L1 + strVal
++ << "/v2/path/float/label-not-explode/."_L1 + strVal
++ << "/v2/path/float/matrix-explode/;floatParameter="_L1 + strVal
++ << "/v2/path/float/matrix-not-explode/;floatParameter="_L1 + strVal;
++ }
++ }
++
++ void OperationParameters::pathFloatParameters()
++ {
++ QFETCH(float, floatValue);
++ QFETCH(QString, expectedSimpleExplode);
++ QFETCH(QString, expectedSimpleNotExplode);
++ QFETCH(QString, expectedLabelExplode);
++ QFETCH(QString, expectedLabelNotExplode);
++ QFETCH(QString, expectedMatrixExplode);
++ QFETCH(QString, expectedMatrixNotExplode);
++
++ FloatResponse res;
++ res.setValue(floatValue);
++
++ // style=simple, explode=true, type=number, format=float
++ res.setStringValue(expectedSimpleExplode);
++ CALL_TEST_NUMERIC_OPERATION(simpleExplodeFloat, floatValue, res);
++
++ // style=simple, explode=false, type=number, format=float
++ res.setStringValue(expectedSimpleNotExplode);
++ CALL_TEST_NUMERIC_OPERATION(simpleNotExplodeFloat, floatValue, res);
++
++ // style=label, explode=true, type=number, format=float
++ res.setStringValue(expectedLabelExplode);
++ CALL_TEST_NUMERIC_OPERATION(labelExplodeFloat, floatValue, res);
++
++ // style=label, explode=false, type=number, format=float
++ res.setStringValue(expectedLabelNotExplode);
++ CALL_TEST_NUMERIC_OPERATION(labelNotExplodeFloat, floatValue, res);
++
++ // style=matrix, explode=true, type=number, format=float
++ res.setStringValue(expectedMatrixExplode);
++ CALL_TEST_NUMERIC_OPERATION(matrixExplodeFloat, floatValue, res);
++
++ // style=matrix, explode=false, type=number, format=float
++ res.setStringValue(expectedMatrixNotExplode);
++ CALL_TEST_NUMERIC_OPERATION(matrixNotExplodeFloat, floatValue, res);
++ }
++
++ void OperationParameters::pathDoubleParameters_data()
++ {
++ QTest::addColumn<double>("doubleValue");
++ QTest::addColumn<QString>("expectedSimpleExplode");
++ QTest::addColumn<QString>("expectedSimpleNotExplode");
++ QTest::addColumn<QString>("expectedLabelExplode");
++ QTest::addColumn<QString>("expectedLabelNotExplode");
++ QTest::addColumn<QString>("expectedMatrixExplode");
++ QTest::addColumn<QString>("expectedMatrixNotExplode");
++
++ const std::array<double, 5> values = {22.012345, 2.987653212346578627, 2.987653212346578627e9,
++ std::numeric_limits<double>::max(),
++ std::numeric_limits<double>::min()};
++ for (auto d_val : values) {
++ const QString strVal = QUrl::toPercentEncoding(
++ QString::number(d_val, 'g', QLocale::FloatingPointShortest));
++ QTest::addRow("double %s", strVal.toLatin1().constData())
++ << d_val
++ << "/v2/path/double/simple-explode/"_L1 + strVal
++ << "/v2/path/double/simple-not-explode/"_L1 + strVal
++ << "/v2/path/double/label-explode/."_L1 + strVal
++ << "/v2/path/double/label-not-explode/."_L1 + strVal
++ << "/v2/path/double/matrix-explode/;doubleParameter="_L1 + strVal
++ << "/v2/path/double/matrix-not-explode/;doubleParameter="_L1 + strVal;
++ }
++ }
++
++ void OperationParameters::pathDoubleParameters()
++ {
++ QFETCH(double, doubleValue);
++ QFETCH(QString, expectedSimpleExplode);
++ QFETCH(QString, expectedSimpleNotExplode);
++ QFETCH(QString, expectedLabelExplode);
++ QFETCH(QString, expectedLabelNotExplode);
++ QFETCH(QString, expectedMatrixExplode);
++ QFETCH(QString, expectedMatrixNotExplode);
++
++ DoubleResponse res;
++ res.setValue(doubleValue);
++
++ // style=simple, explode=true, type=number, format=double
++ res.setStringValue(expectedSimpleExplode);
++ CALL_TEST_NUMERIC_OPERATION(simpleExplodeDouble, doubleValue,res);
++
++ // style=simple, explode=false, type=number, format=double
++ res.setStringValue(expectedSimpleNotExplode);
++ CALL_TEST_NUMERIC_OPERATION(simpleNotExplodeDouble, doubleValue,res);
++
++ // style=label, explode=true, type=number, format=double
++ res.setStringValue(expectedLabelExplode);
++ CALL_TEST_NUMERIC_OPERATION(labelExplodeDouble, doubleValue,res);
++
++ // style=label, explode=false, type=number, format=double
++ res.setStringValue(expectedLabelNotExplode);
++ CALL_TEST_NUMERIC_OPERATION(labelNotExplodeDouble, doubleValue,res);
++
++ // style=matrix, explode=true, type=number, format=double
++ res.setStringValue(expectedMatrixExplode);
++ CALL_TEST_NUMERIC_OPERATION(matrixExplodeDouble, doubleValue,res);
++
++ // style=matrix, explode=false, type=number, format=double
++ res.setStringValue(expectedMatrixNotExplode);
++ CALL_TEST_NUMERIC_OPERATION(matrixNotExplodeDouble, doubleValue,res);
++ }
++
++ void OperationParameters::pathArrayParameters_data()
++ {
++ QTest::addColumn<QList<int>>("arrayIntValues");
++ QTest::addColumn<QString>("expectedSimpleExplode");
++ QTest::addColumn<QString>("expectedSimpleNotExplode");
++ QTest::addColumn<QString>("expectedLabelExplode");
++ QTest::addColumn<QString>("expectedLabelNotExplode");
++ QTest::addColumn<QString>("expectedMatrixExplode");
++ QTest::addColumn<QString>("expectedMatrixNotExplode");
++ QTest::newRow("QList<int>({-90, 0, 0, 2, 87867})") << QList<int>({-90, 0, 0, 2, 87867})
++ << "/v2/path/array/simple-explode/-90,0,0,2,87867"
++ << "/v2/path/array/simple-not-explode/-90,0,0,2,87867"
++ << "/v2/path/array/label-explode/.-90.0.0.2.87867"
++ << "/v2/path/array/label-not-explode/.-90,0,0,2,87867"
++ << "/v2/path/array/matrix-explode/;arrayParameter=-90;arrayParameter=0;arrayParameter=0;arrayParameter=2;arrayParameter=87867"
++ << "/v2/path/array/matrix-not-explode/;arrayParameter=-90,0,0,2,87867";
++ QTest::newRow("QList<int>({1, 2, -9, 90})") << QList<int>({1, 2, -9, 90})
++ << "/v2/path/array/simple-explode/1,2,-9,90"
++ << "/v2/path/array/simple-not-explode/1,2,-9,90"
++ << "/v2/path/array/label-explode/.1.2.-9.90"
++ << "/v2/path/array/label-not-explode/.1,2,-9,90"
++ << "/v2/path/array/matrix-explode/;arrayParameter=1;arrayParameter=2;arrayParameter=-9;arrayParameter=90"
++ << "/v2/path/array/matrix-not-explode/;arrayParameter=1,2,-9,90";
++
++ }
++
++ void OperationParameters::pathArrayParameters()
++ {
++ QFETCH(QList<int>, arrayIntValues);
++ QFETCH(QString, expectedSimpleExplode);
++ QFETCH(QString, expectedSimpleNotExplode);
++ QFETCH(QString, expectedLabelExplode);
++ QFETCH(QString, expectedLabelNotExplode);
++ QFETCH(QString, expectedMatrixExplode);
++ QFETCH(QString, expectedMatrixNotExplode);
++
++ // style=simple, explode=true, type=array
++ CALL_TEST_OPERATION(simpleExplodeArray, arrayIntValues, expectedSimpleExplode);
++
++ // style=simple, explode=false, type=array
++ CALL_TEST_OPERATION(simpleNotExplodeArray, arrayIntValues, expectedSimpleNotExplode);
++
++ // style=label, explode=true, type=array
++ CALL_TEST_OPERATION(labelExplodeArray, arrayIntValues, expectedLabelExplode);
++
++ // style=label, explode=false, type=array
++ CALL_TEST_OPERATION(labelNotExplodeArray, arrayIntValues, expectedLabelNotExplode);
++
++ // style=matrix, explode=true, type=array
++ CALL_TEST_OPERATION(matrixExplodeArray, arrayIntValues, expectedMatrixExplode);
++
++ // style=matrix, explode=false, type=array
++ CALL_TEST_OPERATION(matrixNotExplodeArray, arrayIntValues, expectedMatrixNotExplode);
++ }
++
++ void OperationParameters::pathAnyTypeParameters_data()
++ {
++ // Use "ΣΨ" to test non-ascii characters.
++ // They're encoded as 0xCE 0xA3 0xCE 0xA8 in UTF-8, and should be %-encoded
++ // similarly.
++
++ TestObject obj;
++ obj.setName("Super*+,;=!$&'()Puper");
++ obj.setStatus("Awake or Not $ΣΨ");
++
++ QTest::addColumn<QJsonValue>("jsonValue");
++ QTest::addColumn<QString>("expectedSimpleExplode");
++ QTest::addColumn<QString>("expectedSimpleNotExplode");
++ QTest::addColumn<QString>("expectedLabelExplode");
++ QTest::addColumn<QString>("expectedLabelNotExplode");
++ QTest::addColumn<QString>("expectedMatrixExplode");
++ QTest::addColumn<QString>("expectedMatrixNotExplode");
++ QTest::newRow("QJsonValue(string)") << QJsonValue("ΣΨ*+,;=!$&'()John Doe")
++ << "/v2/path/anytype/simple-explode/%CE%A3%CE%A8%2A%2B%2C%3B%3D%21%24%26%27%28%29John%20Doe"
++ << "/v2/path/anytype/simple-not-explode/%CE%A3%CE%A8%2A%2B%2C%3B%3D%21%24%26%27%28%29John%20Doe"
++ << "/v2/path/anytype/label-explode/.%CE%A3%CE%A8%2A%2B%2C%3B%3D%21%24%26%27%28%29John%20Doe"
++ << "/v2/path/anytype/label-not-explode/.%CE%A3%CE%A8%2A%2B%2C%3B%3D%21%24%26%27%28%29John%20Doe"
++ << "/v2/path/anytype/matrix-explode/;anytypeParameter=%CE%A3%CE%A8%2A%2B%2C%3B%3D%21%24%26%27%28%29John%20Doe"
++ << "/v2/path/anytype/matrix-not-explode/;anytypeParameter=%CE%A3%CE%A8%2A%2B%2C%3B%3D%21%24%26%27%28%29John%20Doe";
++ QTest::newRow("QJsonValue(int)") << QJsonValue(100)
++ << "/v2/path/anytype/simple-explode/100"
++ << "/v2/path/anytype/simple-not-explode/100"
++ << "/v2/path/anytype/label-explode/.100"
++ << "/v2/path/anytype/label-not-explode/.100"
++ << "/v2/path/anytype/matrix-explode/;anytypeParameter=100"
++ << "/v2/path/anytype/matrix-not-explode/;anytypeParameter=100";
++ QTest::newRow("QJsonValue(array)") << QJsonValue({ 1, 2.2, QString("ΣΨStrange*+,;=!$&'()")})
++ << "/v2/path/anytype/simple-explode/1,2.2,%CE%A3%CE%A8Strange%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/anytype/simple-not-explode/1,2.2,%CE%A3%CE%A8Strange%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/anytype/label-explode/.1.2.2.%CE%A3%CE%A8Strange%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/anytype/label-not-explode/.1,2.2,%CE%A3%CE%A8Strange%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/anytype/matrix-explode/;anytypeParameter=1;anytypeParameter=2.2;anytypeParameter=%CE%A3%CE%A8Strange%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/anytype/matrix-not-explode/;anytypeParameter=1,2.2,%CE%A3%CE%A8Strange%2A%2B%2C%3B%3D%21%24%26%27%28%29";
++ QTest::newRow("QJsonValue(object)") << obj.asJsonValue()
++ << "/v2/path/anytype/simple-explode/name=Super%2A%2B%2C%3B%3D%21%24%26%27%28%29Puper,status=Awake%20or%20Not%20%24%CE%A3%CE%A8"
++ << "/v2/path/anytype/simple-not-explode/name,Super%2A%2B%2C%3B%3D%21%24%26%27%28%29Puper,status,Awake%20or%20Not%20%24%CE%A3%CE%A8"
++ << "/v2/path/anytype/label-explode/.name=Super%2A%2B%2C%3B%3D%21%24%26%27%28%29Puper.status=Awake%20or%20Not%20%24%CE%A3%CE%A8"
++ << "/v2/path/anytype/label-not-explode/.name,Super%2A%2B%2C%3B%3D%21%24%26%27%28%29Puper,status,Awake%20or%20Not%20%24%CE%A3%CE%A8"
++ << "/v2/path/anytype/matrix-explode/;name=Super%2A%2B%2C%3B%3D%21%24%26%27%28%29Puper;status=Awake%20or%20Not%20%24%CE%A3%CE%A8"
++ << "/v2/path/anytype/matrix-not-explode/;anytypeParameter=name,Super%2A%2B%2C%3B%3D%21%24%26%27%28%29Puper,status,Awake%20or%20Not%20%24%CE%A3%CE%A8";
++ }
++
++ void OperationParameters::pathAnyTypeParameters()
++ {
++ QFETCH(QJsonValue, jsonValue);
++ QFETCH(QString, expectedSimpleExplode);
++ QFETCH(QString, expectedSimpleNotExplode);
++ QFETCH(QString, expectedLabelExplode);
++ QFETCH(QString, expectedLabelNotExplode);
++ QFETCH(QString, expectedMatrixExplode);
++ QFETCH(QString, expectedMatrixNotExplode);
++
++ // style=simple, explode=true, type=AnyType
++ CALL_TEST_OPERATION(simpleExplodeAnytype, jsonValue, expectedSimpleExplode);
++
++ // style=simple, explode=false, type=AnyType
++ CALL_TEST_OPERATION(simpleNotExplodeAnytype, jsonValue, expectedSimpleNotExplode);
++
++ // style=label, explode=true, type=AnyType
++ CALL_TEST_OPERATION(labelExplodeAnytype, jsonValue, expectedLabelExplode);
++
++ // style=label, explode=false, type=AnyType
++ CALL_TEST_OPERATION(labelNotExplodeAnytype, jsonValue, expectedLabelNotExplode);
++
++ // style=matrix, explode=true, type=AnyType
++ CALL_TEST_OPERATION(matrixExplodeAnytype, jsonValue, expectedMatrixExplode);
++
++ // style=matrix, explode=false, type=AnyType
++ CALL_TEST_OPERATION(matrixNotExplodeAnytype, jsonValue, expectedMatrixNotExplode);
++ }
++
++ void OperationParameters::pathObjectParameters_data()
++ {
++ TestObject object;
++ object.setName("Igor");
++ object.setStatus("Sleep");
++ QTest::addColumn<TestObject>("objectValue");
++ QTest::addColumn<QString>("expectedSimpleExplode");
++ QTest::addColumn<QString>("expectedSimpleNotExplode");
++ QTest::addColumn<QString>("expectedLabelExplode");
++ QTest::addColumn<QString>("expectedLabelNotExplode");
++ QTest::addColumn<QString>("expectedMatrixExplode");
++ QTest::addColumn<QString>("expectedMatrixNotExplode");
++ QTest::newRow("TestObject({Igor, Sleep})") << object
++ << "/v2/path/object/simple-explode/name=Igor,status=Sleep"
++ << "/v2/path/object/simple-not-explode/name,Igor,status,Sleep"
++ << "/v2/path/object/label-explode/.name=Igor.status=Sleep"
++ << "/v2/path/object/label-not-explode/.name,Igor,status,Sleep"
++ << "/v2/path/object/matrix-explode/;name=Igor;status=Sleep"
++ << "/v2/path/object/matrix-not-explode/;objectParameter=name,Igor,status,Sleep";
++ object.setName("TestName123");
++ object.setStatus("Maybe-Awake");
++ QTest::newRow("TestObject({TestName123, Maybe-Awake})") << object
++ << "/v2/path/object/simple-explode/name=TestName123,status=Maybe-Awake"
++ << "/v2/path/object/simple-not-explode/name,TestName123,status,Maybe-Awake"
++ << "/v2/path/object/label-explode/.name=TestName123.status=Maybe-Awake"
++ << "/v2/path/object/label-not-explode/.name,TestName123,status,Maybe-Awake"
++ << "/v2/path/object/matrix-explode/;name=TestName123;status=Maybe-Awake"
++ << "/v2/path/object/matrix-not-explode/;objectParameter=name,TestName123,status,Maybe-Awake";
++ object.setName("SoMeΣΨ");
++ object.setStatus(" *+,;=!$&'()");
++ QTest::newRow("QOAITestObject({SoMeΣΨ, ' *+,;=!$&'()'})") << object
++ << "/v2/path/object/simple-explode/name=SoMe%CE%A3%CE%A8,status=%20%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/object/simple-not-explode/name,SoMe%CE%A3%CE%A8,status,%20%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/object/label-explode/.name=SoMe%CE%A3%CE%A8.status=%20%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/object/label-not-explode/.name,SoMe%CE%A3%CE%A8,status,%20%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/object/matrix-explode/;name=SoMe%CE%A3%CE%A8;status=%20%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/path/object/matrix-not-explode/;objectParameter=name,SoMe%CE%A3%CE%A8,status,%20%2A%2B%2C%3B%3D%21%24%26%27%28%29";
++
++ }
++
++ void OperationParameters::pathObjectParameters()
++ {
++ QFETCH(TestObject, objectValue);
++ QFETCH(QString, expectedSimpleExplode);
++ QFETCH(QString, expectedSimpleNotExplode);
++ QFETCH(QString, expectedLabelExplode);
++ QFETCH(QString, expectedLabelNotExplode);
++ QFETCH(QString, expectedMatrixExplode);
++ QFETCH(QString, expectedMatrixNotExplode);
++
++ // style=simple, explode=true, type=object
++ CALL_TEST_OPERATION(simpleExplodeObject, objectValue, expectedSimpleExplode);
++
++ // style=simple, explode=false, type=object
++ CALL_TEST_OPERATION(simpleNotExplodeObject, objectValue, expectedSimpleNotExplode);
++
++ // style=label, explode=true, type=object
++ CALL_TEST_OPERATION(labelExplodeObject, objectValue, expectedLabelExplode);
++
++ // style=label, explode=false, type=object
++ CALL_TEST_OPERATION(labelNotExplodeObject, objectValue, expectedLabelNotExplode);
++
++ // style=matrix, explode=true, type=object
++ CALL_TEST_OPERATION(matrixExplodeObject, objectValue, expectedMatrixExplode);
++
++ // style=matrix, explode=false, type=object
++ CALL_TEST_OPERATION(matrixNotExplodeObject, objectValue, expectedMatrixNotExplode);
++ }
++
++ void OperationParameters::pathStringMapParameters_data()
++ {
++ QMap<QString, QString> stringMap;
++ const QString val1 = "str1 *+,;=!$&'()"_L1;
++ stringMap["key1"_L1] = val1;
++ const QString key2 = "key2 *+,;=!$&'()"_L1;
++ stringMap[key2] = "str2"_L1;
++
++ QTest::addColumn<QMap<QString, QString>>("mapValue");
++ QTest::addColumn<QString>("expectedSimpleExplode");
++ QTest::addColumn<QString>("expectedSimpleNotExplode");
++ QTest::addColumn<QString>("expectedLabelExplode");
++ QTest::addColumn<QString>("expectedLabelNotExplode");
++ QTest::addColumn<QString>("expectedMatrixExplode");
++ QTest::addColumn<QString>("expectedMatrixNotExplode");
++
++ QString urlEncodedVal1 = QUrl::toPercentEncoding(val1);
++ QString urlEncodedKey2 = QUrl::toPercentEncoding(key2);
++
++ QTest::newRow("QMap<QString, QString>")
++ << stringMap
++ << "/v2/path/map/string-mapping/simple-explode/key1="_L1 + urlEncodedVal1 + ","_L1 +
++ urlEncodedKey2 + "=str2"_L1
++ << "/v2/path/map/string-mapping/simple-not-explode/key1,"_L1 + urlEncodedVal1 + ","_L1 +
++ urlEncodedKey2 + ",str2"_L1
++ << "/v2/path/map/string-mapping/label-explode/.key1="_L1 + urlEncodedVal1 + "."_L1 +
++ urlEncodedKey2 + "=str2"_L1
++ << "/v2/path/map/string-mapping/label-not-explode/.key1,"_L1 + urlEncodedVal1 + ","_L1 +
++ urlEncodedKey2 + ",str2"_L1
++ << "/v2/path/map/string-mapping/matrix-explode/;key1="_L1 + urlEncodedVal1 + ";"_L1 +
++ urlEncodedKey2 + "=str2"_L1
++ << "/v2/path/map/string-mapping/matrix-not-explode/;mapParameter=key1,"_L1 + urlEncodedVal1
++ + ","_L1 + urlEncodedKey2 + ",str2"_L1;
++
++ stringMap.remove(key2);
++
++ stringMap["key1"_L1] = "str1"_L1;
++ stringMap["* +key2"_L1] = "str2"_L1;
++ urlEncodedKey2 = QUrl::toPercentEncoding("* +key2"_L1);
++
++ // With QMap, the items are always sorted by key. Therefore, we can notice an order change.
++ QTest::newRow("QMap<QString, QString> order change")
++ << stringMap
++ << "/v2/path/map/string-mapping/simple-explode/"_L1 + urlEncodedKey2 + "=str2,key1=str1"_L1
++ << "/v2/path/map/string-mapping/simple-not-explode/"_L1 + urlEncodedKey2 +
++ ",str2,key1,str1"_L1
++ << "/v2/path/map/string-mapping/label-explode/."_L1 + urlEncodedKey2 + "=str2.key1=str1"_L1
++ << "/v2/path/map/string-mapping/label-not-explode/."_L1 + urlEncodedKey2
++ + ",str2,key1,str1"_L1
++ << "/v2/path/map/string-mapping/matrix-explode/;"_L1 + urlEncodedKey2 + "=str2;key1=str1"_L1
++ << "/v2/path/map/string-mapping/matrix-not-explode/;mapParameter="_L1 + urlEncodedKey2 +
++ ",str2,key1,str1"_L1;
++
++ const QString nonAsciiKey = QStringLiteral("πσ"); // 0xcf 0x80 0xcf 0x83
++ const QString nonAsciiValue = QStringLiteral("ΣΨ"); // 0xce 0xa3 0xce 0xa8
++
++ QMap<QString, QString> nonAsciiMap;
++ nonAsciiMap[nonAsciiKey] = nonAsciiValue;
++ nonAsciiMap[QStringLiteral("key")] = QStringLiteral("value");
++ QTest::newRow("non-ascii-map")
++ << nonAsciiMap
++ << u"/v2/path/map/string-mapping/simple-explode/key=value,%CF%80%CF%83=%CE%A3%CE%A8"_s
++ << u"/v2/path/map/string-mapping/simple-not-explode/key,value,%CF%80%CF%83,%CE%A3%CE%A8"_s
++ << u"/v2/path/map/string-mapping/label-explode/.key=value.%CF%80%CF%83=%CE%A3%CE%A8"_s
++ << u"/v2/path/map/string-mapping/label-not-explode/.key,value,%CF%80%CF%83,%CE%A3%CE%A8"_s
++ << u"/v2/path/map/string-mapping/matrix-explode/;key=value;%CF%80%CF%83=%CE%A3%CE%A8"_s
++ << u"/v2/path/map/string-mapping/matrix-not-explode/;mapParameter=key,value,%CF%80%CF%83,%CE%A3%CE%A8"_s;
++ }
++
++ void OperationParameters::pathStringMapParameters()
++ {
++ using StringMap = QMap<QString, QString>;
++ QFETCH(StringMap, mapValue);
++ QFETCH(QString, expectedSimpleExplode);
++ QFETCH(QString, expectedSimpleNotExplode);
++ QFETCH(QString, expectedLabelExplode);
++ QFETCH(QString, expectedLabelNotExplode);
++ QFETCH(QString, expectedMatrixExplode);
++ QFETCH(QString, expectedMatrixNotExplode);
++
++ // style=simple, explode=true, type=object
++ CALL_TEST_OPERATION(simpleExplodeStringMap, mapValue, expectedSimpleExplode);
++
++ // style=simple, explode=false, type=object
++ CALL_TEST_OPERATION(simpleNotExplodeStringMap, mapValue, expectedSimpleNotExplode);
++
++ // style=label, explode=true, type=object
++ CALL_TEST_OPERATION(labelExplodeStringMap, mapValue, expectedLabelExplode);
++
++ // style=label, explode=false, type=object
++ CALL_TEST_OPERATION(labelNotExplodeStringMap, mapValue, expectedLabelNotExplode);
++
++ // style=matrix, explode=true, type=object
++ CALL_TEST_OPERATION(matrixExplodeStringMap, mapValue, expectedMatrixExplode);
++
++ // style=matrix, explode=false, type=object
++ CALL_TEST_OPERATION(matrixNotExplodeStringMap, mapValue, expectedMatrixNotExplode);
++ }
++
++ // In OpenAPI terminology, a string to model mapping refers to a map with string keys and object values.
++ void OperationParameters::pathModelMapParameters()
++ {
++ QMap<QString, TestObject> mapValue;
++ TestObject obj1;
++ obj1.setName("Azer"_L1);
++ obj1.setStatus("Ready"_L1);
++ mapValue["key1"_L1] = obj1;
++ TestObject obj2;
++ obj2.setName("Celine"_L1);
++ obj2.setStatus("Present"_L1);
++ mapValue["key2"_L1] = obj2;
++
++ const char* warningMsg = "Serialization of complex array or object properties in path or query "
++ "parameters is undefined. The generated result will not conform to "
++ "the OpenAPI standard.";
++
++ // style=simple, explode=true, type=map with object values
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(simpleExplodeModelMap, mapValue);
++
++ // style=simple, explode=false, type=map with object values
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(simpleNotExplodeModelMap, mapValue);
++
++ // style=label, explode=true, type=map with object values
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(labelExplodeModelMap, mapValue);
++
++ // style=label, explode=false, type=map with object values
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(labelNotExplodeModelMap, mapValue);
++
++ // style=matrix, explode=true, type=map with object values
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(matrixExplodeModelMap, mapValue);
++
++ // style=matrix, explode=false, type=map with object values
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(matrixNotExplodeModelMap, mapValue);
++ }
++
++ void OperationParameters::multiplePathParameters()
++ {
++ QString str1("First string param");
++ QString str2("Second string param");
++ QList<int> array {-90, 0, 0, 2, 87867};
++
++ QString expectedResult;
++ bool done = false;
++
++ connect(this, &OperationParameters::simpleExplodeStringsFinished,
++ this, [&](const QString &summary) {
++ done = true;
++ QCOMPARE(getStatusString(summary), expectedResult);
++ });
++ connect(this, &OperationParameters::simpleExplodeStringsErrorOccurred,
++ this, [&](QNetworkReply::NetworkError errType, const QString &errStr) {
++ done = false;
++ qDebug() << errType << errStr;
++ });
++
++ expectedResult = "/v2/path/strings/simple-explode/First%20string%20param/"
++ "Second%20string%20param";
++ simpleExplodeStrings(str1,str2);
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++
++ connect(this, &OperationParameters::labelStringMatrixArrayNotExplodeFinished,
++ this, [&](const QString &summary) {
++ done = true;
++ QCOMPARE(getStatusString(summary), expectedResult);
++ });
++ connect(this, &OperationParameters::labelStringMatrixArrayNotExplodeErrorOccurred,
++ this, [&](QNetworkReply::NetworkError errType, const QString &errStr) {
++ done = false;
++ qDebug() << errType << errStr;
++ });
++
++ expectedResult = "/v2/path/string/label-not-explode/array/matrix-not-explode/"
++ ".First%20string%20param/;arrayParameter=-90,0,0,2,87867";
++ labelStringMatrixArrayNotExplode(str1, array);
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ // The latest implementation is done based on this information:
++ // NOTE! We add data-driven test-cases for queries later with adding new operations in the OP.yaml.
++ void OperationParameters::queryParameters()
++ {
++ // style=form, explode=true, type=array
++ CALL_TEST_POST_OPERATION(formExplodeArray, QList<int>({-90, 0, 0, 2, 87867}),
++ "/v2/query/array/form-explode/formExplodeArray?arrayParameter=-90&arrayParameter=0&arrayParameter=0&arrayParameter=2&arrayParameter=87867");
++
++ // style=form, explode=false, type=array
++ CALL_TEST_POST_OPERATION(formNotExplodeArray, QList<int>({1, 2, -9, 90}),
++ "/v2/query/array/form-not-explode/formNotExplodeArray?arrayParameter=1,2,-9,90");
++
++ // Only style=FORM supports primitive types (string, int, double, float)
++ // style=form, explode=true, type=string
++ CALL_TEST_POST_OPERATION(formExplodeString, OptionalParameter<QString>("hello, guys!"),
++ "/v2/query/string/form-explode/formExplodeString?stringParameter=hello%2C%20guys%21");
++
++ // style=form, explode=false, type=string
++ CALL_TEST_POST_OPERATION(formNotExplodeString, OptionalParameter<QString>("hello, guys!"),
++ "/v2/query/string/form-not-explode/formNotExplodeString?stringParameter=hello%2C%20guys%21");
++
++ // style=form, explode=true, type=integer, format=int64
++ CALL_TEST_POST_OPERATION(formExplodeInt, OptionalParameter<qint64>(22),
++ "/v2/query/int/form-explode/formExplodeInt?intParameter=22");
++
++ // style=form, explode=false, type=integer, format=int64
++ CALL_TEST_POST_OPERATION(formNotExplodeInt, OptionalParameter<qint64>(22),
++ "/v2/query/int/form-not-explode/formNotExplodeInt?intParameter=22");
++
++ // style=form, explode=true, type=number, format=float
++ float f_val = 22.0123f;
++ FloatResponse f_res;
++ f_res.setValue(f_val);
++ QString strVal = QString::number(f_val, 'g', QLocale::FloatingPointShortest);
++ QString expectedPath = "/v2/query/float/form-explode/formExplodeFloat?floatParameter="_L1 +
++ strVal;
++ f_res.setStringValue(expectedPath);
++ CALL_TEST_NUMERIC_OPERATION(formExplodeFloat, OptionalParameter<float>(22.0123f),
++ f_res);
++
++ // style=form, explode=false, type=number, format=float
++ expectedPath = "/v2/query/float/form-not-explode/formNotExplodeFloat?floatParameter="_L1 +
++ strVal;
++ f_res.setStringValue(expectedPath);
++ CALL_TEST_NUMERIC_OPERATION(formNotExplodeFloat, OptionalParameter<float>(22.0123f),
++ f_res);
++
++ double d_val = 2.987653212346578627e9;
++ strVal = QString::number(d_val, 'g', QLocale::FloatingPointShortest);
++ DoubleResponse d_res;
++ d_res.setValue(d_val);
++
++ // style=form, explode=true, type=number, format=double
++ expectedPath = "/v2/query/double/form-explode/formExplodeDouble?doubleParameter="_L1 + strVal;
++ d_res.setStringValue(expectedPath);
++ CALL_TEST_NUMERIC_OPERATION(formExplodeDouble, OptionalParameter<double>(d_val), d_res);
++
++ // style=form, explode=false, type=number, format=double
++ expectedPath = "/v2/query/double/form-not-explode/formNotExplodeDouble?doubleParameter="_L1 +
++ strVal;
++ d_res.setStringValue(expectedPath);
++ CALL_TEST_NUMERIC_OPERATION(formNotExplodeDouble, OptionalParameter<double>(d_val),
++ d_res);
++
++ // style=form, explode=true, type=object
++ TestObject formObj;
++ formObj.setName("TestName+123");
++ formObj.setStatus("Awake");
++ CALL_TEST_POST_OPERATION(formExplodeObject, formObj,
++ "/v2/query/object/form-explode/formExplodeObject?name=TestName%2B123&status=Awake");
++
++ // style=form, explode=false, type=object
++ CALL_TEST_POST_OPERATION(formNotExplodeObject, formObj,
++ "/v2/query/object/form-not-explode/formNotExplodeObject?objectParameter=name,TestName%2B123,status,Awake");
++
++ // style=form, explode=true, type=map with string values
++ QMap<QString, QString> formStringMap;
++ const QString val1 = "str1 *+,;=!$&'()"_L1;
++ const QString key2 = "key2 *+,;=!$&'()"_L1;
++ formStringMap["key1"_L1] = val1;
++ formStringMap[key2] = "str2"_L1;
++ QString urlEncodedVal1 = QUrl::toPercentEncoding(val1);
++ QString urlEncodedKey2 = QUrl::toPercentEncoding(key2);
++ CALL_TEST_POST_OPERATION(formExplodeStringMap, formStringMap,
++ "/v2/query/map/string-mapping/form-explode/formExplodeMap?"
++ "key1="_L1 + urlEncodedVal1 + "&"_L1 + urlEncodedKey2 + "=str2");
++
++ // style=form, explode=false, type=map with string values
++ CALL_TEST_POST_OPERATION(formNotExplodeStringMap, formStringMap,
++ "/v2/query/map/string-mapping/form-not-explode/formNotExplodeMap?"
++ "mapParameter=key1,"_L1 + urlEncodedVal1 + ","_L1 + urlEncodedKey2 + ",str2");
++
++ QMap<QString, TestObject> mapValue;
++ TestObject obj1;
++ obj1.setName("Azer"_L1);
++ obj1.setStatus("Ready"_L1);
++ mapValue["key1"_L1] = obj1;
++ TestObject obj2;
++ obj2.setName("Celine"_L1);
++ obj2.setStatus("Present"_L1);
++ mapValue["key2"_L1] = obj2;
++
++ const char* warningMsg = "Serialization of complex array or object properties in path or query "
++ "parameters is undefined. The generated result will not conform to "
++ "the OpenAPI standard.";
++
++ // style=form, explode=true, type=map with object values
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(formExplodeModelMap, mapValue);
++
++ // style=form, explode=false, type=map with object values
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(formNotExplodeModelMap, mapValue);
++
++ // style=spaceDelimited, explode=false, type=array
++ CALL_TEST_POST_OPERATION(spaceDelimitedNotExplodeArray, QList<int>({1, 2, -9, 90}),
++ "/v2/query/array/spaceDelimited-not-explode/spaceDelimitedNotExplodeArray?arrayParameter=1%202%20-9%2090");
++
++ // style=spaceDelimited, explode=false, type=object
++ TestObject spaceDelimitedObj;
++ spaceDelimitedObj.setName("TestName 123 *+,;=!$&'()");
++ spaceDelimitedObj.setStatus("Awake!");
++ CALL_TEST_POST_OPERATION(spaceDelimitedNotExplodeObject, spaceDelimitedObj,
++ "/v2/query/object/spaceDelimited-not-explode/spaceDelimitedNotExplodeObject?objectParameter=name%20TestName%20123%20%2A%2B%2C%3B%3D%21%24%26%27%28%29%20status%20Awake%21");
++
++ // style=spaceDelimited, explode=false, type=map with string values
++ QMap<QString, QString> spaceDelimitedStringMap;
++ spaceDelimitedStringMap["key1"_L1] = val1;
++ spaceDelimitedStringMap[key2] = "str2"_L1;
++ CALL_TEST_POST_OPERATION(spaceDelimitedNotExplodeStringMap, spaceDelimitedStringMap,
++ "/v2/query/map/string-mapping/spaceDelimited-not-explode/"
++ "spaceDelimitedNotExplodeMap?mapParameter="
++ "key1%20"_L1 + urlEncodedVal1 + "%20"_L1 + urlEncodedKey2
++ + "%20str2"_L1);
++
++ // style=spaceDelimited, explode=false, with object values
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(spaceDelimitedNotExplodeModelMap, mapValue);
++
++ // Primitives are not defined for spaceDelimited and pipeDelimited, so anytype=array or anytype-object are not possible
++ // style=spaceDelimited, explode=false, type=anytype array
++ CALL_TEST_POST_OPERATION(spaceDelimitedNotExplodeAnytype, QJsonValue({ 1, 2.2, QString("Strange *+,;=!$&'()")}),
++ "/v2/query/anytype/spaceDelimited-not-explode/spaceDelimitedNotExplodeAnytype?anytypeParameter=1%202.2%20Strange%20%2A%2B%2C%3B%3D%21%24%26%27%28%29");
++
++ // style=spaceDelimited, explode=false, type=anytype object
++ CALL_TEST_POST_OPERATION(spaceDelimitedNotExplodeAnytype, spaceDelimitedObj.asJsonValue(),
++ "/v2/query/anytype/spaceDelimited-not-explode/spaceDelimitedNotExplodeAnytype?anytypeParameter=name%20TestName%20123%20%2A%2B%2C%3B%3D%21%24%26%27%28%29%20status%20Awake%21");
++
++ // style=pipeDelimited, explode=false, type=array
++ CALL_TEST_POST_OPERATION(pipeDelimitedNotExplodeArray, QList<int>({1, 2, -9, 90}),
++ "/v2/query/array/pipeDelimited-not-explode/pipeDelimitedNotExplodeArray?arrayParameter=1%7C2%7C-9%7C90");
++
++ // style=pipeDelimited, explode=false, type=object
++ TestObject pipeDelimitedObj;
++ pipeDelimitedObj.setName("pipeDelimited=TestName");
++ pipeDelimitedObj.setStatus("pipeDelimited-Sleeping *+,;=!$&'()");
++ CALL_TEST_POST_OPERATION(pipeDelimitedNotExplodeObject, pipeDelimitedObj,
++ "/v2/query/object/pipeDelimited-not-explode/pipeDelimitedNotExplodeObject?objectParameter=name%7CpipeDelimited%3DTestName%7Cstatus%7CpipeDelimited-Sleeping%20%2A%2B%2C%3B%3D%21%24%26%27%28%29");
++
++ // style=pipeDelimited, explode=false, type=map with string values
++ QMap<QString, QString> pipeDelimitedStringMap;
++ pipeDelimitedStringMap["key1"_L1] = val1;
++ pipeDelimitedStringMap[key2] = "str2"_L1;
++ CALL_TEST_POST_OPERATION(pipeDelimitedNotExplodeStringMap, pipeDelimitedStringMap,
++ "/v2/query/map/string-mapping/pipeDelimited-not-explode/"
++ "pipeDelimitedNotExplodeMap?mapParameter="
++ "key1%7C"_L1 + urlEncodedVal1 + "%7C"_L1 + urlEncodedKey2
++ + "%7Cstr2"_L1);
++
++ // style=pipeDelimited, explode=false, with object values
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(pipeDelimitedNotExplodeModelMap, mapValue);
++
++ // style=pipeDelimited, explode=false, type=anytype array
++ CALL_TEST_POST_OPERATION(pipeDelimitedNotExplodeAnytype, QJsonValue({ 1, 2.2, QString("Strange *+,;=!$&'()")}),
++ "/v2/query/anytype/pipeDelimited-not-explode/pipeDelimitedNotExplodeAnytype?anytypeParameter=1%7C2.2%7CStrange%20%2A%2B%2C%3B%3D%21%24%26%27%28%29");
++
++ // style=pipeDelimited, explode=false, type=anytype object
++ CALL_TEST_POST_OPERATION(pipeDelimitedNotExplodeAnytype, pipeDelimitedObj.asJsonValue(),
++ "/v2/query/anytype/pipeDelimited-not-explode/pipeDelimitedNotExplodeAnytype?anytypeParameter=name%7CpipeDelimited%3DTestName%7Cstatus%7CpipeDelimited-Sleeping%20%2A%2B%2C%3B%3D%21%24%26%27%28%29");
++
++ // style=deepObject, explode=true, type=object
++ TestObject deepObjectObj;
++ deepObjectObj.setName("deepObject *+,;=!$&'()-TestName");
++ deepObjectObj.setStatus("deepObject-Sleeping");
++ CALL_TEST_POST_OPERATION(deepObjectExplodeObject, deepObjectObj,
++ "/v2/query/object/deepObject-explode/deepObjectExplodeObject?objectParameter%5Bname%5D=deepObject%20%2A%2B%2C%3B%3D%21%24%26%27%28%29-TestName&objectParameter%5Bstatus%5D=deepObject-Sleeping");
++
++ // style=deepObject, explode=true, type=anytype object
++ CALL_TEST_POST_OPERATION(deepObjectExplodeAnytype, deepObjectObj.asJsonValue(),
++ "/v2/query/anytype/deepObject-explode/deepObjectExplodeAnytype?anytypeParameter%5Bname%5D=deepObject%20%2A%2B%2C%3B%3D%21%24%26%27%28%29-TestName&anytypeParameter%5Bstatus%5D=deepObject-Sleeping");
++
++ // style=deepObject, explode=true, type=map with string values
++ QMap<QString, QString> deepObjectStringMap;
++ deepObjectStringMap["key1"_L1] = val1;
++ deepObjectStringMap[key2] = "str2"_L1;
++ CALL_TEST_POST_OPERATION(deepObjectExplodeStringMap, deepObjectStringMap,
++ "/v2/query/map/string-mapping/deepObject-explode/deepObjectExplodeMap?"
++ "mapParameter%5Bkey1%5D="_L1 + urlEncodedVal1 + "&"_L1
++ + "mapParameter%5B"_L1 + urlEncodedKey2
++ + "%5D=str2"_L1);
++
++ // style=deepObject, explode=false, with object values
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(deepObjectExplodeModelMap, mapValue);
++ }
++
++ void OperationParameters::queryAnyTypeParameters_data()
++ {
++ TestObject obj;
++ obj.setName("Super Puper *+,;=!$&'()");
++ obj.setStatus("Awake!");
++
++ QTest::addColumn<QJsonValue>("jsonValue");
++ QTest::addColumn<QString>("expectedFormExplode");
++ QTest::addColumn<QString>("expectedFormNotExplode");
++ QTest::newRow("QJsonValue(string)") << QJsonValue("John Doe")
++ << "/v2/query/anytype/form-explode/formExplodeAnytype?anytypeParameter=John%20Doe"
++ << "/v2/query/anytype/form-not-explode/formNotExplodeAnytype?anytypeParameter=John%20Doe";
++ QTest::newRow("QJsonValue(int)") << QJsonValue(100)
++ << "/v2/query/anytype/form-explode/formExplodeAnytype?anytypeParameter=100"
++ << "/v2/query/anytype/form-not-explode/formNotExplodeAnytype?anytypeParameter=100";
++ QTest::newRow("QJsonValue(array)") << QJsonValue({ 1, 2.2, QString("Strange *+,;=!$&'()") })
++ << "/v2/query/anytype/form-explode/formExplodeAnytype?anytypeParameter=1&anytypeParameter=2.2&anytypeParameter=Strange%20%2A%2B%2C%3B%3D%21%24%26%27%28%29"
++ << "/v2/query/anytype/form-not-explode/formNotExplodeAnytype?anytypeParameter=1,2.2,Strange%20%2A%2B%2C%3B%3D%21%24%26%27%28%29";
++ QTest::newRow("QJsonValue(object)") << obj.asJsonValue()
++ << "/v2/query/anytype/form-explode/formExplodeAnytype?name=Super%20Puper%20%2A%2B%2C%3B%3D%21%24%26%27%28%29&status=Awake%21"
++ << "/v2/query/anytype/form-not-explode/formNotExplodeAnytype?anytypeParameter=name,Super%20Puper%20%2A%2B%2C%3B%3D%21%24%26%27%28%29,status,Awake%21";
++ QTest::newRow("QJsonValue(Null)") << QJsonValue(QJsonValue::Null)
++ << "/v2/query/anytype/form-explode/formExplodeAnytype?anytypeParameter="
++ << "/v2/query/anytype/form-not-explode/formNotExplodeAnytype?anytypeParameter=";
++ QTest::newRow("QJsonValue(Undefined)") << QJsonValue(QJsonValue::Undefined)
++ << "/v2/query/anytype/form-explode/formExplodeAnytype?anytypeParameter="
++ << "/v2/query/anytype/form-not-explode/formNotExplodeAnytype?anytypeParameter=";
++ QTest::newRow("QJsonValue()") << QJsonValue()
++ << "/v2/query/anytype/form-explode/formExplodeAnytype?anytypeParameter="
++ << "/v2/query/anytype/form-not-explode/formNotExplodeAnytype?anytypeParameter=";
++ }
++
++ // spaceDelimited, pipeDelimited, deepObject are NOT defined for Primitives.
++ // It means the standard doesn't say anything about those combinations. So we can skip
++ // spaceDelimited, pipeDelimited, deepObject cases for AnyType(primitives).
++ // AnyType(array) is not defined for deepObject.
++ void OperationParameters::queryAnyTypeParameters()
++ {
++ QFETCH(QJsonValue, jsonValue);
++ QFETCH(QString, expectedFormExplode);
++ QFETCH(QString, expectedFormNotExplode);
++
++ // style=form, explode=true, type=anytype
++ CALL_TEST_POST_OPERATION(formExplodeAnytype, jsonValue, expectedFormExplode);
++ // style=form, explode=false, type=anytype
++ CALL_TEST_POST_OPERATION(formNotExplodeAnytype, jsonValue, expectedFormNotExplode);
++ }
++
++ void OperationParameters::pathNACombinations()
++ {
++ // style=form, explode=true, type=string : Invalid style for path parameters.
++ // Falling back to the default style simple instead.
++ #ifdef Q_OS_WIN
++ const char *warningMsg = "'form' style is invalid for path parameters.\r\n"
++ "Allowed styles are: 'matrix', 'label' and 'simple'.\r\n"
++ "Falling back to the default style 'simple'.";
++ #else
++ const char *warningMsg = "'form' style is invalid for path parameters.\nAllowed styles are: "
++ "'matrix', 'label' and 'simple'.\nFalling back to the default style "
++ "'simple'.";
++ #endif
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_TEST_OPERATION(invalidFormExplodeString, QString("Test!*%"),
++ "/v2/path/string/invalid-form-explode/formExplodeString/Test%21%2A%25");
++ }
++
++ /**
++ * Pay attention, the behavior of spaceDelimited, pipeDelimited and deepObject combinations
++ * below are undefined!!!
++ * But we decided to generate a warning and to fallback to:
++ * - explode=false for spaceDelimited and pipeDelimited cases.
++ * - explode=true for deepObject case.
++ * - the default query style 'form' in case the style is unsupported.
++ **/
++ void OperationParameters::queryNACombinations()
++ {
++ const char *warningMsg;
++
++ // style=spaceDelimited, explode=false, type=string
++ #ifdef Q_OS_WIN
++ warningMsg = "'spaceDelimited' style is invalid for primitive parameters.\r\n"
++ "Falling back to the default style: 'form'.";
++ #else
++ warningMsg = "'spaceDelimited' style is invalid for primitive parameters.\n"
++ "Falling back to the default style: 'form'.";
++ #endif
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_TEST_OPERATION(spaceDelimitedNotExplodeString, QString("string * test!"_L1),
++ "/v2/query/string/spaceDelimited-not-explode/spaceDelimitedNotExplodeString?stringParameter=string%20%2A%20test%21");
++
++ // style=spaceDelimited, explode=false, type=anytype string : Invalid combination.
++ QTest::ignoreMessage(QtWarningMsg, "Be aware that 'spaceDelimited' style is invalid for "
++ "primitive parameters. The generated result will not "
++ "conform to the OpenAPI standard.");
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(spaceDelimitedNotExplodeAnytype,
++ QJsonValue("Hey*+,;=!$&'( )"));
++
++ // style=spaceDelimited, explode=true, type=array
++ ignoreInvalidExplodeWarningMsg("arrayParameter", "spaceDelimited", true);
++ CALL_TEST_POST_OPERATION(spaceDelimitedExplodeArray, QList<int>({-90, 0, 0, 2, 87867}),
++ "/v2/query/array/spaceDelimited-explode/spaceDelimitedExplodeArray?arrayParameter=-90%200%200%202%2087867");
++
++ // style=spaceDelimited, explode=true, type=empty array
++ ignoreInvalidExplodeWarningMsg("arrayParameter", "spaceDelimited", true);
++ CALL_TEST_POST_OPERATION(spaceDelimitedExplodeArray, QList<int>(),
++ "/v2/query/array/spaceDelimited-explode/spaceDelimitedExplodeArray?arrayParameter=");
++
++ // style=pipeDelimited, explode=false, type=anytype string : Invalid combination.
++ QTest::ignoreMessage(QtWarningMsg, "Be aware that 'pipeDelimited' style is invalid for "
++ "primitive parameters. The generated result will not "
++ "conform to the OpenAPI standard.");
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(pipeDelimitedNotExplodeAnytype,
++ QJsonValue("*+,;=!$&'( Invalid )"));
++
++ // style=pipeDelimited, explode=true, type=array
++ ignoreInvalidExplodeWarningMsg("arrayParameter", "pipeDelimited", true);
++ CALL_TEST_POST_OPERATION(pipeDelimitedExplodeArray, QList<int>({-90, 0, 0, 2, 87867}),
++ "/v2/query/array/pipeDelimited-explode/pipeDelimitedExplodeArray?arrayParameter=-90%7C0%7C0%7C2%7C87867");
++
++ // style=pipeDelimited, explode=true, type=empty array
++ ignoreInvalidExplodeWarningMsg("arrayParameter", "pipeDelimited", true);
++ CALL_TEST_POST_OPERATION(pipeDelimitedExplodeArray, QList<int>(),
++ "/v2/query/array/pipeDelimited-explode/pipeDelimitedExplodeArray?arrayParameter=");
++
++ // style=spaceDelimited, explode=true, type=object
++ TestObject spaceDelimitedObj;
++ spaceDelimitedObj.setName("TestName123");
++ spaceDelimitedObj.setStatus("Awake");
++ ignoreInvalidExplodeWarningMsg("objectParameter", "spaceDelimited", true);
++ CALL_TEST_POST_OPERATION(spaceDelimitedExplodeObject, spaceDelimitedObj,
++ "/v2/query/object/spaceDelimited-explode/spaceDelimitedExplodeObject?objectParameter=name%20TestName123%20status%20Awake");
++
++ // style=pipeDelimited, explode=true, type=object
++ TestObject pipeDelimitedObj;
++ pipeDelimitedObj.setName("pipeDelimited-TestName");
++ pipeDelimitedObj.setStatus("pipeDelimited-Sleeping");
++ ignoreInvalidExplodeWarningMsg("objectParameter", "pipeDelimited", true);
++ CALL_TEST_POST_OPERATION(pipeDelimitedExplodeObject, pipeDelimitedObj,
++ "/v2/query/object/pipeDelimited-explode/pipeDelimitedExplodeObject?objectParameter=name%7CpipeDelimited-TestName%7Cstatus%7CpipeDelimited-Sleeping");
++
++ // style=deepObject, explode=false, type=object
++ TestObject deepObjectObj;
++ deepObjectObj.setName("deepObject-TestName");
++ deepObjectObj.setStatus("deepObject-Sleeping");
++ ignoreInvalidExplodeWarningMsg("objectParameter", "deepObject", false);
++ CALL_TEST_POST_OPERATION(deepObjectNotExplodeObject, deepObjectObj,
++ "/v2/query/object/deepObject-not-explode/deepObjectNotExplodeObject?objectParameter%5Bname%5D=deepObject-TestName&objectParameter%5Bstatus%5D=deepObject-Sleeping");
++
++ // style=deepObject, explode=false, type=string : Invalid combination.
++ // Falling back to the default style form instead.
++ warningMsg = "'deepObject' style is only valid for parameters of type 'object'.\n"
++ "Falling back to the default style: 'form'.";
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_TEST_POST_OPERATION(invalidDeepObjectNotExplodeString, QString("Test!"),
++ "/v2/query/string/invalid-deepObject-not-explode/deepObjectNotExplodeString?stringParameter=Test%21");
++
++ // style=deepObject, explode=false, type=array : Invalid combination.
++ // Falling back to the default style form instead.
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_TEST_POST_OPERATION(invalidDeepObjectNotExplodeArray, QList<int>({-90, 0, 0, 2, 87867}),
++ "/v2/query/array/invalid-deepObject-not-explode/deepObjectNotExplodeArray?arrayParameter=-90,0,0,2,87867");
++
++ // style=deepObject, explode=false, type=anytype string : Invalid combination.
++ warningMsg = "Be aware that 'deepObject' style is only valid for parameters of type 'object'. "
++ "The generated result will not conform to the OpenAPI standard.";
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(deepObjectExplodeAnytype, QJsonValue("*Invalid )"));
++
++ // style=deepObject, explode=false, type=anytype array : Invalid combination.
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_POST_NO_EXPECTED_RESULT_TEST_OPERATION(deepObjectExplodeAnytype,
++ QJsonValue({-90, 0, 0, 2, 87867}));
++
++ // style=matrix, explode=true, type=string : Invalid style for query parameters.
++ // Falling back to the default style form instead.
++ #ifdef Q_OS_WIN
++ warningMsg = "'matrix' style is invalid for query parameters.\r\nAllowed styles are: 'form', "
++ "'spaceDelimited', 'pipeDelimited' and 'deepObject'.\r\nFalling back to the "
++ "default style 'form'.";
++ #else
++ warningMsg = "'matrix' style is invalid for query parameters.\nAllowed styles are: 'form', "
++ "'spaceDelimited', 'pipeDelimited' and 'deepObject'.\nFalling back to the default "
++ "style 'form'.";
++ #endif
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ CALL_TEST_POST_OPERATION(invalidMatrixExplodeString, QString("Test!"),
++ "/v2/query/string/invalid-matrix-explode/matrixExplodeString?stringParameter=Test%21");
++ }
++
++ void OperationParameters::pathAndQueryUndefined()
++ {
++ QString emptyLine;
++ QJsonValue nullJson(QJsonValue::Null);
++ QJsonValue undefinedJson(QJsonValue::Undefined);
++ QJsonValue emptyJson;
++
++ // style=form, explode=true, type=string
++ CALL_TEST_POST_OPERATION(formExplodeString, emptyLine, "/v2/query/string/form-explode/formExplodeString?stringParameter=");
++
++ // style=form, explode=false, type=string
++ CALL_TEST_POST_OPERATION(formNotExplodeString, emptyLine, "/v2/query/string/form-not-explode/formNotExplodeString?stringParameter=");
++
++ // style=matrix, explode=true, type=string
++ CALL_TEST_OPERATION(matrixExplodeString, emptyLine, "/v2/path/string/matrix-explode/;stringParameter");
++
++ // style=matrix, explode=false, type=string
++ CALL_TEST_OPERATION(matrixNotExplodeString, emptyLine, "/v2/path/string/matrix-not-explode/;stringParameter");
++
++ // style=matrix, explode=true, type=AnyType(NULL)
++ CALL_TEST_OPERATION(matrixExplodeAnytype, nullJson, "/v2/path/anytype/matrix-explode/;anytypeParameter");
++
++ // style=matrix, explode=false, type=AnyType(NULL)
++ CALL_TEST_OPERATION(matrixNotExplodeAnytype, nullJson, "/v2/path/anytype/matrix-not-explode/;anytypeParameter");
++
++ // style=matrix, explode=true, type=AnyType(undefined)
++ CALL_TEST_OPERATION(matrixExplodeAnytype, undefinedJson, "/v2/path/anytype/matrix-explode/;anytypeParameter");
++
++ // style=matrix, explode=false, type=AnyType(undefined)
++ CALL_TEST_OPERATION(matrixNotExplodeAnytype, undefinedJson, "/v2/path/anytype/matrix-not-explode/;anytypeParameter");
++
++ // style=matrix, explode=true, type=AnyType(empty)
++ CALL_TEST_OPERATION(matrixExplodeAnytype, emptyJson, "/v2/path/anytype/matrix-explode/;anytypeParameter");
++
++ // style=matrix, explode=false, type=AnyType(empty)
++ CALL_TEST_OPERATION(matrixNotExplodeAnytype, emptyJson, "/v2/path/anytype/matrix-not-explode/;anytypeParameter");
++
++ // undefined case for form with empty object
++ // style=form, explode=false, type=object
++ CALL_TEST_POST_OPERATION(formNotExplodeObject, TestObject(),
++ "/v2/query/object/form-not-explode/formNotExplodeObject?objectParameter=");
++
++ // undefined case for form with empty object
++ // style=form, explode=true, type=empty object
++ CALL_TEST_POST_OPERATION(formExplodeObject, TestObject(),
++ "/v2/query/object/form-explode/formExplodeObject?objectParameter=");
++
++ // style=form, explode=true, type=empty array
++ CALL_TEST_POST_OPERATION(formExplodeArray, QList<int>(),
++ "/v2/query/array/form-explode/formExplodeArray?arrayParameter=");
++
++ // style=form, explode=false, type=empty array
++ CALL_TEST_POST_OPERATION(formNotExplodeArray, QList<int>(),
++ "/v2/query/array/form-not-explode/formNotExplodeArray?arrayParameter=");
++
++ // defines the undefined column for null/emptied parameters,
++ // servers decline empty LABEL or SIMPLE operations like
++ // "/path/string/label-not-explode/." or "/path/string/simple-explode/"
++ // and return a code 404
++ //
++ // style=label, explode=true, type=string
++ CALL_NOT_FOUND_TEST_OPERATION(labelExplodeString, emptyLine);
++
++ // style=label, explode=false, type=string
++ CALL_NOT_FOUND_TEST_OPERATION(labelNotExplodeString, emptyLine);
++
++ // style=simple, explode=true, type=string
++ CALL_NOT_FOUND_TEST_OPERATION(simpleExplodeString, emptyLine);
++
++ // style=simple, explode=false, type=string
++ CALL_NOT_FOUND_TEST_OPERATION(simpleNotExplodeString, emptyLine);
++
++ // style=simple, explode=true, type=AnyType(null)
++ CALL_NOT_FOUND_TEST_OPERATION(simpleExplodeAnytype, nullJson);
++
++ // style=simple, explode=false, type=AnyType(null)
++ CALL_NOT_FOUND_TEST_OPERATION(simpleNotExplodeAnytype, nullJson);
++
++ // style=simple, explode=true, type=AnyType(undefinedJson)
++ CALL_NOT_FOUND_TEST_OPERATION(simpleExplodeAnytype, undefinedJson);
++
++ // style=simple, explode=false, type=AnyType(undefinedJson)
++ CALL_NOT_FOUND_TEST_OPERATION(simpleNotExplodeAnytype, undefinedJson);
++
++ // style=simple, explode=true, type=AnyType(emptyJson)
++ CALL_NOT_FOUND_TEST_OPERATION(simpleExplodeAnytype, emptyJson);
++
++ // style=simple, explode=false, type=AnyType(emptyJson)
++ CALL_NOT_FOUND_TEST_OPERATION(simpleNotExplodeAnytype, emptyJson);
++
++ // style=label, explode=true, type=AnyType(undefined)
++ CALL_NOT_FOUND_TEST_OPERATION(labelExplodeAnytype, undefinedJson);
++
++ // style=label, explode=false, type=AnyType(undefined)
++ CALL_NOT_FOUND_TEST_OPERATION(labelNotExplodeAnytype, undefinedJson);
++
++ // style=label, explode=true, type=AnyType(undefined)
++ CALL_NOT_FOUND_TEST_OPERATION(labelExplodeAnytype, nullJson);
++
++ // style=label, explode=false, type=AnyType(undefined)
++ CALL_NOT_FOUND_TEST_OPERATION(labelNotExplodeAnytype, nullJson);
++
++ // style=label, explode=true, type=AnyType(emptyJson)
++ CALL_NOT_FOUND_TEST_OPERATION(labelExplodeAnytype, emptyJson);
++
++ // style=label, explode=false, type=AnyType(emptyJson)
++ CALL_NOT_FOUND_TEST_OPERATION(labelNotExplodeAnytype, emptyJson);
++ }
++
++ void OperationParameters::severalQueryParametersPerOPeration()
++ {
++ QString aParam("First param");
++ QString bParam("second param");
++ qint32 cParam = -3499;
++ QString expectedResult;
++ bool done = false;
++
++ connect(this, &OperationParameters::formExplodeStringOptionsFinished,
++ this, [&](const QString &summary) {
++ done = true;
++ QCOMPARE(getStatusString(summary), expectedResult);
++ });
++ connect(this, &OperationParameters::formExplodeStringOptionsErrorOccurred,
++ this, [&](QNetworkReply::NetworkError, const QString &) {
++ done = false;
++ });
++
++ expectedResult = "/v2/query/strings/form-explode/formExplodeStringOptions?stringParameterA=First%20param&stringParameterB=second%20param&stringParameterC=-3499";
++ formExplodeStringOptions(bParam, OptionalParameter<QString>(aParam),
++ OptionalParameter<qint32>(cParam));
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ // NOTE: Empty optional parameters are being
++ // excluded from the resulting URL!
++ done = false;
++ expectedResult = "/v2/query/strings/form-explode/formExplodeStringOptions?stringParameterA=First%20param&stringParameterB=second%20param";
++ formExplodeStringOptions(bParam, aParam);
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ expectedResult = "/v2/query/strings/form-explode/formExplodeStringOptions?stringParameterB=second%20param";
++ formExplodeStringOptions(bParam);
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ // NOTE: QtOpenAPI::OptionalParameter<T>() is equal to EMPTY optional parameter
++ // The value will be excluded from the url
++ done = false;
++ expectedResult = "/v2/query/strings/form-explode/formExplodeStringOptions?stringParameterB=";
++ formExplodeStringOptions("");
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ expectedResult = "/v2/query/strings/form-explode/formExplodeStringOptions?stringParameterB=";
++ formExplodeStringOptions("", OptionalParameter<QString>());
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ expectedResult = "/v2/query/strings/form-explode/formExplodeStringOptions?stringParameterB=";
++ formExplodeStringOptions("", OptionalParameter<QString>(), OptionalParameter<qint32>());
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ // NOTE: NULL != Empty! optional(Null) is being serialized like
++ done = false;
++ expectedResult = "/v2/query/strings/form-explode/formExplodeStringOptions?stringParameterA=&stringParameterB=";
++ formExplodeStringOptions("", OptionalParameter<QString>(OptionalParameter<QString>::IsNull));
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ expectedResult = "/v2/query/strings/form-explode/formExplodeStringOptions?stringParameterA=&stringParameterB=&stringParameterC=";
++ formExplodeStringOptions("", OptionalParameter<QString>(OptionalParameter<QString>::IsNull), OptionalParameter<qint32>(OptionalParameter<qint32>::IsNull));
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ expectedResult = "/v2/query/strings/form-explode/formExplodeStringOptions?stringParameterA=&stringParameterB=";
++ formExplodeStringOptions("", OptionalParameter<QString>(OptionalParameter<QString>::IsNull), OptionalParameter<qint32>());
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ // NOTE: Empty string != empty parameter. It is being serialized like
++ done = false;
++ expectedResult = "/v2/query/strings/form-explode/formExplodeStringOptions?stringParameterA=&stringParameterB=&stringParameterC=";
++ formExplodeStringOptions("", OptionalParameter<QString>(""), OptionalParameter<qint32>(OptionalParameter<qint32>::IsNull));
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ expectedResult = "/v2/query/strings/form-explode/formExplodeStringOptions?stringParameterA=%20end%21&stringParameterB=The&stringParameterC=100";
++ formExplodeStringOptions("The", OptionalParameter<QString>(" end!"), OptionalParameter<qint32>(100));
++ QCOMPARE(m_manager->m_operationPath, expectedResult);
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ TestObject testObject;
++ testObject.setName("John");
++ testObject.setStatus("Sleepy");
++ testObject.setAge(12);
++ formExplodeDifferentOptions(50, OptionalParameter<TestObject>(testObject),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getStatusString(summary), "/v2/query/strings/form-explode/formExplodeDifferentOptions?age=12&name=John&status=Sleepy&stringParameterB=50");
++ });
++
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ formExplodeDifferentOptions(50, OptionalParameter<TestObject>(),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getStatusString(summary), "/v2/query/strings/form-explode/formExplodeDifferentOptions?stringParameterB=50");
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ // NOTE: Parameter is not nullable in yaml file, so it's being excluded
++ // from the serialization, even if Null is passed as a 2d argument.
++ done = false;
++ formExplodeDifferentOptions(50, OptionalParameter<TestObject>(OptionalParameter<TestObject>::IsNull), this,
++ [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getStatusString(summary), "/v2/query/strings/form-explode/formExplodeDifferentOptions?stringParameterB=50");
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::pathAndQueryParameters()
++ {
++ QString expectedResult;
++ QString stringParam("str * param");
++ QList<int> arrayParam({22, -150, 0});
++
++ expectedResult = "/v2/path/string/matrix-explode/;stringParameter=str%20%2A%20param/query/array/spaceDelimited-not-explode?arrayParameter=22%20-150%200"_L1;
++ CALL_TEST_OPERATION_MULTI_PARAMS(queryAndPathParams, expectedResult, stringParam, arrayParam);
++
++ // style=form, explode=true, type=string : Invalid style for path parameters => use simple style
++ // style=label, explode=true, type=array : Invalid style for query parameters => use form style
++ #ifdef Q_OS_WIN
++ const char *pathWarningMsg = "'form' style is invalid for path parameters.\r\nAllowed styles "
++ "are: 'matrix', 'label' and 'simple'.\r\nFalling back to the "
++ "default style 'simple'.";
++ const char *queryWarningMsg = "'label' style is invalid for query parameters.\r\nAllowed "
++ "styles are: 'form', 'spaceDelimited', 'pipeDelimited' and "
++ "'deepObject'.\r\nFalling back to the default style 'form'.";
++ #else
++ const char *pathWarningMsg = "'form' style is invalid for path parameters.\nAllowed styles are:"
++ " 'matrix', 'label' and 'simple'.\nFalling back to the default "
++ "style 'simple'.";
++ const char *queryWarningMsg = "'label' style is invalid for query parameters.\nAllowed styles "
++ "are: 'form', 'spaceDelimited', 'pipeDelimited' and 'deepObject'."
++ "\nFalling back to the default style 'form'.";
++ #endif
++ QTest::ignoreMessage(QtWarningMsg, pathWarningMsg);
++ QTest::ignoreMessage(QtWarningMsg, queryWarningMsg);
++ expectedResult = "/v2/path/string/invalid-form-explode/str%20%2A%20param/query/array/invalid-label-explode?arrayParameter=22&arrayParameter=-150&arrayParameter=0"_L1;
++ CALL_TEST_OPERATION_MULTI_PARAMS(invalidStylesQueryPathParams, expectedResult, stringParam,
++ arrayParam);
++ }
++
++ /**
++ * Serialization of Headers is a tricky thing in the referenced SPEC OpenApi 3.1.1
++ *
++ * Historically, many custom HTTP headers use semicolon-delimited key-value pairs, e.g.:
++ * Content-Type: application/json; charset=utf-8
++ * It means that a custom header with multiple values could look like:
++ * X-Custom-Header: id=123; role=admin
++ *
++ * But rfc7230 defines that if the header with the same field name appears
++ * multiple times (e.g. X-Custom-Header), the server, client, or proxy can combine
++ * them into one line, separated by commas.
++ * According to the RFC, when multiple headers have the same field name,
++ * their values are combined into a single header. For example:
++ * X-Custom-Header: value1
++ * X-Custom-Header: value2
++ * Should be presented as: X-Custom-Header: value1, value2
++ *
++ * The newer RFC 9110 makes no assumption that comma-separated values are not valid, but!
++ * In short it says: ONLY the headers that are explicitly defined as LIST-BASED
++ * can be merged using a comma as a separator.
++ *
++ * The OpenApi 3.1.1 defines that header can contain multiple values, separated by commas,
++ * by using style="simple" for operation parameter headers.
++ * Another useful reference link:
++ *
++ * What does it mean for us?
++ * Our purpose is to use the Openapi 3.1.1 SPEC as the reference.
++ * So, we are likely somehow compliant to the rfc7230 now.
++ * NOTE: Servers that are generated by OpenAPI generators correctly parse these headers.
++ *
++ * If users want to use ";" as a seperator between different values of the same header,
++ * they can apply some tricks, like following *.yaml example:
++ *
++ * parameters:
++ * - name: X-User-Context
++ * in: header
++ * required: true
++ * description: "Structured user context in key=value pairs.
++ * Fields: id, role, source.
++ * Format: `id=123; role=admin; source=web`
++ * This header is a single string value, not a list.
++ * Parsers must not split on commas."
++ * schema:
++ * type: string
++ * example: id=123; role=admin; source=web
++ *
++ * Another way is using content + media type for custom header definition.
++ *
++ * NOTE: OpenAPI does not say anything about percent-encoding for header,
++ * though it doesn't mean it is safe to use special characters, like
++ * gen-delims / sub-delims in the header.
++ * There should be some additional agreements between client/server
++ * parts in case of special symbols usage.
++ */
++ void OperationParameters::headerAnyTypeParameters_data()
++ {
++ TestObject obj;
++ obj.setName("Super Puper *+,;=!$&'()");
++ obj.setStatus("Awake!");
++
++ QTest::addColumn<QJsonValue>("jsonValue");
++ QTest::addColumn<QString>("expectedExplodeResult");
++ QTest::addColumn<QString>("expectedNotExplodeResult");
++ QTest::newRow("QJsonValue(string)")
++ << QJsonValue("John Doe") << QString("John Doe") << QString("John Doe");
++ QTest::newRow("QJsonValue(int)")
++ << QJsonValue(100) << QString("100") << QString("100");
++ QTest::newRow("QJsonValue(array)")
++ << QJsonValue({ 1, 2.2, QString("Strange *+,;=!$&'()") })
++ << QString("1,2.2,Strange *+,;=!$&'()") << QString("1,2.2,Strange *+,;=!$&'()");
++ QTest::newRow("QJsonValue(object)")
++ << obj.asJsonValue()
++ << QString("name=Super Puper *+,;=!$&'(),status=Awake!")
++ << QString("name,Super Puper *+,;=!$&'(),status,Awake!");
++ QTest::newRow("QJsonValue(Null)")
++ << QJsonValue(QJsonValue::Null) << QString() << QString();
++ QTest::newRow("QJsonValue(Undefined)")
++ << QJsonValue(QJsonValue::Undefined) << QString() << QString();
++ QTest::newRow("QJsonValue()") << QJsonValue() << QString() << QString();
++ }
++
++ void OperationParameters::headerAnyTypeParameters()
++ {
++ QFETCH(QJsonValue, jsonValue);
++ QFETCH(QString, expectedExplodeResult);
++ QFETCH(QString, expectedNotExplodeResult);
++ bool done = false;
++ headerSimpleExplodeAnytype(jsonValue,
++ OptionalParameter<QString>("I am a QUERY string"),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Any-Type-Parameter"),
++ expectedExplodeResult);
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ headerSimpleNotExplodeAnytype(jsonValue, OptionalParameter<QString>(),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Any-Type-Parameter"),
++ expectedNotExplodeResult);
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::headerStringParameters_data()
++ {
++ QTest::addColumn<QString>("stringValue");
++ QTest::addColumn<QString>("expectedExplodeResult");
++ QTest::addColumn<QString>("expectedNotExplodeResult");
++
++ QTest::newRow("QString with sub-delims")
++ << QString("I am a simple string *+,;=!$&'()")
++ << QString("I am a simple string *+,;=!$&'()")
++ << QString("I am a simple string *+,;=!$&'()");
++ QTest::newRow("QString with numbers")
++ << QString("1236498910") << QString("1236498910") << QString("1236498910");
++ QTest::newRow("Empty string") << QString() << QString() << QString();
++ }
++
++ void OperationParameters::headerStringParameters()
++ {
++ QFETCH(QString, stringValue);
++ QFETCH(QString, expectedExplodeResult);
++ QFETCH(QString, expectedNotExplodeResult);
++ bool done = false;
++ headerSimpleExplodeString(stringValue,
++ OptionalParameter<QString>("I am a QUERY string"),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "String-Parameter"),
++ expectedExplodeResult);
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ headerSimpleNotExplodeString(stringValue, OptionalParameter<QString>(),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "String-Parameter"),
++ expectedNotExplodeResult);
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::headerArrayParameters_data()
++ {
++ QTest::addColumn<QList<qint32>>("arrayValue");
++ QTest::addColumn<QString>("expectedExplodeResult");
++ QTest::addColumn<QString>("expectedNotExplodeResult");
++
++ QTest::newRow("QList<qint32>{regular numbers}")
++ << QList<qint32>({0, 1, -7654, 1987357, 0, 0, 1876, -997675})
++ << QString("0,1,-7654,1987357,0,0,1876,-997675")
++ << QString("0,1,-7654,1987357,0,0,1876,-997675");
++ QTest::newRow("QList<qint32>{limits}")
++ << QList<qint32>({std::numeric_limits<qint32>::min(),
++ 0, std::numeric_limits<qint32>::max()})
++ << QString("-2147483648,0,2147483647") << QString("-2147483648,0,2147483647");
++ QTest::newRow("QList<qint32>{empty}")
++ << QList<qint32>({}) << QString() << QString();
++ }
++
++ void OperationParameters::headerArrayParameters()
++ {
++ QFETCH(QList<qint32>, arrayValue);
++ QFETCH(QString, expectedExplodeResult);
++ QFETCH(QString, expectedNotExplodeResult);
++ bool done = false;
++ headerSimpleExplodeArray(arrayValue,
++ OptionalParameter<QString>("I am a QUERY string"),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Array-Parameter"),
++ expectedExplodeResult);
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ headerSimpleNotExplodeArray(OptionalParameter<QList<qint32>>(arrayValue),
++ OptionalParameter<QString>(),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Array-Parameter"),
++ expectedNotExplodeResult);
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::headerObjectParameters_data()
++ {
++ TestObject obj1, obj2, obj3;
++ obj1.setName("Super Puper *+,;=!$&'()");
++ obj1.setStatus("Awake!");
++ obj2.setName("Simple123456789");
++ obj2.setStatus("Sleeping");
++
++ QTest::addColumn<TestObject>("objectValue");
++ QTest::addColumn<QString>("expectedExplodeResult");
++ QTest::addColumn<QString>("expectedNotExplodeResult");
++
++ QTest::newRow("Object with sub-delim characters")
++ << obj1
++ << QString("name=Super Puper *+,;=!$&'(),status=Awake!")
++ << QString("name,Super Puper *+,;=!$&'(),status,Awake!");
++ QTest::newRow("Object with simple strings")
++ << obj2 << QString("name=Simple123456789,status=Sleeping")
++ << QString("name,Simple123456789,status,Sleeping");
++ QTest::newRow("Empty object") << obj3 << QString() << QString();
++ }
++
++ void OperationParameters::headerObjectParameters()
++ {
++ QFETCH(TestObject, objectValue);
++ QFETCH(QString, expectedExplodeResult);
++ QFETCH(QString, expectedNotExplodeResult);
++ bool done = false;
++ headerSimpleExplodeObject(objectValue,
++ OptionalParameter<QString>("I am a QUERY string"),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Object-Parameter"),
++ expectedExplodeResult);
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ headerSimpleNotExplodeObject(objectValue, OptionalParameter<QString>(),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Object-Parameter"),
++ expectedNotExplodeResult);
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::headerMapParameters_data()
++ {
++ QMap<QString, QString> stringMap;
++ stringMap["key1"_L1] = QString("Super Puper *+,;=!$&'()");
++ stringMap["key2"_L1] = QString("Simple123456789");
++
++ QTest::addColumn<QMap<QString, QString>>("mapValue");
++ QTest::addColumn<QString>("expectedExplodeResult");
++ QTest::addColumn<QString>("expectedNotExplodeResult");
++
++ QTest::newRow("Non-empty map") << stringMap
++ << QString("key1=Super Puper *+,;=!$&'(),key2=Simple123456789")
++ << QString("key1,Super Puper *+,;=!$&'(),key2,Simple123456789");
++ QTest::newRow("Empty map") << QMap<QString, QString>() << QString() << QString();
++ }
++
++ void OperationParameters::headerMapParameters()
++ {
++ using StringMap = QMap<QString, QString>;
++ QFETCH(StringMap, mapValue);
++ QFETCH(QString, expectedExplodeResult);
++ QFETCH(QString, expectedNotExplodeResult);
++ bool done = false;
++ headerSimpleExplodeMap(mapValue, OptionalParameter<QString>(""),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Map-Parameter"),
++ expectedExplodeResult);
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ headerSimpleNotExplodeMap(mapValue,
++ OptionalParameter<QString>("I am a QUERY string"),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Map-Parameter"),
++ expectedNotExplodeResult);
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::headerAdditionalCases_data()
++ {
++ QTest::addColumn<QString>("stringValue");
++ QTest::addColumn<QString>("expectedExplodeResult1");
++ QTest::addColumn<QString>("expectedExplodeResult2");
++
++ QTest::newRow("QString(simple string 1)")
++ << QString("name=John;status=Sleep;age=34")
++ << QString("name=John;status=Sleep;age=34")
++ << QString("name=John;status=Sleep;age=34");
++ QTest::newRow("QString(simple string 2)")
++ << QString() << QString()
++ << QString("application/x-www-form-urlencoded");
++ }
++
++ void OperationParameters::headerAdditionalCases()
++ {
++ QFETCH(QString, stringValue);
++ QFETCH(QString, expectedExplodeResult1);
++ QFETCH(QString, expectedExplodeResult2);
++ bool done = false;
++ headerSimpleNotExplodeCustom(OptionalParameter<QString>(stringValue), this,
++ [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "String-Parameter"),
++ expectedExplodeResult1);
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ // FIX ME: the name Content-Type should be ignored.
++ // Content-Type == "application/x-www-form-urlencoded" for empty string here, because
++ // the name is a duplicate of the actual Content-Type, and the actual one
++ // cannot be empty in the Qt Network.
++ done = false;
++ headerSimpleNotExplodeContentType(stringValue,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Content-Type"),
++ expectedExplodeResult2);
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::severalHeaderParameters()
++ {
++ bool done = false;
++ TestObject obj;
++ obj.setName("object-header"_L1);
++ obj.setStatus("Awake!"_L1);
++
++ severalHeaderParametersOp(QString("String-header!"_L1), 300, obj,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "String-Parameter"_L1),
++ "String-header!"_L1);
++ QCOMPARE(getHeaderValue(summary, "Int-Parameter"_L1), "300"_L1);
++ QCOMPARE(getHeaderValue(summary, "Object-Parameter"_L1),
++ "name=object-header,status=Awake!"_L1);
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::headerInvalidStyle()
++ {
++ const QString stringValue = "Just some random string"_L1;
++ bool done = false;
++
++ #ifdef Q_OS_WIN
++ const char *warningMsg = "'label' style is invalid for header parameters.\r\n"
++ "Falling back to the default style 'simple'.";
++ #else
++ const char *warningMsg = "'label' style is invalid for header parameters.\n"
++ "Falling back to the default style 'simple'.";
++ #endif
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ headerInvalidLabelNotExplodeString(stringValue, this,
++ [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "String-Parameter"), stringValue);
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ /**
++ * Cookies are a part of Operation parameters.
++ * Cookies support only style=form. Cookies can be presented with
++ * a schema or a content type.
++ *
++ * For more see:
++ *
++ * Cookies are also an interesting topic.
++ * The thing is, cookies are not well defined in the OpenAPI 3.1.1,
++ * which is our referenced SPEC version.
++ *
++ * There are 2 cases supported:
++ * - normal case: style=form + explode=false;
++ * - not well defined case: style=form + explode=true;
++ *
++ * What does "not well defined case" mean?
++ * It means such serialization is not supported by any known frameworks
++ * or RFC, except OpenApi 3.1.1 itself.
++ *
++ * What does RFC say?
++ * Historically, Cookies are "name1=value1; name2=value2" pairs separated by semicolons.
++ * See Cookie header semantic rules:
++ *
++ * So, each "name=value" pair is being interpreted as a separate cookie.
++ * And the delimiter between seperate cookies is ';', not a ','.
++ *
++ * Servers do understand commas inside a cookie value,
++ * as long as they are part of a single cookie's value.
++ * For example: "key=value1,text,value2,something" is a valid single cookie.
++ *
++ * And no, servers do not interpret commas as a separator for multiple
++ * "key=value" pairs. It means servers do not interpret the following
++ * case as two separate cookies:
++ * "name1=value1, name2=value2"
++ *
++ * If you want to set several separate independent cookies,
++ * you need to use semicolons as follows:
++ * "name1=value1; name2=value2"
++ *
++ * Speaking about our current Cookie implementation,
++ * it supports OpenApi 3.1.1 as it is.
++ *
++ * See supported examples with object type:
++ * style=form, explode=false looks like: "color=R,100,G,200,B,150"
++ * style=form, explode=true looks like: "R=100&G=200&B=150"
++ *
++ * Problems of explode=true are obvious here:
++ * - It looks like separate pairs.
++ * - It uses an inappropriate '&' delimiter.
++ *
++ * Users should decide if they want to use explode=true.
++ * It will always mean that the server side should be aware of it somehow
++ * by additional implementation agreements.
++ *
++ * But in general using explode=true is not recommended, though
++ * there is no reason to prohibit it entirely.
++ */
++ void OperationParameters::cookieAnyTypeParameters_data()
++ {
++ TestObject obj;
++ obj.setName("Super Puper");
++ obj.setStatus("Awake!");
++
++ QTest::addColumn<QJsonValue>("jsonValue");
++ QTest::addColumn<QString>("expectedExplodeResult");
++ QTest::addColumn<QString>("expectedNotExplodeResult");
++
++ QTest::newRow("QJsonValue(string)")
++ << QJsonValue("John Doe")
++ << QString("anytypeParameter=John Doe")
++ << QString("anytypeParameter=John Doe");
++ QTest::newRow("QJsonValue(int)")
++ << QJsonValue(100)
++ << QString("anytypeParameter=100") << QString("anytypeParameter=100");
++ QTest::newRow("QJsonValue(array)")
++ << QJsonValue({ 1, 2.2, QString("Strange") })
++ << QString("anytypeParameter=1&anytypeParameter=2.2&anytypeParameter=Strange")
++ << QString("anytypeParameter=1,2.2,Strange");
++ QTest::newRow("QJsonValue(object)")
++ << obj.asJsonValue()
++ << QString("name=Super Puper&status=Awake!")
++ << QString("anytypeParameter=name,Super Puper,status,Awake!");
++ QTest::newRow("QJsonValue(Null)")
++ << QJsonValue(QJsonValue::Null)
++ << QString("anytypeParameter=")
++ << QString("anytypeParameter=");
++ QTest::newRow("QJsonValue(Undefined)")
++ << QJsonValue(QJsonValue::Undefined)
++ << QString("anytypeParameter=")
++ << QString("anytypeParameter=");
++ QTest::newRow("QJsonValue()")
++ << QJsonValue()
++ << QString("anytypeParameter=")
++ << QString("anytypeParameter=");
++ }
++
++ void OperationParameters::cookieAnyTypeParameters()
++ {
++ QFETCH(QJsonValue, jsonValue);
++ QFETCH(QString, expectedExplodeResult);
++ QFETCH(QString, expectedNotExplodeResult);
++ bool done = false;
++ // NOTE: if you send cookies from a client to a server, use "Cookie" header name.
++ // If you send cookies from the server to the client, then use "Set-Cookie" header name.
++ // All cookie related operations use "Cookie" inside, because it's a Client part.
++ cookieExplodeAnytype(jsonValue,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Cookie"_L1),
++ expectedExplodeResult);
++ if (jsonValue.type() == QJsonValue::Object) {
++ // explode=true for objects doesn't work well on server side.
++ // It is not possible to detect cookie value by Parameter Name
++ // on server side, because the serialized string doesn't contain it:
++ // "name=Super Puper&status=Awake!"
++ // Just be aware.
++ QCOMPARE(getObjectValue(summary, "error"_L1).toString(),
++ "http: named cookie not present"_L1);
++ } else {
++ QVERIFY(getObjectValue(summary, "error"_L1).toString().isEmpty());
++ }
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ cookieNotExplodeAnytype(jsonValue,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Cookie"_L1),
++ expectedNotExplodeResult);
++ QVERIFY(getObjectValue(summary, "error"_L1).toString().isEmpty());
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::cookieStringParameters_data()
++ {
++ QTest::addColumn<QString>("stringValue");
++ QTest::addColumn<QString>("expectedExplodeResult");
++ QTest::addColumn<QString>("expectedNotExplodeResult");
++
++ QTest::newRow("QString with sub-delims")
++ << QString("I am a simple string")
++ << QString("stringParameter=I am a simple string")
++ << QString("stringParameter=I am a simple string");
++ QTest::newRow("QString with numbers")
++ << QString("1236498910")
++ << QString("stringParameter=1236498910")
++ << QString("stringParameter=1236498910");
++ QTest::newRow("Empty string")
++ << QString() << QString("stringParameter=") << QString("stringParameter=");
++ }
++
++ void OperationParameters::cookieStringParameters()
++ {
++ QFETCH(QString, stringValue);
++ QFETCH(QString, expectedExplodeResult);
++ QFETCH(QString, expectedNotExplodeResult);
++ bool done = false;
++ cookieExplodeString(stringValue, QString("I am a PATH string"),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Cookie"_L1),
++ expectedExplodeResult);
++ QVERIFY(getObjectValue(summary, "error"_L1).toString().isEmpty());
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ cookieNotExplodeString(QString("I am a PATH string"), stringValue,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Cookie"_L1),
++ expectedNotExplodeResult);
++ QVERIFY(getObjectValue(summary, "error"_L1).toString().isEmpty());
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::cookieArrayParameters_data()
++ {
++ QTest::addColumn<QList<qint32>>("arrayValue");
++ QTest::addColumn<QString>("expectedExplodeResult");
++ QTest::addColumn<QString>("expectedNotExplodeResult");
++
++ QTest::newRow("QList<qint32>{regular numbers}")
++ << QList<qint32>({0, 1, -7654, 1987357, 0, 0, 1876, -997675})
++ << QString("arrayParameter=0&arrayParameter=1&arrayParameter=-7654&arrayParameter=1987357&arrayParameter=0&arrayParameter=0&arrayParameter=1876&arrayParameter=-997675")
++ << QString("arrayParameter=0,1,-7654,1987357,0,0,1876,-997675");
++ QTest::newRow("QList<qint32>{limits}")
++ << QList<qint32>({std::numeric_limits<qint32>::min(),
++ 0, std::numeric_limits<qint32>::max()})
++ << QString("arrayParameter=-2147483648&arrayParameter=0&arrayParameter=2147483647")
++ << QString("arrayParameter=-2147483648,0,2147483647");
++ QTest::newRow("QList<qint32>{empty}")
++ << QList<qint32>({}) << QString("arrayParameter=") << QString("arrayParameter=");
++ }
++
++ void OperationParameters::cookieArrayParameters()
++ {
++ QFETCH(QList<qint32>, arrayValue);
++ QFETCH(QString, expectedExplodeResult);
++ QFETCH(QString, expectedNotExplodeResult);
++ bool done = false;
++ cookieExplodeArray(arrayValue,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Cookie"_L1),
++ expectedExplodeResult);
++ QVERIFY(getObjectValue(summary, "error"_L1).toString().isEmpty());
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ cookieNotExplodeArray(arrayValue,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Cookie"_L1),
++ expectedNotExplodeResult);
++ QVERIFY(getObjectValue(summary, "error"_L1).toString().isEmpty());
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::cookieObjectParameters_data()
++ {
++ TestObject obj1, obj2, obj3;
++ obj1.setName("Super Puper");
++ obj1.setStatus("Awake!");
++ obj2.setName("Simple123456789");
++ obj2.setStatus("Sleeping");
++
++ QTest::addColumn<TestObject>("objectValue");
++ QTest::addColumn<QString>("expectedExplodeResult");
++ QTest::addColumn<QString>("expectedNotExplodeResult");
++
++ QTest::newRow("Object with sub-delim characters")
++ << obj1
++ << QString("name=Super Puper&status=Awake!")
++ << QString("objectParameter=name,Super Puper,status,Awake!");
++ QTest::newRow("Object with simple strings")
++ << obj2
++ << QString("name=Simple123456789&status=Sleeping")
++ << QString("objectParameter=name,Simple123456789,status,Sleeping");
++ QTest::newRow("Empty object") << obj3
++ << QString("objectParameter=")
++ << QString("objectParameter=");
++ }
++
++ void OperationParameters::cookieObjectParameters()
++ {
++ QFETCH(TestObject, objectValue);
++ QFETCH(QString, expectedExplodeResult);
++ QFETCH(QString, expectedNotExplodeResult);
++ bool done = false;
++ cookieExplodeObject(objectValue,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Cookie"_L1),
++ expectedExplodeResult);
++ // explode=true for objects doesn't work well on server side.
++ // It is not possible to detect cookie value by Parameter Name
++ // on server side, because the serialized string doesn't contain it:
++ // obj1 = "name=Super Puper&status=Awake!"
++ // obj2 = "name=Simple123456789&status=Sleeping".
++ // Just be aware.
++ if (!objectValue.asJsonValue().toObject().empty()) {
++ QCOMPARE(getObjectValue(summary, "error"_L1).toString(),
++ "http: named cookie not present"_L1);
++ } else {
++ // Obj3 is empty, so we send objectParameter=
++ // as described in "undefined" column
++ // see style=form, explode=true
++ QVERIFY(getObjectValue(summary, "error"_L1).toString().isEmpty());
++ }
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ cookieNotExplodeObject(objectValue,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Cookie"_L1),
++ expectedNotExplodeResult);
++ QVERIFY(getObjectValue(summary, "error"_L1).toString().isEmpty());
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::cookieMapParameters_data()
++ {
++ QMap<QString, QString> stringMap;
++ stringMap["key1"_L1] = QString("Super Puper");
++ stringMap["key2"_L1] = QString("Simple123456789");
++
++ QTest::addColumn<QMap<QString, QString>>("mapValue");
++ QTest::addColumn<QString>("expectedExplodeResult");
++ QTest::addColumn<QString>("expectedNotExplodeResult");
++
++ QTest::newRow("Non-empty map")
++ << stringMap
++ << QString("key1=Super Puper&key2=Simple123456789")
++ << QString("mapParameter=key1,Super Puper,key2,Simple123456789");
++ QTest::newRow("Empty map")
++ << QMap<QString, QString>()
++ << QString("mapParameter=") << QString("mapParameter=");
++ }
++
++ void OperationParameters::cookieMapParameters()
++ {
++ using StringMap = QMap<QString, QString>;
++ QFETCH(StringMap, mapValue);
++ QFETCH(QString, expectedExplodeResult);
++ QFETCH(QString, expectedNotExplodeResult);
++ bool done = false;
++ cookieExplodeStringMap(mapValue,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Cookie"_L1),
++ expectedExplodeResult);
++ // explode=true for objects doesn't work well on server side.
++ // It is not possible to detect cookie value by parameter name
++ // on server side, because the serialized string doesn't contain it:
++ // "key1=Super Puper&key2=Simple123456789". Just be aware.
++ if (!mapValue.empty()) {
++ QCOMPARE(getObjectValue(summary, "error"_L1).toString(),
++ "http: named cookie not present"_L1);
++ } else {
++ // Map is empty, so we send mapParameter=
++ // as described in "undefined" column
++ // see style=form, explode=true
++ QVERIFY(getObjectValue(summary, "error"_L1).toString().isEmpty());
++ }
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ cookieNotExplodeStringMap(mapValue,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Cookie"_L1),
++ expectedNotExplodeResult);
++ QVERIFY(getObjectValue(summary, "error"_L1).toString().isEmpty());
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::severalCookies()
++ {
++ QMap<QString, QString> stringMap, emptyMap;
++ stringMap["key1"_L1] = QString("Super Puper");
++ stringMap["key2"_L1] = QString("Simple123456789");
++
++ bool done = false;
++ severalNotExplodeCookies(stringMap, 100,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getObjectValue(summary, "cookie1"_L1).toString(),
++ "key1,Super Puper,key2,Simple123456789");
++ QCOMPARE(getObjectValue(summary, "cookie2"_L1).toString(), "100");
++ QCOMPARE(getObjectValue(summary, "size"_L1).toInt(), 2);
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ severalNotExplodeCookies(OptionalParameter<QMap<QString, QString>>(emptyMap),
++ OptionalParameter<qint64>(-999999999),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QVERIFY(getObjectValue(summary,
++ "cookie1"_L1).toString().isEmpty());
++ QCOMPARE(getObjectValue(summary,
++ "cookie2"_L1).toString(), "-999999999");
++ QCOMPARE(getObjectValue(summary, "size"_L1).toInt(), 2);
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ // Cookies are empty, because the empty optional parameter is not being sent at all
++ // so nothing to send back from the server
++ done = true;
++ severalNotExplodeCookies(OptionalParameter<QMap<QString, QString>>(),
++ OptionalParameter<qint64>(),
++ this, [&](const QRestReply &reply, const QString &) {
++ done = reply.isSuccess();
++ });
++ QTRY_COMPARE_EQ(done, false);
++
++ done = false;
++ severalExplodeCookies(stringMap, 100,
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ // Server doesn't parse objects like
++ // "key1=Super Puper&key2=Simple123456789"
++ // because key=value pairs are being interpreted as a
++ // separate values
++ QCOMPARE(getObjectValue(summary, "cookie1"_L1).toString(), "");
++ // Primitive types are OK, the serialization is the same
++ // like explode=false for primitives
++ QCOMPARE(getObjectValue(summary, "cookie2"_L1).toString(), "100");
++ QCOMPARE(getObjectValue(summary, "size"_L1).toInt(), 2);
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ done = false;
++ severalExplodeCookies(OptionalParameter<QMap<QString, QString>>(emptyMap),
++ OptionalParameter<qint64>(-999999999),
++ this, [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getObjectValue(summary, "cookie1"_L1).toString(), "");
++ QCOMPARE(getObjectValue(summary, "cookie2"_L1).toString(),
++ "-999999999");
++ QCOMPARE(getObjectValue(summary, "size"_L1).toInt(), 2);
++ });
++ QTRY_COMPARE_EQ(done, true);
++
++ // Cookies are empty, because the empty optional parameter is not being sent at all
++ // so nothing to send back from the server
++ done = true;
++ severalExplodeCookies(OptionalParameter<QMap<QString, QString>>(),
++ OptionalParameter<qint64>(),
++ this, [&](const QRestReply &reply, const QString &) {
++ done = reply.isSuccess();
++ });
++ QTRY_COMPARE_EQ(done, false);
++ }
++
++ void OperationParameters::cookieInvalidStyle()
++ {
++ const QString stringValue = "Just some random string"_L1;
++ const QString expectedCookie = "stringParameter=Just some random string"_L1;
++ bool done = false;
++
++ #ifdef Q_OS_WIN
++ const char *warningMsg = "'simple' style is invalid for cookie parameters.\r\n"
++ "Falling back to the default style 'form'.";
++ #else
++ const char *warningMsg = "'simple' style is invalid for cookie parameters.\n"
++ "Falling back to the default style 'form'.";
++ #endif
++ QTest::ignoreMessage(QtWarningMsg, warningMsg);
++ cookieNotExplodeStringInvalidStyle(stringValue, this,
++ [&](const QRestReply &reply, const QString &summary) {
++ done = reply.isSuccess();
++ QCOMPARE(getHeaderValue(summary, "Cookie"_L1), expectedCookie);
++ QVERIFY(getObjectValue(summary, "error"_L1).toString().isEmpty());
++ });
++ QTRY_COMPARE_EQ(done, true);
++ }
++
++ void OperationParameters::cleanupTestCase()
++ {
++ if (serverProcess.state() == QProcess::ProcessState::Running) {
++ serverProcess.kill();
++ serverProcess.waitForFinished();
++ }
++ }
++
++ } // QtOpenAPI
++
++ QTEST_MAIN(QtOpenAPI::OperationParameters)
++ #include "tst_operationparameters.moc"