diff options
author | Adrien Leravat <aleravat@witekio.com> | 2018-08-24 04:42:50 +0000 |
---|---|---|
committer | Sona Kurazyan <sona.kurazyan@qt.io> | 2018-12-14 12:20:26 +0000 |
commit | 2b3755c8e6587c6a720dd8dcfaafca6e2ba755a9 (patch) | |
tree | 12c2287fba0edee83e5153ca6ab4574d14d78f23 /tests | |
parent | aeb607e972af3be78cc25435a52a1fbd900fb8af (diff) |
Add the CoAP module, providing a client for Qt
Features:
- Send GET/POST/PUT/DELETE requests
- Discover resources (single server)
- Observe resources and cancel the observation
- Blockwise requests and replies
- Requests (and replies) can be confirmable or non-confirmable
- Some options can be added to the request
- Replies can be received in a separate or piggybacked message
Change-Id: I31e0e20a4f19bdc6d6489281fde73a4ff848eda4
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'tests')
24 files changed, 2270 insertions, 0 deletions
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro new file mode 100644 index 0000000..a6f3a48 --- /dev/null +++ b/tests/auto/auto.pro @@ -0,0 +1,14 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + cmake \ +# TODO: enable the tests below, when CI is configured properly +# qcoapclient \ +# qcoapconnection \ + qcoapinternalreply \ + qcoapinternalrequest \ + qcoapmessage \ + qcoapoption \ + qcoapreply \ + qcoaprequest \ + qcoapresource diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt new file mode 100644 index 0000000..5f7dd02 --- /dev/null +++ b/tests/auto/cmake/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.8) + +project(qmake_cmake_files) + +enable_testing() + +find_package(Qt5Core REQUIRED) + +include("${_Qt5CTestMacros}") + +test_module_includes( + Coap QCoapClient +) diff --git a/tests/auto/cmake/cmake.pro b/tests/auto/cmake/cmake.pro new file mode 100644 index 0000000..6e5acb5 --- /dev/null +++ b/tests/auto/cmake/cmake.pro @@ -0,0 +1,6 @@ +# Cause make to do nothing. +TEMPLATE = subdirs + +CMAKE_QT_MODULES_UNDER_TEST = coap + +CONFIG += ctest_testcase diff --git a/tests/auto/coapnetworksettings.h b/tests/auto/coapnetworksettings.h new file mode 100644 index 0000000..0e1ee04 --- /dev/null +++ b/tests/auto/coapnetworksettings.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Witekio. +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCoap module. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QtCore/qstring.h> +#include <QtNetwork/qhostinfo.h> + +/*! + \internal + + This namespace provides URL and settings used in QtCoap tests. + + Tests require a Californium plugtest server, accessible with + "coap-plugtest-server" host name. You create such server with Docker and + the following command line: + \code + docker run -d --rm -p 5683:5683/udp aleravat/coap-test-server:latest + \endcode + + For more details, see + \l{https://github.com/Pixep/coap-testserver-docker}{https://github.com/Pixep/coap-testserver-docker}. +*/ +namespace QtCoapNetworkSettings +{ + QString testServerHost() + { +#if defined(COAP_TEST_SERVER_IP) + return QStringLiteral(COAP_TEST_SERVER_IP); +#else + static_assert(false, "COAP_TEST_SERVER_IP variable must be set"); +#endif + } + + QString testServerUrl() + { + return QStringLiteral("coap://") + testServerHost() + QStringLiteral(":") + + QString::number(QtCoap::DefaultPort); + } + + QString testServerResource() + { + return testServerUrl() + QStringLiteral("/test"); + } +} diff --git a/tests/auto/coaptestserver.pri b/tests/auto/coaptestserver.pri new file mode 100644 index 0000000..420aa89 --- /dev/null +++ b/tests/auto/coaptestserver.pri @@ -0,0 +1,7 @@ +COAP_TEST_SERVER_IP = $$(COAP_TEST_SERVER_IP) +isEmpty( COAP_TEST_SERVER_IP ) { + error(No IP set for CoAP plugtest server. Please set COAP_TEST_SERVER_IP environment variable to run this test.) +} + +DEFINES += COAP_TEST_SERVER_IP=\\\"$${COAP_TEST_SERVER_IP}\\\" +message(CoAP plugtest server IP set to $$COAP_TEST_SERVER_IP) diff --git a/tests/auto/qcoapclient/qcoapclient.pro b/tests/auto/qcoapclient/qcoapclient.pro new file mode 100644 index 0000000..5c7fe1e --- /dev/null +++ b/tests/auto/qcoapclient/qcoapclient.pro @@ -0,0 +1,8 @@ +QT = testlib core-private network core coap coap-private +CONFIG += testcase + +include(../coaptestserver.pri) + +HEADERS += ../coapnetworksettings.h + +SOURCES += tst_qcoapclient.cpp diff --git a/tests/auto/qcoapclient/tst_qcoapclient.cpp b/tests/auto/qcoapclient/tst_qcoapclient.cpp new file mode 100644 index 0000000..1e4dbe2 --- /dev/null +++ b/tests/auto/qcoapclient/tst_qcoapclient.cpp @@ -0,0 +1,723 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Witekio. +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCoap module. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QCoreApplication> + +#include <QtCoap/qcoapclient.h> +#include <QtCoap/qcoaprequest.h> +#include <QtCoap/qcoapreply.h> +#include <QtCoap/qcoapdiscoveryreply.h> +#include <QtCore/qbuffer.h> +#include <QtNetwork/qnetworkdatagram.h> +#include <private/qcoapclient_p.h> +#include <private/qcoapconnection_p.h> + +#include "../coapnetworksettings.h" + +using namespace QtCoapNetworkSettings; + +class tst_QCoapClient : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void incorrectUrls_data(); + void incorrectUrls(); + void methods_data(); + void methods(); + void separateMethod(); + void socketError(); + void timeout_data(); + void timeout(); + void abort(); + void removeReply(); + void setBlockSize_data(); + void setBlockSize(); + void requestWithQIODevice_data(); + void requestWithQIODevice(); + void multipleRequests(); + void blockwiseReply_data(); + void blockwiseReply(); + void blockwiseRequest_data(); + void blockwiseRequest(); + void discover_data(); + void discover(); + void observe_data(); + void observe(); +}; + +class QCoapConnectionSocketTestsPrivate : public QCoapConnectionPrivate +{ + bool bind() override + { + // Force a socket binding error + QUdpSocket anotherSocket; + anotherSocket.bind(QHostAddress::Any, 6080); + return socket()->bind(QHostAddress::Any, 6080); + } +}; + +class QCoapConnectionSocketTests : public QCoapConnection +{ +public: + QCoapConnectionSocketTests() : + QCoapConnection(*new QCoapConnectionSocketTestsPrivate) + { + createSocket(); + } + +private: + Q_DECLARE_PRIVATE(QCoapConnectionSocketTests) +}; + +class QCoapClientForSocketErrorTests : public QCoapClient +{ +public: + QCoapClientForSocketErrorTests() : + QCoapClient(new QCoapProtocol, new QCoapConnectionSocketTests) + {} + + QCoapConnection *connection() + { + QCoapClientPrivate *privateClient = static_cast<QCoapClientPrivate *>(d_func()); + return privateClient->connection; + } +}; + +class QCoapClientForTests : public QCoapClient +{ +public: + QCoapClientForTests() {} + QCoapClientForTests(QCoapProtocol *protocol, QCoapConnection *connection) : + QCoapClient(protocol, connection) + {} + + QCoapProtocol *protocol() + { + QCoapClientPrivate *privateClient = static_cast<QCoapClientPrivate *>(d_func()); + return privateClient->protocol; + } + QCoapConnection *connection() + { + QCoapClientPrivate *privateClient = static_cast<QCoapClientPrivate *>(d_func()); + return privateClient->connection; + } +}; + +class Helper : public QObject +{ + Q_OBJECT +public: + Helper() {} + +public slots: + void onError(QCoapReply *, QtCoap::Error error) + { + qWarning() << "Network error" << error << "occurred"; + } +}; + +void tst_QCoapClient::incorrectUrls_data() +{ + QWARN("Expect warnings here..."); + QTest::addColumn<QUrl>("url"); + + QTest::newRow("get") << QUrl("wrong://10.20.30.40:5683/test"); + QTest::newRow("post") << QUrl("wrong://10.20.30.40:5683/test"); + QTest::newRow("put") << QUrl("wrong://10.20.30.40:5683/test"); + QTest::newRow("delete") << QUrl("wrong://10.20.30.40:5683/test"); + QTest::newRow("discover") << QUrl("wrong://10.20.30.40:5683/test"); +} + +void tst_QCoapClient::incorrectUrls() +{ + QFETCH(QUrl, url); + + QCoapClient client; + QScopedPointer<QCoapReply> reply; + + if (qstrcmp(QTest::currentDataTag(), "get") == 0) + reply.reset(client.get(url)); + else if (qstrcmp(QTest::currentDataTag(), "post") == 0) + reply.reset(client.post(url)); + else if (qstrcmp(QTest::currentDataTag(), "put") == 0) + reply.reset(client.put(url)); + else if (qstrcmp(QTest::currentDataTag(), "delete") == 0) + reply.reset(client.deleteResource(url)); + else if (qstrcmp(QTest::currentDataTag(), "discover") == 0) + reply.reset(client.discover(url)); + else { + QString error = QLatin1Literal("Unrecognized method '") + QTest::currentDataTag() + "'"; + QFAIL(qPrintable(error)); + } + + QVERIFY2(reply.isNull(), "Request did not fail as expected."); +} + +void tst_QCoapClient::methods_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QtCoap::Method>("method"); + + QTest::newRow("get_no_op") << QUrl(testServerResource()) << QtCoap::Invalid; + QTest::newRow("get") << QUrl(testServerResource()) << QtCoap::Get; + QTest::newRow("get_incorrect_op") << QUrl(testServerResource()) << QtCoap::Put; + QTest::newRow("get_no_port") + << QUrl("coap://" + testServerHost() + "/test") << QtCoap::Get; + QTest::newRow("get_no_scheme_no_port") << QUrl(testServerHost() + "/test") << QtCoap::Get; + QTest::newRow("post_no_op") << QUrl(testServerResource()) << QtCoap::Invalid; + QTest::newRow("post") << QUrl(testServerResource()) << QtCoap::Post; + QTest::newRow("post_incorrect_op") << QUrl(testServerResource()) << QtCoap::Delete; + QTest::newRow("put_no_op") << QUrl(testServerResource()) << QtCoap::Invalid; + QTest::newRow("put") << QUrl(testServerResource()) << QtCoap::Put; + QTest::newRow("put_incorrect_op") << QUrl(testServerResource()) << QtCoap::Post; + QTest::newRow("delete_no_op") << QUrl(testServerResource()) << QtCoap::Invalid; + QTest::newRow("delete") << QUrl(testServerResource()) << QtCoap::Delete; + QTest::newRow("delete_incorrect_op") << QUrl(testServerResource()) << QtCoap::Get; +} + +void tst_QCoapClient::methods() +{ + QFETCH(QUrl, url); + QFETCH(QtCoap::Method, method); + + QCoapClient client; + + QCoapRequest request(url); + if (method != QtCoap::Invalid) + request.setMethod(method); + + QSignalSpy spyClientFinished(&client, SIGNAL(finished(QCoapReply *))); + + QScopedPointer<QCoapReply> reply; + if (qstrncmp(QTest::currentDataTag(), "get", 3) == 0) + reply.reset(client.get(request)); + else if (qstrncmp(QTest::currentDataTag(), "post", 4) == 0) + reply.reset(client.post(request)); + else if (qstrncmp(QTest::currentDataTag(), "put", 3) == 0) + reply.reset(client.put(request)); + else if (qstrncmp(QTest::currentDataTag(), "delete", 6) == 0) + reply.reset(client.deleteResource(request)); + else { + QString error = QLatin1Literal("Unrecognized method '") + QTest::currentDataTag() + "'"; + QFAIL(qPrintable(error)); + } + + QVERIFY2(!reply.isNull(), "Request failed unexpectedly"); + QSignalSpy spyReplyFinished(reply.data(), SIGNAL(finished(QCoapReply *))); + QTRY_COMPARE(spyReplyFinished.count(), 1); + QTRY_COMPARE(spyClientFinished.count(), 1); + + QByteArray replyData; + if (!reply.isNull()) { + replyData = reply->readAll(); + if (qstrncmp(QTest::currentDataTag(), "get", 3) == 0) { + QVERIFY(!replyData.isEmpty()); + QCOMPARE(reply->responseCode(), QtCoap::Content); + } else if (qstrncmp(QTest::currentDataTag(), "post", 4) == 0) { + QVERIFY(replyData.isEmpty()); + QCOMPARE(reply->responseCode(), QtCoap::Created); + } else if (qstrncmp(QTest::currentDataTag(), "put", 3) == 0) { + QVERIFY(replyData.isEmpty()); + QCOMPARE(reply->responseCode(), QtCoap::Changed); + } else if (qstrncmp(QTest::currentDataTag(), "delete", 6) == 0) { + QVERIFY(replyData.isEmpty()); + QCOMPARE(reply->responseCode(), QtCoap::Deleted); + } else { + QString error = QLatin1Literal("Unrecognized method '") + QTest::currentDataTag() + "'"; + QFAIL(qPrintable(error)); + } + } +} + +void tst_QCoapClient::separateMethod() +{ + QCoapClient client; + QScopedPointer<QCoapReply> reply(client.get(QUrl(testServerUrl() + "/separate"))); + + QVERIFY2(!reply.isNull(), "Request failed unexpectedly"); + QSignalSpy spyReplyFinished(reply.data(), SIGNAL(finished(QCoapReply *))); + QTRY_COMPARE(spyReplyFinished.count(), 1); + + QByteArray replyData = reply->readAll(); + + QVERIFY(!replyData.isEmpty()); + QCOMPARE(reply->responseCode(), QtCoap::Content); +} + +void tst_QCoapClient::removeReply() +{ + QCoapClient client; + QCoapReply *reply = client.get(QUrl(testServerResource())); + QVERIFY2(reply != nullptr, "Request failed unexpectedly"); + + try { + reply->deleteLater(); + + QEventLoop eventLoop; + QTimer::singleShot(2000, &eventLoop, &QEventLoop::quit); + eventLoop.exec(); + } catch (...) { + QFAIL("Exception occurred after destroying the QCoapReply"); + } +} + +void tst_QCoapClient::setBlockSize_data() +{ + QTest::addColumn<int>("blockSizeSet"); + QTest::addColumn<int>("blockSizeExpected"); + + QTest::newRow("valid_size_0") << 0 << 0; + QTest::newRow("valid_size_16") << 16 << 16; + QTest::newRow("valid_size_1024") << 1024 << 1024; + QTest::newRow("invalid_size_8") << 8 << 0; + QTest::newRow("invalid_size_350") << 350 << 0; + QTest::newRow("invalid_size_2048") << 2048 << 0; +} + +void tst_QCoapClient::setBlockSize() +{ + QFETCH(int, blockSizeSet); + QFETCH(int, blockSizeExpected); + + QCoapClientForTests client; + client.setBlockSize(blockSizeSet); + + QEventLoop eventLoop; + QTimer::singleShot(1000, &eventLoop, &QEventLoop::quit); + eventLoop.exec(); + + QCOMPARE(client.protocol()->blockSize(), blockSizeExpected); +} + +void tst_QCoapClient::requestWithQIODevice_data() +{ + QTest::addColumn<QUrl>("url"); + + QTest::newRow("post") << QUrl(testServerResource()); + QTest::newRow("put") << QUrl(testServerResource()); +} + +void tst_QCoapClient::requestWithQIODevice() +{ + QFETCH(QUrl, url); + + QCoapClient client; + QCoapRequest request(url); + + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + buffer.write("Some data"); + + QScopedPointer<QCoapReply> reply; + if (qstrcmp(QTest::currentDataTag(), "post") == 0) + reply.reset(client.post(request, &buffer)); + else + reply.reset(client.put(request, &buffer)); + + QVERIFY2(!reply.isNull(), "Request failed unexpectedly"); + QSignalSpy spyReplyFinished(reply.data(), SIGNAL(finished(QCoapReply *))); + QTRY_COMPARE(spyReplyFinished.count(), 1); + + QByteArray replyData = reply->readAll(); + + if (qstrcmp(QTest::currentDataTag(), "post") == 0) { + QVERIFY(replyData.isEmpty()); + QCOMPARE(reply->responseCode(), QtCoap::Created); + } else if (qstrcmp(QTest::currentDataTag(), "put") == 0) { + QVERIFY(replyData.isEmpty()); + QCOMPARE(reply->responseCode(), QtCoap::Changed); + } +} + +void tst_QCoapClient::multipleRequests() +{ + QCoapClient client; + QUrl url = QUrl(testServerResource()); + QSignalSpy spyClientFinished(&client, SIGNAL(finished(QCoapReply *))); + + QScopedPointer<QCoapReply> replyGet1(client.get(url)); + QScopedPointer<QCoapReply> replyGet2(client.get(url)); + QScopedPointer<QCoapReply> replyGet3(client.get(url)); + QScopedPointer<QCoapReply> replyGet4(client.get(url)); + + QVERIFY2(!replyGet1.isNull(), "Request failed unexpectedly"); + QVERIFY2(!replyGet2.isNull(), "Request failed unexpectedly"); + QVERIFY2(!replyGet3.isNull(), "Request failed unexpectedly"); + QVERIFY2(!replyGet4.isNull(), "Request failed unexpectedly"); + + QSignalSpy spyReplyGet1Finished(replyGet1.data(), SIGNAL(finished(QCoapReply *))); + QSignalSpy spyReplyGet2Finished(replyGet2.data(), SIGNAL(finished(QCoapReply *))); + QSignalSpy spyReplyGet3Finished(replyGet3.data(), SIGNAL(finished(QCoapReply *))); + QSignalSpy spyReplyGet4Finished(replyGet4.data(), SIGNAL(finished(QCoapReply *))); + + QTRY_COMPARE(spyReplyGet1Finished.count(), 1); + QTRY_COMPARE(spyReplyGet2Finished.count(), 1); + QTRY_COMPARE(spyReplyGet3Finished.count(), 1); + QTRY_COMPARE(spyReplyGet4Finished.count(), 1); + QTRY_COMPARE(spyClientFinished.count(), 4); + + QByteArray replyData1 = replyGet1->readAll(); + QByteArray replyData2 = replyGet2->readAll(); + QByteArray replyData3 = replyGet3->readAll(); + QByteArray replyData4 = replyGet4->readAll(); + + QCOMPARE(replyGet1->responseCode(), QtCoap::Content); + QCOMPARE(replyGet2->responseCode(), QtCoap::Content); + QCOMPARE(replyGet3->responseCode(), QtCoap::Content); + QCOMPARE(replyGet4->responseCode(), QtCoap::Content); + + QVERIFY(replyData1 != replyData2); + QVERIFY(replyData1 != replyData3); + QVERIFY(replyData1 != replyData4); + QVERIFY(replyData2 != replyData3); + QVERIFY(replyData2 != replyData4); + QVERIFY(replyData3 != replyData4); +} + +void tst_QCoapClient::socketError() +{ +#ifdef QT_BUILD_INTERNAL + QCoapClientForSocketErrorTests client; + QUrl url = QUrl(testServerResource()); + + QUdpSocket *socket = client.connection()->socket(); + QVERIFY2(socket, "Socket not properly created with connection"); + QSignalSpy spySocketError(socket, SIGNAL(error(QAbstractSocket::SocketError))); + QScopedPointer<QCoapReply> reply(client.get(url)); + QSignalSpy spyClientError(&client, &QCoapClient::error); + + QTRY_COMPARE_WITH_TIMEOUT(spySocketError.count(), 1, 10000); + QTRY_COMPARE_WITH_TIMEOUT(spyClientError.count(), 1, 1000); + QCOMPARE(spyClientError.first().at(1), QtCoap::AddressInUseError); +#else + QSKIP("Not an internal build, skipping this test"); +#endif +} +void tst_QCoapClient::timeout_data() +{ + QTest::addColumn<int>("timeout"); + QTest::addColumn<int>("maxRetransmit"); + + QTest::newRow("2000/0") << 2000 << 0; + QTest::newRow("2000/2") << 2000 << 2; + QTest::newRow("4000/0") << 4000 << 0; +} + +void tst_QCoapClient::timeout() +{ + QFETCH(int, timeout); + QFETCH(int, maxRetransmit); + + QCoapClientForTests client; + // Trigger a network timeout + client.protocol()->setAckTimeout(timeout); + client.protocol()->setAckRandomFactor(1); + client.protocol()->setMaxRetransmit(maxRetransmit); + QUrl url = QUrl("coap://240.0.0.0:5683/"); // Need an url that returns nothing + + QElapsedTimer timeoutTimer; + timeoutTimer.start(); + QScopedPointer<QCoapReply> reply(client.get(QCoapRequest(url, QCoapMessage::Confirmable))); + QSignalSpy spyClientError(&client, &QCoapClient::error); + QSignalSpy spyReplyError(reply.data(), &QCoapReply::error); + QSignalSpy spyReplyAborted(reply.data(), &QCoapReply::aborted); + QSignalSpy spyReplyFinished(reply.data(), &QCoapReply::finished); + + // Check timeout upper limit + int transmissions = maxRetransmit + 1; + + // 10% Precision expected at least, plus timer precision + QTRY_COMPARE_WITH_TIMEOUT(spyReplyError.count(), 1, static_cast<int>( + 1.1 * client.protocol()->maxTransmitWait() + 20 * transmissions)); + + // Check timeout lower limit + qint64 elapsedTime = timeoutTimer.elapsed(); + QString errorMessage = QString("Timeout was triggered after %1ms, while expecting about %2ms") + .arg(QString::number(elapsedTime), + QString::number(client.protocol()->maxTransmitWait())); + + // 10% Precision expected at least, minus timer precision + QVERIFY2(elapsedTime > 0.9 * client.protocol()->maxTransmitWait() - 20 * transmissions, + qPrintable(errorMessage)); + + QCOMPARE(spyReplyError.first().at(1), QtCoap::TimeOutError); + QCOMPARE(spyReplyFinished.count(), 1); + QCOMPARE(spyReplyAborted.count(), 0); + QCOMPARE(spyClientError.count(), 1); +} + +void tst_QCoapClient::abort() +{ + QCoapClient client; + QUrl url = QUrl(testServerUrl() + "/large"); + + QScopedPointer<QCoapReply> reply(client.get(url)); + QSignalSpy spyReplyFinished(reply.data(), &QCoapReply::finished); + QSignalSpy spyReplyAborted(reply.data(), &QCoapReply::aborted); + QSignalSpy spyReplyError(reply.data(), &QCoapReply::error); + + reply->abortRequest(); + + QEventLoop eventLoop; + QTimer::singleShot(1000, &eventLoop, &QEventLoop::quit); + eventLoop.exec(); + + QCOMPARE(spyReplyAborted.count(), 1); + QCOMPARE(spyReplyFinished.count(), 1); + QCOMPARE(spyReplyError.count(), 0); +} + +void tst_QCoapClient::blockwiseReply_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QCoapMessage::MessageType>("type"); + QTest::addColumn<QByteArray>("replyData"); + + QByteArray data; + data.append("/-------------------------------------------------------------\\\n"); + data.append("| RESOURCE BLOCK NO. 1 OF 5 |\n"); + data.append("| [each line contains 64 bytes] |\n"); + data.append("\\-------------------------------------------------------------/\n"); + data.append("/-------------------------------------------------------------\\\n"); + data.append("| RESOURCE BLOCK NO. 2 OF 5 |\n"); + data.append("| [each line contains 64 bytes] |\n"); + data.append("\\-------------------------------------------------------------/\n"); + data.append("/-------------------------------------------------------------\\\n"); + data.append("| RESOURCE BLOCK NO. 3 OF 5 |\n"); + data.append("| [each line contains 64 bytes] |\n"); + data.append("\\-------------------------------------------------------------/\n"); + data.append("/-------------------------------------------------------------\\\n"); + data.append("| RESOURCE BLOCK NO. 4 OF 5 |\n"); + data.append("| [each line contains 64 bytes] |\n"); + data.append("\\-------------------------------------------------------------/\n"); + data.append("/-------------------------------------------------------------\\\n"); + data.append("| RESOURCE BLOCK NO. 5 OF 5 |\n"); + data.append("| [each line contains 64 bytes] |\n"); + data.append("\\-------------------------------------------------------------/\n"); + + QTest::newRow("get_large") + << QUrl(testServerUrl() + "/large") + << QCoapMessage::NonConfirmable + << data; + QTest::newRow("get_large_separate") + << QUrl(testServerUrl() + "/large-separate") + << QCoapMessage::NonConfirmable + << data; + QTest::newRow("get_large_confirmable") + << QUrl(testServerUrl() + "/large") + << QCoapMessage::Confirmable + << data; + QTest::newRow("get_large_separate_confirmable") + << QUrl(testServerUrl() + "/large-separate") + << QCoapMessage::Confirmable + << data; + QTest::newRow("get_large_16bits") + << QUrl(testServerUrl() + "/large") + << QCoapMessage::NonConfirmable + << data; + QTest::newRow("get_large_16bits_confirmable") + << QUrl(testServerUrl() + "/large") + << QCoapMessage::Confirmable + << data; +} + +void tst_QCoapClient::blockwiseReply() +{ + QFETCH(QUrl, url); + QFETCH(QCoapMessage::MessageType, type); + QFETCH(QByteArray, replyData); + + QCoapClient client; + QCoapRequest request(url); + + if (qstrncmp(QTest::currentDataTag(), "get_large_16bits", 16) == 0) + client.setBlockSize(16); + + request.setType(type); + QScopedPointer<QCoapReply> reply(client.get(request)); + QSignalSpy spyReplyFinished(reply.data(), &QCoapReply::finished); + QSignalSpy spyReplyError(reply.data(), &QCoapReply::error); + Helper helper; + connect(reply.data(), &QCoapReply::error, &helper, &Helper::onError); + + QCOMPARE(spyReplyError.count(), 0); + QTRY_COMPARE_WITH_TIMEOUT(spyReplyFinished.count(), 1, 30000); + QCOMPARE(reply->readAll(), replyData); +} + +void tst_QCoapClient::blockwiseRequest_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QCoapMessage::MessageType>("type"); + QTest::addColumn<QByteArray>("requestData"); + QTest::addColumn<QtCoap::ResponseCode>("responseCode"); + QTest::addColumn<QByteArray>("replyData"); + + QByteArray data; + const char alphabet[] = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; + for (int i = 3; i-- > 0; ) + data.append(alphabet); + + QTest::newRow("large_post_empty_reply") << QUrl(testServerUrl() + "/query") + << QCoapMessage::NonConfirmable + << data + << QtCoap::MethodNotAllowed + << QByteArray(); + QTest::newRow("large_post_large_reply") << QUrl(testServerUrl() + "/large-post") + << QCoapMessage::NonConfirmable + << data + << QtCoap::Changed + << data.toUpper(); +} + +void tst_QCoapClient::blockwiseRequest() +{ + QFETCH(QUrl, url); + QFETCH(QCoapMessage::MessageType, type); + QFETCH(QByteArray, requestData); + QFETCH(QtCoap::ResponseCode, responseCode); + QFETCH(QByteArray, replyData); + + QCoapClient client; + client.setBlockSize(16); + + QCoapRequest request(url); + request.setType(type); + request.addOption(QCoapOption::ContentFormat); + + QScopedPointer<QCoapReply> reply(client.post(request, requestData)); + QSignalSpy spyReplyFinished(reply.data(), SIGNAL(finished(QCoapReply *))); + + QTRY_COMPARE_WITH_TIMEOUT(spyReplyFinished.count(), 1, 30000); + + QByteArray dataReply = reply->readAll(); + QCOMPARE(dataReply, replyData); + QCOMPARE(reply->responseCode(), responseCode); +} + +void tst_QCoapClient::discover_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<int>("resourceNumber"); + + // Californium test server exposes 29 resources + QTest::newRow("discover") << QUrl(testServerUrl()) + << 29; +} + +void tst_QCoapClient::discover() +{ + QFETCH(QUrl, url); + QFETCH(int, resourceNumber); + + QCoapClient client; + + QScopedPointer<QCoapDiscoveryReply> resourcesReply(client.discover(url)); // /.well-known/core + QSignalSpy spyReplyFinished(resourcesReply.data(), SIGNAL(finished(QCoapReply *))); + + QTRY_COMPARE_WITH_TIMEOUT(spyReplyFinished.count(), 1, 30000); + + QCOMPARE(resourcesReply->resources().length(), resourceNumber); + + //! TODO Test discovery content too +} + +void tst_QCoapClient::observe_data() +{ + QWARN("Observe tests may take some time, don't forget to raise Tests timeout in settings."); + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QCoapMessage::MessageType>("type"); + + QTest::newRow("observe") + << QUrl(testServerUrl() + "/obs") + << QCoapMessage::NonConfirmable; + + QTest::newRow("observe_confirmable") + << QUrl(testServerUrl() + "/obs") + << QCoapMessage::Confirmable; + + QTest::newRow("observe_receive") + << QUrl(testServerUrl() + "/obs-non") + << QCoapMessage::NonConfirmable; + + QTest::newRow("observe_receive_confirmable") + << QUrl(testServerUrl() + "/obs-non") + << QCoapMessage::Confirmable; + + QTest::newRow("observe_large") + << QUrl(testServerUrl() + "/obs-large") + << QCoapMessage::NonConfirmable; + + QTest::newRow("observe_large_confirmable") + << QUrl(testServerUrl() + "/obs-large") + << QCoapMessage::Confirmable; + + QTest::newRow("observe_pumping") + << QUrl(testServerUrl() + "/obs-pumping") + << QCoapMessage::NonConfirmable; + + QTest::newRow("observe_pumping_confirmable") + << QUrl(testServerUrl() + "/obs-pumping") + << QCoapMessage::Confirmable; +} + +void tst_QCoapClient::observe() +{ + QFETCH(QUrl, url); + QFETCH(QCoapMessage::MessageType, type); + + QCoapClient client; + QCoapRequest request(url); + + request.setType(type); + QSharedPointer<QCoapReply> reply(client.observe(request), + &QObject::deleteLater); + QSignalSpy spyReplyNotified(reply.data(), &QCoapReply::notified); + QSignalSpy spyReplyFinished(reply.data(), &QCoapReply::finished); + + QTRY_COMPARE_WITH_TIMEOUT(spyReplyNotified.count(), 3, 30000); + client.cancelObserve(reply.data()); + + QVERIFY2(!spyReplyNotified.wait(7000), "'Notify' signal received after cancelling observe"); + QCOMPARE(spyReplyFinished.count(), 1); + + for (QList<QVariant> receivedSignals : qAsConst(spyReplyNotified)) { + QRegularExpression regexp(QStringLiteral("..:..:..")); + QByteArray payload = receivedSignals.at(1).value<QCoapMessage>().payload(); + QString error = QString("Invalid payload for 'notified' signal: %1").arg(QString(payload)); + QVERIFY2(regexp.match(payload).hasMatch(), qPrintable(error)); + } +} + +QTEST_MAIN(tst_QCoapClient) + +#include "tst_qcoapclient.moc" diff --git a/tests/auto/qcoapconnection/qcoapconnection.pro b/tests/auto/qcoapconnection/qcoapconnection.pro new file mode 100644 index 0000000..6c89a38 --- /dev/null +++ b/tests/auto/qcoapconnection/qcoapconnection.pro @@ -0,0 +1,8 @@ +QT = testlib network core-private core coap coap-private +CONFIG += testcase + +include(../coaptestserver.pri) + +HEADERS += ../coapnetworksettings.h + +SOURCES += tst_qcoapconnection.cpp diff --git a/tests/auto/qcoapconnection/tst_qcoapconnection.cpp b/tests/auto/qcoapconnection/tst_qcoapconnection.cpp new file mode 100644 index 0000000..87d1d9f --- /dev/null +++ b/tests/auto/qcoapconnection/tst_qcoapconnection.cpp @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Witekio. +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCoap module. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QCoreApplication> + +#include <QtCore/qglobal.h> +#include <QtCoap/qcoapnamespace.h> +#include <QtCore/qbuffer.h> +#include <QtNetwork/qudpsocket.h> +#include <QtNetwork/qnetworkdatagram.h> +#include <QtCoap/qcoapglobal.h> +#include <QtCoap/qcoapconnection.h> +#include <QtCoap/qcoaprequest.h> +#include <private/qcoapconnection_p.h> +#include <private/qcoapinternalrequest_p.h> +#include "../coapnetworksettings.h" + +using namespace QtCoapNetworkSettings; + +class tst_QCoapConnection : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void ctor(); + void connectToHost(); + void sendRequest_data(); + void sendRequest(); +}; + +class QCoapConnectionForTest : public QCoapConnection +{ + Q_OBJECT +public: + QCoapConnectionForTest(QObject *parent = nullptr) : + QCoapConnection(parent) + {} + + void bindSocketForTest() { d_func()->bindSocket(); } +}; + +void tst_QCoapConnection::ctor() +{ + QCoapConnection connection; + QVERIFY(connection.socket()); +} + +void tst_QCoapConnection::connectToHost() +{ +#ifdef QT_BUILD_INTERNAL + QCoapConnectionForTest connection; + + QUdpSocket *socket = qobject_cast<QUdpSocket*>(connection.socket()); + QSignalSpy spyConnectionBound(&connection, SIGNAL(bound())); + QSignalSpy spySocketStateChanged(socket , SIGNAL(stateChanged(QAbstractSocket::SocketState))); + + QCOMPARE(connection.state(), QCoapConnection::Unconnected); + + connection.bindSocketForTest(); + + QTRY_COMPARE(spySocketStateChanged.count(), 1); + QTRY_COMPARE(spyConnectionBound.count(), 1); + QCOMPARE(connection.state(), QCoapConnection::Bound); +#else + QSKIP("Not an internal build, skipping this test"); +#endif +} + +void tst_QCoapConnection::sendRequest_data() +{ + QTest::addColumn<QString>("protocol"); + QTest::addColumn<QString>("host"); + QTest::addColumn<QString>("path"); + QTest::addColumn<quint16>("port"); + QTest::addColumn<QtCoap::Method>("method"); + QTest::addColumn<QString>("dataHexaHeader"); + QTest::addColumn<QString>("dataHexaPayload"); + + QTest::newRow("simple_get_request") + << "coap://" + << testServerHost() + << "/test" + << quint16(QtCoap::DefaultPort) + << QtCoap::Get + << "5445" + << "61626364c0211eff547970653a203120284e4f4e290a436f64653a2031202847" + "4554290a4d49443a2032343830360a546f6b656e3a203631363236333634"; + + QTest::newRow("simple_put_request") + << "coap://" + << testServerHost() + << "/test" + << quint16(QtCoap::DefaultPort) + << QtCoap::Put + << "5444" + << "61626364"; + + QTest::newRow("simple_post_request") + << "coap://" + << testServerHost() + << "/test" + << quint16(QtCoap::DefaultPort) + << QtCoap::Post + << "5441" + << "61626364896c6f636174696f6e31096c6f636174696f6e32096c6f636174696f" + "6e33"; + + QTest::newRow("simple_delete_request") + << "coap://" + << testServerHost() + << "/test" + << quint16(QtCoap::DefaultPort) + << QtCoap::Delete + << "5442" + << "61626364"; +} + +void tst_QCoapConnection::sendRequest() +{ +#ifdef QT_BUILD_INTERNAL + QFETCH(QString, protocol); + QFETCH(QString, host); + QFETCH(QString, path); + QFETCH(quint16, port); + QFETCH(QtCoap::Method, method); + QFETCH(QString, dataHexaHeader); + QFETCH(QString, dataHexaPayload); + + QCoapConnectionForTest connection; + + QSignalSpy spySocketReadyRead(connection.socket(), &QUdpSocket::readyRead); + QSignalSpy spyConnectionReadyRead(&connection, &QCoapConnection::readyRead); + + QCoapRequest request(protocol + host + path); + request.setMessageId(24806); + request.setToken(QByteArray("abcd")); + request.setMethod(method); + QVERIFY(connection.socket() != nullptr); + QCoapInternalRequest internalRequest(request); + connection.sendRequest(internalRequest.toQByteArray(), host, port); + + QTRY_COMPARE(spySocketReadyRead.count(), 1); + QTRY_COMPARE(spyConnectionReadyRead.count(), 1); + + QNetworkDatagram datagram = spyConnectionReadyRead.first() + .first().value<QNetworkDatagram>(); + + QVERIFY(QString(datagram.data().toHex()).startsWith(dataHexaHeader)); + QVERIFY(QString(datagram.data().toHex()).endsWith(dataHexaPayload)); +#else + QSKIP("Not an internal build, skipping this test"); +#endif +} + +QTEST_MAIN(tst_QCoapConnection) + +#include "tst_qcoapconnection.moc" diff --git a/tests/auto/qcoapinternalreply/qcoapinternalreply.pro b/tests/auto/qcoapinternalreply/qcoapinternalreply.pro new file mode 100644 index 0000000..a8e0777 --- /dev/null +++ b/tests/auto/qcoapinternalreply/qcoapinternalreply.pro @@ -0,0 +1,4 @@ +QT = testlib core-private network core coap coap-private +CONFIG += testcase + +SOURCES += tst_qcoapinternalreply.cpp diff --git a/tests/auto/qcoapinternalreply/tst_qcoapinternalreply.cpp b/tests/auto/qcoapinternalreply/tst_qcoapinternalreply.cpp new file mode 100644 index 0000000..58bd51b --- /dev/null +++ b/tests/auto/qcoapinternalreply/tst_qcoapinternalreply.cpp @@ -0,0 +1,251 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Witekio. +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCoap module. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QCoreApplication> + +#include <private/qcoapinternalreply_p.h> +#include <private/qcoapreply_p.h> + +#ifdef QT_BUILD_INTERNAL + +class tst_QCoapInternalReply : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void parseReplyPdu_data(); + void parseReplyPdu(); + void updateReply_data(); + void updateReply(); + void requestData(); + void abortRequest(); +}; + +void tst_QCoapInternalReply::parseReplyPdu_data() +{ + QTest::addColumn<QtCoap::ResponseCode>("responseCode"); + QTest::addColumn<QCoapMessage::MessageType>("type"); + QTest::addColumn<quint16>("messageId"); + QTest::addColumn<QByteArray>("token"); + QTest::addColumn<quint8>("tokenLength"); + QTest::addColumn<QList<QCoapOption::OptionName>>("optionsNames"); + QTest::addColumn<QList<quint8>>("optionsLengths"); + QTest::addColumn<QList<QByteArray>>("optionsValues"); + QTest::addColumn<QString>("payload"); + QTest::addColumn<QString>("pduHexa"); + + QList<QCoapOption::OptionName> optionsNamesReply({QCoapOption::ContentFormat, + QCoapOption::MaxAge}); + QList<quint8> optionsLengthsReply({0, 1}); + QList<QByteArray> optionsValuesReply({"", QByteArray::fromHex("1e")}); + + QList<QCoapOption::OptionName> bigOptionNameReply({QCoapOption::Size1}); + QList<quint8> bigOptionLengthReply({26}); + QList<QByteArray> bigOptionValueReply({QByteArray("abcdefghijklmnopqrstuvwxyz")}); + + QTest::newRow("reply_with_options_and_payload") + << QtCoap::Content + << QCoapMessage::NonConfirmable + << quint16(64463) + << QByteArray("4647f09b") + << quint8(4) + << optionsNamesReply + << optionsLengthsReply + << optionsValuesReply + << "Type: 1 (NON)\nCode: 1 (GET)\nMID: 56400\nToken: 4647f09b" + << "5445fbcf4647f09bc0211eff547970653a203120284e4f4e290a436f64653a20" + "312028474554290a4d49443a2035363430300a546f6b656e3a20343634376630" + "3962"; + + QTest::newRow("reply_with_payload") + << QtCoap::Content + << QCoapMessage::NonConfirmable + << quint16(64463) + << QByteArray("4647f09b") + << quint8(4) + << QList<QCoapOption::OptionName>() + << QList<quint8>() + << QList<QByteArray>() + << "Type: 1 (NON)\nCode: 1 (GET)\nMID: 56400\nToken: 4647f09b" + << "5445fbcf4647f09bff547970653a203120284e4f4e290a436f64653a20312028" + "474554290a4d49443a2035363430300a546f6b656e3a203436343766303962"; + + QTest::newRow("reply_with_options") + << QtCoap::Content + << QCoapMessage::NonConfirmable + << quint16(64463) + << QByteArray("4647f09b") + << quint8(4) + << optionsNamesReply + << optionsLengthsReply + << optionsValuesReply + << "" + << "5445fbcf4647f09bc0211e"; + + QTest::newRow("reply_only") + << QtCoap::Content + << QCoapMessage::NonConfirmable + << quint16(64463) + << QByteArray("4647f09b") + << quint8(4) + << QList<QCoapOption::OptionName>() + << QList<quint8>() + << QList<QByteArray>() + << "" + << "5445fbcf4647f09b"; + + QTest::newRow("reply_with_big_option") + << QtCoap::Content + << QCoapMessage::NonConfirmable + << quint16(64463) + << QByteArray("4647f09b") + << quint8(4) + << bigOptionNameReply + << bigOptionLengthReply + << bigOptionValueReply + << "" + << "5445fbcf4647f09bdd2f0d6162636465666768696a6b6c6d6e6f707172737475" + "767778797a"; +} + +void tst_QCoapInternalReply::parseReplyPdu() +{ + QFETCH(QtCoap::ResponseCode, responseCode); + QFETCH(QCoapMessage::MessageType, type); + QFETCH(quint16, messageId); + QFETCH(QByteArray, token); + QFETCH(quint8, tokenLength); + QFETCH(QList<QCoapOption::OptionName>, optionsNames); + QFETCH(QList<quint8>, optionsLengths); + QFETCH(QList<QByteArray>, optionsValues); + QFETCH(QString, payload); + QFETCH(QString, pduHexa); + + QScopedPointer<QCoapInternalReply> + reply(QCoapInternalReply::createFromFrame(QByteArray::fromHex(pduHexa.toUtf8()))); + + QCOMPARE(reply->message()->type(), type); + QCOMPARE(reply->message()->tokenLength(), tokenLength); + QCOMPARE(reply->responseCode(), responseCode); + QCOMPARE(reply->message()->messageId(), messageId); + QCOMPARE(reply->message()->token().toHex(), token); + QCOMPARE(reply->message()->optionCount(), optionsNames.count()); + for (int i = 0; i < reply->message()->optionCount(); ++i) { + QCoapOption option = reply->message()->option(i); + QCOMPARE(option.name(), optionsNames.at(i)); + QCOMPARE(option.length(), optionsLengths.at(i)); + QCOMPARE(option.value(), optionsValues.at(i)); + } + QCOMPARE(reply->message()->payload(), payload); +} + +class QCoapReplyForTests : public QCoapReply +{ +public: + QCoapReplyForTests(const QCoapRequest &req) : QCoapReply (req) {} + + void setRunning(const QCoapToken &token, QCoapMessageId messageId) + { + Q_D(QCoapReply); + d->_q_setRunning(token, messageId); + } + void setContentAndFinished(const QCoapInternalReply *internal) + { + Q_D(QCoapReply); + d->_q_setContent(internal->senderAddress(), *internal->message(), internal->responseCode()); + d->_q_setFinished(); + } +}; + +void tst_QCoapInternalReply::updateReply_data() +{ + QTest::addColumn<QByteArray>("data"); + + QTest::newRow("success") << QByteArray("Data for the updating test"); +} + +void tst_QCoapInternalReply::updateReply() +{ + QFETCH(QByteArray, data); + + QCoapReplyForTests reply((QCoapRequest())); + QCoapInternalReply internalReply; + internalReply.message()->setPayload(data); + QSignalSpy spyReplyFinished(&reply, &QCoapReply::finished); + + reply.setContentAndFinished(&internalReply); + + QTRY_COMPARE_WITH_TIMEOUT(spyReplyFinished.count(), 1, 1000); + QCOMPARE(reply.readAll(), data); +} + +void tst_QCoapInternalReply::requestData() +{ + QCoapReplyForTests reply((QCoapRequest())); + reply.setRunning("token", 543); + + QCOMPARE(reply.request().token(), QByteArray("token")); + QCOMPARE(reply.request().messageId(), 543); +} + +void tst_QCoapInternalReply::abortRequest() +{ + QCoapReplyForTests reply((QCoapRequest())); + reply.setRunning("token", 543); + + QSignalSpy spyAborted(&reply, &QCoapReply::aborted); + QSignalSpy spyFinished(&reply, &QCoapReply::finished); + reply.abortRequest(); + + QTRY_COMPARE_WITH_TIMEOUT(spyAborted.count(), 1, 1000); + QList<QVariant> arguments = spyAborted.takeFirst(); + QTRY_COMPARE_WITH_TIMEOUT(spyFinished.count(), 1, 1000); + QVERIFY(arguments.at(0).toByteArray() == "token"); +} + +#else + +class tst_QCoapInternalReply : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + QSKIP("Not an internal build, nothing to test"); + } +}; + +#endif + +QTEST_MAIN(tst_QCoapInternalReply) + +#include "tst_qcoapinternalreply.moc" diff --git a/tests/auto/qcoapinternalrequest/qcoapinternalrequest.pro b/tests/auto/qcoapinternalrequest/qcoapinternalrequest.pro new file mode 100644 index 0000000..a1e57a6 --- /dev/null +++ b/tests/auto/qcoapinternalrequest/qcoapinternalrequest.pro @@ -0,0 +1,5 @@ +QT = testlib core-private network core coap coap-private +CONFIG += testcase + +SOURCES += \ + tst_qcoapinternalrequest.cpp diff --git a/tests/auto/qcoapinternalrequest/tst_qcoapinternalrequest.cpp b/tests/auto/qcoapinternalrequest/tst_qcoapinternalrequest.cpp new file mode 100644 index 0000000..97bf421 --- /dev/null +++ b/tests/auto/qcoapinternalrequest/tst_qcoapinternalrequest.cpp @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Witekio. +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCoap module. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QCoreApplication> + +#include <QtCoap/qcoaprequest.h> +#include <private/qcoapinternalrequest_p.h> + +#ifdef QT_BUILD_INTERNAL + +class tst_QCoapInternalRequest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void requestToFrame_data(); + void requestToFrame(); + void parseUri_data(); + void parseUri(); +}; + +void tst_QCoapInternalRequest::requestToFrame_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QtCoap::Method>("method"); + QTest::addColumn<QCoapMessage::MessageType>("type"); + QTest::addColumn<quint16>("messageId"); + QTest::addColumn<QByteArray>("token"); + QTest::addColumn<QString>("pduHeader"); + QTest::addColumn<QString>("pduPayload"); + + QTest::newRow("request_with_option_and_payload") + << QUrl("coap://10.20.30.40:5683/test") + << QtCoap::Get + << QCoapRequest::NonConfirmable + << quint16(56400) + << QByteArray::fromHex("4647f09b") + << "5401dc504647f09bb474657374ff" + << "Some payload"; + + QTest::newRow("request_domain") + << QUrl("coap://domain.com:5683/test") + << QtCoap::Get + << QCoapRequest::NonConfirmable + << quint16(56400) + << QByteArray::fromHex("4647f09b") + << "5401dc504647f09b3a646f6d61696e2e636f6d8474657374ff" + << "Some payload"; + + QTest::newRow("request_ipv6") + << QUrl("coap://[::ffff:ac11:3]:5683/test") + << QtCoap::Get + << QCoapRequest::NonConfirmable + << quint16(56400) + << QByteArray::fromHex("4647f09b") + << "5401dc504647f09bb474657374ff" + << "Some payload"; + + QTest::newRow("request_without_payload") + << QUrl("coap://10.20.30.40:5683/test") + << QtCoap::Get + << QCoapRequest::NonConfirmable + << quint16(56400) + << QByteArray::fromHex("4647f09b") + << "5401dc504647f09bb474657374" + << ""; + + QTest::newRow("request_without_option") + << QUrl("coap://10.20.30.40:5683/") + << QtCoap::Put + << QCoapRequest::Confirmable + << quint16(56400) + << QByteArray::fromHex("4647f09b") + << "4403dc504647f09bff" + << "Some payload"; + + QTest::newRow("request_only") + << QUrl("coap://10.20.30.40:5683/") + << QtCoap::Get + << QCoapRequest::NonConfirmable + << quint16(56400) + << QByteArray::fromHex("4647f09b") + << "5401dc504647f09b" + << ""; + + QTest::newRow("request_with_multiple_options") + << QUrl("coap://10.20.30.40:5683/test/oui") + << QtCoap::Get + << QCoapRequest::NonConfirmable + << quint16(56400) + << QByteArray::fromHex("4647f09b") + << "5401dc504647f09bb474657374036f7569" + << ""; + + QTest::newRow("request_with_big_option_number") + << QUrl("coap://10.20.30.40:5683/test") + << QtCoap::Get + << QCoapRequest::NonConfirmable + << quint16(56400) + << QByteArray::fromHex("4647f09b") + << "5401dc504647f09bb474657374dd240d6162636465666768696a6b6c6d6e6f70" + "7172737475767778797aff" + << "Some payload"; +} + +void tst_QCoapInternalRequest::requestToFrame() +{ + QFETCH(QUrl, url); + QFETCH(QtCoap::Method, method); + QFETCH(QCoapMessage::MessageType, type); + QFETCH(quint16, messageId); + QFETCH(QByteArray, token); + QFETCH(QString, pduHeader); + QFETCH(QString, pduPayload); + + QCoapRequest request(url); + request.setType(type); + request.setMethod(method); + request.setPayload(pduPayload.toUtf8()); + request.setMessageId(messageId); + request.setToken(token); + if (qstrcmp(QTest::currentDataTag(), "request_with_big_option_number") == 0) + request.addOption(QCoapOption::Size1, QByteArray("abcdefghijklmnopqrstuvwxyz")); + + QByteArray pdu; + pdu.append(pduHeader); + if (!pduPayload.isEmpty()) + pdu.append(pduPayload.toUtf8().toHex()); + + QCoapInternalRequest internalRequest(request); + QCOMPARE(internalRequest.toQByteArray().toHex(), pdu); +} + +void tst_QCoapInternalRequest::parseUri_data() +{ + qRegisterMetaType<QVector<QCoapOption>>(); + QTest::addColumn<QUrl>("uri"); + QTest::addColumn<QUrl>("proxyUri"); + QTest::addColumn<QVector<QCoapOption>>("options"); + + QTest::newRow("port_path") + << QUrl("coap://10.20.30.40:1234/test/path1") + << QUrl() + << QVector<QCoapOption>({ + QCoapOption(QCoapOption::UriPort, 1234), + QCoapOption(QCoapOption::UriPath, "test"), + QCoapOption(QCoapOption::UriPath, "path1") }); + + QTest::newRow("path_query") + << QUrl("coap://10.20.30.40/test/path1/?rd=25&nd=4") + << QUrl() + << QVector<QCoapOption>({ + QCoapOption(QCoapOption::UriPath, "test"), + QCoapOption(QCoapOption::UriPath, "path1"), + QCoapOption(QCoapOption::UriQuery, "rd=25"), + QCoapOption(QCoapOption::UriQuery, "nd=4") }); + + QTest::newRow("host_path_query") + << QUrl("coap://aa.bb.cc.com:5683/test/path1/?rd=25&nd=4") + << QUrl() + << QVector<QCoapOption>({ + QCoapOption(QCoapOption::UriHost, "aa.bb.cc.com"), + QCoapOption(QCoapOption::UriPath, "test"), + QCoapOption(QCoapOption::UriPath, "path1"), + QCoapOption(QCoapOption::UriQuery, "rd=25"), + QCoapOption(QCoapOption::UriQuery, "nd=4") }); + + QTest::newRow("proxy_url") + << QUrl("coap://aa.bb.cc.com:5683/test/path1/?rd=25&nd=4") + << QUrl("coap://10.20.30.40/test:5684/othertest/path") + << QVector<QCoapOption>({ + QCoapOption(QCoapOption::ProxyUri, "coap://10.20.30.40/test:5684/othertest/path") }); +} + +void tst_QCoapInternalRequest::parseUri() +{ + QFETCH(QUrl, uri); + QFETCH(QUrl, proxyUri); + QFETCH(QVector<QCoapOption>, options); + + QCoapRequest request(uri, QCoapMessage::NonConfirmable, proxyUri); + QCoapInternalRequest internalRequest(request); + + for (QCoapOption opt : options) + QVERIFY2(internalRequest.message()->options().contains(opt), "Missing option"); + + QCOMPARE(options.count(), internalRequest.message()->optionCount()); +} + +#else + +class tst_QCoapInternalRequest : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + QSKIP("Not an internal build, nothing to test"); + } +}; + +#endif + +QTEST_APPLESS_MAIN(tst_QCoapInternalRequest) + +#include "tst_qcoapinternalrequest.moc" diff --git a/tests/auto/qcoapmessage/qcoapmessage.pro b/tests/auto/qcoapmessage/qcoapmessage.pro new file mode 100644 index 0000000..47179d6 --- /dev/null +++ b/tests/auto/qcoapmessage/qcoapmessage.pro @@ -0,0 +1,4 @@ +QT = testlib core-private network core coap +CONFIG += testcase + +SOURCES += tst_qcoapmessage.cpp diff --git a/tests/auto/qcoapmessage/tst_qcoapmessage.cpp b/tests/auto/qcoapmessage/tst_qcoapmessage.cpp new file mode 100644 index 0000000..90dcd64 --- /dev/null +++ b/tests/auto/qcoapmessage/tst_qcoapmessage.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Witekio. +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCoap module. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QCoreApplication> + +#include <QtCoap/qcoapmessage.h> + +class tst_QCoapMessage : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void copyAndDetach(); + void setMessageType_data(); + void setMessageType(); + void addOption_string(); + void addOption_uint_data(); + void addOption_uint(); + void removeOption(); + void urlOptions(); +}; + +void tst_QCoapMessage::copyAndDetach() +{ + QCoapMessage a; + a.setMessageId(3); + a.setPayload("payload"); + a.setToken("token"); + a.setType(QCoapMessage::Acknowledgment); + a.setVersion(5); + + // Test the copy + QCoapMessage b(a); + QVERIFY2(b.messageId() == 3, "Message not copied correctly"); + QVERIFY2(b.payload() == "payload", "Message not copied correctly"); + QVERIFY2(b.token() == "token", "Message not copied correctly"); + QVERIFY2(b.type() == QCoapMessage::Acknowledgment, "Message not copied correctly"); + QVERIFY2(b.version() == 5, "Message not copied correctly"); + + // Detach + b.setMessageId(9); + QCOMPARE(b.messageId(), 9); + QCOMPARE(a.messageId(), 3); +} + +void tst_QCoapMessage::setMessageType_data() +{ + QTest::addColumn<QCoapMessage::MessageType>("type"); + + QTest::newRow("acknowledgment") << QCoapMessage::Acknowledgment; + QTest::newRow("confirmable") << QCoapMessage::Confirmable; + QTest::newRow("non-confirmable") << QCoapMessage::NonConfirmable; + QTest::newRow("reset") << QCoapMessage::Reset; +} + +void tst_QCoapMessage::setMessageType() +{ + QFETCH(QCoapMessage::MessageType, type); + QCoapMessage message; + message.setType(type); + QCOMPARE(message.type(), type); + + //! TODO extend QCoapMessage tests +} + +void tst_QCoapMessage::addOption_string() +{ + //! TODO with one and more than one identical options +} + +void tst_QCoapMessage::addOption_uint_data() +{ + QTest::addColumn<quint32>("value"); + QTest::addColumn<int>("size"); + + QTest::newRow("4 bytes") << (quint32)0xF0aF0010 << 4; + QTest::newRow("3 bytes") << (quint32)0x300010 << 3; + QTest::newRow("2 bytes") << (quint32)0x5010 << 2; + QTest::newRow("1 byte") << (quint32)0x80 << 1; +} + +void tst_QCoapMessage::addOption_uint() +{ + QFETCH(quint32, value); + QFETCH(int, size); + + QCoapOption option(QCoapOption::Block1, value); + + QCOMPARE(option.valueToInt(), value); + QCOMPARE(option.value().size(), size); +} + +void tst_QCoapMessage::removeOption() +{ + //! TODO with one and more than one identical options +} + +void tst_QCoapMessage::urlOptions() +{ + //! TODO Test the following from the RFC: + // For example, the following three URIs are equivalent and cause the + // same options and option values to appear in the CoAP messages: + // coap://example.com:5683/~sensors/temp.xml + // coap://EXAMPLE.com/%7Esensors/temp.xml + // coap://EXAMPLE.com:/%7esensors/temp.xml +} + +QTEST_APPLESS_MAIN(tst_QCoapMessage) + +#include "tst_qcoapmessage.moc" diff --git a/tests/auto/qcoapoption/qcoapoption.pro b/tests/auto/qcoapoption/qcoapoption.pro new file mode 100644 index 0000000..411dc4c --- /dev/null +++ b/tests/auto/qcoapoption/qcoapoption.pro @@ -0,0 +1,4 @@ +QT = testlib core-private core coap coap-private +CONFIG += testcase + +SOURCES += tst_qcoapoption.cpp diff --git a/tests/auto/qcoapoption/tst_qcoapoption.cpp b/tests/auto/qcoapoption/tst_qcoapoption.cpp new file mode 100644 index 0000000..2541fad --- /dev/null +++ b/tests/auto/qcoapoption/tst_qcoapoption.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Witekio. +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCoap module. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> + +#include <QtCoap/qcoapoption.h> + +class tst_QCoapOption : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void constructWithQByteArray(); + void constructWithQStringView(); + void constructWithCString(); + void constructWithInteger(); + void constructWithUtf8Characters(); +}; + +void tst_QCoapOption::constructWithQByteArray() +{ + QByteArray ba = "some data"; + QCoapOption option(QCoapOption::LocationPath, ba); + + QCOMPARE(option.value(), ba); +} + +void tst_QCoapOption::constructWithQStringView() +{ + QString str = "some data"; + QCoapOption option(QCoapOption::LocationPath, str); + + QCOMPARE(option.value(), str.toUtf8()); +} + +void tst_QCoapOption::constructWithCString() +{ + const char *str = "some data"; + QCoapOption option(QCoapOption::LocationPath, str); + + QCOMPARE(option.value(), QByteArray(str)); +} + +void tst_QCoapOption::constructWithInteger() +{ + quint32 value = 64000; + QCoapOption option(QCoapOption::Size1, value); + + QCOMPARE(option.valueToInt(), value); +} + +void tst_QCoapOption::constructWithUtf8Characters() +{ + QByteArray ba = "\xc3\xa9~\xce\xbb\xe2\x82\xb2"; + QCoapOption option(QCoapOption::LocationPath, ba); + + QCOMPARE(option.value(), ba); +} + +QTEST_APPLESS_MAIN(tst_QCoapOption) + +#include "tst_qcoapoption.moc" diff --git a/tests/auto/qcoapreply/qcoapreply.pro b/tests/auto/qcoapreply/qcoapreply.pro new file mode 100644 index 0000000..acd82b9 --- /dev/null +++ b/tests/auto/qcoapreply/qcoapreply.pro @@ -0,0 +1,4 @@ +QT = testlib core-private network core coap coap-private +CONFIG += testcase + +SOURCES += tst_qcoapreply.cpp diff --git a/tests/auto/qcoapreply/tst_qcoapreply.cpp b/tests/auto/qcoapreply/tst_qcoapreply.cpp new file mode 100644 index 0000000..b1f9006 --- /dev/null +++ b/tests/auto/qcoapreply/tst_qcoapreply.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Witekio. +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCoap module. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QCoreApplication> + +#include <QtCoap/qcoapreply.h> +#include <private/qcoapreply_p.h> + +class tst_QCoapReply : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void updateReply_data(); + void updateReply(); + void requestData(); + void abortRequest(); +}; + +class QCoapReplyForTests : public QCoapReply +{ +public: + QCoapReplyForTests(const QCoapRequest &req) : QCoapReply (req) {} + + void setRunning(const QCoapToken &token, QCoapMessageId messageId) + { + Q_D(QCoapReply); + d->_q_setRunning(token, messageId); + } +}; + +void tst_QCoapReply::updateReply_data() +{ + QTest::addColumn<QByteArray>("payload"); + QTest::addColumn<QtCoap::ResponseCode>("responseCode"); + QTest::addColumn<QtCoap::Error>("error"); + + QTest::newRow("success") + << QByteArray("Some data") + << QtCoap::Content + << QtCoap::NoError; + QTest::newRow("content error") + << QByteArray("Error") + << QtCoap::BadRequest + << QtCoap::NoError; + QTest::newRow("finished error") + << QByteArray("Error") + << QtCoap::Content + << QtCoap::BadRequestError; + QTest::newRow("content & finished errors") + << QByteArray("2Errors") + << QtCoap::BadGateway + << QtCoap::BadRequestError; +} + +void tst_QCoapReply::updateReply() +{ + QFETCH(QByteArray, payload); + QFETCH(QtCoap::ResponseCode, responseCode); + QFETCH(QtCoap::Error, error); + + QByteArray token = "\xAF\x01\xC2"; + int id = 645; + + QCoapReply reply((QCoapRequest())); + QCoapMessage message; + message.setToken(token); + message.setMessageId(id); + message.setPayload(payload); + + QSignalSpy spyReplyFinished(&reply, &QCoapReply::finished); + QSignalSpy spyReplyNotified(&reply, &QCoapReply::notified); + QSignalSpy spyReplyError(&reply, &QCoapReply::error); + QSignalSpy spyReplyAborted(&reply, &QCoapReply::aborted); + + QMetaObject::invokeMethod(&reply, "_q_setContent", + Q_ARG(QHostAddress, QHostAddress()), + Q_ARG(QCoapMessage, message), + Q_ARG(QtCoap::ResponseCode, responseCode)); + QMetaObject::invokeMethod(&reply, "_q_setFinished", + Q_ARG(QtCoap::Error, error)); + + QCOMPARE(spyReplyFinished.count(), 1); + QCOMPARE(spyReplyNotified.count(), 0); + QCOMPARE(spyReplyAborted.count(), 0); + if (error != QtCoap::NoError || QtCoap::isError(responseCode)) { + QVERIFY(spyReplyError.count() > 0); + QCOMPARE(reply.isSuccessful(), false); + } else { + QCOMPARE(spyReplyError.count(), 0); + QCOMPARE(reply.isSuccessful(), true); + } + + QCOMPARE(reply.readAll(), payload); + QCOMPARE(reply.readAll(), QByteArray()); + QCOMPARE(reply.responseCode(), responseCode); + QCOMPARE(reply.message().token(), token); + QCOMPARE(reply.message().messageId(), id); +} + +void tst_QCoapReply::requestData() +{ +#ifdef QT_BUILD_INTERNAL + QCoapReplyForTests reply((QCoapRequest())); + reply.setRunning("token", 543); + + QCOMPARE(reply.request().token(), QByteArray("token")); + QCOMPARE(reply.request().messageId(), 543); +#else + QSKIP("Not an internal build, skipping this test"); +#endif +} + +void tst_QCoapReply::abortRequest() +{ +#ifdef QT_BUILD_INTERNAL + QCoapReplyForTests reply((QCoapRequest())); + reply.setRunning("token", 543); + + QSignalSpy spyAborted(&reply, &QCoapReply::aborted); + QSignalSpy spyFinished(&reply, &QCoapReply::finished); + reply.abortRequest(); + + QTRY_COMPARE_WITH_TIMEOUT(spyAborted.count(), 1, 1000); + QList<QVariant> arguments = spyAborted.takeFirst(); + QTRY_COMPARE_WITH_TIMEOUT(spyFinished.count(), 1, 1000); + QVERIFY(arguments.at(0).toByteArray() == "token"); + QCOMPARE(reply.isSuccessful(), false); +#else + QSKIP("Not an internal build, skipping this test"); +#endif +} + +QTEST_MAIN(tst_QCoapReply) + +#include "tst_qcoapreply.moc" diff --git a/tests/auto/qcoaprequest/qcoaprequest.pro b/tests/auto/qcoaprequest/qcoaprequest.pro new file mode 100644 index 0000000..e25ce28 --- /dev/null +++ b/tests/auto/qcoaprequest/qcoaprequest.pro @@ -0,0 +1,4 @@ +QT = testlib core-private network core coap coap-private +CONFIG += testcase + +SOURCES += tst_qcoaprequest.cpp diff --git a/tests/auto/qcoaprequest/tst_qcoaprequest.cpp b/tests/auto/qcoaprequest/tst_qcoaprequest.cpp new file mode 100644 index 0000000..98329fe --- /dev/null +++ b/tests/auto/qcoaprequest/tst_qcoaprequest.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Witekio. +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCoap module. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QCoreApplication> + +#include <QtCoap/qcoapglobal.h> +#include <QtCoap/qcoapnamespace.h> +#include <QtCoap/qcoaprequest.h> +#include <QtCoap/qcoapconnection.h> + +class tst_QCoapRequest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void ctor_data(); + void ctor(); + void setUrl_data(); + void setUrl(); + void setMethod_data(); + void setMethod(); + void enableObserve(); + void copyAndDetach(); +}; + +void tst_QCoapRequest::ctor_data() +{ + QTest::addColumn<QUrl>("url"); + + QTest::newRow("empty") << QUrl(); + QTest::newRow("coap") << QUrl("coap://vs0.inf.ethz.ch:5683/test"); +} + +void tst_QCoapRequest::ctor() +{ + QFETCH(QUrl, url); + + QCoapRequest request(url); + QCOMPARE(request.url(), url); +} + +void tst_QCoapRequest::setUrl_data() +{ + QTest::addColumn<QUrl>("inputUrl"); + QTest::addColumn<QUrl>("expectedUrl"); + + QTest::newRow("empty") << QUrl() << QUrl(); + QTest::newRow("coap") << QUrl("coap://10.11.12.13:5683/test") << QUrl("coap://10.11.12.13:5683/test"); + QTest::newRow("other_port") << QUrl("coap://10.11.12.13:8888/test") << QUrl("coap://10.11.12.13:8888/test"); + QTest::newRow("no_port") << QUrl("coap://vs0.inf.ethz.ch/test") << QUrl("coap://vs0.inf.ethz.ch:5683/test"); + QTest::newRow("no_scheme_no_port") << QUrl("vs0.inf.ethz.ch/test") << QUrl("coap://vs0.inf.ethz.ch:5683/test"); + QTest::newRow("incorrect_scheme") << QUrl("http://vs0.inf.ethz.ch:5683/test") << QUrl(); + QTest::newRow("invalid") << QUrl("-coap://vs0.inf.ethz.ch:5683/test") << QUrl(); +} + +void tst_QCoapRequest::setUrl() +{ + QFETCH(QUrl, inputUrl); + QFETCH(QUrl, expectedUrl); + + QCoapRequest request; + request.setUrl(inputUrl); + QCOMPARE(request.url(), expectedUrl); +} + +void tst_QCoapRequest::setMethod_data() +{ + QTest::addColumn<QtCoap::Method>("method"); + + QTest::newRow("get") << QtCoap::Get; + QTest::newRow("put") << QtCoap::Put; + QTest::newRow("post") << QtCoap::Post; + QTest::newRow("delete") << QtCoap::Delete; + QTest::newRow("other") << QtCoap::Other; +} + +void tst_QCoapRequest::setMethod() +{ + QFETCH(QtCoap::Method, method); + + QCoapRequest request; + request.setMethod(method); + QCOMPARE(request.method(), method); +} + +void tst_QCoapRequest::enableObserve() +{ + QCoapRequest request; + + QCOMPARE(request.isObserve(), false); + request.enableObserve(); + + QCOMPARE(request.isObserve(), true); +} + +void tst_QCoapRequest::copyAndDetach() +{ + QCoapRequest a; + a.setMessageId(3); + a.setPayload("payload"); + a.setToken("token"); + a.setType(QCoapMessage::Acknowledgment); + a.setVersion(5); + a.setMethod(QtCoap::Delete); + QUrl testUrl("coap://url:500/resource"); + a.setUrl(testUrl); + QUrl testProxyUrl("test://proxyurl"); + a.setProxyUrl(testProxyUrl); + + // Test the QCoapMessage copy + QCoapMessage b(a); + QVERIFY2(b.messageId() == 3, "Message not copied correctly"); + QVERIFY2(b.payload() == "payload", "Message not copied correctly"); + QVERIFY2(b.token() == "token", "Message not copied correctly"); + QVERIFY2(b.type() == QCoapMessage::Acknowledgment, "Message not copied correctly"); + QVERIFY2(b.version() == 5, "Message not copied correctly"); + + // Test the QCoapRequest copy + QCoapRequest c(a); + QVERIFY2(c.method() == QtCoap::Delete, "Request not copied correctly"); + QVERIFY2(c.url() == testUrl, "Request not copied correctly"); + QVERIFY2(c.proxyUrl() == testProxyUrl, "Request not copied correctly"); + + // Detach + c.setMessageId(9); + QCOMPARE(c.messageId(), 9); + QCOMPARE(a.messageId(), 3); +} + +QTEST_APPLESS_MAIN(tst_QCoapRequest) + +#include "tst_qcoaprequest.moc" diff --git a/tests/auto/qcoapresource/qcoapresource.pro b/tests/auto/qcoapresource/qcoapresource.pro new file mode 100644 index 0000000..6687ad2 --- /dev/null +++ b/tests/auto/qcoapresource/qcoapresource.pro @@ -0,0 +1,4 @@ +QT = testlib network core-private core coap +CONFIG += testcase + +SOURCES += tst_qcoapresource.cpp diff --git a/tests/auto/qcoapresource/tst_qcoapresource.cpp b/tests/auto/qcoapresource/tst_qcoapresource.cpp new file mode 100644 index 0000000..63fe88f --- /dev/null +++ b/tests/auto/qcoapresource/tst_qcoapresource.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Witekio. +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCoap module. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QCoreApplication> + +#include <QtCoap/qcoapresource.h> +#include <QtCoap/qcoapprotocol.h> + +class tst_QCoapResource : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void parseCoreLink_data(); + void parseCoreLink(); +}; + +void tst_QCoapResource::parseCoreLink_data() +{ + QTest::addColumn<int>("resourceNumber"); + QTest::addColumn<QString>("senderAddress"); + QTest::addColumn<QList<QString>>("pathList"); + QTest::addColumn<QList<QString>>("titleList"); + QTest::addColumn<QList<QString>>("resourceTypeList"); + QTest::addColumn<QList<uint>>("contentFormatList"); + QTest::addColumn<QList<QString>>("interfaceList"); + QTest::addColumn<QList<int>>("maximumSizeList"); + QTest::addColumn<QList<bool>>("observableList"); + QTest::addColumn<QByteArray>("coreLinkList"); + + QList<QString> pathList; + pathList << "/obs" << "/separate" << "/seg1" << "/seg1/seg2" << "/large-separate" + << "/.well-known/core" << "/multi-format" << "/path" + << "/path/sub1" << "/link1" << "/validate" << "/test" + << "/query" << "/large-post" << "/obs-non" << "/shutdown"; + + QList<QString> titleList; + titleList << "Observable resource which changes every 5 seconds" + << "Resource which cannot be served immediately and which cannot be acknowledged in a piggy-backed way" + << "Long path resource" + << "Long path resource" + << "Large resource" + << "" + << "Resource that exists in different content formats (text/plain utf8 and application/xml)" + << "Hierarchical link description entry" + << "Hierarchical link description sub-resource" + << "Link test resource" + << "Resource which varies" + << "Default test resource" + << "Resource accepting query parameters" + << "Handle PostOperation with two-way blockwise transfer" + << "Observable resource which changes every 5 seconds" + << ""; + + QList<QString> resourceTypeList; + resourceTypeList << "observe" << "" << "" << "" << "block" << "" << "" << "" << "" + << "Type1 Type2" << "" << "" << "" << "block" << "observe" << ""; + + QList<uint> contentFormatList; + contentFormatList << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 40 << 0 << 0 << 0 << 0 + << 0 << 0 << 0 << 0; + + QList<QString> interfaceList; + interfaceList << "" << "" << "" << "" << "" << "" << "" << "" << "" << "If1" + << "" << "" << "" << "" << "" << ""; + + QList<int> maximumSizeList; + maximumSizeList << -1 << -1 << -1 << -1 << 1280 << -1 << -1 << -1 << -1 << -1 + << -1 << -1 << -1 << -1 << -1 << -1; + + QList<bool> observableList; + observableList << true << false << false << false << false << false << false + << false << false << false << false << false << false << false + << true << false; + + QByteArray coreLinks; + // Resources are separated by a comma + coreLinks.append("</obs>;obs;rt=\"observe\";title=\"Observable resource which changes every" + " 5 seconds\",</separate>;title=\"Resource which cannot be served immediately" + " and which cannot be acknowledged in a piggy-backed way\",</seg1>;title=\"" + "Long path resource\",</seg1/seg2>;title=\"Long path resource\"," + "</large-separate>;rt=\"block\";sz=1280;title=\"Large resource\"," + "</.well-known/core>,</multi-format>;ct=\"0 41\";title=\"Resource that exists" + " in different content formats (text/plain utf8 and application/xml)\"," + "</path>;ct=40;title=\"Hierarchical link description entry\",</path/sub1>;" + "title=\"Hierarchical link description sub-resource\",</link1>;if=\"If1\";" + "rt=\"Type1 Type2\";title=\"Link test resource\",</validate>;title=\"Resource" + " which varies\",</test>;title=\"Default test resource\",</query>;" + "title=\"Resource accepting query parameters\",</large-post>;rt=\"block\";" + "title=\"Handle PostOperation with two-way blockwise transfer\",</obs-non>;" + "obs;rt=\"observe\";title=\"Observable resource which changes every 5 " + "seconds\",</shutdown>"); + + QTest::newRow("parse") << 16 + << QString("10.20.30.40") + << pathList + << titleList + << resourceTypeList + << contentFormatList + << interfaceList + << maximumSizeList + << observableList + << coreLinks; +} + +void tst_QCoapResource::parseCoreLink() +{ + QFETCH(int, resourceNumber); + QFETCH(QString, senderAddress); + QFETCH(QList<QString>, pathList); + QFETCH(QList<QString>, titleList); + QFETCH(QList<QString>, resourceTypeList); + QFETCH(QList<uint>, contentFormatList); + QFETCH(QList<QString>, interfaceList); + QFETCH(QList<int>, maximumSizeList); + QFETCH(QList<bool>, observableList); + QFETCH(QByteArray, coreLinkList); + + const QVector<QCoapResource> resourceList = QCoapProtocol::resourcesFromCoreLinkList(QHostAddress(senderAddress), coreLinkList); + + QCOMPARE(resourceList.size(), resourceNumber); + + int resourceIndex = 0; + for (const auto &resource : resourceList) { + QCOMPARE(resource.host(), QHostAddress(senderAddress)); + QCOMPARE(resource.path(), pathList[resourceIndex]); + QCOMPARE(resource.title(), titleList[resourceIndex]); + QCOMPARE(resource.resourceType(), resourceTypeList[resourceIndex]); + QCOMPARE(resource.contentFormat(), contentFormatList[resourceIndex]); + QCOMPARE(resource.interface(), interfaceList[resourceIndex]); + QCOMPARE(resource.maximumSize(), maximumSizeList[resourceIndex]); + QCOMPARE(resource.observable(), observableList[resourceIndex]); + ++resourceIndex; + } +} + +QTEST_APPLESS_MAIN(tst_QCoapResource) + +#include "tst_qcoapresource.moc" diff --git a/tests/tests.pro b/tests/tests.pro new file mode 100644 index 0000000..157ef34 --- /dev/null +++ b/tests/tests.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += auto |