diff options
author | Juha Vuolle <juha.vuolle@qt.io> | 2024-01-19 11:33:39 +0200 |
---|---|---|
committer | Juha Vuolle <juha.vuolle@qt.io> | 2024-01-29 19:02:37 +0200 |
commit | 9ba5c7ff6aa42c5701cf950d2137467a2d178833 (patch) | |
tree | 59e5116c50e9a314cbec4a68267f719846f14e01 /tests/auto/network/access | |
parent | be6c651be4ccfa4f70009bcbb51bef39638e0fba (diff) |
Make QRest* APIs non-owning and non-duplicating
Note: documentation will be updated in a follow-up commit
This commit makes QRestReply and QRestAccessManager
classes lighter, non-owning wrappers. Furthermore their
APIs don't duplicate the wrapped QNetwork* APIs.
This makes it easier to use / opt-in to these helpers
in pre-existing applications which are based on
QNetworkAccessManager and QNetworkReply.
Since APIs are no longer duplicated, the QRest
classes are more obviously a convenience _wrapper_,
as opposed to being an alternative vertical stack.
In practice this change consists of:
- QRestAM never instantiates QNetworkAccessManager,
but accepts it via constructor. It does not take
ownership of the QNetworkAccessManager.
- QRestReply accepts QNetworkReply via constructor. It
does not take ownership of the QNetworkReply
- Signals and most duplicated functions are removed
from both QRestAM and QRR.
- QRestReply is no longer a QObject
- Since QRestAM doesn't have much to report anymore,
the debug operator is dropped.
Resulted from API-review
Pick-to: 6.7
Change-Id: Ib62d9cc2df41cac631396a84bb7ec4d2d54b0c8c
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
Diffstat (limited to 'tests/auto/network/access')
-rw-r--r-- | tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp | 858 |
1 files changed, 305 insertions, 553 deletions
diff --git a/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp b/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp index d1018952bd..890b7609e1 100644 --- a/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp +++ b/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp @@ -29,63 +29,76 @@ class tst_QRestAccessManager : public QObject Q_OBJECT private slots: - void initTestCase(); - void initialization(); void destruction(); void callbacks(); - void threading(); - void networkRequestReply(); - void abort(); - void authentication(); - void userInfo(); + void requests(); + void reply(); void errors(); void body(); void json(); void text(); void textStreaming(); - void download(); - void upload(); - void timeout(); private: - void memberHandler(QRestReply *reply); + void memberHandler(QRestReply &reply); friend class Transient; - QList<QRestReply*> m_expectedReplies; - QList<QRestReply*> m_actualReplies; + QList<QNetworkReply*> m_expectedReplies; + QList<QNetworkReply*> m_actualReplies; }; -void tst_QRestAccessManager::initTestCase() +void tst_QRestAccessManager::initialization() { - qRegisterMetaType<QRestReply*>(); // For QSignalSpy + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QRestAccessManager: QNetworkAccesManager is nullptr"); + QRestAccessManager manager1(nullptr); + QVERIFY(!manager1.networkAccessManager()); + + QNetworkAccessManager qnam; + QRestAccessManager manager2(&qnam); + QVERIFY(manager2.networkAccessManager()); } -void tst_QRestAccessManager::initialization() +void tst_QRestAccessManager::reply() { - QRestAccessManager manager; - QVERIFY(manager.networkAccessManager()); - QCOMPARE(manager.deletesRepliesOnFinished(), true); + QNetworkAccessManager qnam; + + QNetworkReply *nr = qnam.get(QNetworkRequest(QUrl{"someurl"})); + QRestReply rr1(nr); + QCOMPARE(rr1.networkReply(), nr); + + // Move-construct + QRestReply rr2(std::move(rr1)); + QCOMPARE(rr2.networkReply(), nr); + + // Move-assign + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QRestReply: QNetworkReply is nullptr"); + QRestReply rr3(nullptr); + rr3 = std::move(rr2); + QCOMPARE(rr3.networkReply(), nr); } -#define VERIFY_REPLY_OK(METHOD) \ - QTRY_VERIFY(replyFromServer); \ +#define VERIFY_REPLY_OK(METHOD) \ +{ \ + QTRY_VERIFY(networkReply); \ + QRestReply restReply(networkReply); \ QCOMPARE(serverSideRequest.method, METHOD); \ - QVERIFY(replyFromServer->isSuccess()); \ - QVERIFY(!replyFromServer->hasError()); \ - replyFromServer->deleteLater(); \ - replyFromServer = nullptr; \ + QVERIFY(restReply.isSuccess()); \ + QVERIFY(!restReply.hasError()); \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ +} -void tst_QRestAccessManager::networkRequestReply() +void tst_QRestAccessManager::requests() { // A basic test for each HTTP method against the local testserver. - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); QNetworkRequest request(server.url()); request.setRawHeader("Content-Type"_ba, "text/plain"); // To silence missing content-type warn - QRestReply *replyFromServer = nullptr; + QNetworkReply *networkReply = nullptr; std::unique_ptr<QHttpMultiPart> multiPart; QHttpPart part; part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text\"")); @@ -101,7 +114,7 @@ void tst_QRestAccessManager::networkRequestReply() response = serverSideResponse; }); - auto callback = [&](QRestReply *reply) { replyFromServer = reply; }; + auto callback = [&](QRestReply &reply) { networkReply = reply.networkReply(); }; const QByteArray byteArrayData{"some_data"_ba}; const QJsonObject jsonObjectData{{"key1", "value1"}, {"key2", "value2"}}; const QJsonArray jsonArrayData{{"arrvalue1", "arrvalue2", QJsonObject{{"key1", "value1"}}}}; @@ -255,44 +268,9 @@ void tst_QRestAccessManager::networkRequestReply() //manager.sendCustomRequest(request, "FOOBAR", this, [](){}); // No verb || no data } -void tst_QRestAccessManager::abort() +void tst_QRestAccessManager::memberHandler(QRestReply &reply) { - // Test aborting requests - QRestAccessManager manager; - HttpTestServer server; - QTRY_VERIFY(server.isListening()); - QNetworkRequest request(server.url()); - - QSignalSpy finishedSpy(&manager, &QRestAccessManager::requestFinished); - int callbackCount = 0; - auto callback = [&](QRestReply*) { - callbackCount++; - }; - - // Abort without any requests - manager.abortRequests(); - QTest::qWait(20); - QCOMPARE(finishedSpy.size(), 0); - - // Abort immediately after requesting - manager.get(request, this, callback); - manager.abortRequests(); - QTRY_COMPARE(callbackCount, 1); - QTRY_COMPARE(finishedSpy.size(), 1); - - // Abort after request has been sent out - server.setHandler([&](HttpData, HttpData&, ResponseControl &control) { - control.respond = false; - manager.abortRequests(); - }); - manager.get(request, this, callback); - QTRY_COMPARE(callbackCount, 2); - QTRY_COMPARE(finishedSpy.size(), 2); -} - -void tst_QRestAccessManager::memberHandler(QRestReply *reply) -{ - m_actualReplies.append(reply); + m_actualReplies.append(reply.networkReply()); } // Class that is destroyed during an active request. @@ -303,9 +281,9 @@ class Transient : public QObject public: explicit Transient(tst_QRestAccessManager *test) : QObject(test), m_test(test) {} - void memberHandler(QRestReply *reply) + void memberHandler(QRestReply &reply) { - m_test->m_actualReplies.append(reply); + m_test->m_actualReplies.append(reply.networkReply()); } private: @@ -313,53 +291,51 @@ private: }; template <typename Functor, std::enable_if_t< - QtPrivate::AreFunctionsCompatible<void(*)(QRestReply*), Functor>::value, bool> = true> + QtPrivate::AreFunctionsCompatible<void(*)(QRestReply&), Functor>::value, bool> = true> inline constexpr bool isCompatibleCallback(Functor &&) { return true; } template <typename Functor, std::enable_if_t< - !QtPrivate::AreFunctionsCompatible<void(*)(QRestReply*), Functor>::value, bool> = true, + !QtPrivate::AreFunctionsCompatible<void(*)(QRestReply&), Functor>::value, bool> = true, typename = void> inline constexpr bool isCompatibleCallback(Functor &&) { return false; } void tst_QRestAccessManager::callbacks() { - QRestAccessManager manager; + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); - manager.setDeletesRepliesOnFinished(false); // Don't autodelete so we can compare results later QNetworkRequest request{u"i_dont_exist"_s}; // Will result in ProtocolUnknown error - QSignalSpy managerFinishedSpy(&manager, &QRestAccessManager::requestFinished); - auto lambdaHandler = [this](QRestReply *reply) { m_actualReplies.append(reply); }; - QRestReply *reply = nullptr; + auto lambdaHandler = [this](QRestReply &reply) { m_actualReplies.append(reply.networkReply()); }; Transient *transient = nullptr; QByteArray data{"some_data"}; // Compile-time tests for callback signatures - static_assert(isCompatibleCallback([](QRestReply*){})); // Correct signature + static_assert(isCompatibleCallback([](QRestReply&){})); // Correct signature static_assert(isCompatibleCallback(lambdaHandler)); static_assert(isCompatibleCallback(&Transient::memberHandler)); static_assert(isCompatibleCallback([](){})); // Less parameters are allowed - static_assert(!isCompatibleCallback([](QString){})); // Wrong parameter type - static_assert(!isCompatibleCallback([](QNetworkReply*){})); // Wrong parameter type - static_assert(!isCompatibleCallback([](const QString &){})); // Wrong parameter type - static_assert(!isCompatibleCallback([](QRestReply*, QString){})); // Too many parameters + static_assert(!isCompatibleCallback([](QString){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](QRestReply*){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](const QString &){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](QRestReply&, QString){})); // Too many parameters // -- Test without data - // Without callback - reply = manager.get(request); - QCOMPARE(reply->isFinished(), false); // Test this once here + // Without callback using signals and slot + QNetworkReply* reply = manager.get(request); m_expectedReplies.append(reply); - QObject::connect(reply, &QRestReply::finished, lambdaHandler); + QObject::connect(reply, &QNetworkReply::finished, this, + [this, reply](){m_actualReplies.append(reply);}); // With lambda callback, without context object m_expectedReplies.append(manager.get(request, nullptr, lambdaHandler)); m_expectedReplies.append(manager.get(request, nullptr, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With lambda callback and context object m_expectedReplies.append(manager.get(request, this, lambdaHandler)); m_expectedReplies.append(manager.get(request, this, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With member callback and context object m_expectedReplies.append(manager.get(request, this, &tst_QRestAccessManager::memberHandler)); // With context object that is destroyed, there should be no callback or eg. crash. @@ -369,31 +345,26 @@ void tst_QRestAccessManager::callbacks() // Let requests finish QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); - QTRY_COMPARE(managerFinishedSpy.size(), m_actualReplies.size()); for (auto reply: m_actualReplies) { - QVERIFY(!reply->isSuccess()); - QVERIFY(reply->hasError()); - QCOMPARE(reply->error(), QNetworkReply::ProtocolUnknownError); - QCOMPARE(reply->isFinished(), true); - reply->deleteLater(); + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); + restReply.networkReply()->deleteLater(); } m_actualReplies.clear(); m_expectedReplies.clear(); - managerFinishedSpy.clear(); // -- Test with data - reply = manager.post(request, data); - m_expectedReplies.append(reply); - QObject::connect(reply, &QRestReply::finished, lambdaHandler); - // With lambda callback, without context object m_expectedReplies.append(manager.post(request, data, nullptr, lambdaHandler)); m_expectedReplies.append(manager.post(request, data, nullptr, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With lambda callback and context object m_expectedReplies.append(manager.post(request, data, this, lambdaHandler)); m_expectedReplies.append(manager.post(request, data, this, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With member callback and context object m_expectedReplies.append(manager.post(request, data, this, &tst_QRestAccessManager::memberHandler)); @@ -404,31 +375,27 @@ void tst_QRestAccessManager::callbacks() // Let requests finish QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); - QTRY_COMPARE(managerFinishedSpy.size(), m_actualReplies.size()); for (auto reply: m_actualReplies) { - QVERIFY(!reply->isSuccess()); - QVERIFY(reply->hasError()); - QCOMPARE(reply->error(), QNetworkReply::ProtocolUnknownError); - QCOMPARE(reply->isFinished(), true); + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); reply->deleteLater(); } m_actualReplies.clear(); m_expectedReplies.clear(); - managerFinishedSpy.clear(); // -- Test GET with data separately, as GET provides methods that are usable with and - // without data, and fairly easy to get the qrestaccessmanager.h template SFINAE subtly wrong - reply = manager.get(request, data); - m_expectedReplies.append(reply); - QObject::connect(reply, &QRestReply::finished, lambdaHandler); + // without data, and fairly easy to get the qrestaccessmanager.h template SFINAE subtly wrong. // With lambda callback, without context object m_expectedReplies.append(manager.get(request, data, nullptr, lambdaHandler)); m_expectedReplies.append(manager.get(request, data, nullptr, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With lambda callback and context object m_expectedReplies.append(manager.get(request, data, this, lambdaHandler)); m_expectedReplies.append(manager.get(request, data, this, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With member callback and context object m_expectedReplies.append(manager.get(request, data, this, &tst_QRestAccessManager::memberHandler)); @@ -439,75 +406,30 @@ void tst_QRestAccessManager::callbacks() // Let requests finish QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); - QTRY_COMPARE(managerFinishedSpy.size(), m_actualReplies.size()); for (auto reply: m_actualReplies) { - QVERIFY(!reply->isSuccess()); - QVERIFY(reply->hasError()); - QCOMPARE(reply->error(), QNetworkReply::ProtocolUnknownError); - QCOMPARE(reply->isFinished(), true); - reply->deleteLater(); + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); + restReply.networkReply()->deleteLater(); } m_actualReplies.clear(); m_expectedReplies.clear(); - managerFinishedSpy.clear(); -} - -class RestWorker : public QObject -{ - Q_OBJECT -public: - explicit RestWorker(QObject *parent = nullptr) : QObject(parent) - { - m_manager = new QRestAccessManager(this); - } - QRestAccessManager *m_manager; - -public slots: - void issueRestRequest() - { - QNetworkRequest request{u"i_dont_exist"_s}; - m_manager->get(request, this, [this](QRestReply *reply){ - emit result(reply->body()); - }); - } -signals: - void result(const QByteArray &data); -}; - -void tst_QRestAccessManager::threading() -{ - // QRestAccessManager and QRestReply are only allowed to use in the thread they live in. - - // A "sanity test" for checking that there are no problems with running the QRestAM - // in another thread. - QThread restWorkThread; - RestWorker restWorker; - restWorker.moveToThread(&restWorkThread); - - QList<QByteArray> results; - QObject::connect(&restWorker, &RestWorker::result, this, [&](const QByteArray &data){ - results.append(data); - }); - restWorkThread.start(); - - QMetaObject::invokeMethod(&restWorker, &RestWorker::issueRestRequest); - QTRY_COMPARE(results.size(), 1); - restWorkThread.quit(); - restWorkThread.wait(); } void tst_QRestAccessManager::destruction() { - QRestAccessManager *manager = new QRestAccessManager; - manager->setDeletesRepliesOnFinished(false); // Don't autodelete so we can compare results later + std::unique_ptr<QNetworkAccessManager> qnam = std::make_unique<QNetworkAccessManager>(); + std::unique_ptr<QRestAccessManager> manager = std::make_unique<QRestAccessManager>(qnam.get()); QNetworkRequest request{u"i_dont_exist"_s}; // Will result in ProtocolUnknown error m_expectedReplies.clear(); m_actualReplies.clear(); - auto handler = [this](QRestReply *reply) { m_actualReplies.append(reply); }; + auto handler = [this](QRestReply &reply) { m_actualReplies.append(reply.networkReply()); }; // Delete reply immediately, make sure nothing bad happens and that there is no callback - QRestReply *reply = manager->get(request, this, handler); - delete reply; + QNetworkReply *networkReply = manager->get(request, this, handler); + delete networkReply; QTest::qWait(20); // allow some time for the callback to arrive (it shouldn't) QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 @@ -516,118 +438,39 @@ void tst_QRestAccessManager::destruction() manager->post(request, "data"_ba, this, handler); QTest::ignoreMessage(QtWarningMsg, "Access manager destroyed while 2 requests were still" " in progress"); - delete manager; + manager.reset(); QTest::qWait(20); QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 -} - -void tst_QRestAccessManager::authentication() -{ - // Test the case where server responds with '401' (authentication required). - // The QRestAM emits an authenticationRequired signal, which is used to the username/password. - // The QRestAM/QNAM underneath then automatically resends the request. - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); - HttpTestServer server; - QTRY_VERIFY(server.isListening()); - QNetworkRequest request(server.url()); - QRestReply *replyFromServer = nullptr; - - HttpData serverSideRequest; - server.setHandler([&](HttpData request, HttpData &response, ResponseControl&) { - if (!request.headers.contains(Header::Authorization)) { - response.status = 401; - response.headers.append(Header::WWWAuthenticate, "Basic realm=\"secret_place\""_ba); - } else { - response.status = 200; - } - serverSideRequest = request; // store for checking later the 'Authorization' header value - }); - QObject::connect(&manager, &QRestAccessManager::authenticationRequired, this, - [](QRestReply*, QAuthenticator *authenticator) { - authenticator->setUser(u"a_user"_s); - authenticator->setPassword(u"a_password"_s); - }); - - // Issue a GET request without any authorization data. - int finishedCount = 0; - manager.get(request, this, [&](QRestReply *reply) { - finishedCount++; - replyFromServer = reply; - }); - QTRY_VERIFY(replyFromServer); - // Server and QRestAM/QNAM exchange req/res twice, but finished() should be emitted just once - QCOMPARE(finishedCount, 1); - const auto resultHeaders = serverSideRequest.headers.values(Header::Authorization); - QVERIFY(!resultHeaders.empty()); - QCOMPARE(resultHeaders.first(), "Basic YV91c2VyOmFfcGFzc3dvcmQ="_ba); -} - -void tst_QRestAccessManager::userInfo() -{ - // Tests setting of username and password into the request factory - using ReplyPtr = std::unique_ptr<QRestReply, QScopedPointerDeleteLater>; - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); - HttpTestServer server; - QTRY_VERIFY(server.isListening()); - - QNetworkRequestFactory factory(server.url()); - factory.setUserName(u"a_user"_s); - const auto password = u"a_password"_s; - factory.setPassword(password); - - HttpData serverSideRequest; - server.setHandler([&](HttpData request, HttpData& response, ResponseControl&) { - if (!request.headers.contains(Header::Authorization)) { - response.status = 401; - response.headers.append(Header::WWWAuthenticate,"Basic realm=\"secret_place\""_ba); - } else { - response.status = 200; - } - serverSideRequest = request; // store for checking later the 'Authorization' header value - }); - - ReplyPtr reply(manager.get(factory.createRequest())); - QTRY_VERIFY(reply.get()->isFinished()); - QVERIFY(reply.get()->isSuccess()); - QCOMPARE(reply.get()->httpStatus(), 200); - const auto resultHeaders = serverSideRequest.headers.values(Header::Authorization); - QVERIFY(!resultHeaders.empty()); - QCOMPARE(resultHeaders.first(), "Basic YV91c2VyOmFfcGFzc3dvcmQ="_ba); - - // Verify that debug output does not contain password - QString debugOutput; - QDebug debug(&debugOutput); - debug << factory; - QVERIFY(debugOutput.contains("password = (is set)")); - QVERIFY(!debugOutput.contains(password)); + // Destroy the underlying QNAM while requests in progress + manager = std::make_unique<QRestAccessManager>(qnam.get()); + manager->get(request, this, handler); + manager->post(request, "data"_ba, this, handler); + qnam.reset(); + QTest::qWait(20); + QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 } #define VERIFY_HTTP_ERROR_STATUS(STATUS) \ +{ \ serverSideResponse.status = STATUS; \ - reply = manager.get(request); \ - QObject::connect(reply, &QRestReply::errorOccurred, this, \ - [&](){ errorSignalReceived = true; }); \ - QTRY_VERIFY(reply->isFinished()); \ - QVERIFY(!errorSignalReceived); \ - QVERIFY(!reply->hasError()); \ - QCOMPARE(reply->httpStatus(), serverSideResponse.status); \ - QCOMPARE(reply->error(), QNetworkReply::NetworkError::NoError); \ - QVERIFY(!reply->isSuccess()); \ - reply->deleteLater(); \ + QRestReply restReply(manager.get(request)); \ + QTRY_VERIFY(restReply.networkReply()->isFinished()); \ + QVERIFY(!restReply.hasError()); \ + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); \ + QCOMPARE(restReply.error(), QNetworkReply::NetworkError::NoError); \ + QVERIFY(!restReply.isSuccess()); \ + restReply.networkReply()->deleteLater(); \ +} \ void tst_QRestAccessManager::errors() { // Tests the distinction between HTTP and other (network/protocol) errors - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); QNetworkRequest request(server.url()); - QRestReply *reply = nullptr; - bool errorSignalReceived = false; HttpData serverSideResponse; // The response data the server responds with server.setHandler([&](HttpData, HttpData &response, ResponseControl &) { @@ -656,48 +499,45 @@ void tst_QRestAccessManager::errors() VERIFY_HTTP_ERROR_STATUS(504) // QNetworkReply::UnknownServerError VERIFY_HTTP_ERROR_STATUS(505) // QNetworkReply::UnknownServerError - // Test that actual network/protocol errors come through - reply = manager.get({}); // Empty url - QObject::connect(reply, &QRestReply::errorOccurred, this, [&](){ errorSignalReceived = true; }); - QTRY_VERIFY(reply->isFinished()); - QTRY_VERIFY(errorSignalReceived); - QVERIFY(reply->hasError()); - QVERIFY(!reply->isSuccess()); - QCOMPARE(reply->error(), QNetworkReply::ProtocolUnknownError); - reply->deleteLater(); - errorSignalReceived = false; - - reply = manager.get(QNetworkRequest{{"http://non-existent.foo.bar.test"}}); - QObject::connect(reply, &QRestReply::errorOccurred, this, [&](){ errorSignalReceived = true; }); - QTRY_VERIFY(reply->isFinished()); - QTRY_VERIFY(errorSignalReceived); - QVERIFY(reply->hasError()); - QVERIFY(!reply->isSuccess()); - QCOMPARE(reply->error(), QNetworkReply::HostNotFoundError); - reply->deleteLater(); - errorSignalReceived = false; - - reply = manager.get(request); - QObject::connect(reply, &QRestReply::errorOccurred, this, [&](){ errorSignalReceived = true; }); - reply->abort(); - QTRY_VERIFY(reply->isFinished()); - QTRY_VERIFY(errorSignalReceived); - QVERIFY(reply->hasError()); - QVERIFY(!reply->isSuccess()); - QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError); - reply->deleteLater(); - errorSignalReceived = false; + { + // Test that actual network/protocol errors come through + QRestReply restReply(manager.get({})); // Empty url + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + restReply.networkReply()->deleteLater(); + } + + { + QRestReply restReply(manager.get(QNetworkRequest{{"http://non-existent.foo.bar.test"}})); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::HostNotFoundError); + restReply.networkReply()->deleteLater(); + } + + { + QRestReply restReply(manager.get(request)); + restReply.networkReply()->abort(); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::OperationCanceledError); + restReply.networkReply()->deleteLater(); + } } void tst_QRestAccessManager::body() { // Test using QRestReply::body() data accessor - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); QNetworkRequest request(server.url()); - QRestReply *replyFromServer = nullptr; + QNetworkReply *networkReply = nullptr; HttpData serverSideRequest; // The request data the server received HttpData serverSideResponse; // The response data the server responds with @@ -706,46 +546,57 @@ void tst_QRestAccessManager::body() response = serverSideResponse; }); - serverSideResponse.status = 200; - serverSideResponse.body = "some_data"_ba; - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - QCOMPARE(replyFromServer->body(), serverSideResponse.body); - QCOMPARE(replyFromServer->httpStatus(), serverSideResponse.status); - QVERIFY(!replyFromServer->hasError()); - QVERIFY(replyFromServer->isSuccess()); - replyFromServer->deleteLater(); - replyFromServer = nullptr; - - serverSideResponse.body = ""_ba; // Empty - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - QCOMPARE(replyFromServer->body(), serverSideResponse.body); - replyFromServer->deleteLater(); - replyFromServer = nullptr; - - serverSideResponse.status = 500; - serverSideResponse.body = "some_other_data"_ba; - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - QCOMPARE(replyFromServer->body(), serverSideResponse.body); - QCOMPARE(replyFromServer->httpStatus(), serverSideResponse.status); - QVERIFY(!replyFromServer->hasError()); - QVERIFY(!replyFromServer->isSuccess()); - replyFromServer->deleteLater(); - replyFromServer = nullptr; + { + serverSideResponse.status = 200; + serverSideResponse.body = "some_data"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.body(), serverSideResponse.body); + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); + QVERIFY(!restReply.hasError()); + QVERIFY(restReply.isSuccess()); + networkReply->deleteLater(); + networkReply = nullptr; + } + + { + serverSideResponse.status = 200; + serverSideResponse.body = ""_ba; // Empty + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.body(), serverSideResponse.body); + networkReply->deleteLater(); + networkReply = nullptr; + } + + { + serverSideResponse.status = 500; + serverSideResponse.body = "some_other_data"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.body(), serverSideResponse.body); + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); + QVERIFY(!restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + networkReply->deleteLater(); + networkReply = nullptr; + } } void tst_QRestAccessManager::json() { // Test using QRestReply::json() and jsonArray() data accessors - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); QNetworkRequest request(server.url()); - QRestReply *replyFromServer = nullptr; + QNetworkReply *networkReply = nullptr; QJsonDocument responseJsonDocument; + std::optional<QJsonDocument> json; QJsonParseError parseError; HttpData serverSideRequest; // The request data the server received @@ -756,95 +607,98 @@ void tst_QRestAccessManager::json() response = serverSideResponse; }); - // Test receiving valid json object - serverSideResponse.body = "{\"key1\":\"value1\",""\"key2\":\"value2\"}\n"_ba; - replyFromServer = manager.get(request); - // Read unfinished reply - QVERIFY(!replyFromServer->isFinished()); - QTest::ignoreMessage(QtWarningMsg, "Attempt to read json() of an unfinished reply, ignoring."); - parseError.error = QJsonParseError::ParseError::DocumentTooLarge; // Reset to impossible value - QVERIFY(!replyFromServer->json(&parseError)); - QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); - // Read finished reply - QTRY_VERIFY(replyFromServer->isFinished()); - parseError.error = QJsonParseError::ParseError::DocumentTooLarge; - std::optional json = replyFromServer->json(&parseError); - QVERIFY(json); - QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); - responseJsonDocument = *json; - QVERIFY(responseJsonDocument.isObject()); - QCOMPARE(responseJsonDocument["key1"], "value1"); - QCOMPARE(responseJsonDocument["key2"], "value2"); - replyFromServer->deleteLater(); - replyFromServer = nullptr; - - // Test receiving an invalid json object - serverSideResponse.body = "foobar"_ba; - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - parseError.error = QJsonParseError::ParseError::DocumentTooLarge; - QVERIFY(!replyFromServer->json(&parseError).has_value()); // std::nullopt returned - QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::NoError); - QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::DocumentTooLarge); - QCOMPARE_GT(parseError.offset, 0); - replyFromServer->deleteLater(); - replyFromServer = nullptr; - - // Test receiving valid json array - serverSideResponse.body = "[\"foo\", \"bar\"]\n"_ba; - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - parseError.error = QJsonParseError::ParseError::DocumentTooLarge; - json = replyFromServer->json(&parseError); - QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); - QVERIFY(json); - responseJsonDocument = *json; - QVERIFY(responseJsonDocument.isArray()); - QCOMPARE(responseJsonDocument.array().size(), 2); - QCOMPARE(responseJsonDocument[0].toString(), "foo"_L1); - QCOMPARE(responseJsonDocument[1].toString(), "bar"_L1); - replyFromServer->deleteLater(); - replyFromServer = nullptr; - - // Test receiving an invalid json array - serverSideResponse.body = "foobar"_ba; - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - parseError.error = QJsonParseError::ParseError::DocumentTooLarge; - QVERIFY(!replyFromServer->json(&parseError).has_value()); // std::nullopt returned - QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::NoError); - QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::DocumentTooLarge); - QCOMPARE_GT(parseError.offset, 0); - replyFromServer->deleteLater(); - replyFromServer = nullptr; + { + // Test receiving valid json object + serverSideResponse.body = "{\"key1\":\"value1\",""\"key2\":\"value2\"}\n"_ba; + networkReply = manager.get(request); + // Read unfinished reply + QVERIFY(!networkReply->isFinished()); + QTest::ignoreMessage(QtWarningMsg, "Attempt to read json() of an unfinished reply, ignoring."); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; // Reset to impossible value + QRestReply restReply(networkReply); + QVERIFY(!restReply.json(&parseError)); + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + // Read finished reply + QTRY_VERIFY(networkReply->isFinished()); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + json = restReply.json(&parseError); + QVERIFY(json); + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + responseJsonDocument = *json; + QVERIFY(responseJsonDocument.isObject()); + QCOMPARE(responseJsonDocument["key1"], "value1"); + QCOMPARE(responseJsonDocument["key2"], "value2"); + networkReply->deleteLater(); + networkReply = nullptr; + } + + { + // Test receiving an invalid json object + serverSideResponse.body = "foobar"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + QVERIFY(!restReply.json(&parseError).has_value()); // std::nullopt returned + QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::NoError); + QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::DocumentTooLarge); + QCOMPARE_GT(parseError.offset, 0); + networkReply->deleteLater(); + networkReply = nullptr; + } + + { + // Test receiving valid json array + serverSideResponse.body = "[\"foo\", \"bar\"]\n"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + json = restReply.json(&parseError); + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + QVERIFY(json); + responseJsonDocument = *json; + QVERIFY(responseJsonDocument.isArray()); + QCOMPARE(responseJsonDocument.array().size(), 2); + QCOMPARE(responseJsonDocument[0].toString(), "foo"_L1); + QCOMPARE(responseJsonDocument[1].toString(), "bar"_L1); + networkReply->deleteLater(); + networkReply = nullptr; + } } #define VERIFY_TEXT_REPLY_OK \ - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); \ - QTRY_VERIFY(replyFromServer); \ - responseString = replyFromServer->text(); \ +{ \ + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); \ + QTRY_VERIFY(networkReply); \ + QRestReply restReply(networkReply); \ + responseString = restReply.text(); \ QCOMPARE(responseString, sourceString); \ - replyFromServer->deleteLater(); \ - replyFromServer = nullptr; \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ +} #define VERIFY_TEXT_REPLY_ERROR(WARNING_MESSAGE) \ - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); \ - QTRY_VERIFY(replyFromServer); \ +{ \ + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); \ + QTRY_VERIFY(networkReply); \ QTest::ignoreMessage(QtWarningMsg, WARNING_MESSAGE); \ - responseString = replyFromServer->text(); \ + QRestReply restReply(networkReply); \ + responseString = restReply.text(); \ QVERIFY(responseString.isEmpty()); \ - replyFromServer->deleteLater(); \ - replyFromServer = nullptr; \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ +} void tst_QRestAccessManager::text() { // Test using QRestReply::text() data accessor with various text encodings - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); QNetworkRequest request(server.url()); - QRestReply *replyFromServer = nullptr; + QNetworkReply *networkReply = nullptr; QJsonObject responseJsonObject; QStringEncoder encUTF8("UTF-8"); @@ -899,16 +753,19 @@ void tst_QRestAccessManager::text() serverSideResponse.body = encUTF32(sourceString); VERIFY_TEXT_REPLY_OK; - // Unsuccessful UTF-32, wrong encoding indicated (indicated charset UTF-32 but data is UTF-8) - serverSideResponse.headers.removeAll(Header::ContentType); - serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-32"_ba); - serverSideResponse.body = encUTF8(sourceString); - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - responseString = replyFromServer->text(); - QCOMPARE_NE(responseString, sourceString); - replyFromServer->deleteLater(); - replyFromServer = nullptr; + { + // Unsuccessful UTF-32, wrong encoding indicated (indicated UTF-32 but data is UTF-8) + serverSideResponse.headers.removeAll(Header::ContentType); + serverSideResponse.headers.append(Header::ContentType, "text/plain; charset=UTF-32"_ba); + serverSideResponse.body = encUTF8(sourceString); + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + responseString = restReply.text(); + QCOMPARE_NE(responseString, sourceString); + networkReply->deleteLater(); + networkReply = nullptr; + } // Unsupported encoding serverSideResponse.headers.removeAll(Header::ContentType); @@ -926,10 +783,11 @@ void tst_QRestAccessManager::text() void tst_QRestAccessManager::textStreaming() { // Tests textual data received in chunks - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); // Create long text data const QString expectedData = u"사랑abcd€fghiklmnΩpqrstuvwx愛사랑A사랑BCD€FGHIJKLMNΩPQRsTUVWXYZ愛"_s; @@ -949,143 +807,37 @@ void tst_QRestAccessManager::textStreaming() control.responseChunkSize = 5; // tell testserver to send data in chunks of this size }); - QNetworkRequest request(server.url()); - QRestReply *reply = manager.get(request); - QObject::connect(reply, &QRestReply::readyRead, this, [&](QRestReply *reply) { - cumulativeReceivedText += reply->text(); - // Tell testserver that test is ready for next chunk - responseControl->readyForNextChunk = true; - }); - QTRY_VERIFY(reply->isFinished()); - QCOMPARE(cumulativeReceivedText, expectedData); - - cumulativeReceivedText.clear(); - // Broken UTF-8 characters after first five ok characters - serverSideResponse.body = - "12345"_ba + "\xF0\x28\x8C\x28\xA0\xB0\xC0\xD0" + "abcde"_ba; - reply = manager.get(request); - QObject::connect(reply, &QRestReply::readyRead, this, [&](QRestReply *reply) { - static bool firstTime = true; - if (!firstTime) // First text part is without warnings - QTest::ignoreMessage(QtWarningMsg, "text() Decoding error occurred"); - firstTime = false; - cumulativeReceivedText += reply->text(); - // Tell testserver that test is ready for next chunk - responseControl->readyForNextChunk = true; - }); - QTRY_VERIFY(reply->isFinished()); - QCOMPARE(cumulativeReceivedText, "12345"_ba); -} - -void tst_QRestAccessManager::download() -{ - // Test case where data is received in chunks. - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); - HttpTestServer server; - QTRY_VERIFY(server.isListening()); - QNetworkRequest request(server.url()); - HttpData serverSideResponse; // The response data the server responds with - constexpr qsizetype dataSize = 1 * 1024 * 1024; // 1 MB - QByteArray expectedData{dataSize, 0}; - for (qsizetype i = 0; i < dataSize; ++i) // initialize the data we download - expectedData[i] = i % 100; - QByteArray cumulativeReceivedData; - qsizetype cumulativeReceivedBytesAvailable = 0; - - serverSideResponse.body = expectedData; - ResponseControl *responseControl = nullptr; - serverSideResponse.status = 200; - // Set content-length header so that underlying QNAM is able to report bytesTotal correctly - serverSideResponse.headers.append(Header::ContentType, - QString::number(expectedData.size()).toLatin1()); - server.setHandler([&](HttpData, HttpData &response, ResponseControl &control) { - response = serverSideResponse; - responseControl = &control; // store for later - control.responseChunkSize = 1024; // tell testserver to send data in chunks of this size - }); - - QRestReply* reply = manager.get(request, this, [&responseControl](QRestReply */*reply*/){ - responseControl = nullptr; // all finished, no more need for controlling the response - }); - - QObject::connect(reply, &QRestReply::readyRead, this, [&](QRestReply *reply) { - static bool testOnce = true; - if (!reply->isFinished() && testOnce) { - // Test once that reading json of an unfinished reply will not work - testOnce = false; - QTest::ignoreMessage(QtWarningMsg, "Attempt to read json() of an unfinished" - " reply, ignoring."); - reply->json(); - } - - cumulativeReceivedBytesAvailable += reply->bytesAvailable(); - cumulativeReceivedData += reply->body(); - // Tell testserver that test is ready for next chunk - responseControl->readyForNextChunk = true; - }); - - qint64 totalBytes = 0; - qint64 receivedBytes = 0; - QObject::connect(reply, &QRestReply::downloadProgress, this, - [&](qint64 bytesReceived, qint64 bytesTotal) { - if (totalBytes == 0 && bytesTotal > 0) - totalBytes = bytesTotal; - receivedBytes = bytesReceived; - }); - QTRY_VERIFY(reply->isFinished()); - reply->deleteLater(); - reply = nullptr; - // Checks specific for readyRead() and bytesAvailable() - QCOMPARE(cumulativeReceivedData, expectedData); - QCOMPARE(cumulativeReceivedBytesAvailable, expectedData.size()); - // Checks specific for downloadProgress() - QCOMPARE(totalBytes, expectedData.size()); - QCOMPARE(receivedBytes, expectedData.size()); -} - -void tst_QRestAccessManager::upload() -{ - // This test tests uploadProgress signal - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); - HttpTestServer server; - QTRY_VERIFY(server.isListening()); - QNetworkRequest request(server.url()); - request.setRawHeader("Content-Type"_ba, "text/plain"); // To silence missing content-type warn - QByteArray expectedData{1 * 1024 * 1024, 0}; // 1 MB - server.setHandler([&](HttpData, HttpData &, ResponseControl &) {}); - - QRestReply* reply = manager.post(request, expectedData); - QSignalSpy uploadProgressSpy(reply, &QRestReply::uploadProgress); - QTRY_VERIFY(reply->isFinished()); - QVERIFY(!uploadProgressSpy.isEmpty()); - reply->deleteLater(); - - // Check that bytesTotal is correct already in the first signal - const QList<QVariant> first = uploadProgressSpy.first(); - QCOMPARE(first.size(), 3); - QCOMPARE(first.at(1).toLongLong(), expectedData.size()); - - // Check that we sent all bytes - const QList<QVariant> last = uploadProgressSpy.last(); - QCOMPARE(last.size(), 3); - QEXPECT_FAIL("", "Fails due to QTBUG-44782", Continue); - QCOMPARE(last.at(0).toLongLong(), expectedData.size()); -} - -void tst_QRestAccessManager::timeout() -{ - constexpr auto defaultTimeout = 0ms; - constexpr auto timeout = 150ms; - - QRestAccessManager manager; - QCOMPARE(manager.transferTimeout(), defaultTimeout); - QCOMPARE(manager.networkAccessManager()->transferTimeoutAsDuration(), defaultTimeout); + { + QRestReply restReply(manager.get(request)); + QObject::connect(restReply.networkReply(), &QNetworkReply::readyRead, this, [&]() { + cumulativeReceivedText += restReply.text(); + // Tell testserver that test is ready for next chunk + responseControl->readyForNextChunk = true; + }); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QCOMPARE(cumulativeReceivedText, expectedData); + restReply.networkReply()->deleteLater(); + } - manager.setTransferTimeout(timeout); - QCOMPARE(manager.transferTimeout(), timeout); - QCOMPARE(manager.networkAccessManager()->transferTimeoutAsDuration(), timeout); + { + cumulativeReceivedText.clear(); + // Broken UTF-8 characters after first five ok characters + serverSideResponse.body = + "12345"_ba + "\xF0\x28\x8C\x28\xA0\xB0\xC0\xD0" + "abcde"_ba; + QRestReply restReply(manager.get(request)); + QObject::connect(restReply.networkReply(), &QNetworkReply::readyRead, this, [&]() { + static bool firstTime = true; + if (!firstTime) // First text part is without warnings + QTest::ignoreMessage(QtWarningMsg, "text() Decoding error occurred"); + firstTime = false; + cumulativeReceivedText += restReply.text(); + // Tell testserver that test is ready for next chunk + responseControl->readyForNextChunk = true; + }); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QCOMPARE(cumulativeReceivedText, "12345"_ba); + restReply.networkReply()->deleteLater(); + } } QTEST_MAIN(tst_QRestAccessManager) |