rewrite for eet 2.0 api version v4
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
find_package(Qt6 6.5 REQUIRED COMPONENTS Test)
|
||||
|
||||
add_executable(EetCppV4RequestTest
|
||||
eetv4request_test.cpp
|
||||
)
|
||||
|
||||
set_target_properties(EetCppV4RequestTest PROPERTIES
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
target_link_libraries(EetCppV4RequestTest
|
||||
PRIVATE
|
||||
EetCpp::EetCpp
|
||||
Qt6::Test
|
||||
)
|
||||
|
||||
add_test(NAME EetCpp.v4.request COMMAND EetCppV4RequestTest)
|
||||
|
||||
add_executable(EetCppV4XmlTest
|
||||
eetv4xml_test.cpp
|
||||
)
|
||||
|
||||
set_target_properties(EetCppV4XmlTest PROPERTIES
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
target_link_libraries(EetCppV4XmlTest
|
||||
PRIVATE
|
||||
EetCpp::EetCpp
|
||||
Qt6::Test
|
||||
)
|
||||
|
||||
add_test(NAME EetCpp.v4.xml COMMAND EetCppV4XmlTest)
|
||||
|
||||
add_executable(EetCppV4SoapTest
|
||||
eetv4soap_test.cpp
|
||||
)
|
||||
|
||||
set_target_properties(EetCppV4SoapTest PROPERTIES
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
target_link_libraries(EetCppV4SoapTest
|
||||
PRIVATE
|
||||
EetCpp::EetCpp
|
||||
Qt6::Test
|
||||
)
|
||||
|
||||
add_test(NAME EetCpp.v4.soap COMMAND EetCppV4SoapTest)
|
||||
|
||||
add_executable(EetCppV4SignerTest
|
||||
eetv4signer_test.cpp
|
||||
)
|
||||
|
||||
set_target_properties(EetCppV4SignerTest PROPERTIES
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
target_link_libraries(EetCppV4SignerTest
|
||||
PRIVATE
|
||||
EetCpp::EetCpp
|
||||
Qt6::Test
|
||||
)
|
||||
|
||||
add_test(NAME EetCpp.v4.signer COMMAND EetCppV4SignerTest)
|
||||
|
||||
add_executable(EetCppV4ResponseTest
|
||||
eetv4response_test.cpp
|
||||
)
|
||||
|
||||
set_target_properties(EetCppV4ResponseTest PROPERTIES
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
target_link_libraries(EetCppV4ResponseTest
|
||||
PRIVATE
|
||||
EetCpp::EetCpp
|
||||
Qt6::Test
|
||||
)
|
||||
|
||||
add_test(NAME EetCpp.v4.response COMMAND EetCppV4ResponseTest)
|
||||
|
||||
add_executable(EetCppV4ClientTest
|
||||
eetv4client_test.cpp
|
||||
)
|
||||
|
||||
set_target_properties(EetCppV4ClientTest PROPERTIES
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
target_link_libraries(EetCppV4ClientTest
|
||||
PRIVATE
|
||||
EetCpp::EetCpp
|
||||
Qt6::Test
|
||||
)
|
||||
|
||||
add_test(NAME EetCpp.v4.client COMMAND EetCppV4ClientTest)
|
||||
|
||||
add_executable(EetCppV4ClientIntegrationTest
|
||||
eetv4client_integration_test.cpp
|
||||
)
|
||||
|
||||
set_target_properties(EetCppV4ClientIntegrationTest PROPERTIES
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
target_link_libraries(EetCppV4ClientIntegrationTest
|
||||
PRIVATE
|
||||
EetCpp::EetCpp
|
||||
Qt6::Test
|
||||
)
|
||||
|
||||
add_test(NAME EetCpp.v4.client.integration COMMAND EetCppV4ClientIntegrationTest)
|
||||
|
||||
add_executable(EetCaTest
|
||||
eetca_test.cpp
|
||||
)
|
||||
|
||||
set_target_properties(EetCaTest PROPERTIES
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
target_link_libraries(EetCaTest
|
||||
PRIVATE
|
||||
EetCpp::EetCpp
|
||||
Qt6::Test
|
||||
)
|
||||
|
||||
add_test(NAME EetCpp.ca COMMAND EetCaTest)
|
||||
|
||||
add_executable(EetCaIntegrationTest
|
||||
eetca_integration_test.cpp
|
||||
)
|
||||
|
||||
set_target_properties(EetCaIntegrationTest PROPERTIES
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
target_link_libraries(EetCaIntegrationTest
|
||||
PRIVATE
|
||||
EetCpp::EetCpp
|
||||
Qt6::Test
|
||||
)
|
||||
|
||||
add_test(NAME EetCpp.ca.integration COMMAND EetCaIntegrationTest)
|
||||
@@ -0,0 +1,150 @@
|
||||
#include "eetca.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#include <QSslServer>
|
||||
#include <QTemporaryDir>
|
||||
#include <QtTest>
|
||||
|
||||
namespace
|
||||
{
|
||||
class MockCaServer : public QSslServer
|
||||
{
|
||||
public:
|
||||
explicit MockCaServer(const QByteArray &pkcs12)
|
||||
: m_pkcs12(pkcs12)
|
||||
{
|
||||
connect(this, &QSslServer::pendingConnectionAvailable, this, [this] {
|
||||
QSslSocket *socket = qobject_cast<QSslSocket *>(nextPendingConnection());
|
||||
if (socket == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
connect(socket, &QSslSocket::readyRead, this, [this, socket] {
|
||||
const QByteArray request = socket->readAll();
|
||||
const QByteArray firstLine = request.left(request.indexOf("\r\n"));
|
||||
const QList<QByteArray> parts = firstLine.split(' ');
|
||||
if (parts.size() < 2 || !request.contains("Authorization: Bearer "))
|
||||
{
|
||||
socket->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
QByteArray body;
|
||||
int status = 200;
|
||||
if (parts.at(1) == "/api/request/renew")
|
||||
{
|
||||
body = "{\"reqId\":\"123e4567-e89b-42d3-a456-426614174000\"}";
|
||||
status = 201;
|
||||
}
|
||||
else if (parts.at(1) == "/api/request/123e4567-e89b-42d3-a456-426614174000/status")
|
||||
{
|
||||
body = "{\"reqId\":\"123e4567-e89b-42d3-a456-426614174000\","
|
||||
"\"state\":\"ISSUED\"}";
|
||||
}
|
||||
else if (parts.at(1) == "/api/request/not-finished")
|
||||
{
|
||||
body = "{\"count\":1,\"data\":[\"123e4567-e89b-42d3-a456-426614174000\"]}";
|
||||
}
|
||||
else if (parts.at(1) == "/api/request/123e4567-e89b-42d3-a456-426614174000/claim-download")
|
||||
{
|
||||
body = "{\"pkcs12\":\"" + m_pkcs12.toBase64() +
|
||||
"\",\"password\":\"ABcd1234\",\"certificate\":{"
|
||||
"\"sn\":\"12345\",\"validFrom\":\"2026-01-01T00:00:00+00:00\","
|
||||
"\"validTo\":\"2027-01-01T00:00:00+00:00\",\"state\":\"VALID\"}}";
|
||||
}
|
||||
else if (parts.at(1) == "/api/request/123e4567-e89b-42d3-a456-426614174000/ack-download")
|
||||
{
|
||||
status = 204;
|
||||
}
|
||||
else
|
||||
{
|
||||
status = 404;
|
||||
body = "{\"code\":4,\"message\":\"Unknown object.\"}";
|
||||
}
|
||||
const QByteArray response =
|
||||
"HTTP/1.1 " + QByteArray::number(status) + " OK\r\n"
|
||||
"Content-Type: application/json\r\nContent-Length: " +
|
||||
QByteArray::number(body.size()) + "\r\nConnection: close\r\n\r\n" + body;
|
||||
socket->write(response);
|
||||
socket->disconnectFromHost();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
QByteArray m_pkcs12;
|
||||
};
|
||||
}
|
||||
|
||||
class EetCaIntegrationTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void runsCertificateWorkflow();
|
||||
};
|
||||
|
||||
void EetCaIntegrationTest::runsCertificateWorkflow()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
QVERIFY(directory.isValid());
|
||||
const QString keyPath = directory.filePath(QStringLiteral("key.pem"));
|
||||
const QString certPath = directory.filePath(QStringLiteral("certificate.pem"));
|
||||
const QString p12Path = directory.filePath(QStringLiteral("certificate.p12"));
|
||||
auto runOpenSsl = [](const QStringList &arguments) {
|
||||
QProcess process;
|
||||
process.start(QStringLiteral("openssl"), arguments);
|
||||
QVERIFY2(process.waitForFinished(30000), qPrintable(process.errorString()));
|
||||
QVERIFY2(process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0,
|
||||
process.readAllStandardError().constData());
|
||||
};
|
||||
runOpenSsl({QStringLiteral("req"), QStringLiteral("-x509"), QStringLiteral("-newkey"),
|
||||
QStringLiteral("rsa:2048"), QStringLiteral("-nodes"), QStringLiteral("-subj"),
|
||||
QStringLiteral("/CN=localhost"), QStringLiteral("-keyout"), keyPath,
|
||||
QStringLiteral("-out"), certPath, QStringLiteral("-days"), QStringLiteral("1")});
|
||||
runOpenSsl({QStringLiteral("pkcs12"), QStringLiteral("-export"), QStringLiteral("-out"),
|
||||
p12Path, QStringLiteral("-inkey"), keyPath, QStringLiteral("-in"), certPath,
|
||||
QStringLiteral("-passout"), QStringLiteral("pass:test")});
|
||||
QFile p12File(p12Path);
|
||||
QVERIFY(p12File.open(QIODevice::ReadOnly));
|
||||
const QByteArray p12 = p12File.readAll();
|
||||
|
||||
MockCaServer server(p12);
|
||||
QSslConfiguration ssl = QSslConfiguration::defaultConfiguration();
|
||||
QFile keyFile(keyPath);
|
||||
QFile certificateFile(certPath);
|
||||
QVERIFY(keyFile.open(QIODevice::ReadOnly));
|
||||
QVERIFY(certificateFile.open(QIODevice::ReadOnly));
|
||||
ssl.setPrivateKey(QSslKey(keyFile.readAll(), QSsl::Rsa, QSsl::Pem));
|
||||
ssl.setLocalCertificate(QSslCertificate(certificateFile.readAll(), QSsl::Pem));
|
||||
server.setSslConfiguration(ssl);
|
||||
QVERIFY(server.listen(QHostAddress::LocalHost));
|
||||
|
||||
QSslConfiguration clientSsl = QSslConfiguration::defaultConfiguration();
|
||||
clientSsl.setCaCertificates({ssl.localCertificate()});
|
||||
EetCaClient client({
|
||||
QUrl(QStringLiteral("https://localhost:%1/api").arg(server.serverPort())),
|
||||
p12Path, QByteArrayLiteral("test"), 5000, clientSsl
|
||||
});
|
||||
QString error;
|
||||
QUuid requestId;
|
||||
QVERIFY2(client.requestRenewal(&requestId, &error), qPrintable(error));
|
||||
QCOMPARE(requestId, QUuid(QStringLiteral("123e4567-e89b-42d3-a456-426614174000")));
|
||||
EetCaRequestStatus status;
|
||||
QVERIFY2(client.requestStatus(requestId, &status, &error), qPrintable(error));
|
||||
QCOMPARE(status.state, QStringLiteral("ISSUED"));
|
||||
QList<QUuid> unfinished;
|
||||
QVERIFY2(client.listUnfinished(&unfinished, &error), qPrintable(error));
|
||||
QCOMPARE(unfinished, QList<QUuid>{requestId});
|
||||
EetCaPkcs12 package;
|
||||
QVERIFY2(client.claimDownload(requestId, &package, &error), qPrintable(error));
|
||||
QCOMPARE(package.pkcs12, p12);
|
||||
QCOMPARE(package.password, QByteArrayLiteral("ABcd1234"));
|
||||
QVERIFY2(client.acknowledgeDownload(requestId, &error), qPrintable(error));
|
||||
}
|
||||
|
||||
QTEST_MAIN(EetCaIntegrationTest)
|
||||
#include "eetca_integration_test.moc"
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "eetca.h"
|
||||
|
||||
#include <QtTest>
|
||||
|
||||
class EetCaTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void rejectsInvalidConfiguration();
|
||||
void rejectsMissingSigningCertificate();
|
||||
};
|
||||
|
||||
void EetCaTest::rejectsInvalidConfiguration()
|
||||
{
|
||||
EetCaClient client({QUrl(QStringLiteral("http://localhost/api")),
|
||||
QStringLiteral("missing.p12"), QByteArrayLiteral("password"), 1000});
|
||||
QUuid requestId;
|
||||
QString error;
|
||||
QVERIFY(!client.requestRenewal(&requestId, &error));
|
||||
QCOMPARE(error, QStringLiteral("CA API requires a valid HTTPS base URL and positive timeout."));
|
||||
}
|
||||
|
||||
void EetCaTest::rejectsMissingSigningCertificate()
|
||||
{
|
||||
EetCaClient client({QUrl(QStringLiteral("https://localhost/api")),
|
||||
QStringLiteral("missing.p12"), QByteArrayLiteral("password"), 1000});
|
||||
QUuid requestId;
|
||||
QString error;
|
||||
QVERIFY(!client.requestRenewal(&requestId, &error));
|
||||
QCOMPARE(error, QStringLiteral("Cannot read the CA signing PKCS#12 file."));
|
||||
}
|
||||
|
||||
QTEST_MAIN(EetCaTest)
|
||||
#include "eetca_test.moc"
|
||||
@@ -0,0 +1,139 @@
|
||||
#include "eetv4client.h"
|
||||
#include "eetv4signer.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#include <QSslServer>
|
||||
#include <QTemporaryDir>
|
||||
#include <QtTest>
|
||||
|
||||
namespace
|
||||
{
|
||||
class MockEetServer : public QSslServer
|
||||
{
|
||||
public:
|
||||
explicit MockEetServer(const QByteArray &response)
|
||||
: m_response(response)
|
||||
{
|
||||
connect(this, &QSslServer::pendingConnectionAvailable, this, [this] {
|
||||
QSslSocket *socket = qobject_cast<QSslSocket *>(nextPendingConnection());
|
||||
if (socket == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
connect(socket, &QSslSocket::readyRead, this, [this, socket] {
|
||||
const QByteArray request = socket->readAll();
|
||||
const qsizetype bodyStart = request.indexOf("\r\n\r\n");
|
||||
const QByteArray soap = bodyStart < 0 ? QByteArray()
|
||||
: request.mid(bodyStart + 4);
|
||||
QString verificationError;
|
||||
m_requestVerified = !soap.isEmpty() &&
|
||||
EetV4Signer::verifySoap(soap, &verificationError);
|
||||
const QByteArray http =
|
||||
"HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n"
|
||||
"Content-Length: " + QByteArray::number(m_response.size()) +
|
||||
"\r\nConnection: close\r\n\r\n" + m_response;
|
||||
socket->write(http);
|
||||
socket->disconnectFromHost();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool requestVerified() const
|
||||
{
|
||||
return m_requestVerified;
|
||||
}
|
||||
|
||||
private:
|
||||
QByteArray m_response;
|
||||
bool m_requestVerified{false};
|
||||
};
|
||||
}
|
||||
|
||||
class EetV4ClientIntegrationTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void sendsSignedSaleAndParsesPok();
|
||||
};
|
||||
|
||||
void EetV4ClientIntegrationTest::sendsSignedSaleAndParsesPok()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
QVERIFY(directory.isValid());
|
||||
const QString keyPath = directory.filePath(QStringLiteral("key.pem"));
|
||||
const QString certPath = directory.filePath(QStringLiteral("certificate.pem"));
|
||||
const QString p12Path = directory.filePath(QStringLiteral("certificate.p12"));
|
||||
auto runOpenSsl = [](const QStringList &arguments) {
|
||||
QProcess process;
|
||||
process.start(QStringLiteral("openssl"), arguments);
|
||||
QVERIFY2(process.waitForFinished(30000), qPrintable(process.errorString()));
|
||||
QVERIFY2(process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0,
|
||||
process.readAllStandardError().constData());
|
||||
};
|
||||
runOpenSsl({QStringLiteral("req"), QStringLiteral("-x509"), QStringLiteral("-newkey"),
|
||||
QStringLiteral("rsa:2048"), QStringLiteral("-nodes"), QStringLiteral("-subj"),
|
||||
QStringLiteral("/CN=localhost"), QStringLiteral("-keyout"), keyPath,
|
||||
QStringLiteral("-out"), certPath, QStringLiteral("-days"), QStringLiteral("1")});
|
||||
runOpenSsl({QStringLiteral("pkcs12"), QStringLiteral("-export"), QStringLiteral("-out"),
|
||||
p12Path, QStringLiteral("-inkey"), keyPath, QStringLiteral("-in"), certPath,
|
||||
QStringLiteral("-passout"), QStringLiteral("pass:test")});
|
||||
|
||||
const QByteArray unsignedResponse =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
||||
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
|
||||
" xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/"
|
||||
"oasis-200401-wss-wssecurity-secext-1.0.xsd\""
|
||||
" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/"
|
||||
"oasis-200401-wss-wssecurity-utility-1.0.xsd\">"
|
||||
"<soap:Header/><soap:Body wsu:Id=\"response-body\">"
|
||||
"<Odpoved xmlns=\"http://fs.gov.cz/eet/schema/v4\">"
|
||||
"<Hlavicka uuid_zpravy=\"123e4567-e89b-42d3-a456-426614174000\" "
|
||||
"dat_prij=\"2026-10-13T20:00:00+02:00\"/>"
|
||||
"<Potvrzeni pok=\"123e4567-e89b-42d3-a456-426614174000-01\"/>"
|
||||
"</Odpoved></soap:Body></soap:Envelope>";
|
||||
QString signingError;
|
||||
const QByteArray signedResponse = EetV4Signer::signSoap(
|
||||
unsignedResponse, p12Path, QByteArrayLiteral("test"), &signingError);
|
||||
QVERIFY2(!signedResponse.isEmpty(), qPrintable(signingError));
|
||||
|
||||
MockEetServer server(signedResponse);
|
||||
QSslConfiguration serverSsl = QSslConfiguration::defaultConfiguration();
|
||||
QFile keyFile(keyPath);
|
||||
QFile certificateFile(certPath);
|
||||
QVERIFY(keyFile.open(QIODevice::ReadOnly));
|
||||
QVERIFY(certificateFile.open(QIODevice::ReadOnly));
|
||||
serverSsl.setPrivateKey(QSslKey(keyFile.readAll(), QSsl::Rsa, QSsl::Pem));
|
||||
serverSsl.setLocalCertificate(QSslCertificate(certificateFile.readAll(), QSsl::Pem));
|
||||
server.setSslConfiguration(serverSsl);
|
||||
QVERIFY(server.listen(QHostAddress::LocalHost));
|
||||
|
||||
QSslConfiguration clientSsl = QSslConfiguration::defaultConfiguration();
|
||||
clientSsl.setCaCertificates({serverSsl.localCertificate()});
|
||||
EetV4Client client({
|
||||
QUrl(QStringLiteral("https://localhost:%1/eet").arg(server.serverPort())),
|
||||
5000, clientSsl
|
||||
});
|
||||
EetV4SaleRequest request;
|
||||
request.sentAt = QDateTime::currentDateTime().toOffsetFromUtc(0);
|
||||
request.saleAt = request.sentAt;
|
||||
request.taxpayerEic = QStringLiteral("CZ123456789");
|
||||
request.unitId = 1;
|
||||
request.cashRegisterId = QStringLiteral("KASA1");
|
||||
request.receiptNumber = QStringLiteral("TEST-1");
|
||||
QVERIFY(EetV4Amount::fromString(QStringLiteral("100.00"), &request.totalSale));
|
||||
|
||||
EetV4Response response;
|
||||
QString error;
|
||||
QVERIFY2(client.sendSale(request, p12Path, QByteArrayLiteral("test"), &response, &error),
|
||||
qPrintable(error));
|
||||
QVERIFY(server.requestVerified());
|
||||
QVERIFY(response.isSuccess());
|
||||
QCOMPARE(response.pok, QStringLiteral("123e4567-e89b-42d3-a456-426614174000-01"));
|
||||
}
|
||||
|
||||
QTEST_MAIN(EetV4ClientIntegrationTest)
|
||||
#include "eetv4client_integration_test.moc"
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "eetv4client.h"
|
||||
|
||||
#include <QtTest>
|
||||
|
||||
class EetV4ClientTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void rejectsNonHttpsEndpoint();
|
||||
void rejectsNonPositiveTimeout();
|
||||
};
|
||||
|
||||
void EetV4ClientTest::rejectsNonHttpsEndpoint()
|
||||
{
|
||||
EetV4Client client({QUrl(QStringLiteral("http://localhost/eet")), 1000});
|
||||
EetV4Response response;
|
||||
QString error;
|
||||
QVERIFY(!client.sendSale({}, QStringLiteral("missing.p12"), {}, &response, &error));
|
||||
QCOMPARE(error, QStringLiteral("EET endpoint must be a valid HTTPS URL."));
|
||||
}
|
||||
|
||||
void EetV4ClientTest::rejectsNonPositiveTimeout()
|
||||
{
|
||||
EetV4Client client({QUrl(QStringLiteral("https://localhost/eet")), 0});
|
||||
EetV4Response response;
|
||||
QString error;
|
||||
QVERIFY(!client.sendSale({}, QStringLiteral("missing.p12"), {}, &response, &error));
|
||||
QCOMPARE(error, QStringLiteral("EET timeout must be positive."));
|
||||
}
|
||||
|
||||
QTEST_MAIN(EetV4ClientTest)
|
||||
#include "eetv4client_test.moc"
|
||||
@@ -0,0 +1,64 @@
|
||||
#include <QTest>
|
||||
|
||||
#include "../libEet/eetv4request.h"
|
||||
|
||||
class EetV4RequestTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void parsesFixedPointAmounts()
|
||||
{
|
||||
EetV4Amount amount;
|
||||
QString error;
|
||||
|
||||
QVERIFY(EetV4Amount::fromString(QStringLiteral("123.45"), &amount, &error));
|
||||
QCOMPARE(amount.cents(), qint64(12345));
|
||||
QCOMPARE(amount.toString(), QStringLiteral("123.45"));
|
||||
|
||||
QVERIFY(EetV4Amount::fromString(QStringLiteral("-0.01"), &amount, &error));
|
||||
QCOMPARE(amount.cents(), qint64(-1));
|
||||
QCOMPARE(amount.toString(), QStringLiteral("-0.01"));
|
||||
}
|
||||
|
||||
void rejectsInvalidFixedPointAmounts()
|
||||
{
|
||||
EetV4Amount amount;
|
||||
QString error;
|
||||
|
||||
QVERIFY(!EetV4Amount::fromString(QStringLiteral("1"), &amount, &error));
|
||||
QVERIFY(!EetV4Amount::fromString(QStringLiteral("100000000.00"), &amount, &error));
|
||||
QVERIFY(!EetV4Amount::fromString(QStringLiteral("-0.00"), &amount, &error));
|
||||
}
|
||||
|
||||
void validatesSaleRequest()
|
||||
{
|
||||
EetV4SaleRequest request;
|
||||
request.sentAt = QDateTime(QDate(2027, 1, 1), QTime(12, 0),
|
||||
QTimeZone::fromSecondsAheadOfUtc(3600));
|
||||
request.saleAt = request.sentAt;
|
||||
request.taxpayerEic = QStringLiteral("CZ00000019");
|
||||
request.unitId = 11;
|
||||
request.cashRegisterId = QStringLiteral("1");
|
||||
request.receiptNumber = QStringLiteral("123");
|
||||
|
||||
QStringList errors;
|
||||
QVERIFY(request.validate(&errors));
|
||||
QVERIFY(errors.isEmpty());
|
||||
|
||||
request.taxpayerEic = QStringLiteral("CZ12");
|
||||
QVERIFY(!request.validate(&errors));
|
||||
QVERIFY(!errors.isEmpty());
|
||||
}
|
||||
|
||||
void formatsOffsetDateTime()
|
||||
{
|
||||
const QDateTime dateTime(QDate(2027, 1, 1), QTime(12, 34, 56),
|
||||
QTimeZone::fromSecondsAheadOfUtc(-18000));
|
||||
QCOMPARE(EetV4SaleRequest::formatDateTime(dateTime),
|
||||
QStringLiteral("2027-01-01T12:34:56-05:00"));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(EetV4RequestTest)
|
||||
#include "eetv4request_test.moc"
|
||||
@@ -0,0 +1,9 @@
|
||||
QT += core testlib
|
||||
CONFIG += c++11 console testcase
|
||||
TEMPLATE = app
|
||||
|
||||
INCLUDEPATH += ../libEet
|
||||
|
||||
SOURCES += \
|
||||
eetv4request_test.cpp \
|
||||
../libEet/eetv4request.cpp
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "eetv4response.h"
|
||||
|
||||
#include <QtTest>
|
||||
|
||||
class EetV4ResponseTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void parsesConfirmation();
|
||||
void parsesError();
|
||||
void parsesSoapFault();
|
||||
void rejectsInvalidResponse();
|
||||
};
|
||||
|
||||
void EetV4ResponseTest::parsesConfirmation()
|
||||
{
|
||||
const QByteArray soap =
|
||||
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
|
||||
"<soap:Body><Odpoved xmlns=\"http://fs.gov.cz/eet/schema/v4\">"
|
||||
"<Hlavicka uuid_zpravy=\"123e4567-e89b-42d3-a456-426614174000\" "
|
||||
"dat_prij=\"2026-10-13T20:00:00+02:00\"/>"
|
||||
"<Potvrzeni pok=\"123e4567-e89b-42d3-a456-426614174000-01\" test=\"true\"/>"
|
||||
"<Varovani kod_varov=\"12\"/>"
|
||||
"</Odpoved></soap:Body></soap:Envelope>";
|
||||
EetV4Response response;
|
||||
QString error;
|
||||
QVERIFY2(EetV4ResponseParser::parseSoap(soap, &response, &error), qPrintable(error));
|
||||
QCOMPARE(response.status, EetV4ResponseStatus::Success);
|
||||
QCOMPARE(response.pok, QStringLiteral("123e4567-e89b-42d3-a456-426614174000-01"));
|
||||
QCOMPARE(response.messageUuid, QStringLiteral("123e4567-e89b-42d3-a456-426614174000"));
|
||||
QVERIFY(response.test);
|
||||
QCOMPARE(response.warningCodes, QList<int>{12});
|
||||
QVERIFY(response.receivedAt.isValid());
|
||||
}
|
||||
|
||||
void EetV4ResponseTest::parsesError()
|
||||
{
|
||||
const QByteArray soap =
|
||||
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
|
||||
"<soap:Body><Odpoved xmlns=\"http://fs.gov.cz/eet/schema/v4\">"
|
||||
"<Hlavicka dat_odmit=\"2026-10-13T20:00:00Z\"/>"
|
||||
"<Chyba kod=\"-25\">Invalid certificate</Chyba>"
|
||||
"</Odpoved></soap:Body></soap:Envelope>";
|
||||
EetV4Response response;
|
||||
QVERIFY(EetV4ResponseParser::parseSoap(soap, &response));
|
||||
QCOMPARE(response.status, EetV4ResponseStatus::Error);
|
||||
QCOMPARE(response.errorCode, -25);
|
||||
QCOMPARE(response.message, QStringLiteral("Invalid certificate"));
|
||||
QVERIFY(response.rejectedAt.isValid());
|
||||
}
|
||||
|
||||
void EetV4ResponseTest::parsesSoapFault()
|
||||
{
|
||||
const QByteArray soap =
|
||||
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
|
||||
"<soap:Body><soap:Fault><faultcode>soap:Server</faultcode>"
|
||||
"<faultstring>Service unavailable</faultstring></soap:Fault>"
|
||||
"</soap:Body></soap:Envelope>";
|
||||
EetV4Response response;
|
||||
QVERIFY(EetV4ResponseParser::parseSoap(soap, &response));
|
||||
QCOMPARE(response.status, EetV4ResponseStatus::SoapFault);
|
||||
QCOMPARE(response.message, QStringLiteral("soap:Server: Service unavailable"));
|
||||
}
|
||||
|
||||
void EetV4ResponseTest::rejectsInvalidResponse()
|
||||
{
|
||||
const QByteArray soap =
|
||||
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
|
||||
"<soap:Body><Odpoved xmlns=\"http://fs.gov.cz/eet/schema/v4\">"
|
||||
"<Hlavicka/><Potvrzeni pok=\"not-a-pok\"/>"
|
||||
"</Odpoved></soap:Body></soap:Envelope>";
|
||||
EetV4Response response;
|
||||
QString error;
|
||||
QVERIFY(!EetV4ResponseParser::parseSoap(soap, &response, &error));
|
||||
QVERIFY(!error.isEmpty());
|
||||
}
|
||||
|
||||
QTEST_MAIN(EetV4ResponseTest)
|
||||
#include "eetv4response_test.moc"
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "eetv4signer.h"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QTemporaryDir>
|
||||
#include <QtTest>
|
||||
|
||||
class EetV4SignerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void signsSoapWithPkcs12();
|
||||
};
|
||||
|
||||
void EetV4SignerTest::signsSoapWithPkcs12()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
QVERIFY(directory.isValid());
|
||||
|
||||
const QString keyPath = directory.filePath(QStringLiteral("key.pem"));
|
||||
const QString certificatePath = directory.filePath(QStringLiteral("certificate.pem"));
|
||||
const QString bundlePath = directory.filePath(QStringLiteral("certificate.p12"));
|
||||
auto runOpenSsl = [](const QStringList &arguments) {
|
||||
QProcess process;
|
||||
process.start(QStringLiteral("openssl"), arguments);
|
||||
QVERIFY2(process.waitForFinished(30000), qPrintable(process.errorString()));
|
||||
QVERIFY2(process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0,
|
||||
process.readAllStandardError().constData());
|
||||
};
|
||||
runOpenSsl({QStringLiteral("req"), QStringLiteral("-x509"), QStringLiteral("-newkey"),
|
||||
QStringLiteral("rsa:2048"), QStringLiteral("-nodes"), QStringLiteral("-subj"),
|
||||
QStringLiteral("/CN=EetCppTest"), QStringLiteral("-keyout"), keyPath,
|
||||
QStringLiteral("-out"), certificatePath, QStringLiteral("-days"), QStringLiteral("1")});
|
||||
runOpenSsl({QStringLiteral("pkcs12"), QStringLiteral("-export"), QStringLiteral("-out"),
|
||||
bundlePath, QStringLiteral("-inkey"), keyPath, QStringLiteral("-in"),
|
||||
certificatePath, QStringLiteral("-passout"), QStringLiteral("pass:test")});
|
||||
|
||||
const QByteArray unsignedSoap =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
||||
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
|
||||
" xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\""
|
||||
" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">"
|
||||
"<soap:Header/><soap:Body wsu:Id=\"body-id\"><test/></soap:Body></soap:Envelope>";
|
||||
QString error;
|
||||
const QByteArray signedSoap = EetV4Signer::signSoap(
|
||||
unsignedSoap, bundlePath, QByteArrayLiteral("test"), &error);
|
||||
QVERIFY2(!signedSoap.isEmpty(), qPrintable(error));
|
||||
QVERIFY(signedSoap.contains("<ds:Signature"));
|
||||
QVERIFY(signedSoap.contains("URI=\"#body-id\""));
|
||||
QVERIFY(signedSoap.contains("BinarySecurityToken"));
|
||||
|
||||
QString verificationError;
|
||||
QVERIFY2(EetV4Signer::verifySoap(signedSoap, &verificationError),
|
||||
qPrintable(verificationError));
|
||||
|
||||
QByteArray tamperedSoap = signedSoap;
|
||||
tamperedSoap.replace("<test/>", "<test>changed</test>");
|
||||
QVERIFY(!EetV4Signer::verifySoap(tamperedSoap, &verificationError));
|
||||
}
|
||||
|
||||
QTEST_MAIN(EetV4SignerTest)
|
||||
#include "eetv4signer_test.moc"
|
||||
@@ -0,0 +1,67 @@
|
||||
#include <QTest>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "../libEet/eetv4soap.h"
|
||||
|
||||
class EetV4SoapTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void serializesSoapEnvelope()
|
||||
{
|
||||
EetV4SaleRequest request;
|
||||
request.messageUuid = QUuid(QStringLiteral("550e8400-e29b-41d4-a716-446655440000"));
|
||||
request.sentAt = QDateTime(QDate(2027, 1, 1), QTime(12, 0),
|
||||
QTimeZone::fromSecondsAheadOfUtc(3600));
|
||||
request.saleAt = request.sentAt;
|
||||
request.taxpayerEic = QStringLiteral("CZ00000019");
|
||||
request.unitId = 11;
|
||||
request.cashRegisterId = QStringLiteral("1");
|
||||
request.receiptNumber = QStringLiteral("123");
|
||||
EetV4Amount::fromString(QStringLiteral("123.45"), &request.totalSale);
|
||||
|
||||
QString error;
|
||||
const QByteArray soap = EetV4Soap::serializeUnsigned(request, QByteArrayLiteral("body-1"), &error);
|
||||
|
||||
QVERIFY2(!soap.isEmpty(), qPrintable(error));
|
||||
QVERIFY(soap.startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
|
||||
QVERIFY(soap.contains("<soap:Envelope"));
|
||||
QVERIFY(soap.contains("<soap:Header/>"));
|
||||
QVERIFY(soap.contains("<soap:Body wsu:Id=\"body-1\">"));
|
||||
QVERIFY(soap.contains("<Trzba xmlns=\"http://fs.gov.cz/eet/schema/v4\">"));
|
||||
QVERIFY(soap.endsWith("</soap:Body></soap:Envelope>"));
|
||||
}
|
||||
|
||||
void rejectsInvalidBodyId()
|
||||
{
|
||||
EetV4SaleRequest request;
|
||||
QString error;
|
||||
|
||||
QVERIFY(EetV4Soap::serializeUnsigned(request, QByteArrayLiteral("invalid id"), &error).isEmpty());
|
||||
QVERIFY(!error.isEmpty());
|
||||
}
|
||||
|
||||
void producesParseableSoap()
|
||||
{
|
||||
EetV4SaleRequest request;
|
||||
request.sentAt = QDateTime(QDate(2027, 1, 1), QTime(12, 0),
|
||||
QTimeZone::fromSecondsAheadOfUtc(3600));
|
||||
request.saleAt = request.sentAt;
|
||||
request.taxpayerEic = QStringLiteral("CZ00000019");
|
||||
request.unitId = 11;
|
||||
request.cashRegisterId = QStringLiteral("1");
|
||||
request.receiptNumber = QStringLiteral("123");
|
||||
EetV4Amount::fromString(QStringLiteral("1.00"), &request.totalSale);
|
||||
|
||||
QXmlStreamReader reader(EetV4Soap::serializeUnsigned(request));
|
||||
while (!reader.atEnd())
|
||||
{
|
||||
reader.readNext();
|
||||
}
|
||||
QVERIFY2(!reader.hasError(), qPrintable(reader.errorString()));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(EetV4SoapTest)
|
||||
#include "eetv4soap_test.moc"
|
||||
@@ -0,0 +1,87 @@
|
||||
#include <QTest>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "../libEet/eetv4xml.h"
|
||||
|
||||
class EetV4XmlTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
static EetV4SaleRequest validRequest()
|
||||
{
|
||||
EetV4SaleRequest request;
|
||||
request.messageUuid = QUuid(QStringLiteral("550e8400-e29b-41d4-a716-446655440000"));
|
||||
request.sentAt = QDateTime(QDate(2027, 1, 1), QTime(12, 0),
|
||||
QTimeZone::fromSecondsAheadOfUtc(3600));
|
||||
request.saleAt = request.sentAt;
|
||||
request.taxpayerEic = QStringLiteral("CZ00000019");
|
||||
request.unitId = 11;
|
||||
request.cashRegisterId = QStringLiteral("1");
|
||||
request.receiptNumber = QStringLiteral("123");
|
||||
|
||||
EetV4Amount::fromString(QStringLiteral("123.45"), &request.totalSale);
|
||||
return request;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void serializesRequiredAttributes()
|
||||
{
|
||||
QString error;
|
||||
const QByteArray xml = EetV4Xml::serializeSale(validRequest(), &error);
|
||||
|
||||
QVERIFY2(!xml.isEmpty(), qPrintable(error));
|
||||
QVERIFY(xml.startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
|
||||
QVERIFY(xml.contains("xmlns=\"http://fs.gov.cz/eet/schema/v4\""));
|
||||
QVERIFY(xml.contains("uuid_zpravy=\"550e8400-e29b-41d4-a716-446655440000\""));
|
||||
QVERIFY(xml.contains("eic_popl=\"CZ00000019\""));
|
||||
QVERIFY(xml.contains("id_jednotky=\"11\""));
|
||||
QVERIFY(xml.contains("celk_trzba=\"123.45\""));
|
||||
QVERIFY(!xml.contains("fik"));
|
||||
QVERIFY(!xml.contains("pkp"));
|
||||
QVERIFY(!xml.contains("bkp"));
|
||||
}
|
||||
|
||||
void serializesOptionalAttributes()
|
||||
{
|
||||
EetV4SaleRequest request = validRequest();
|
||||
request.verificationMode = true;
|
||||
request.authorizingTaxpayerEic = QStringLiteral("CZ00000020");
|
||||
request.multipleTaxpayerAuthorization = true;
|
||||
request.hasIntendedSettlement = true;
|
||||
request.hasSettlement = true;
|
||||
EetV4Amount::fromString(QStringLiteral("100.00"), &request.intendedSettlement);
|
||||
EetV4Amount::fromString(QStringLiteral("25.00"), &request.settlement);
|
||||
|
||||
const QByteArray xml = EetV4Xml::serializeSale(request);
|
||||
|
||||
QVERIFY(xml.contains("overeni=\"true\""));
|
||||
QVERIFY(xml.contains("eic_poverujiciho=\"CZ00000020\""));
|
||||
QVERIFY(xml.contains("povereni_vice_popl=\"true\""));
|
||||
QVERIFY(xml.contains("urceno_cerp_zuct=\"100.00\""));
|
||||
QVERIFY(xml.contains("cerp_zuct=\"25.00\""));
|
||||
}
|
||||
|
||||
void rejectsInvalidRequest()
|
||||
{
|
||||
EetV4SaleRequest request = validRequest();
|
||||
request.taxpayerEic = QStringLiteral("invalid");
|
||||
|
||||
QString error;
|
||||
QVERIFY(EetV4Xml::serializeSale(request, &error).isEmpty());
|
||||
QVERIFY(!error.isEmpty());
|
||||
}
|
||||
|
||||
void producesParseableXml()
|
||||
{
|
||||
QXmlStreamReader reader(EetV4Xml::serializeSale(validRequest()));
|
||||
while (!reader.atEnd())
|
||||
{
|
||||
reader.readNext();
|
||||
}
|
||||
QVERIFY2(!reader.hasError(), qPrintable(reader.errorString()));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(EetV4XmlTest)
|
||||
#include "eetv4xml_test.moc"
|
||||
Reference in New Issue
Block a user