/**************************************************************************** ** ** This file is part of the KD Soap project. ** ** SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company ** ** SPDX-License-Identifier: MIT ** ****************************************************************************/ #include "httpserver_p.h" #include "wsdl_mywsdl_document.h" #include "wsdl_thomas-bayer.h" #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_OPENSSL #include #include #else // workaround moc issue class KDSoapSslHandler { }; Q_DECLARE_METATYPE(KDSoapSslHandler *) #endif #include #include using namespace KDSoapUnitTestHelpers; static const char *xmlBegin = ""; static const char *xmlNamespaces = "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"" " xmlns:soap-enc=\"http://schemas.xmlsoap.org/soap/encoding/\"" " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"" " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""; static const char *myWsdlNamespace = "http://www.kdab.com/xml/MyWsdl/"; class WsdlDocumentTest : public QObject { Q_OBJECT public: WsdlDocumentTest() : m_expectedDelayedCalls(0) { } private: static KDAB__AddEmployee addEmployeeParameters() { KDAB__EmployeeAchievements achievements; QList lst; KDAB__EmployeeAchievement achievement1; achievement1.setType(QByteArray("Project")); achievement1.setLabel(QString::fromLatin1("Management")); achievement1.setTime(QDate(2011, 06, 27)); lst.append(achievement1); KDAB__EmployeeAchievement achievement2; achievement2.setType(QByteArray("Development")); achievement2.setLabel(QString::fromLatin1("C++")); achievement2.setTime(QString::fromLatin1("today")); lst.append(achievement2); achievements.setItems(lst); KDAB__EmployeeType employeeType; employeeType.setType(KDAB__EmployeeTypeEnum::Developer); employeeType.setOtherRoles(QList() << KDAB__EmployeeTypeEnum::TeamLeader); employeeType.setTeam(QList() << QString::fromLatin1("Minitel")); KDAB__AddEmployee addEmployeeParams; addEmployeeParams.setEmployeeType(employeeType); addEmployeeParams.setEmployeeName(QString::fromLatin1("David Faure")); addEmployeeParams.setEmployeeCountry(QString::fromLatin1("France")); addEmployeeParams.setEmployeeAchievements(achievements); KDAB__EmployeeId id; id.setId(5); addEmployeeParams.setEmployeeId(id); return addEmployeeParams; } static QByteArray requestXmlTemplate() { return QByteArray(xmlEnvBegin11()) + " xmlns:n1=\"http://www.kdab.com/xml/MyWsdl/\">%1" "" "" "" "TeamLeader" "Minitel" "" "David Faure" "France" "" "" "50726f6a656374" // Project "Management" "2011-06-27" "" "" "446576656c6f706d656e74" // Development "C++" "today" "" "" "" "5" "" "" "" + xmlEnvEnd() + '\n'; // added by QXmlStreamWriter::writeEndDocument } static QByteArray expectedHeader() { return QByteArray("" "" "foo" "bar" "" "" "id" "" ""); } static QByteArray addEmployeeResponse() { return QByteArray(xmlEnvBegin11()) + ">" "" "" "" "" "466F6F" // "Foo" " " + xmlEnvEnd(); } private Q_SLOTS: void initTestCase() { qRegisterMetaType(); #ifndef QT_NO_OPENSSL qRegisterMetaType>(); qRegisterMetaType(); #endif } // Using wsdl-generated code, make a call, and check the xml that was sent, // and check that the server's response was correctly parsed. void testMyWsdlPublic() { HttpServerThread server(addEmployeeResponse(), HttpServerThread::Public); // For testing the http server with telnet or wget: // httpGet(server.endPoint()); // QEventLoop testLoop; // testLoop.exec(); MyWsdlDocument service; service.setEndPoint(server.endPoint()); KDAB__LoginElement login; login.setUser(QLatin1String("foo")); login.setPass(QLatin1String("bar")); KDAB__SessionElement session; session.setSessionId(QLatin1String("id")); service.setLoginHeader(login); service.setSessionHeader(session); KDAB__AddEmployee addEmployeeParams = addEmployeeParameters(); QByteArray ret = service.addEmployee(addEmployeeParams); if (!service.lastError().isEmpty()) { qDebug() << service.lastError(); } QVERIFY(service.lastError().isEmpty()); QCOMPARE(ret, QByteArray("Foo")); // Check what we sent { QByteArray expectedRequestXml = requestXmlTemplate(); expectedRequestXml.replace("%1", expectedHeader()); QVERIFY(xmlBufferCompare(server.receivedData(), expectedRequestXml)); QCOMPARE(QString::fromUtf8(server.receivedData().constData()), QString::fromUtf8(expectedRequestXml.constData())); const QByteArray headers = server.receivedHeaders(); QVERIFY(headers.contains("SoapAction: \"http://www.kdab.com/AddEmployee\"") || headers.contains("soapaction: \"http://www.kdab.com/AddEmployee\"")); // Qt >= 6.8 } // Test utf8 addEmployeeParams.setEmployeeName(QString::fromUtf8("Hervé")); addEmployeeParams.setEmployeeCountry(QString::fromUtf8("фгн7")); // random russian letters { // This second call also tests that persistent headers are indeed persistent. server.resetReceivedBuffers(); QByteArray expectedRequestXml = requestXmlTemplate(); expectedRequestXml.replace("%1", expectedHeader()); expectedRequestXml.replace("David Faure", "Hervé"); expectedRequestXml.replace("France", "фгн7"); ret = service.addEmployee(addEmployeeParams); QVERIFY(service.lastError().isEmpty()); QCOMPARE(ret, QByteArray("Foo")); QVERIFY(xmlBufferCompare(server.receivedData(), expectedRequestXml)); } // Test removing headers { server.resetReceivedBuffers(); service.clearLoginHeader(); service.clearSessionHeader(); ret = service.addEmployee(addEmployeeParameters()); QByteArray expectedRequestXml = requestXmlTemplate(); expectedRequestXml.replace("%1", ""); QVERIFY(xmlBufferCompare(server.receivedData(), expectedRequestXml)); } } void testSslError() { #ifdef QT_NO_OPENSSL return; #else if (!QSslSocket::supportsSsl()) { return; // see above } // Use SSL on the server, without adding the CA certificate (done by setSslConfiguration()) HttpServerThread server(addEmployeeResponse(), HttpServerThread::Ssl); MyWsdlDocument service; service.setEndPoint(server.endPoint()); QVERIFY(server.endPoint().startsWith(QLatin1String("https"))); QSignalSpy sslErrorsSpy(service.clientInterface()->sslHandler(), &KDSoapSslHandler::sslErrors); // We need to use async API to test sslHandler, see documentation there. ListEmployeesJob *job = new ListEmployeesJob(&service); connect(job, &ListEmployeesJob::finished, this, &WsdlDocumentTest::slotListEmployeesJobFinished); job->start(); m_eventLoop.exec(); // Disable SSL so that termination can happen normally (do it asap, in case of failure below) server.disableSsl(); QVERIFY2(job->faultAsString().contains(QLatin1String("SSL handshake failed")), qPrintable(service.lastError())); QCOMPARE(sslErrorsSpy.count(), 1); const QList errors = sslErrorsSpy.at(0).at(1).value>(); QVERIFY(errors.count() > 0); #ifdef Q_OS_LINUX // Windows seems to get "Unknown error". Bah... And OSX has UnableToVerifyFirstCertificate at position 1. QCOMPARE(( int )errors.at(0).error(), ( int )QSslError::UnableToGetLocalIssuerCertificate); if (errors.count() > 2) { QCOMPARE(( int )errors.at(1).error(), ( int )QSslError::CertificateUntrusted); QCOMPARE(( int )errors.at(2).error(), ( int )QSslError::UnableToVerifyFirstCertificate); } else { QCOMPARE(( int )errors.at(1).error(), ( int )QSslError::UnableToVerifyFirstCertificate); } #endif #endif } void testSslErrorHandledBySlot() { #ifdef QT_NO_OPENSSL return; #else if (!QSslSocket::supportsSsl()) { return; // see above } m_errors.clear(); // Use SSL on the server, without adding the CA certificate (done by setSslConfiguration()) HttpServerThread server(addEmployeeResponse(), HttpServerThread::Ssl); MyWsdlDocument service; service.setEndPoint(server.endPoint()); KDSoapSslHandler *sslHandler = service.clientInterface()->sslHandler(); connect(sslHandler, &KDSoapSslHandler::sslErrors, this, &WsdlDocumentTest::slotSslHandlerErrors); AddEmployeeJob *job = new AddEmployeeJob(&service); job->setParameters(addEmployeeParameters()); connect(job, &AddEmployeeJob::finished, this, &WsdlDocumentTest::slotAddEmployeeJobFinished); job->start(); m_eventLoop.exec(); // Disable SSL so that termination can happen normally (do it asap, in case of failure below) server.disableSsl(); QVERIFY(m_errors.count() > 0); QCOMPARE(job->faultAsString(), QString()); QCOMPARE(QString::fromLatin1(job->resultParameters().constData()), QString::fromLatin1("Foo")); #endif } #ifdef Q_OS_UNIX void testSslErrorIgnoredUpfront() { #ifdef QT_NO_OPENSSL return; #else if (!QSslSocket::supportsSsl()) { return; // see above } m_errors.clear(); // Use SSL on the server, without adding the CA certificate (done by setSslConfiguration()) HttpServerThread server(addEmployeeResponse(), HttpServerThread::Ssl); MyWsdlDocument service; service.setEndPoint(server.endPoint()); QList certs = QSslCertificate::fromPath(QString::fromLatin1(":/certs/test-127.0.0.1-cert.pem")); QCOMPARE(certs.count(), 1); service.clientInterface()->ignoreSslErrors(QList() << QSslError(QSslError::UnableToGetLocalIssuerCertificate, certs.at(0)) << QSslError(QSslError::CertificateUntrusted, certs.at(0)) << QSslError(QSslError::UnableToVerifyFirstCertificate, certs.at(0))); AddEmployeeJob *job = new AddEmployeeJob(&service); job->setParameters(addEmployeeParameters()); connect(job, &AddEmployeeJob::finished, this, &WsdlDocumentTest::slotAddEmployeeJobFinished); job->start(); m_eventLoop.exec(); // Disable SSL so that termination can happen normally (do it asap, in case of failure below) server.disableSsl(); QCOMPARE(m_errors.count(), 0); QCOMPARE(job->faultAsString(), QString()); QCOMPARE(QString::fromLatin1(job->resultParameters().constData()), QString::fromLatin1("Foo")); #endif } #endif // SOAP-79 / issue29 void testSslErrorSyncCall() { #ifdef QT_NO_OPENSSL return; #else if (!QSslSocket::supportsSsl()) { return; // see above } m_errors.clear(); // Use SSL on the server, without adding the CA certificate (done by setSslConfiguration()) HttpServerThread server(addEmployeeResponse(), HttpServerThread::Ssl); MyWsdlDocument service; service.setEndPoint(server.endPoint()); QSignalSpy sslErrorsSpy(service.clientInterface()->sslHandler(), &KDSoapSslHandler::sslErrors); QByteArray ret = service.addEmployee(addEmployeeParameters()); QVERIFY(ret.isEmpty()); QCOMPARE(service.lastErrorCode(), static_cast(QNetworkReply::SslHandshakeFailedError)); QCOMPARE(service.lastFaultCode(), QString::number(QNetworkReply::SslHandshakeFailedError)); QVERIFY2(service.lastError().contains(QLatin1String("SSL handshake failed")), qPrintable(service.lastError())); // Disable SSL so that termination can happen normally (do it asap, in case of failure below) server.disableSsl(); QCOMPARE(m_errors.count(), 0); QCOMPARE(sslErrorsSpy.count(), 1); QTest::qWait(100); // process some events #endif } void testMyWsdlSSL() { #ifdef QT_NO_OPENSSL return; #else if (!QSslSocket::supportsSsl()) { QSKIP("No SSL support on this machine, check that ssleay.so/ssleay32.dll is installed"); } QVERIFY(KDSoapUnitTestHelpers::setSslConfiguration()); HttpServerThread server(addEmployeeResponse(), HttpServerThread::Ssl); // For testing the http server with telnet or wget: // qDebug() << "endPoint=" << server.endPoint(); // httpGet(server.endPoint()); // QEventLoop testLoop; // testLoop.exec(); MyWsdlDocument service; service.setEndPoint(server.endPoint()); QVERIFY(server.endPoint().startsWith(QLatin1String("https"))); KDAB__LoginElement login; login.setUser(QLatin1String("foo")); login.setPass(QLatin1String("bar")); KDAB__SessionElement session; session.setSessionId(QLatin1String("id")); service.setLoginHeader(login); service.setSessionHeader(session); QByteArray ret = service.addEmployee(addEmployeeParameters()); if (!service.lastError().isEmpty()) { qDebug() << service.lastError(); } QVERIFY(service.lastError().isEmpty()); QCOMPARE(ret, QByteArray("Foo")); // Check what we sent QByteArray expectedRequestXml = requestXmlTemplate(); expectedRequestXml.replace("%1", expectedHeader()); QVERIFY(xmlBufferCompare(server.receivedData(), expectedRequestXml)); QCOMPARE(QString::fromUtf8(server.receivedData().constData()), QString::fromUtf8(expectedRequestXml.constData())); const QByteArray headers = server.receivedHeaders(); QVERIFY(headers.contains("SoapAction: \"http://www.kdab.com/AddEmployee\"") || headers.contains("soapaction: \"http://www.kdab.com/AddEmployee\"")); #endif } void testSimpleType() { HttpServerThread server(countryResponse(), HttpServerThread::Public); MyWsdlDocument service; service.setEndPoint(server.endPoint()); KDAB__EmployeeNameParams params; params.setEmployeeName(KDAB__EmployeeName(QString::fromUtf8("David Ä Faure"))); const KDAB__EmployeeCountryResponse employeeCountryResponse = service.getEmployeeCountry(params); if (!service.lastError().isEmpty()) { qDebug() << service.lastError(); } QVERIFY(service.lastError().isEmpty()); QCOMPARE(employeeCountryResponse.employeeCountry().value(), QString::fromLatin1("France")); QVERIFY(xmlBufferCompare(server.receivedData(), expectedCountryRequest())); QCOMPARE(QString::fromUtf8(server.receivedData().constData()), QString::fromUtf8(expectedCountryRequest().constData())); } void testEmptyResponse() { HttpServerThread server(emptyResponse(), HttpServerThread::Public); MyWsdlDocument service; service.setEndPoint(server.endPoint()); KDAB__EmployeeNameParams params; const KDAB__EmployeeCountryResponse employeeCountryResponse = service.getEmployeeCountry(params); QCOMPARE(service.lastError(), QString()); // no error, just an empty struct QCOMPARE(employeeCountryResponse.employeeCountry().value(), QString()); } // Test enum deserialization void testEnums() { // Prepare response QByteArray responseData = QByteArray(xmlEnvBegin11()) + ">" "" "Minitel" "TeamLeader" "" "" + xmlEnvEnd(); HttpServerThread server(responseData, HttpServerThread::Public); MyWsdlDocument service; service.setEndPoint(server.endPoint()); KDAB__EmployeeNameParams params; params.setEmployeeName(KDAB__EmployeeName(QLatin1String("Joe"))); const KDAB__EmployeeType employeeType = service.getEmployeeType(params); if (!service.lastError().isEmpty()) { qDebug() << service.lastError(); } QVERIFY(service.lastError().isEmpty()); QCOMPARE(employeeType.team().first().value().value(), QLatin1String("Minitel")); QCOMPARE(employeeType.otherRoles().count(), 1); QCOMPARE(employeeType.otherRoles().at(0).type(), KDAB__EmployeeTypeEnum::TeamLeader); QCOMPARE(( int )employeeType.type().type(), ( int )KDAB__EmployeeTypeEnum::Developer); } // Test repeated children void testRepeatedChildren() { // Just testing the generated code KDAB__EmployeeNameParams params; KDAB__TestRepeatedChildren children; children.setCenter(42); children.setLow(4); children.setHigh(5); children.setWidth(6); params.setRepeatedChildren(children); } // Test repeated names in nested complex types void testRepeatedNames() { // Just test the generated code, two different classes should have been // created for two structurally different nested complex types of the // same name: KDAB__TelegramRequest req; KDAB__RepeatedName repeatedName; repeatedName.setIntparam1(0); req.setRepeatedName(repeatedName); KDAB__TelegramResponse resp; KDAB__RepeatedName1 repeatedName1; repeatedName1.setStringparam1("test1"); resp.setRepeatedName(repeatedName1); KDAB__EmployeeNameParams params; KDAB__RepeatedName repeatedName_sameStructure; repeatedName_sameStructure.setIntparam1(1); params.setRepeatedName(repeatedName_sameStructure); KDAB__EmployeeCountryResponse resp2; KDAB__RepeatedName1 repeatedName1_sameStructure; repeatedName1_sameStructure.setStringparam1("test2"); resp2.setRepeatedName(repeatedName1_sameStructure); } void testSoapVersion() { // Prepare response QByteArray responseData = QByteArray(xmlEnvBegin11()) + ">" "" "Minitel" "TeamLeader" "" "" + xmlEnvEnd(); HttpServerThread server(responseData, HttpServerThread::Public); MyWsdlDocument service; service.setEndPoint(server.endPoint()); KDAB__EmployeeNameParams params; params.setEmployeeName(KDAB__EmployeeName(QLatin1String("Joe"))); service.setSoapVersion(KDSoapClientInterface::SOAP1_1); KDAB__EmployeeType employeeType = service.getEmployeeType(params); QVERIFY(service.lastError().isEmpty()); service.setSoapVersion(KDSoapClientInterface::SOAP1_2); KDAB__EmployeeType employeeType2 = service.getEmployeeType(params); QVERIFY(service.lastError().isEmpty()); } // Was http://www.service-repository.com/service/wsdl?id=163859, but it disappeared. // Local WSDL file: thomas-bayer.wsdl void testSequenceInResponse() { // Prepare response QByteArray responseData = QByteArray(xmlEnvBegin11()) + ">" "Great BritainIreland" " " + xmlEnvEnd(); HttpServerThread server(responseData, HttpServerThread::Public); NamesServiceService serv; serv.setEndPoint(server.endPoint()); const QStringList countries = serv.getCountries().country(); // the wsdl should have named it "countries"... QCOMPARE(countries.count(), 2); QCOMPARE(countries[0], QString::fromLatin1("Great Britain")); QCOMPARE(countries[1], QString::fromLatin1("Ireland")); const QByteArray expectedRequestXml = QByteArray(xmlEnvBegin11()) + ">" "" "" "" + xmlEnvEnd(); QVERIFY(xmlBufferCompare(server.receivedData(), expectedRequestXml)); // Same test without using generated code { const QString messageNamespace = QString::fromLatin1("http://namesservice.thomas_bayer.com/"); KDSoapClientInterface client(server.endPoint(), messageNamespace); KDSoapMessage message; KDSoapPendingCall pendingCall = client.asyncCall(QLatin1String("getCountries"), message); KDSoapPendingCallWatcher *watcher = new KDSoapPendingCallWatcher(pendingCall, this); connect(watcher, &KDSoapPendingCallWatcher::finished, this, &WsdlDocumentTest::slotFinished); m_eventLoop.exec(); // qDebug() << m_returnMessage; QCOMPARE(m_returnMessage.arguments()[0].value().toString(), QString::fromLatin1("Great Britain")); QCOMPARE(m_returnMessage.arguments()[1].value().toString(), QString::fromLatin1("Ireland")); } } void testAnyType() { // Prepare response QByteArray responseData = QByteArray(xmlEnvBegin11()) + ">" "" "response" "42" "Forty-two" "Minitel" "" "Project" "Management" "" "" "" + xmlEnvEnd(); HttpServerThread server(responseData, HttpServerThread::Public); MyWsdlDocument service; service.setEndPoint(server.endPoint()); KDAB__AnyType anyType; anyType.setInput(KDSoapValue(QString::fromLatin1("foo"), QString::fromLatin1("Value"), KDSoapNamespaceManager::xmlSchema2001(), QString::fromLatin1("string"))); KDSoapValueList schemaChildValues; KDSoapValue testVal(QLatin1String("test"), QString::fromLatin1("input")); testVal.setNamespaceUri(KDSoapNamespaceManager::xmlSchema2001()); schemaChildValues.append(testVal); KDSoapValue inputSchema(QLatin1String("schema"), schemaChildValues); inputSchema.setNamespaceUri(KDSoapNamespaceManager::xmlSchema2001()); anyType.setSchema(inputSchema); const KDAB__AnyTypeResponse response = service.testAnyType(anyType); const QList values = response.return_(); QCOMPARE(values.count(), 4); QCOMPARE(values.at(0).value().toInt(), 42); QCOMPARE(values.at(1).value().toString(), QString::fromLatin1("Forty-two")); QCOMPARE(values.at(2).value().toString(), QString::fromLatin1("Minitel")); const QList achievements = values.at(3).childValues(); QCOMPARE(achievements.count(), 2); QCOMPARE(achievements.at(0).value().toString(), QString::fromLatin1("Project")); QCOMPARE(achievements.at(1).value().toString(), QString::fromLatin1("Management")); const QByteArray expectedRequestXml = QByteArray(xmlEnvBegin11()) + ">" "" "" "input" "Value" "" "" + xmlEnvEnd(); QVERIFY(xmlBufferCompare(server.receivedData(), expectedRequestXml)); const KDSoapValue schema = response.schema(); QCOMPARE(schema.name(), QString::fromLatin1("schema")); QCOMPARE(schema.namespaceUri(), KDSoapNamespaceManager::xmlSchema2001()); const QByteArray expectedResponseSchemaXml = "" "" "response" ""; QVERIFY(xmlBufferCompare(schema.toXml(), expectedResponseSchemaXml)); } // Document/literal, not wrapped -> the operation name doesn't appear void testByteArrays() { // Prepare response QByteArray responseData = QByteArray(xmlEnvBegin11()) + ">" "466f6f" "" + xmlEnvEnd(); HttpServerThread server(responseData, HttpServerThread::Public); MyWsdlDocument service; service.setEndPoint(server.endPoint()); const KDAB__TelegramType ret = service.sendRawTelegram(KDAB__TelegramType("Hello")); QCOMPARE(service.lastError(), QString()); QCOMPARE(ret.value(), QByteArray("Foo")); const QByteArray expectedRequestXml = QByteArray(xmlEnvBegin11()) + ">" "" "48656c6c6f" "" + xmlEnvEnd(); QVERIFY(xmlBufferCompare(server.receivedData(), expectedRequestXml)); } // Client+server tests void testServerAddEmployee(); void testServerAddEmployeeJob(); void testServerPostByHand(); void testServerEmptyArgs(); void testServerFaultSync(); void testServerFaultAsync(); void testSendTelegram(); void testSendHugeTelegram(); void testServerDelayedCall(); void testSyncCallAfterServerDelayedCall(); void testServerTwoDelayedCalls(); void testDisconnectDuringDelayedCall(); void testServerDifferentPath(); void testServerDifferentPathFault(); public slots: void slotFinished(KDSoapPendingCallWatcher *watcher) { m_returnMessage = watcher->returnMessage(); m_eventLoop.quit(); } void slotDelayedAddEmployeeDone(const QByteArray &data) { // qDebug() << Q_FUNC_INFO << data; m_delayedData << data; if (--m_expectedDelayedCalls == 0) { m_eventLoop.quit(); } } void slotAddEmployeeJobFinished(KDSoapJob *) { // TODO: we should emit a signal with the proper job type? // AddEmployeeJob* addJob = static_cast(job); m_eventLoop.quit(); } void slotListEmployeesJobFinished(KDSoapJob *) { m_eventLoop.quit(); } void slotSslHandlerErrors(KDSoapSslHandler *handler, const QList &errors) { #ifdef QT_NO_OPENSSL Q_UNUSED(handler); Q_UNUSED(errors); #else // This is just for testing error handling. Don't write this in your actual code... #ifdef Q_OS_UNIX // Windows seems to get "Unknown error". Bah... // if (errors.at(0).error() == QSslError::UnableToGetLocalIssuerCertificate) QList certs = QSslCertificate::fromPath(QString::fromLatin1(":/certs/test-127.0.0.1-cert.pem")); QCOMPARE(certs.count(), 1); handler->ignoreSslErrors(QList() << QSslError(QSslError::UnableToGetLocalIssuerCertificate, certs.at(0)) << QSslError(QSslError::CertificateUntrusted, certs.at(0)) << QSslError(QSslError::UnableToVerifyFirstCertificate, certs.at(0))); #else handler->ignoreSslErrors(); #endif m_errors = errors; #endif } private: QEventLoop m_eventLoop; KDSoapMessage m_returnMessage; #ifndef QT_NO_OPENSSL QList m_errors; #endif int m_expectedDelayedCalls; QList m_delayedData; static QByteArray emptyResponse() { return QByteArray(xmlEnvBegin11()) + ">" + xmlEnvEnd(); } static QByteArray countryResponse() { return QByteArray(xmlEnvBegin11()) + ">" "France" " " + xmlEnvEnd(); } static QByteArray expectedCountryRequest() { return QByteArray(xmlEnvBegin11()) + ">" "" "" "David Ä Faure" "" "000" "0" "" "" + xmlEnvEnd() + '\n'; // added by QXmlStreamWriter::writeEndDocument } }; #include "KDSoapServer.h" #include "KDSoapServerObjectInterface.h" class MyJob : public QObject { Q_OBJECT public: MyJob(const KDSoapDelayedResponseHandle &handle) : m_handle(handle) { QTimer::singleShot(200, this, &MyJob::slotDone); } KDSoapDelayedResponseHandle responseHandle() const { return m_handle; } Q_SIGNALS: void done(MyJob *); private Q_SLOTS: void slotDone() { emit done(this); } private: KDSoapDelayedResponseHandle m_handle; }; class NameServiceServerObject : public NamesServiceServiceServerBase /* generated from thomas-bayer.wsdl */ { Q_OBJECT public: virtual TNS__GetCountriesResponse getCountries(const TNS__GetCountries &) override { TNS__GetCountriesResponse response; response.setCountry(QStringList() << QLatin1String("Great Britain") << QLatin1String("Ireland")); return response; } TNS__GetNamesInCountryResponse getNamesInCountry(const TNS__GetNamesInCountry ¶meters) override { TNS__GetNamesInCountryResponse response; if (parameters.country() == QLatin1String("Ireland")) { response.setName(QStringList() << QLatin1String("Name1") << QLatin1String("Name2")); } return response; } void getNameInfoResponse(const KDSoapDelayedResponseHandle &responseHandle, const TNS__GetNameInfoResponse &ret); virtual TNS__GetNameInfoResponse getNameInfo(const TNS__GetNameInfo ¶meters) override { setFault(QLatin1String("Server.Implementation"), QLatin1String("Not implemented"), QLatin1String(metaObject()->className()), parameters.name()); return TNS__GetNameInfoResponse(); } }; class DocServerObject : public MyWsdlDocumentServerBase /* generated from mywsdl_document.wsdl */ { Q_OBJECT public: QByteArray addEmployee(const KDAB__AddEmployee ¶meters) override { // qDebug() << "addEmployee called"; const QString name = KDAB__LimitedString(parameters.employeeName()).value(); if (name.isEmpty()) { setFault(QLatin1String("Client.Data"), QLatin1String("Empty employee name"), QLatin1String("DocServerObject"), tr("Employee name must not be empty")); return QByteArray(); } // TODO generate a helper method for this! KDSoapHeaders headers; KDSoapMessage sessionHeader; KDSoapValue sessionElement(QString::fromLatin1("SessionElement"), QVariant()); sessionElement.setNamespaceUri(QLatin1String(myWsdlNamespace)); KDSoapValue sessionIdElement(QString::fromLatin1("sessionId"), QString::fromLatin1("returned_id")); sessionElement.childValues().append(sessionIdElement); sessionHeader.childValues().append(sessionElement); headers.append(sessionHeader); setResponseHeaders(headers); return "added " + name.toLatin1(); } QByteArray delayedAddEmployee(const KDAB__AddEmployee ¶meters) override { // qDebug() << "delayedAddEmployee called"; Q_UNUSED(parameters); m_lastMethodCalled = QLatin1String("delayedAddEmployee"); KDSoapDelayedResponseHandle handle = prepareDelayedResponse(); MyJob *job = new MyJob(handle); connect(job, &MyJob::done, this, &DocServerObject::slotDelayedResponse); return "THIS VALUE IS IGNORED"; } void listEmployees() override { m_lastMethodCalled = QLatin1String("listEmployees"); } void heart_beat() override { m_lastMethodCalled = QLatin1String("heartbeat"); } KDAB__AnyTypeResponse testAnyType(const KDAB__AnyType ¶meters) override { KDAB__AnyTypeResponse response; response.setReturn(QList() << parameters.input()); return response; } KDAB__EmployeeCountryResponse getEmployeeCountry(const KDAB__EmployeeNameParams &employeeNameParams) override { KDAB__EmployeeCountryResponse resp; if (QString(employeeNameParams.employeeName().value()) == QLatin1String("David")) { resp.setEmployeeCountry(QString::fromLatin1("France")); } else { resp.setEmployeeCountry(QString::fromLatin1("Unknown country!")); } return resp; } KDAB__EmployeeType getEmployeeType(const KDAB__EmployeeNameParams &employeeNameParams) override { KDAB__EmployeeType type; type.setWeakType(QVariant(42)); // anySimpleType was mapped to QVariant if (QString(employeeNameParams.employeeName().value()) == QLatin1String("David")) { type.setTeam(QList() << KDAB__TeamName(QString::fromLatin1("Minitel"))); } return type; } KDAB__TelegramType sendRawTelegram(const KDAB__TelegramType &telegram) override { return QByteArray("Got ") + telegram; } KDAB__TelegramResponse sendTelegram(const KDAB__TelegramRequest ¶meters) override { KDAB__TelegramResponse resp; resp.setTelegramHex(QByteArray("Received ") + parameters.telegramHex()); resp.setTelegramBase64(QByteArray("Received ") + parameters.telegramBase64()); return resp; } // Normally you don't reimplement this. This is just to store req and resp for the unittest. void processRequest(const KDSoapMessage &request, KDSoapMessage &response, const QByteArray &soapAction) override { m_request = request; MyWsdlDocumentServerBase::processRequest(request, response, soapAction); m_response = response; // qDebug() << "processRequest: done. " << this << "Response name=" << response.name(); } void processRequestWithPath(const KDSoapMessage &request, KDSoapMessage &response, const QByteArray &soapAction, const QString &path) override { Q_UNUSED(request); Q_UNUSED(response); if (path == QLatin1String("/xml/NamesService?foo")) { /*&& soapAction == "http://namesservice.thomas_bayer.com/getCountries"*/ m_nameServiceServerObject.processRequest(request, response, soapAction); doneProcessingRequestWithPath(m_nameServiceServerObject); } else { setFault(QLatin1String("Client.Data"), QString::fromLatin1("Action %1 not found in path %2").arg(QLatin1String(soapAction.constData()), path)); } } KDSoapMessage m_request; KDSoapMessage m_response; QString m_lastMethodCalled; private Q_SLOTS: void slotDelayedResponse(MyJob *job) { delayedAddEmployeeResponse(job->responseHandle(), QByteArray("delayed reply works")); job->deleteLater(); // TODO test delayed fault. m_lastMethodCalled = QString::fromLatin1("slotDelayedResponse"); } private: NameServiceServerObject m_nameServiceServerObject; }; class DocServer : public KDSoapServer { Q_OBJECT public: DocServer() : KDSoapServer() , m_lastServerObject(0) { setPath(QLatin1String("/xml")); } virtual QObject *createServerObject() override { m_lastServerObject = new DocServerObject; return m_lastServerObject; } DocServerObject *lastServerObject() { return m_lastServerObject; } private: DocServerObject *m_lastServerObject; // only for unittest purposes }; void WsdlDocumentTest::testServerAddEmployee() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); MyWsdlDocument service; service.setEndPoint(server->endPoint()); QByteArray ret = service.addEmployee(addEmployeeParameters()); QVERIFY(server->lastServerObject()); const QByteArray expectedResponseXml = "" "" "6164646564204461766964204661757265" "\n"; // qDebug() << server->lastServerObject() << "response name" << server->lastServerObject()->m_response.name(); // Note: that's the response as sent by the generated code. // But then the server socket code will call messageToXml, possibly with a method name, // we can't debug that here. The next test is for that. QVERIFY(xmlBufferCompare(server->lastServerObject()->m_response.toXml(), expectedResponseXml)); QCOMPARE(service.lastError(), QString()); QCOMPARE(QString::fromLatin1(ret.constData()), QString::fromLatin1("added David Faure")); KDSoapMessage sessionHeader = service.clientInterface()->lastResponseHeaders().header(QLatin1String("SessionElement")); KDSoapValue sessionIdValue = sessionHeader.arguments().child(QLatin1String("sessionId")); QCOMPARE(sessionIdValue.value().toString(), QLatin1String("returned_id")); } void WsdlDocumentTest::testServerAddEmployeeJob() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); MyWsdlDocument service; service.setEndPoint(server->endPoint()); AddEmployeeJob *job = new AddEmployeeJob(&service); job->setParameters(addEmployeeParameters()); job->start(); connect(job, &AddEmployeeJob::finished, this, &WsdlDocumentTest::slotAddEmployeeJobFinished); m_eventLoop.exec(); QCOMPARE(service.lastError(), QString()); QByteArray ret = job->resultParameters(); QCOMPARE(QString::fromLatin1(ret.constData()), QString::fromLatin1("added David Faure")); KDAB__SessionElement outputSession = job->outputHeader(); QCOMPARE(outputSession.sessionId(), QLatin1String("returned_id")); } static QByteArray rawCountryMessage() { return "" "David" ""; } void WsdlDocumentTest::testServerPostByHand() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); QUrl url(server->endPoint()); QNetworkRequest request(url); request.setRawHeader("SoapAction", "http://www.kdab.com/xml/MyWsdl/getEmployeeCountry"); QString soapHeader = QString::fromLatin1("text/xml;charset=utf-8"); request.setHeader(QNetworkRequest::ContentTypeHeader, soapHeader.toUtf8()); QNetworkAccessManager accessManager; QNetworkReply *reply = accessManager.post(request, rawCountryMessage()); QEventLoop loop; connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); loop.exec(); const QByteArray response = reply->readAll(); const QByteArray expected = "" "" "" "France" "" "" "\n"; QVERIFY(xmlBufferCompare(response, expected)); QCOMPARE(response.constData(), expected.constData()); } void WsdlDocumentTest::testServerEmptyArgs() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); MyWsdlDocument service; service.setEndPoint(server->endPoint()); service.heart_beat(); // ensure the method is generated service.listEmployees(); QVERIFY(server->lastServerObject()); QCOMPARE(server->lastServerObject()->m_lastMethodCalled, QString::fromLatin1("listEmployees")); QCOMPARE(service.lastError(), QString()); } void WsdlDocumentTest::testServerFaultSync() // test the error signals emitted on error, in sync calls { TestServerThread serverThread; DocServer *server = serverThread.startThread(); MyWsdlDocument service; service.setEndPoint(server->endPoint()); QSignalSpy addEmployeeErrorSpy(&service, &MyWsdlDocument::addEmployeeError); QSignalSpy soapErrorSpy(&service, &MyWsdlDocument::soapError); service.addEmployee(KDAB__AddEmployee()); QCOMPARE(service.lastFaultCode(), QString::fromLatin1("Client.Data")); QCOMPARE(service.lastError(), QString::fromLatin1("Fault code Client.Data: Empty employee name (DocServerObject). Error detail: Employee name must not be empty")); // Sync call doesn't emit signals QCOMPARE(soapErrorSpy.count(), 0); QCOMPARE(addEmployeeErrorSpy.count(), 0); } void WsdlDocumentTest::testServerFaultAsync() // test the error signals emitted on error, in async calls { TestServerThread serverThread; DocServer *server = serverThread.startThread(); MyWsdlDocument service; service.setEndPoint(server->endPoint()); QSignalSpy addEmployeeErrorSpy(&service, &MyWsdlDocument::addEmployeeError); QSignalSpy soapErrorSpy(&service, &MyWsdlDocument::soapError); service.asyncAddEmployee(KDAB__AddEmployee()); connect(&service, &MyWsdlDocument::soapError, &m_eventLoop, &QEventLoop::quit); m_eventLoop.exec(); QCOMPARE(soapErrorSpy.count(), 1); QCOMPARE(addEmployeeErrorSpy.count(), 1); QCOMPARE(soapErrorSpy[0][0].toString(), QString::fromLatin1("addEmployee")); KDSoapMessage msg = soapErrorSpy[0][1].value(); QCOMPARE(msg.faultAsString(), QString::fromLatin1("Fault code Client.Data: Empty employee name (DocServerObject). Error detail: Employee name must not be empty")); } void WsdlDocumentTest::testSendTelegram() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); MyWsdlDocument service; service.setEndPoint(server->endPoint()); KDAB__TelegramRequest req; req.setTelegramHex(KDAB__TelegramType("Hello")); req.setTelegramBase64(QByteArray("Hello")); const KDAB__TelegramResponse ret = service.sendTelegram(req); QCOMPARE(service.lastError(), QString()); QCOMPARE(ret.telegramHex().value(), QByteArray("Received Hello")); QCOMPARE(ret.telegramBase64().constData(), "Received Hello"); // Check the request as received by the server. const QByteArray expectedRequestXml = QByteArray(xmlBegin) + "" "48656c6c6f" "SGVsbG8=" "0" ""; const QString msgNS = QString::fromLatin1("http://www.kdab.com/xml/MyWsdl/"); // Note that "qualified" is false in m_request, since it was created dynamically by the server -> no namespaces QVERIFY(xmlBufferCompare(server->lastServerObject()->m_request.toXml(KDSoapValue::LiteralUse, msgNS), expectedRequestXml)); const QByteArray expectedResponseXml = QByteArray(xmlBegin) + "" "52656365697665642048656c6c6f" "UmVjZWl2ZWQgSGVsbG8=" "" ""; // m_response, however, has qualified = true (set by the generated code). QVERIFY(xmlBufferCompare(server->lastServerObject()->m_response.toXml(KDSoapValue::LiteralUse, msgNS), expectedResponseXml)); } void WsdlDocumentTest::testSendHugeTelegram() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); MyWsdlDocument service; service.setEndPoint(server->endPoint()); QByteArray hugePayload; hugePayload.resize(50000); char ch = 'A'; for (int i = 0; i < hugePayload.size(); ++i) { hugePayload[i] = ++ch; if (ch == 'z' + 1) { ch = 'A'; } } KDAB__TelegramRequest req; req.setTelegramHex(KDAB__TelegramType(hugePayload)); req.setTelegramBase64(QByteArray(hugePayload)); const KDAB__TelegramResponse ret = service.sendTelegram(req); QCOMPARE(service.lastError(), QString()); QCOMPARE(ret.telegramHex().value(), QByteArray("Received ") + hugePayload); QCOMPARE(ret.telegramBase64().constData(), QByteArray("Received " + hugePayload).constData()); } void WsdlDocumentTest::testServerDelayedCall() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); MyWsdlDocument service; service.setEndPoint(server->endPoint()); const QByteArray ret = service.delayedAddEmployee(addEmployeeParameters()); QCOMPARE(service.lastError(), QString()); QCOMPARE(QString::fromLatin1(ret.constData()), QString::fromLatin1("delayed reply works")); } void WsdlDocumentTest::testSyncCallAfterServerDelayedCall() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); MyWsdlDocument service; service.setEndPoint(server->endPoint()); const QByteArray ret = service.delayedAddEmployee(addEmployeeParameters()); QCOMPARE(service.lastError(), QString()); QCOMPARE(QString::fromLatin1(ret.constData()), QString::fromLatin1("delayed reply works")); const QByteArray ret2 = service.addEmployee(addEmployeeParameters()); QCOMPARE(service.lastError(), QString()); QCOMPARE(QString::fromLatin1(ret2.constData()), QString::fromLatin1("added David Faure")); } void WsdlDocumentTest::testServerTwoDelayedCalls() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); MyWsdlDocument service; service.setEndPoint(server->endPoint()); connect(&service, &MyWsdlDocument::delayedAddEmployeeDone, this, &WsdlDocumentTest::slotDelayedAddEmployeeDone); m_expectedDelayedCalls = 2; // Interestingly, this doesn't test what I thought it would test. // Making two async calls means QNAM will connect two different client sockets to the server, // which means we have two different KDSoapServerSockets, not one. // So basically we can't test "disable socket while waiting for delayed response" // in KDSoapServerSocket, because QNAM already protects us from the old mistake of // "sending two requests without waiting for the response of the first request". service.asyncDelayedAddEmployee(addEmployeeParameters()); service.asyncDelayedAddEmployee(addEmployeeParameters()); m_eventLoop.exec(); QCOMPARE(service.lastError(), QString()); const QString expected = QString::fromLatin1("delayed reply works"); QCOMPARE(QString::fromLatin1(m_delayedData.at(0).constData()), expected); QCOMPARE(QString::fromLatin1(m_delayedData.at(1).constData()), expected); } // SOAP-34 / SOAP-67 void WsdlDocumentTest::testDisconnectDuringDelayedCall() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); QPointer serverObject; { MyWsdlDocument service; service.setEndPoint(server->endPoint()); DelayedAddEmployeeJob *job = new DelayedAddEmployeeJob(&service); job->setParameters(addEmployeeParameters()); job->start(); // Wait until the server method is called QTRY_VERIFY(server->lastServerObject()); serverObject = server->lastServerObject(); QTRY_COMPARE(serverObject->m_lastMethodCalled, QString::fromLatin1("delayedAddEmployee")); delete job; } // Disconnect the client // Now that there's one server object per socket (#289), the server object gets deleted before the delayed response QTRY_VERIFY(serverObject.isNull()); } // Same as testSequenceInResponse (thomas-bayer.wsdl), but as a server test, by calling DocServer on a different path void WsdlDocumentTest::testServerDifferentPath() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); NamesServiceService serv; QString endPoint = server->endPoint(); // ends with "/xml" endPoint += QLatin1String("/../xml/./NamesService?foo"); serv.setEndPoint(endPoint); const QStringList countries = serv.getCountries().country(); // the wsdl should have named it "countries"... QCOMPARE(countries.count(), 2); QCOMPARE(countries[0], QString::fromLatin1("Great Britain")); QCOMPARE(countries[1], QString::fromLatin1("Ireland")); } // now with a fault void WsdlDocumentTest::testServerDifferentPathFault() { TestServerThread serverThread; DocServer *server = serverThread.startThread(); NamesServiceService serv; QString endPoint = server->endPoint(); // ends with "/xml" endPoint += QLatin1String("/../xml/./NamesService?foo"); serv.setEndPoint(endPoint); TNS__GetNameInfo req; req.setName(QLatin1String("DOESNOTEXIST")); const TNS__NameInfo names = serv.getNameInfo(req).nameinfo(); QCOMPARE(serv.lastError(), QLatin1String("Fault code Server.Implementation: Not implemented (NameServiceServerObject). Error detail: DOESNOTEXIST")); } QTEST_MAIN(WsdlDocumentTest) #include "test_wsdl_document.moc"