Compare commits

..

108 Commits

Author SHA1 Message Date
Ondřej Fischer
4a2df4661e Not using zlib 2020-10-20 17:16:04 +02:00
Ondřej Fischer
b1897b6a74 add get function for request process time 2020-09-16 12:33:12 +02:00
Filip Bucek
b54f2b332e status should return http status ( -1 when error, 0 not started ) 2020-02-18 14:50:57 +01:00
Filip Bucek
396c046afc removed suppressing message ( no message on linux ) 2020-02-18 14:47:28 +01:00
Filip Bucek
aff09b92da disabled soap test ( service unavailable )
* renamed files in script based on their names
2020-02-18 14:44:01 +01:00
Filip Bucek
fdfe31e064 fix: wrong finish time in request handling
* fixed signal warning
* suppressing SSL messages in tests
2020-02-18 13:49:36 +01:00
Filip Bucek
d4c4d75b49 Merge branch 'edit-cppcheck' into 'master'
Edit cppcheck

See merge request spaceti-app/integrations/common/server-sdk!18
2019-12-16 09:28:20 +00:00
Filip Bucek
2b12a67f52 fixed typo: missing p in suppress variable 2019-12-16 10:25:36 +01:00
Filip Bucek
46829870f0 ensuring server-sdk have unusedFunction suppresion 2019-12-16 10:19:48 +01:00
Ondřej Fischer
a555fd93e2 delete suppress unused function 2019-12-16 10:05:36 +01:00
Filip Bucek
19f748cc73 Merge branch 'improve/make-property-const-ref' into 'master'
const ref in table_property set function

See merge request spaceti-app/integrations/common/server-sdk!17
2019-12-12 10:19:26 +00:00
Ondřej Fischer
28a61d7c12 const ref in table_property 2019-12-12 11:12:37 +01:00
Filip Bucek
3640c0db5d Merge branch 'feat/process-request' into 'master'
ability to check whether networkreply was really created ( rare test )

See merge request spaceti-app/integrations/common/server-sdk!16
2019-12-05 08:51:20 +00:00
Filip Bucek
9088d93a4e ability to check whether networkreply was really created ( rare test ) 2019-12-05 09:48:10 +01:00
Filip Bucek
b643f25421 Merge branch 'upgrade/add-filter-to-update-table' into 'master'
add fillter to update

See merge request spaceti-app/integrations/common/server-sdk!15
2019-12-03 09:19:34 +00:00
Ondřej Fischer
90541c3421 add fillter to update 2019-12-02 18:13:17 +01:00
Filip Bucek
031b6c2535 Merge branch 'fix/clang-checks' into 'master'
clang: fixing warnings without impact on function

See merge request spaceti-app/integrations/common/server-sdk!14
2019-12-02 09:05:33 +00:00
Filip Bucek
e4673cf66a clang: fix warning + making TABLE_PROPERTY private 2019-12-02 10:02:09 +01:00
Filip Bucek
ec406abc6f clang: fixing warnings without impact on function 2019-12-02 09:55:59 +01:00
Filip Bucek
a818055b75 Merge branch 'fix/changed-applogger-message' into 'master'
changed info message for applogger to debug

See merge request spaceti-app/integrations/common/server-sdk!13
2019-11-28 11:00:58 +00:00
Filip Bucek
dbe8aa5775 changed info message for applogger to debug 2019-11-28 11:53:33 +01:00
Filip Bucek
744bc7a785 Merge branch 'fix/codecovere-filter' into 'master'
fix: not filtering out serversdk

See merge request spaceti-app/integrations/common/server-sdk!12
2019-11-28 10:52:50 +00:00
Filip Bucek
924cfbde0f Merge branch 'temp/functionality-because-of-colnod-(test)' into 'master'
send request in finish signal

See merge request spaceti-app/integrations/common/server-sdk!11
2019-11-28 10:15:46 +00:00
Ondřej Fischer
2041fed9fa send request in finish signal 2019-11-28 10:47:33 +01:00
Filip Bucek
5ee82764e7 fix: not filtering out serversdk 2019-11-27 16:38:29 +01:00
Filip Bucek
61feac4632 removed Q_ASSERT for validation opened database
When error database contains information about url etc.
2019-11-27 14:16:09 +01:00
Filip Bucek
ee08677641 Merge branch 'feat/adding-connection-timetout' into 'master'
adding timeout to postgresql connection

See merge request spaceti-app/integrations/common/server-sdk!10
2019-11-27 11:37:13 +00:00
Filip Bucek
2e574f7b49 adding timeout to postgresql connection 2019-11-27 12:35:32 +01:00
Filip Bucek
9623578997 fix missing const 2019-11-25 16:28:08 +01:00
Filip Bucek
99089d1b12 emitting error when SQL error 2019-11-25 16:24:33 +01:00
Filip Bucek
eddfc401b0 request manager: outputting error 2019-11-25 12:58:15 +01:00
Filip Bucek
9fc60947eb Merge branch 'feat/logger' into 'master'
Feat/logger

See merge request spaceti-app/integrations/common/server-sdk!9
2019-11-25 08:20:33 +00:00
Filip Bucek
38a7da300d disabled color output for Windows 2019-11-25 08:46:59 +01:00
Filip Bucek
5025a68891 WIP: better logger output 2019-11-20 17:57:57 +01:00
Filip Bucek
9dd13d2970 Merge branch 'filtered-methods' into 'master'
Filtered methods

See merge request spaceti-app/integrations/common/server-sdk!8
2019-11-19 13:18:47 +00:00
Filip Bucek
f387b600f7 DatabaseTable new method primaryKey 2019-11-19 14:15:40 +01:00
Filip Bucek
c038568124 adding filtered methods 2019-11-19 13:53:05 +01:00
Filip Bucek
7f369b075d unitest -> filteredJson ( maybe make universal ) 2019-11-19 13:40:19 +01:00
Filip Bucek
1cf415820f Added unitest as example where baseclas does not have property of derived class 2019-11-19 11:43:00 +01:00
Filip Bucek
956f51e3e2 Merge branch 'update-unittest' into 'master'
Update unittest

See merge request spaceti-app/integrations/common/server-sdk!7
2019-11-19 09:25:59 +00:00
Filip Bucek
9cc150d50b fixing unittest ( update unittest, primary key, camelCase ) 2019-11-19 10:17:22 +01:00
Filip Bucek
e266b8a8b4 Not using Quid 2019-11-19 09:40:40 +01:00
Ondřej Fischer
583a652cff add create query 2019-11-19 09:15:45 +01:00
Ondřej Fischer
7071d874b0 fix error caused by using toCamelCase 2019-11-18 17:06:49 +01:00
Filip Bucek
793bfde1de Example in tst_databasetable how to handle different Json / Table data
+ camel case handling
2019-11-18 10:41:24 +01:00
Filip Bucek
4dfd4227cb Merge branch 'refs/heads/master' into possible-change
# Conflicts:
#	src/serversdk/databaseutils.h
2019-11-14 14:56:13 +01:00
Ondřej Fischer
34d112062c add function for UPDATE, needs to be edited 2019-11-14 14:46:44 +01:00
Filip Bucek
ad68c85086 fixing previous build: now using DatabaseUtils to get data from table
QSqlQuery selectSQL = db.execPlainSql("SELECT * FROM names");
auto list = utils.tableFromQuery<Names>(selectSQL);
2019-11-14 14:37:47 +01:00
Filip Bucek
8821dfc9b2 removed TABLE_PROPERTY form default class
+ had to subclass
+ removed unused code
2019-11-14 13:33:17 +01:00
Filip Bucek
29f7103ece Merge branch 'fix-collision-with-global-variable' into 'master'
stdout colidate with special variable for Standard output

See merge request spaceti-app/integrations/common/server-sdk!5
2019-11-14 12:18:59 +00:00
Ondřej Fischer
22d45162d4 stdout colidate with special variable for Standard output 2019-11-14 13:09:16 +01:00
Filip Bucek
5ea2b6f3e1 ZLIB enabled only for UNIX based systems 2019-11-14 12:51:28 +01:00
Filip Bucek
886cc3c777 added comment about subclassing DataTable 2019-11-14 10:54:02 +01:00
Filip Bucek
dfe6d3bb03 fix isSelect ( QSqlQuery::isSelect() does not work as expected ) 2019-11-13 14:56:22 +01:00
Filip Bucek
f1d78192f7 Adding test for colnod -> TokenManager -> getTokenUpdated 2019-11-13 10:56:31 +01:00
Filip Bucek
6940cad69e update network request 2019-11-13 10:19:44 +01:00
Filip Bucek
3f3af6cfa6 ci: disabling cache 2019-11-13 08:20:38 +01:00
Filip Bucek
549ba2abe2 fixing problem with build 2019-11-13 08:17:15 +01:00
Filip Bucek
4983bbb782 enabling debug for unittesting 2019-11-13 08:13:32 +01:00
Filip Bucek
b92095a459 SQL type based command move to DatabaseTable 2019-11-13 07:51:15 +01:00
Filip Bucek
16fe2db973 added support for Postgresql database 2019-11-12 17:42:54 +01:00
Filip Bucek
c4c51e5099 updated comments to DatabaseTable 2019-11-12 16:59:41 +01:00
Filip Bucek
136ee8300a added missing <memory> header file 2019-11-12 16:29:12 +01:00
Filip Bucek
493d82896b added missing <memory> header file 2019-11-12 16:22:42 +01:00
Filip Bucek
eb2a6c92fa Possible to INSERT and SELECT from database 2019-11-12 16:16:39 +01:00
Filip Bucek
174ede92ee fixing unittests ( using guiless app ) 2019-11-12 14:13:39 +01:00
Filip Bucek
30925777b4 DatabaseTable can read and write data to JSON + manager can ignore ssl errors 2019-11-12 13:11:30 +01:00
Filip Bucek
a80552654b added status() and networkReply() to Request 2019-11-12 12:02:50 +01:00
Filip Bucek
a7831cae9c updated json handling in databasetable 2019-11-12 11:37:37 +01:00
Filip Bucek
08f7f163d2 added core.h ( which includes <memory> ) 2019-11-12 11:27:06 +01:00
Filip Bucek
d467feb7b9 SQLite with create and insert based on defined struct 2019-11-12 11:25:01 +01:00
Filip Bucek
995898c00c cppcheck: explicit constructor 2019-11-11 18:09:44 +01:00
Filip Bucek
1cd286dba5 added missing <memory> header 2019-11-11 18:07:34 +01:00
Filip Bucek
d98ddf6c5f Adding database Table class for automated handling tables 2019-11-11 18:04:56 +01:00
Filip Bucek
b04332059f Adding SQLite test ( open database ) 2019-11-11 15:39:25 +01:00
Filip Bucek
70e389ab43 Using tst_ in auto tests 2019-11-11 13:12:48 +01:00
Filip Bucek
d54da50d5e moved SDK_TEST_PATH and SDK_ROOT definition to test subproject 2019-11-11 10:34:49 +01:00
Filip Bucek
54d7a5c59a used quoted path ( spaces in paths on Windows ) 2019-11-11 09:56:25 +01:00
Filip Bucek
1fc722b751 adding ansible ( to create test database ) 2019-11-11 09:29:42 +01:00
Filip Bucek
8552cc304f updating ci 2019-11-08 18:33:38 +01:00
Filip Bucek
1d947e931b updating ci 2019-11-08 18:28:06 +01:00
Filip Bucek
33950c96fe updating code coverage 2019-11-08 18:19:23 +01:00
Filip Bucek
ee507683c6 updating ci 2019-11-08 17:58:17 +01:00
Filip Bucek
4ded480088 using artifact with dependancy 2019-11-08 17:51:32 +01:00
Filip Bucek
22e281a2a7 using again 'cache' 2019-11-08 17:42:01 +01:00
Filip Bucek
00596d53db using make check 2019-11-08 17:13:59 +01:00
Filip Bucek
8f2fcedeb7 updating code coverage test 2019-11-08 16:36:46 +01:00
Filip Bucek
5618a7d00c updating tests 2019-11-08 16:28:15 +01:00
Filip Bucek
ce9e59d604 adding dependancy 2019-11-08 16:22:30 +01:00
Filip Bucek
01bd3c9753 adding dependancy 2019-11-08 16:21:53 +01:00
Filip Bucek
0d4f436e23 using artifacts instead of cache 2019-11-08 16:10:15 +01:00
Filip Bucek
1fe9632720 Merge branch 'feature/soap-test' into 'master'
Feature/soap test

See merge request spaceti-app/integrations/common/server-sdk!3
2019-11-08 14:46:45 +00:00
Filip Bucek
8c137c391f updated comments 2019-11-08 15:43:56 +01:00
Filip Bucek
0e2922f673 disabled http/2 - does not work on Qt 5.9.8 also for SoapRequest 2019-11-08 14:13:35 +01:00
Filip Bucek
1c2d69786c disabled http/2 - does not work on Qt 5.9.8 2019-11-08 14:11:17 +01:00
Filip Bucek
aeb98c0f06 updated unitests 2019-11-08 14:08:58 +01:00
Filip Bucek
ea0258cef1 Merge branch 'master' into feature/soap-test
# Conflicts:
#	scripts/check.sh
2019-11-08 13:32:59 +01:00
Filip Bucek
ea042620b1 SOAP is working with tests 2019-11-08 13:32:34 +01:00
Filip Bucek
b6950b9bfe fix lineending checks for .git 2019-11-08 11:26:09 +01:00
Filip Bucek
57e0245b1b fixing check script for lineendings 2019-11-08 11:15:34 +01:00
Filip Bucek
27a533d52b fixing check script for lineendings 2019-11-08 11:14:59 +01:00
Filip Bucek
c24bfa5296 Merge branch 'fix/line-endings' into 'master'
Fix/line endings

See merge request spaceti-app/integrations/common/server-sdk!2
2019-11-08 10:05:44 +00:00
Filip Bucek
02caeb86c9 lineending: fixed 2019-11-08 11:01:30 +01:00
Filip Bucek
0cf21cf7c2 WIP: fixed missing check ( lineendings ) 2019-11-08 10:56:22 +01:00
Filip Bucek
cb61286f33 WIP: check: lineending -> fixing 2019-11-08 10:55:20 +01:00
Filip Bucek
e650163631 Merge branch 'feat/compression' into 'master'
added compression with tests

See merge request spaceti-app/integrations/common/server-sdk!1
2019-11-08 08:34:43 +00:00
Filip Bucek
6909ae182c added compression with tests 2019-11-08 09:31:46 +01:00
Filip Bucek
0c7887d944 removing unnecessary code 2019-11-08 08:06:56 +01:00
69 changed files with 3019 additions and 773 deletions

View File

@ -2,9 +2,17 @@
stages:
- build
- test
- quality
# ########
# To clone also submodules
variables:
PRO_FILE: ../server-sdk.pro
SCRIPT_PATH: scripts
#cache:
# paths:
# - build/
##########
# Build
##########
build: # name of task
@ -12,17 +20,14 @@ build: # name of task
tags: # Tags are used to choose correct gitlab-runner
- shell # spaceti-runner server has running runners and one has tag 'hw'
script: # run in bash
- if [ -d build ];then rm -rf build; fi
- if [ -d build ]; then rm -rf build; fi
- mkdir -p build
- cd build
- qmake ../server-sdk.pro
- qmake $PRO_FILE
- make -j4
cache:
paths:
- build/
# ########
##########
# Test
##########
test:
@ -30,39 +35,56 @@ test:
tags:
- shell
script:
- if [ -d build ]; then rm -rf build; fi
- mkdir -p build
- cd build
- make check test
- qmake CONFIG+=debug $PRO_FILE
- make check
# memory leak check for unittest
valgrind:
stage: test
tags:
- shell
script:
- if [ -d build ]; then rm -rf build; fi
- mkdir -p build
- cd build
- valgrind make check test
- qmake $PRO_FILE
- valgrind make check
# Quality of code
cppcheck:
stage: test
tags:
- shell
# Probably needed to allow failure
#allow_failure: true
script: # run in bash
- scripts/cppcheck.sh
#allow_failure: true # Probably needed to allow failure
script:
- $SCRIPT_PATH/cppcheck.sh
dependencies: []
# Common checks ( Line endings )
lineendings:
stage: test
tags:
- shell
script:
- $SCRIPT_PATH/check.sh
dependencies: []
codecoverage:
stage: test
tags:
- shell
script:
- scripts/codecoverage.sh
- $SCRIPT_PATH/codecoverage.sh
coverage: '/^\s+functions..:\s+(\d+\.\d+%).+$/'
dependencies: []
doc:
stage: test
tags:
- shell
script:
- scripts/doc.sh clean # 'clean' better clean before generating documentation
- $SCRIPT_PATH/doc.sh clean # 'clean' better clean before generating documentation
dependencies: []

7
ansible/ansible.cfg Normal file
View File

@ -0,0 +1,7 @@
[defaults]
# problem with 'create_db' 'Ensure database is created'
# @see https://github.com/georchestra/ansible/issues/55
# Have to make temorary files readable by all, beacuse when chaning user
# -> that use does not have access to temp file ( run scripts )
allow_world_readable_tmpfiles=true

7
ansible/ansible.md Normal file
View File

@ -0,0 +1,7 @@
# Ansible
Ansible file taken from: server-tools
This will instlal `serversdk` test database on `spaceti-experimental`
`ansible/playbook.sh install-postgres`

View File

@ -0,0 +1,8 @@
- hosts: db
become: yes
vars_files:
- postgresql/vars.yml
vars:
roles:
- geerlingguy.postgresql

10
ansible/inventory.yml Normal file
View File

@ -0,0 +1,10 @@
---
db:
hosts:
spaceti-experimental:
ansible_host: 192.168.1.94
server_name: 'spaceti-experimental'
ansible_user: 'filip'
ansible_private_key_file: '~/.ssh/spaceti_gitlab'
# ansible_python_interpreter: '/usr/bin/python'

6
ansible/playbook.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd $SRCDIR
ansible-playbook -i inventory.yml $1.yml

View File

@ -0,0 +1,19 @@
no_log: no
postgresql_databases:
- name: serversdk
postgresql_users:
- name: postgres
password: 34rjkciea12
postgresql_group: postgres
postgresql_global_config_options:
- option: listen_addresses
value: '*'
postgresql_hba_entries:
- { type: local, database: all, user: postgres, auth_method: peer }
- { type: local, database: all, user: all, auth_method: peer }
- { type: host, database: all, user: all, address: '127.0.0.1/32', auth_method: md5 }
- { type: host, database: all, user: all, address: '::1/128', auth_method: md5 }
- { type: host, database: all, user: all, address: '0.0.0.0/0', auth_method: md5 } # full access
# - { type: host, database: all, user: all, address: '178.209.130.196/32', auth_method: md5 } # access from specific IP only
postgresql_restarted_state: "restarted"

View File

@ -2,3 +2,33 @@
[![pipeline status](https://gitlab.com/spaceti-app/integrations/common/server-sdk/badges/master/pipeline.svg)](https://gitlab.com/spaceti-app/integrations/common/server-sdk/commits/master)
[![coverage report](https://gitlab.com/spaceti-app/integrations/common/server-sdk/badges/master/coverage.svg)](https://gitlab.com/spaceti-app/integrations/common/server-sdk/commits/master)
## Examples
### How to use predefined classes
#### DatabaseTable, RequestMangager
```cpp
class LoginTable : public serversdk::DatabaseTable {
Q_OBJECT
TABLE_PROPERTY(QString, username, set_username, "");
TABLE_PROPERTY(QString, password, set_password, "")
};
serversdk::RequestManager reqMan;
reqMan.setIgnoreSsl(true);
auto loginRequest = std::make_shared<serversdk::Request>("POST", "https://192.168.1.3:8443/AaaManager/authSession", login->jsonData());
// Set basic authorization
loginRequest->setUserName(login->username());
loginRequest->setPassword(login->password());
reqMan.processRequest(loginRequest);
connect(loginRequest.get(), &serversdk::Request::finished, [=](){
qDebug() << "Request is finished";
});
```

20
scripts/check.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source $CURRENT_DIR/common.sh
SOURCE_DIR=$PWD
cd $SOURCE_DIR
info "Checking line ending"
RESULT=$( find . -not -type d ! -path '*.git/*' -exec file "{}" ";" | grep "ASCII text, with CRLF" | cat )
if [ ! -z "$RESULT" ]; then
echo "----------------------------------"
echo "$RESULT"
echo "----------------------------------"
fail "LF check finished with error"
fi

View File

@ -15,8 +15,8 @@ PRO_FILE=$(ls *.pro)
# Ensuring server-sdk wont be filtered out for server-sdk
NAME=$( basename $PWD )
if [ "$NAME" != "server-sdk" ]; then
COVERAGE_FILTER+=" *server-sdk*"
if [ "$NAME" != "serversdk" ]; then
COVERAGE_FILTER+=" *serversdk*"
fi
# Ensuring 'src' dir exists
@ -35,6 +35,7 @@ fi
if [ -d $BUILDPATH ]; then info "Removing build path: $BUILDPATH"; rm -rf $BUILDPATH; fi
info "Creating: $BUILDPATH"
mkdir -p $BUILDPATH
cd $BUILDPATH
@ -49,7 +50,7 @@ info "Build"
make -j4 # build
info "Run unittests"
make check test
make check
echo "--------------------"

0
scripts/common.sh Normal file → Executable file
View File

View File

@ -7,8 +7,13 @@ SOURCE_DIR=$PWD
EXCLUDE_DIR=build
CHECK_DIR=$SOURCE_DIR/src
SUPPRESS="--suppress=missingInclude --suppress=unusedFunction"
SUPPRESS="--suppress=missingInclude"
# Ensuring server-sdk will supress 'unuseFunction' warning
NAME=$( basename $PWD )
if [ "$NAME" == "server-sdk" ]; then
SUPPRESS=$SUPPRESS" --suppress=unusedFunction"
fi
info cppcheck version: `cppcheck --version`
if [ ! -d src ]; then

View File

@ -14,7 +14,5 @@ OTHER_FILES += \
scripts/* \
doc/Doxy* \
doc/pages/* \
serversdk.pri \
serversdkint.pri \
*.pri \
message($$OUT_ROOT)

View File

@ -31,9 +31,11 @@ win32:CONFIG(release, debug|release): LIBS += -L$$SDKDIR/release/ -lserversdk
else:win32:CONFIG(debug, debug|release): LIBS += -L$$SDKDIR/debug/ -lserversdk
else:unix: LIBS += -L$$SDKDIR/ -lserversdk
QT += network sql
CONFIG += c++14
INCLUDEPATH += $$PWD/src/
DEPENDPATH += $$PWD/src/serversdk
INCLUDEPATH += $$shell_quote($$PWD/src/)
DEPENDPATH += $$shell_quote($$PWD/src/serversdk)
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$SDKDIR/release/libserversdk.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$SDKDIR/debug/libserversdk.a

View File

@ -23,11 +23,15 @@ SDKDIR=$$ROOT_OUT/src/serversdk
win32:CONFIG(release, debug|release): LIBS += -L$$SDKDIR/release/ -lserversdk
else:win32:CONFIG(debug, debug|release): LIBS += -L$$SDKDIR/debug/ -lserversdk
else:unix: LIBS += -L$$SDKDIR/ -lserversdk
else:unix: LIBS += -L$$SDKDIR/ -lserversdk -lz
QT += network sql
CONFIG += c++14
INCLUDEPATH += $$PWD/src/
DEPENDPATH += $$PWD/src/serversdk
# enable zlib compression on linux
unix: DEFINES += USE_ZLIB
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$SDKDIR/release/libserversdk.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$SDKDIR/debug/libserversdk.a

View File

@ -10,7 +10,7 @@ void installLogger(QtMsgType type, const QMessageLogContext &context, const QStr
void initAppLogger() {
auto res = qInstallMessageHandler(serversdk::Logger::outputMessage);
if (res)
qCInfo(sdkcore) << "user message handler qInstallMessageHandler installed sucessfully";
qCDebug(sdkcore) << "user message handler qInstallMessageHandler installed sucessfully";
else
qCWarning(sdkcore) << "failed to install message handler qInstallMessageHandler";
}

View File

@ -1,39 +1,138 @@
#include "databasemanager.h"
#include "logger.h"
namespace serversdk {
DatabaseManager::DatabaseManager(const QString& host, const QString& dbName, const QString& user, const QString& password, bool dryMode)
: mDryMode(dryMode)
DatabaseManager::DatabaseManager()
{
db = QSqlDatabase::addDatabase("QPSQL");
db.setDatabaseName(dbName);
db.setHostName(host);
db.setUserName(user); // home-office stuff -->> db.setPort(80);
db.setPassword(password);
if (db.open())
{
query = QSqlQuery("query", db);
} else {
log("Opening error" + db.lastError().text());
emit error();
}
DatabaseManager::~DatabaseManager()
{
if (mDatabase.isValid()) {
mDatabase.close();
mDatabase = QSqlDatabase(); // invalidate database
qDebug(sdkdb) << "removing database connection:" << mConnectionName;
QSqlDatabase::removeDatabase(mConnectionName);
}
}
bool DatabaseManager::executeQuery(QSqlQuery &query)
/**
* @brief Creates new SQLite connection
* @param path to database ( will be created when none )
* @param connectionName
* @return
*/
bool DatabaseManager::openSQLite(const QString &path, const QString &connectionName)
{
if (mDryMode){
QString sqlStatement = getLastQuery(query);
qDebug() << sqlStatement;
mConnectionName = connectionName;
mDatabase = QSqlDatabase::addDatabase("QSQLITE", mConnectionName);
mDatabase.setDatabaseName(path);
int res = mDatabase.open();
if (!res) {
qInfo(sdkdb) << "Not possible to open database from path:" << path;
}
if (!mDryMode || query.isSelect()){
if (!query.exec()){
QString msg = QString("Source: Database Manager, Error: %1").arg(query.lastError().text());
log(QString(msg));
emit error();
return res;
}
bool DatabaseManager::openPostgres(const QString &host, const QString &dbName, const QString &user, const QString &password, const QString &connectionName)
{
Q_ASSERT_X(mConnectionName.isEmpty(), "open postgres", "Expecting only one database per DatabaseManager");
mConnectionName = connectionName;
mDatabase = QSqlDatabase::addDatabase("QPSQL", mConnectionName);
mDatabase.setDatabaseName(dbName);
mDatabase.setHostName(host);
mDatabase.setUserName(user); // home-office stuff -->> db.setPort(80);
mDatabase.setPassword(password);
// @see https://www.qtcentre.org/threads/39913-QSqlDatabase-connection-timeout
mDatabase.setConnectOptions("connect_timeout=10");
int res = mDatabase.open();
if (!res) {
qInfo(sdkdb) << "Not possible to open database from url:" << host;
}
return res;
}
bool DatabaseManager::isValid() const
{
return mDatabase.isValid();
}
bool DatabaseManager::isOpen() const
{
return mDatabase.isOpen();
}
void DatabaseManager::close()
{
mDatabase.close();
}
QSqlQuery DatabaseManager::execPlainSql(const QString &sqlText)
{
QSqlQuery query(mDatabase);
return executeQuery(query, sqlText);
}
QSqlQuery DatabaseManager::executeQuery(QSqlQuery &query, const QString &sqlText)
{
// Get correct SQL statement and correct isSelect
// Need for output correct SQL command
// isSelect is needed for correct bahaviour of 'dryMode' only select should be executed
QString sqlStatement = sqlText;
bool isSelect = false;
if ( sqlText.isEmpty()) {
sqlStatement = getLastQuery(query);
}
return true;
if (sqlText.startsWith("SELECT", Qt::CaseInsensitive)) {
isSelect = true;
}
if (mDryMode) {
qCInfo(sdkdb) << "SQL statement: " << sqlStatement;
}
// Execute only SELECT statement or when not drymode
if ( isSelect || !mDryMode ) {
int res = false;
if ( ! sqlText.isEmpty()) { // prepared statement.
res = query.exec(sqlText);
return processResult(res, query);
}
res = query.exec();
return processResult(res, query);
}
return query;
}
QSqlQuery DatabaseManager::createQuery()
{
QSqlQuery query(mDatabase);
return query;
}
QSqlQuery DatabaseManager::processResult(int res, const QSqlQuery &query) const
{
if ( !res ) {
QString msg = QString("Source: Database Manager \nError --------\n%1\nSQL-------\n%2\n--------")
.arg(query.lastError().text(), query.lastQuery());
qCDebug(sdkdb).noquote() << msg;
emit sqlError(query);
log(msg);
}
return query;
}
QString DatabaseManager::getLastQuery(const QSqlQuery& query) const
@ -51,6 +150,23 @@ QString DatabaseManager::getLastQuery(const QSqlQuery& query) const
void DatabaseManager::log(const QString &msg) const
{
qDebug() << "database logger implement logger";
qDebug() << msg;
}
bool DatabaseManager::dryMode() const
{
return mDryMode;
}
void DatabaseManager::setDryMode(bool dryMode)
{
mDryMode = dryMode;
}
QSqlDatabase DatabaseManager::database() const
{
Q_ASSERT(mDatabase.isValid());
return mDatabase;
}
} // core

View File

@ -17,19 +17,39 @@ class DatabaseManager : public QObject
Q_OBJECT
friend class Logger;
public:
DatabaseManager();
DatabaseManager(const QString& host, const QString& dbName, const QString& user, const QString& password, bool mDryMode = false);
~DatabaseManager() override;
// Attributes
bool dryMode() const;
void setDryMode(bool dryMode);
bool openSQLite(const QString &path, const QString &connectionName = QStringLiteral("qt_sql_default_connection"));
bool openPostgres(const QString& host, const QString& dbName, const QString& user, const QString& password, const QString &connectionName = QStringLiteral("qt_sql_default_connection"));
bool isValid() const;
bool isOpen() const;
void close();
// SQL handling
QSqlQuery execPlainSql(const QString &sqlText);
QSqlQuery executeQuery(QSqlQuery &query, const QString &sqlText = QString());
QSqlQuery createQuery();
QSqlDatabase database() const;
private:
bool executeQuery(QSqlQuery &query);
QSqlQuery processResult(int res, const QSqlQuery &query) const;
QString getLastQuery(const QSqlQuery &query) const;
void log(const QString &msg) const;
signals:
void error();
void sqlError(const QSqlQuery &query) const;
private:
QSqlDatabase db;
QString mConnectionName;
QSqlDatabase mDatabase;
QSqlQuery query;
bool mDryMode = false;
};

View File

@ -0,0 +1,157 @@
#include "databasetable.h"
#include "logger.h"
#include <QJsonDocument>
#include <QMetaProperty>
#include <memory>
namespace serversdk {
DatabaseTable::DatabaseTable(QObject *parent) : QObject(parent)
{
}
QString DatabaseTable::primaryKey() const
{
const QMetaObject *metaObj = this->metaObject();
for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) {
QMetaProperty prop = metaObj->property(i);
if (!prop.isUser())
continue;
return prop.name();
}
return QString();
}
QByteArray DatabaseTable::jsonData(const QSet<QString> &filters) const
{
auto variantMap = this->toVariantMap(filters);
QJsonDocument doc = QJsonDocument::fromVariant(variantMap);
return doc.toJson(QJsonDocument::Compact);
}
bool DatabaseTable::fromJson(const QByteArray &data)
{
// in: JSON ( e.g. {"id": 300} )
QJsonDocument doc = QJsonDocument::fromJson(data);
QVariantMap variant = qvariant_cast<QVariantMap>(doc.toVariant());
// Populate this object data
bool res = fromVariantMap(variant);
return res;
}
/** Creates universal variant structure to hold key / value pairs */
QVariantMap DatabaseTable::toVariantMap(const QSet<QString> &filters) const
{
QVariantMap variantMap;
const QMetaObject *metaObj = this->metaObject();
for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) {
QMetaProperty prop = metaObj->property(i);
// Skip non USER so -> ( Q_PROPERTY( ...... USER false ) only
if (!prop.isUser())
continue;
// Get key -> value pairs
QString name = prop.name();
// Filter property
if (filters.contains(name))
continue;
QVariant::Type type = prop.type();
QVariant value(type);
value.setValue(prop.read(this));
variantMap.insert(name, value);
}
return variantMap;
}
bool DatabaseTable::fromVariantMap(const QVariantMap &variantMap)
{
const QMetaObject *metaObj = this->metaObject();
for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) {
QMetaProperty prop = metaObj->property(i);
// Skip non USER so -> ( Q_PROPERTY( ...... USER false ) only
if (!prop.isUser())
continue;
// Property name
QString name = prop.name();
// Get value from variant map
QVariant value = variantMap.value(name);
prop.write(this, value);
}
return true;
}
QList<std::shared_ptr<DatabaseTable> > DatabaseTable::fromListVariantMap(const QList<QVariantMap> &listMap)
{
QList<std::shared_ptr<DatabaseTable>> list;
for (const QVariantMap &map : listMap) {
auto data = std::make_shared<DatabaseTable>();
data->fromVariantMap(map);
list << data;
}
return list;
}
QString DatabaseTable::textSQLIteType(QVariant::Type type) const
{
switch (type) {
case QVariant::Int:
return QStringLiteral("INTEGER");
break;
case QVariant::Uuid:
return QStringLiteral("TEXT");
break;
case QVariant::String:
return QStringLiteral("TEXT");
break;
case QVariant::DateTime:
return QStringLiteral("INTEGER");
break;
case QVariant::Bool:
return QStringLiteral("INTEGER");
break;
default:
return QString();
break;
}
}
QString DatabaseTable::textPostgresType(QVariant::Type type) const
{
switch (type) {
case QVariant::Int:
return QStringLiteral("INTEGER");
break;
case QVariant::Uuid:
return QStringLiteral("TEXT");
break;
case QVariant::String:
return QStringLiteral("TEXT");
break;
case QVariant::DateTime:
return QStringLiteral("INTEGER");
break;
case QVariant::Bool:
return QStringLiteral("INTEGER");
break;
default:
return QString();
break;
}
}
} // namespace serversdk

View File

@ -0,0 +1,166 @@
#pragma once
#include <QObject>
#include <QVariantMap>
#include <QSet>
#include <memory>
#define TABLE_PROPERTY(TYPE, NAME, SETNAME, INITVAL) \
Q_PROPERTY(TYPE NAME READ NAME WRITE SETNAME NOTIFY NAME ## Changed USER true) \
public: \
TYPE NAME() const { return a_ ## NAME ; } \
Q_SLOT void SETNAME(const TYPE &value) { \
if (a_ ## NAME == value) return; \
a_ ## NAME = value; \
emit NAME ## Changed(value); \
} \
Q_SIGNAL void NAME ## Changed(TYPE value);\
private: \
TYPE a_ ## NAME = INITVAL;
namespace serversdk {
// Default TABLE_PROPERTY
/** # DatabaseTable
*
* Class for automatic CREATE/INSERT/SELECT and JSON handling
*
* ## Usage
*
* 1. define custom class by subclassint serversdk::DatabaseTable
*
* @code
class Names : public serversdk::DatabaseTable {
Q_OBJECT
TABLE_PROPERTY(int, id, set_id, 0);
TABLE_PROPERTY(QString, name, set_name, 0);
};
int main() {
// Prepare temporary path for database
QTemporaryDir dir;
QString dbpath = dir.path() + "/testdb.sqlite";
serversdk::DatabaseManager db;
db.openSQLite(dbpath);
Names names;
// CREATE TABLE
serversdk::DatabaseUtils utils(&db);
QSqlQuery query = utils.createTable(&names, "names");
// INSERT INTO TABLE
names.set_id(2);
names.set_name("Testname");
utils.insertIntoTable(&names, "names");
// SELECT FROM TABLE
QSqlQuery selectSQL = db.execPlainSql("SELECT * FROM names");
auto list = utils.tableFromQuery<Names>(selectSQL);
}
*
* @endcode
*
* ## Warning
*
* Derivation of derived class from DatabaseTable does not work ( missing properties )
*
* @code
* in: TABLE_PROPERTY
* Q_PROPERTY
* @endcode
*
*
*
*
*/
class DatabaseTable : public QObject
{
Q_OBJECT
public:
explicit DatabaseTable(QObject *parent = nullptr);
QString primaryKey() const;
/**
* @brief Creates JSON from QMetaObject data ( Q_PROPERTY ) -> TABLE_PROPERTY
* @return text json e.g. { "id": 00344 }
*/
virtual QByteArray jsonData(const QSet<QString> &filters = {}) const;
/**
* @brief Populates object with JSON {"id": 300} )
* @return true if success TODO: not implemented corrrectly
*/
virtual bool fromJson(const QByteArray &data);
/**
* @brief Creates VariantMap from QMetaObject data ( Q_PROPERTY )
* @return QVariantMap containing property with key/value pairs
*/
virtual QVariantMap toVariantMap(const QSet<QString> &filters = {}) const;
/**
* @brief Populates object with QVariantMap
* @return true if success TODO: not implemented corrrectly
*/
bool fromVariantMap(const QVariantMap &variantMap);
/**
* @brief Creates QList of objects from SELECT query
* @param rows with QVariantMap ( rows of key/value pairs )
* @return List of objects
*/
static QList<std::shared_ptr<DatabaseTable>> fromListVariantMap(const QList<QVariantMap> &listMap);
/**
* @brief Convert QVariant type to SQL type
* @param QVariant type ( QString, int, QDateTime )
* @return text form of database type ( INTEGER, TEXT )
*/
virtual QString textSQLIteType(QVariant::Type type) const;
/**
* @brief Convert QVariant type to SQL type
* @param QVariant type ( QString, int, QDateTime )
* @return text form of database type ( INTEGER, TEXT )
*
* # Sublassing
*
* When it is neccessary to use different type it is possible to subclass and override this method.
*
* @code
class BackendTable : public serversdk::DatabaseTable
{
QString textPostgresType(QVariant::Type type) const override;
}
BackendTable::textPostgresType(QVariant::Type type) const override
{
// For case when saving date time as ms since epoch
if (type == QVariant::DateTime) {
return QStringLiteral("INTEGER");
}
return DatabaseTable::textPostgresType(type);
}
* @endcode
*
*
*
*
*/
virtual QString textPostgresType(QVariant::Type type) const;
};
} // namespace serversdk

View File

@ -0,0 +1,229 @@
#include "databaseutils.h"
#include "databasemanager.h"
#include "databasetable.h"
#include "logger.h"
#include <QMetaProperty>
#include <utility>
namespace serversdk {
/**
* @brief DatabaseUtils::createTableSQL
* @param table
* @return
*
* @code{sql}
* "CREATE TABLE colsunod_bject ("
"id character(36) primary key NOT NULL,"
"description character varying(255),"
"last_update timestamp NOT NULL,"
"name character varying(255),"
"pin character varying(255),"
"last_modified timestamp,"
"deleted BOOLEAN)"
* @endcode
*
* Default types:
*
*
*
*/
DatabaseUtils::DatabaseUtils(DatabaseManager *manager, QString type)
: mDatabaseManager(manager), mSqlType(std::move(type))
{
Q_ASSERT(mDatabaseManager);
}
QString DatabaseUtils::createTableText(const DatabaseTable *table, const QString &table_name) const
{
QString userData;
if (table_name.isEmpty()) {
return QString();
}
// out: "CREATE TABLE colnod_subject ( "
QString sql = QString("CREATE TABLE %1 ( ").arg(table_name);
const QMetaObject *metaObj = table->metaObject();
int total = metaObj->propertyCount();
int first = true;
for (int i = metaObj->propertyOffset(); i < total; ++i) {
QMetaProperty prop = metaObj->property(i);
// Skip non USER so -> ( Q_PROPERTY( ...... USER false ) only
if (!prop.isUser())
continue;
QString name = fromCamelCase(prop.name());
QVariant::Type type = prop.type();
QString textType;
// Getting correct type based on QVariant
if (sqlType() == "SQLITE") {
textType = table->textSQLIteType(type);
} else if (sqlType() == "PSQL") {
textType = table->textSQLIteType(type);
//return QString();
} else {
qCDebug(sdkdb) << "Implemented only for SQLITE";
return QString();
}
// in: id TEXT
// out: "CREATE TABLE colnod_subject ( id TEXT "
sql += QString("%1 %2").arg(name,textType);
// in: PRIMARY KEY
// out: "CREATE TABLE colnod_subject ( id TEXT PRIMARY KEY"
if (first) { // first row
sql += " PRIMARY KEY NOT NULL";
first = false;
}
// in: NOT NULL
// out: "CREATE TABLE colnod_subject ( id TEXT PRIMARY KEY NOT NULL"
if (!userData.isEmpty()) {
sql += " " + userData;
}
// out: "CREATE TABLE colnod_subject ( id TEXT PRIMARY KEY NOT NULL,"
if (i < (total-1)) { // second last
sql += ", ";
}
}
// out: ".. subject_id TEXT)"
sql+= " )";
return sql;
}
QSqlQuery DatabaseUtils::createTable(const DatabaseTable *table, const QString &table_name) const
{
// CREATE TABLE ...
QString sql = createTableText(table, table_name);
qCDebug(sdkdb) << "SQL:" << sql;
return mDatabaseManager->execPlainSql(sql);
}
/**
* @brief DatabaseUtils::insertIntoTable
* @param table
* @param table_name
* @return
*
* @todo Loop VariantMap change to Loop over propertiess
*/
QSqlQuery DatabaseUtils::insertIntoTable(const DatabaseTable *table, const QString &table_name) const
{
QString columns;
QString values;
QVariantMap valueMap = table->toVariantMap();
// in: id / 23233
// out: columns: id
// out: values: :id
auto keys = valueMap.keys();
for (const auto& _key : keys) {
QString key = fromCamelCase(_key);
if (!columns.isEmpty()) {
columns += ", ";
values += ", ";
}
columns += key;
values += ":" + key;
}
QSqlQuery query(mDatabaseManager->database());
QString queryText = QString("INSERT INTO %1 ( %2 ) VALUES ( %3 );").arg(table_name).arg(columns).arg(values);
query.prepare(queryText);
for (const auto& _key : keys ) {
QString key = fromCamelCase(_key);
auto value = valueMap.value(_key);
query.bindValue(":"+key, value);
}
QSqlQuery res = mDatabaseManager->executeQuery(query);
return res;
}
QSqlQuery DatabaseUtils::updateTable(const DatabaseTable *table, const QString &table_name, const QSet<QString> &filters) const
{
QString columnsAndValues;
QString primaryKeyCondition;
QVariantMap valueMap = table->toVariantMap(filters);
auto keys = valueMap.keys();
Q_ASSERT(!keys.isEmpty());
// Primary key is first user property ( first VariantMap item )
// in ( primary key ): id
// out: id = :id
const QString primaryKey = table->primaryKey();
primaryKeyCondition = primaryKey + " = :" + primaryKey;
for (const auto& _key : keys) {
QString key = fromCamelCase(_key);
if (!columnsAndValues.isEmpty()) {
columnsAndValues += ", ";
}
columnsAndValues += key + " = :" + key;
}
QSqlQuery query(mDatabaseManager->database());
QString queryText = QString("UPDATE %1 SET %2 WHERE %3;").arg(table_name).arg(columnsAndValues).arg(primaryKeyCondition);
query.prepare(queryText);
for (const auto& _key : keys ) {
QString key = fromCamelCase(_key);
auto value = valueMap.value(_key);
query.bindValue(":"+key, value);
}
QSqlQuery res = mDatabaseManager->executeQuery(query);
return res;
}
QString DatabaseUtils::sqlType() const
{
return mSqlType;
}
void DatabaseUtils::setSqlType(const QString &sqlType)
{
mSqlType = sqlType;
}
QString DatabaseUtils::fromCamelCase(const QString &s)
{
static QRegularExpression regExp1 {"(.)([A-Z][a-z]+)"};
static QRegularExpression regExp2 {"([a-z0-9])([A-Z])"};
QString result = s;
result.replace(regExp1, "\\1_\\2");
result.replace(regExp2, "\\1_\\2");
return result.toLower();
}
QString serversdk::DatabaseUtils::toCamelCase(const QString &s)
{
QStringList parts = s.split('_', QString::SkipEmptyParts);
for (int i=1; i<parts.size(); ++i)
parts[i].replace(0, 1, parts[i][0].toUpper());
return parts.join("");
}
} // namespace serversdk

View File

@ -0,0 +1,67 @@
#pragma once
#include "databasetable.h"
#include <QObject>
#include <QRegularExpression>
#include <QSqlQuery>
#include <QSqlRecord>
class DatabaseUtilsTest;
namespace serversdk {
class DatabaseManager;
class DatabaseUtils : public QObject
{
Q_OBJECT
friend class ::DatabaseUtilsTest;
public:
explicit DatabaseUtils(DatabaseManager *manager = nullptr, QString type = "SQLITE");
QSqlQuery createTable(const DatabaseTable *table, const QString &table_name) const;
QSqlQuery insertIntoTable(const DatabaseTable *table, const QString &table_name) const;
QSqlQuery updateTable(const DatabaseTable *table, const QString &table_name, const QSet<QString> &filters = {}) const;
template <class T>
QList<std::shared_ptr<T>> tableFromQuery(QSqlQuery query) const {
QList<std::shared_ptr<T>> list;
while (query.next()) {
auto rec = query.record();
int total = rec.count();
QMap<QString, QVariant> map;
for (int column = 0; column < total; ++column) {
QString columnName = rec.fieldName(column);
QString propertyName = toCamelCase(columnName);
map.insert(propertyName, query.value(column));
}
auto data = std::make_shared<T>();
data->fromVariantMap(map);
list << data;
}
return list;
}
QString sqlType() const;
void setSqlType(const QString &sqlType);
static QString fromCamelCase(const QString &s);
static QString toCamelCase(const QString& s);
private:
QString createTableText(const DatabaseTable *table, const QString &table_name) const;
private:
DatabaseManager *mDatabaseManager = nullptr;
QString mSqlType;
};
} // namespace serversdk

View File

@ -3,15 +3,22 @@
#include <QLoggingCategory>
#include <QFileInfo>
#include <QDateTime>
Q_LOGGING_CATEGORY(sdkcore, "sdk.core");
Q_LOGGING_CATEGORY(sdknetwork, "sdk.network");
Q_LOGGING_CATEGORY(sdkcompress, "sdk.compress");
Q_LOGGING_CATEGORY(sdkutils, "sdk.utils");
Q_LOGGING_CATEGORY(sdkutilsdb, "sdk.utils.db");
Q_LOGGING_CATEGORY(sdkdb, "sdk.db");
namespace serversdk {
Logger* Logger::sConsumer = nullptr;
//bool Logger::sShowFunction = false;
#ifdef Q_OS_UNIX
bool Logger::mColorOutput = true;
#else
bool Logger::mColorOutput = false;
#endif
void Logger::setConsumer(Logger *consumer)
@ -22,6 +29,7 @@ void Logger::setConsumer(Logger *consumer)
Logger::Logger(QObject *parent) : QObject(parent)
{
qRegisterMetaType<LoggerMessage>();
}
QList<LoggerMessage> Logger::getNewMessages(int currentCount)
@ -42,6 +50,16 @@ QList<LoggerMessage> Logger::getNewMessages(int currentCount)
return list;
}
QString Logger::toColor(const QString &color, const QString &text)
{
return color + text + "\033[0m";
}
void Logger::setColorOutput(bool colorOutput)
{
mColorOutput = colorOutput;
}
void Logger::outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
@ -49,23 +67,29 @@ void Logger::outputMessage(QtMsgType type, const QMessageLogContext &context, co
QString text;
QString textType;
QString colorTextType;
switch (type) {
case QtDebugMsg:
textType = QStringLiteral("debug");
textType = QStringLiteral("DEBUG");
colorTextType = Logger::toColor("\033[0;31m", textType);
break;
case QtWarningMsg:
textType = QStringLiteral("warning");
textType = QStringLiteral("WARN");
colorTextType = Logger::toColor("\033[0;33m", textType);
break;
case QtCriticalMsg:
textType = QStringLiteral("critical");
textType = QStringLiteral("CRITICAL");
colorTextType = Logger::toColor("\033[0;31m", textType);
break;
case QtFatalMsg:
textType = QStringLiteral("fatal");
textType = QStringLiteral("FATAL");
colorTextType = Logger::toColor("\033[0;31m", textType);
break;
#if QT_VERSION >= 0x050500
case QtInfoMsg:
textType = QStringLiteral("info");
textType = QStringLiteral("INFO");
colorTextType = Logger::toColor("\033[0;34m", textType);
break;
#endif
}
@ -77,14 +101,19 @@ void Logger::outputMessage(QtMsgType type, const QMessageLogContext &context, co
// Output message
//text += QString("%1 %2:%3 %4").arg(context.category).arg(shortFile).arg(context.line).arg(msg);
// New output syntax
text = QStringLiteral("[%1] %2 [ %3:%4 | %5 | %6 ]")
.arg(textType, msg, shortFile)
.arg(context.line)
.arg(context.function, context.category);
QDateTime now = QDateTime::currentDateTime();
const QString textTypeUni = mColorOutput ? colorTextType : textType;
text = QStringLiteral("[%1 %2 %3] %4") // [ %3:%4 | %5 | %6 ]")
.arg(now.toString("dd-MM-yyyy hh:mm:ss.z"))
.arg(textTypeUni)
.arg(context.category)
.arg(msg);
QTextStream out(stderr);
out << text << "\n"; //.toLocal8Bit() + "\n";
//out.flush(); // INVSDK-37 ORK-102 crash on windows when flush() -> possible threading problem
if (type == QtFatalMsg)
abort();

View File

@ -10,12 +10,13 @@
Q_DECLARE_LOGGING_CATEGORY(sdkcore)
Q_DECLARE_LOGGING_CATEGORY(sdknetwork)
Q_DECLARE_LOGGING_CATEGORY(sdkcompress)
Q_DECLARE_LOGGING_CATEGORY(sdkutils)
Q_DECLARE_LOGGING_CATEGORY(sdkutilsdb)
Q_DECLARE_LOGGING_CATEGORY(sdkdb)
namespace serversdk {
/**
* # Loggger
/** # Loggger
*
* Class for logging qDebug qWarning qInfo and qCritical messages
*
@ -44,13 +45,27 @@ public:
// User defined consumer ( messages will be sent there using insertMessage )
static Logger* sConsumer;
/// Default constructor
/**
* # Default values
*
* * bits - Expected 32bits
* * subjectId - must be valid ( not possible to add token withou valid subject )
*
* @m_class{m-block m-error}
@par Third-party license info
This utility depends on the [Magnum engine](https://magnum.graphics).
It's licensed under MIT, so all you need to do is mention it in the
credits of your commercial app.
*
*
*/
explicit Logger(QObject *parent = nullptr);
/**
* @brief User specific consumer of logger messages
* @param consumer - sublass of serversdk::Loger
* @param consumer - sublass of serversdk::Logger
*
* @code
* #include "core.h"
@ -76,17 +91,22 @@ public:
*/
static void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg);
/** Pusing messges from installed message handler */
/** Pushing messges from installed message handler */
virtual void pushMessage(const LoggerMessage& logmsg);
QList<serversdk::LoggerMessage> getNewMessages(int currentCount);
static QString toColor(const QString &color, const QString &text);
/** I will enable color output for terminal ( linux only ) */
void setColorOutput(bool colorOutput);
signals:
void messageReceived(const serversdk::LoggerMessage &message);
void messageListUpdated();
private:
QList<LoggerMessage> mLogMessages;
QList<LoggerMessage> mLogMessages; //!< Test
QMutex mMutex;
static bool mColorOutput;
};

View File

@ -6,18 +6,25 @@
namespace serversdk {
Request::~Request()
Request::Request(QString type, QString url, QByteArray requestData)
: mType(std::move(type)), mUrl(std::move(url)), mRequestData(std::move(requestData))
{
}
Request::~Request()
{
if (mNetworkReply)
mNetworkReply->deleteLater();
}
QNetworkRequest Request::networkRequest() const
{
QNetworkRequest request(QUrl(this->mUrl));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json; charset=UTF-8");
if ( !userName().isEmpty() && !password().isEmpty() )
if ( !userName().isEmpty() ) {
request.setRawHeader("Authorization", this->basicAuthorization().toUtf8());
}
if ( !token().isEmpty() ) {
request.setRawHeader("Authorization", this->bearerAuthorization().toUtf8());
@ -28,17 +35,53 @@ QNetworkRequest Request::networkRequest() const
request.setRawHeader("Accept-Encoding", "gzip, deflate");
#endif
// Enabling HTTP/2 ( not supported on Ubuntu 16.04 )
// Enabling HTTP/2 ( does not work on Qt 5.9.8 )
// request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
return request;
}
void Request::appendData(const QByteArray &array)
int Request::status() const
{
Q_ASSERT(mNetworkReply);
if (!mNetworkReply->isFinished())
return 0;
auto variant = mNetworkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (!variant.isValid()) {
return -1;
}
return variant.toInt();
}
void Request::appendResponseData(const QByteArray &array)
{
mResponseData += array;
}
void Request::setNetworkReply(QNetworkReply *networkReply)
{
Q_ASSERT(networkReply);
mNetworkReply = networkReply;
}
QNetworkReply *Request::networkReply() const
{
return mNetworkReply;
}
QString Request::type() const
{
return mType;
}
void Request::setType(const QString &type)
{
mType = type;
}
QString Request::url() const
{
return mUrl;
@ -101,7 +144,6 @@ QString Request::basicAuthorization() const
QByteArray data = concatenated.toLocal8Bit().toBase64();
QString headerData = "Basic " + data;
return headerData;
//request.setRawHeader("Authorization", headerData.toLocal8Bit());
}
QString Request::bearerAuthorization() const
@ -110,4 +152,19 @@ QString Request::bearerAuthorization() const
return QString();
}
qint64 Request::getProcessTime()
{
if (!mStartTime.isNull() && !mFinishTime.isNull()) {
return mStartTime.msecsTo(mFinishTime);
}
return -1;
}
} // core
QDebug operator <<(QDebug dbg, serversdk::Request *request)
{
dbg.noquote() << request->type() << request->url() << "data:" << request->requestData() << "header:" << request->networkReply()->rawHeaderList();
return dbg.maybeSpace();
}

View File

@ -2,6 +2,9 @@
#include <QString>
#include <QNetworkRequest>
#include <QDateTime>
#include <QNetworkReply>
#include <memory>
namespace serversdk {
@ -12,10 +15,13 @@ class Request : public QObject {
public:
Request() = default;
~Request() override;
Request(QString type, QString url, QByteArray responseData = QByteArray());
Request(QString type, QString url, QByteArray requestData = QByteArray());
virtual QNetworkRequest networkRequest() const;
// Data handling data
int status() const;
QNetworkReply *networkReply() const;
void setNetworkReply(QNetworkReply *networkReply);
QByteArray requestData() const;
void setRequestData(const QByteArray &requestData);
QByteArray responseData() const;
@ -23,8 +29,8 @@ public:
// Attributes
QString url() const;
void setUrl(const QString &url);
QString type() const;
void setType(const QString &type);
// Authorization
QString userName() const;
@ -38,17 +44,22 @@ public:
QString basicAuthorization() const;
QString bearerAuthorization() const;
qint64 getProcessTime();
private:
// Additional attributes
void logStartTime() { mStartTime = QDateTime::currentDateTime(); }
void logFinishTime() { mFinishTime = QDateTime::currentDateTime(); }
// Data handling
void appendData(const QByteArray &array);
void appendResponseData(const QByteArray &array);
signals:
void finished();
void finished(std::shared_ptr<Request> request);
private:
QString mType; // POST, GET
QString mType = "GET"; // POST, GET
QString mUrl;
// Authorization
@ -57,8 +68,18 @@ private:
QString mToken;
// Data
QNetworkReply *mNetworkReply = nullptr;
QByteArray mRequestData;
QByteArray mResponseData;
// Additional attributes
QDateTime mStartTime;
QDateTime mFinishTime;
};
} // core
QDebug operator<<(QDebug dbg, serversdk::Request *request);

View File

@ -19,39 +19,73 @@ RequestManager::RequestManager(QObject *parent)
connect(&mNetworkAccessManager, &QNetworkAccessManager::authenticationRequired, [](){
qCWarning(sdknetwork) << "server need authentication";
});
connect(&mNetworkAccessManager, &QNetworkAccessManager::sslErrors, this, &RequestManager::sslErrors);
}
/**
* @brief Soap::downloadRequest
* @param soapRequest
* @brief Process request using QNetworkAccessManger
*
* @param Request
*/
void RequestManager::processRequest(const std::shared_ptr<Request>& request)
bool RequestManager::processRequest(const std::shared_ptr<Request>& request)
{
QNetworkReply *reply = mNetworkAccessManager.post(request->networkRequest(), request->requestData());
QNetworkReply *reply = nullptr;
request->logStartTime();
if (request->type() == "POST") {
if ( request->requestData().isEmpty()) {
qCWarning(sdknetwork) << "Request data is empty -> nothing to send";
}
reply = mNetworkAccessManager.post(request->networkRequest(), request->requestData());
} else if (request->type() == "GET") {
if ( ! request->requestData().isEmpty()) {
qCWarning(sdknetwork) << "Request data is not empty, and you are trying to use get ( not sending any data)";
}
reply = mNetworkAccessManager.get(request->networkRequest());
} else if (request->type() == "PUT") {
reply = mNetworkAccessManager.put(request->networkRequest(), request->requestData());
} else {
qCritical(sdknetwork) << "Using request type:" << request->type() << "is not implemented";
return false;
}
Q_ASSERT(reply);
// Adding reply to final request
reply->setParent(nullptr); // ensure QNetworkreply wont be deleted by QNetworkAccessManager ( it is parent of reply )
request->setNetworkReply(reply);
// Add to map to get correct request when reply is used in signal
mRequestMap.insert(reply, request);
connect(reply, &QNetworkReply::sslErrors, this, &RequestManager::sslErrors);
connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),
this, &RequestManager::replyError);
connect(reply, &QNetworkReply::downloadProgress, this, &RequestManager::downloadProgress);
connect(reply, &QNetworkReply::finished, this, [=]() {
std::shared_ptr<Request> request = mRequestMap.value(reply);
request->logFinishTime();
mRequestMap.remove(reply);
reply->deleteLater();
emit request->finished();
emit request->finished(request);
emit downloadFinished(request);
});
return true;
}
/**
* @brief Handle sslErrors -> only debug output
* @param sslErrors ( from QNetworkReply::sslErrors )
*/
void RequestManager::sslErrors(const QList<QSslError> &sslErrors)
void RequestManager::sslErrors(QNetworkReply *reply, const QList<QSslError> &sslErrors)
{
for (const QSslError &error : sslErrors) {
qCDebug(sdknetwork) << QStringLiteral("SSL error:") << error.errorString();
}
if (ignoreSsl()) {
reply->ignoreSslErrors(sslErrors);
}
}
/**
@ -76,7 +110,6 @@ void RequestManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
encoding = pair.second;
}
}
QByteArray data = reply->readAll();
// Decompress gzip data
@ -87,9 +120,29 @@ void RequestManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
#endif
// Append data to request
request->appendData(data);
request->appendResponseData(data);
emit progresss(bytesReceived, bytesTotal);
}
void RequestManager::replyError(QNetworkReply::NetworkError code)
{
auto *reply = qobject_cast<QNetworkReply*>(sender());
Q_ASSERT(mRequestMap.contains(reply));
std::shared_ptr<Request> request = mRequestMap.value(reply);
qCWarning(sdknetwork) << request.get() << "reply error: " << code;
}
bool RequestManager::ignoreSsl() const
{
return mIgnoreSsl;
}
void RequestManager::setIgnoreSsl(bool ignoreSsl)
{
mIgnoreSsl = ignoreSsl;
}
} // core

View File

@ -19,13 +19,16 @@ class RequestManager : public QObject
public:
explicit RequestManager(QObject *parent = nullptr);
void processRequest(const std::shared_ptr<Request>& request);
bool processRequest(const std::shared_ptr<Request>& request);
bool ignoreSsl() const;
void setIgnoreSsl(bool ignoreSsl);
private:
Q_SLOT void sslErrors(const QList<QSslError> &sslErrors);
Q_SLOT void sslErrors(QNetworkReply *reply, const QList<QSslError> &sslErrors);
Q_SLOT void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
Q_SLOT void replyError(QNetworkReply::NetworkError code);
signals:
void downloadFinished(std::shared_ptr<Request> request);
@ -34,6 +37,7 @@ signals:
private:
QNetworkAccessManager mNetworkAccessManager;
QMap<QNetworkReply*, std::shared_ptr<Request>> mRequestMap;
bool mIgnoreSsl = false;
};
} // core

View File

@ -7,21 +7,29 @@ CONFIG += c++14
INCLUDEPATH += $$PWD
#unix: DEFINES += USE_ZLIB
SOURCES += \
$$PWD/core.cpp \
$$PWD/databasemanager.cpp \
$$PWD/logger.cpp \
$$PWD/loggermessage.cpp \
$$PWD/requestmanager.cpp \
$$PWD/request.cpp \
$$PWD/soaprequest.cpp
core.cpp \
databasemanager.cpp \
databaseutils.cpp \
logger.cpp \
loggermessage.cpp \
requestmanager.cpp \
request.cpp \
utils.cpp \
soaprequest.cpp \
databasetable.cpp
HEADERS += \
$$PWD/core.h \
$$PWD/databasemanager.h \
$$PWD/logger.h \
$$PWD/loggermessage.h \
$$PWD/requestmanager.h \
$$PWD/request.h \
$$PWD/soaprequest.h
core.h \
databasemanager.h \
databaseutils.h \
logger.h \
loggermessage.h \
requestmanager.h \
request.h \
utils.h \
soaprequest.h \
databasetable.h

View File

@ -1,10 +1,13 @@
#include "soaprequest.h"
#include <utility>
namespace serversdk {
SoapRequest::SoapRequest(QString action, QString endpoint, QByteArray data)
: mSoapAction(action)
SoapRequest::SoapRequest(QString action, const QString& endpoint, const QByteArray& data)
: mSoapAction(std::move(action))
{
setType("POST");
setUrl(endpoint);
setRequestData(data);
}
@ -12,7 +15,7 @@ SoapRequest::SoapRequest(QString action, QString endpoint, QByteArray data)
QNetworkRequest SoapRequest::networkRequest() const
{
Q_ASSERT(!this->url().isEmpty());
QNetworkRequest request(QUrl(this->url()));
QNetworkRequest request = Request::networkRequest();
// Have to set header
QString header;
@ -32,9 +35,6 @@ QNetworkRequest SoapRequest::networkRequest() const
// Enabled compression
request.setRawHeader("Accept-Encoding", "gzip, deflate");
// Enabling HTTP/2 ( not supported on Ubuntu 16.04 )
// request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
return request;
}

View File

@ -6,8 +6,9 @@ namespace serversdk {
class SoapRequest : public Request
{
Q_OBJECT
public:
SoapRequest(QString action, QString endpoint, QByteArray data);
SoapRequest(QString action, const QString& endpoint, const QByteArray& data);
QNetworkRequest networkRequest() const override;
QString soapAction() const;

View File

@ -1,22 +1,25 @@
#include "compression.h"
#include "utils.h"
#include <QFileInfo>
#include <QDir>
#ifdef USE_ZLIB
#include <zlib.h> // gzip decompress
#endif
namespace serversdk {
namespace core {
#ifdef USE_ZLIB
/**
* @brief Decompress gzip data
* @param compressed gzip data
* @return decompressed data
*/
QByteArray Compression::decompress(const QByteArray &compressed)
QByteArray Utils::decompress(const QByteArray &compressed)
{
if (compressed.size() <= 4) {
qWarning("gUncompress: Input data is truncated");
qCCritical(sdkutils) << "gUncompress: Input data is truncated";
return QByteArray();
}
@ -68,46 +71,13 @@ QByteArray Compression::decompress(const QByteArray &compressed)
float ratio = (float)compressed.count() / (float)result.count();
float compression = ratio * 100.0;
qCInfo(sdkcompress) << "compress data:" << compressed.count()
qCInfo(sdkutils) << "compress data:" << compressed.count()
<< "uncompressed data:" << result.count()
<< "compression:" << compression;
return result;
}
/**
* @brief Return path to source directory
* @param subpath ( e.g. test/data )
* @return absolute path to desired subpath in source folder
*/
QString Compression::sourcePath(const QString &subpath)
{
QDir dir(__FILE__);
dir.cdUp();
dir.cdUp();
dir.cdUp();
return dir.absoluteFilePath(subpath);
}
/**
* @brief Return data from source directory
* @param subpath ( e.g. test/data/gizp.src )
* @return data of file - QByteArray
*/
QByteArray Compression::sourceData(const QString &subpath)
{
// Get data from file
QString filePath = sourcePath(subpath);
if (! QFileInfo::exists(filePath)) {
qCWarning(sdkcompress) << "path does not exists:" << filePath;
return QByteArray();
}
QFile file(filePath);
if ( file.open(QIODevice::ReadOnly) )
return file.readAll();
return QByteArray();
}
#endif
} // namespace core

18
src/serversdk/utils.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include "logger.h"
#include <QByteArray>
namespace serversdk {
class Utils
{
public:
#ifdef USE_ZLIB
static QByteArray decompress(const QByteArray &compressed);
#endif
};
} // namespace serversdk

View File

@ -3,22 +3,13 @@
#include <QCoreApplication>
#include <QCommandLineParser>
int main(int argc, char *argv[])
{
serversdk::initAppLogger();
qCDebug(sdkcore) << "test message";
qCDebug(sdkcore) << "Application starting";
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName("colnod-connector");
QCoreApplication::setApplicationVersion("1.0");
// Command line parsing
QCommandLineParser parser;
parser.setApplicationDescription("Colnod sync");
QCommandLineOption dryModeOption("dryMode", "ColnodAPI", "Read-only mode, prints changes which would normally be made");
parser.addOption(dryModeOption);
parser.process(app);
QCoreApplication::setApplicationName("example-app");
QCoreApplication::setApplicationVersion("0.1.0");
}

13
test/auto/auto.pro Normal file
View File

@ -0,0 +1,13 @@
TEMPLATE = subdirs
SUBDIRS += \
request/tst_soap.pro \
request/tst_request.pro \
request/tst_requestmanager.pro \
database/tst_databasemanager.pro \
database/tst_sqlite.pro \
database/tst_postgresql.pro \
database/tst_databasetable.pro \
database/tst_databaseutils.pro \
unix: SUBDIRS += tst_utils

View File

@ -0,0 +1,36 @@
#include <QtTest>
#include "serversdk/databasemanager.h"
// add necessary includes here
class DatabaseTest : public QObject
{
Q_OBJECT
public:
DatabaseTest();
~DatabaseTest();
private slots:
void createDb_test();
};
DatabaseTest::DatabaseTest()
{
}
DatabaseTest::~DatabaseTest()
{
}
void DatabaseTest::createDb_test()
{
}
QTEST_APPLESS_MAIN(DatabaseTest)
#include "tst_databasemanager.moc"

View File

@ -0,0 +1,11 @@
QT += testlib
QT -= gui
CONFIG += qt console warn_on depend_includepath testcase
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += tst_databasemanager.cpp
include(../../test.pri)

View File

@ -0,0 +1,169 @@
#include "serversdk/databasetable.h"
#include <QtTest>
#include <memory>
class BookTable : public serversdk::DatabaseTable
{
Q_OBJECT
TABLE_PROPERTY(QString, uuid, setUuid, QString());
TABLE_PROPERTY(int, id, setId, 0);
TABLE_PROPERTY(QString, name, setName, "Test");
TABLE_PROPERTY(QString, author, setAuthor, "Me");
public:
QByteArray filteredJson() {
auto variant = this->toVariantMap();
variant.remove("author");
QJsonDocument doc = QJsonDocument::fromVariant(variant);
return doc.toJson(QJsonDocument::Compact);
}
};
QString enumjson = R"({"authErrors":255,"flags":["Enabled","NoValidity"],"subjectId":"1438aade-3aa6-4885-8e0f-138dedae727a"})";
class EnumTest : public serversdk::DatabaseTable
{
Q_OBJECT
TABLE_PROPERTY(int, authErrors, setAuthErrors, 0);
TABLE_PROPERTY(bool, enabled, setEnabled, false);
TABLE_PROPERTY(bool, noValidity, setNoValidity, false);
TABLE_PROPERTY(QString, subjectId, setSubjectId, QString());
public:
QByteArray jsonData(const QSet<QString> &filters = QSet<QString>()) const override
{
auto variantMap = this->toVariantMap(filters);
auto enabled = variantMap["enabled"].toBool();
auto noValidity = variantMap["noValidity"].toBool();
variantMap.remove("enabled");
variantMap.remove("noValidity");
QVariantList flags;
if ( enabled ) {
flags << "Enabled";
}
if ( noValidity ) {
flags << "NoValidity";
}
variantMap.insert("flags", flags);
QJsonDocument doc = QJsonDocument::fromVariant(variantMap);
return doc.toJson(QJsonDocument::Compact);
}
bool fromJson(const QByteArray &data) override
{
QJsonDocument doc = QJsonDocument::fromJson(data);
QVariantMap variant = qvariant_cast<QVariantMap>(doc.toVariant());
QVariantList flags = variant["flags"].toList();
for (auto value : flags) {
if ( value.toString() == "Enabled") {
variant.insert("enabled", true);
}
if ( value.toString() == "NoValidity") {
variant.insert("noValidity", true);
}
}
variant.remove("flags");
// Populate this object data
bool res = fromVariantMap(variant);
return res;
}
};
class DatabaseTableTest : public QObject
{
Q_OBJECT
public:
DatabaseTableTest();
~DatabaseTableTest();
private slots:
void constructor_test();
void qvariant_test();
void json_test();
void enum_test();
void filter_json_test();
};
DatabaseTableTest::DatabaseTableTest()
{
}
DatabaseTableTest::~DatabaseTableTest()
{
}
void DatabaseTableTest::constructor_test()
{
auto table = std::make_unique<BookTable>();
QCOMPARE(table->id(), 0);
QCOMPARE(table->name(), QStringLiteral("Test"));
QCOMPARE(table->primaryKey(), QStringLiteral("uuid"));
}
void DatabaseTableTest::qvariant_test()
{
auto table = std::make_unique<BookTable>();
QVariantMap map = table->toVariantMap();
QCOMPARE(map.count(), 4);
}
void DatabaseTableTest::json_test()
{
auto table = std::make_unique<BookTable>();
QCOMPARE(table->id(), 0);
QCOMPARE(table->name(), QStringLiteral("Test"));
QByteArray expected(R"({"author":"Me","id":0,"name":"Test","uuid":""})");
QCOMPARE(table->jsonData(), expected);
}
void DatabaseTableTest::enum_test()
{
auto table = std::make_unique<EnumTest>();
table->setAuthErrors(255);
table->setNoValidity(true);
table->setEnabled(true);
table->setSubjectId("1438aade-3aa6-4885-8e0f-138dedae727a");
QCOMPARE(table->jsonData(), enumjson.toUtf8());
EnumTest jsonTest;
jsonTest.fromJson(enumjson.toUtf8());
QCOMPARE(jsonTest.authErrors(), 255);
QCOMPARE(jsonTest.noValidity(), true);
QCOMPARE(jsonTest.enabled(), true);
QCOMPARE(jsonTest.subjectId(), QStringLiteral("1438aade-3aa6-4885-8e0f-138dedae727a"));
}
/** Baseclass must not contain property of derived class ( extended class ) */
void DatabaseTableTest::filter_json_test()
{
auto book = std::make_shared<BookTable>();
QCOMPARE(book->jsonData(), QByteArray(R"({"author":"Me","id":0,"name":"Test","uuid":""})"));
const QByteArray filtered(R"({"id":0,"name":"Test","uuid":""})");
QCOMPARE(book->filteredJson(), filtered);
QCOMPARE(book->jsonData({"author"}), filtered);
}
QTEST_APPLESS_MAIN(DatabaseTableTest)
#include "tst_databasetable.moc"

View File

@ -0,0 +1,11 @@
QT += testlib
QT -= gui
CONFIG += qt console warn_on depend_includepath testcase
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += tst_databasetable.cpp
include(../../test.pri)

View File

@ -0,0 +1,85 @@
#include "serversdk/databasemanager.h"
#include "serversdk/databaseutils.h"
#include "serversdk/databasetable.h"
#include <QtTest>
#include <memory>
class BookTable : public serversdk::DatabaseTable
{
Q_OBJECT
TABLE_PROPERTY(QUuid, id, set_id, QUuid());
TABLE_PROPERTY(QString, description, set_description, "");
TABLE_PROPERTY(QDateTime, last_update, set_last_update, QDateTime::fromSecsSinceEpoch(0));
TABLE_PROPERTY(QString, name, set_name, QString());
TABLE_PROPERTY(QString, pin, set_pin, QString());
TABLE_PROPERTY(QDateTime, last_modified, set_last_modified, QDateTime::fromSecsSinceEpoch(0));
TABLE_PROPERTY(bool, deleted, set_deleted, false);
};
class DatabaseUtilsTest : public QObject
{
Q_OBJECT
public:
DatabaseUtilsTest();
~DatabaseUtilsTest();
QString uuid() const;
void setUuid(QString value);
private slots:
void createTable_test();
void toCamelCase_test();
void fromCamelCase_test();
};
DatabaseUtilsTest::DatabaseUtilsTest()
{
}
DatabaseUtilsTest::~DatabaseUtilsTest()
{
}
void DatabaseUtilsTest::createTable_test()
{
auto table = std::make_unique<BookTable>();
auto db = std::make_unique<serversdk::DatabaseManager>();
serversdk::DatabaseUtils utils(db.get());
QString sql = utils.createTableText(table.get(), "subjects");
QString expected = "CREATE TABLE subjects ( "
"id TEXT PRIMARY KEY NOT NULL, "
"description TEXT, "
"last_update INTEGER, "
"name TEXT, "
"pin TEXT, "
"last_modified INTEGER, "
"deleted INTEGER )";
QCOMPARE(sql, expected);
}
void DatabaseUtilsTest::toCamelCase_test()
{
QString test1 = "id_name";
QCOMPARE(serversdk::DatabaseUtils::toCamelCase(test1), QStringLiteral("idName"));
}
void DatabaseUtilsTest::fromCamelCase_test()
{
QString test1 = "idName";
QCOMPARE(serversdk::DatabaseUtils::fromCamelCase(test1), QStringLiteral("id_name"));
}
QTEST_APPLESS_MAIN(DatabaseUtilsTest)
#include "tst_databaseutils.moc"

View File

@ -0,0 +1,11 @@
QT += testlib
QT -= gui
CONFIG += qt console warn_on depend_includepath testcase
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += tst_databaseutils.cpp
include(../../test.pri)

View File

@ -0,0 +1,105 @@
#include <QtTest>
#include "serversdk/databasemanager.h"
#include "serversdk/databaseutils.h"
#include "serversdk/core.h"
// add necessary includes here
class Name : public serversdk::DatabaseTable {
Q_OBJECT
TABLE_PROPERTY(int, id, set_id, 0);
TABLE_PROPERTY(QString, name, set_name, 0);
};
class PostgresqlTest : public QObject
{
Q_OBJECT
public:
PostgresqlTest();
~PostgresqlTest();
private slots:
void opendb_test();
void createTable_test();
};
PostgresqlTest::PostgresqlTest()
{
}
PostgresqlTest::~PostgresqlTest()
{
}
void PostgresqlTest::opendb_test()
{
// Prepare temporary path for database
const QString host = "192.168.1.94";
const QString databaseName = "serversdk";
const QString user = "postgres";
const QString password = "34rjkciea12";
serversdk::DatabaseManager db;
db.openPostgres(host, databaseName, user, password);
QVERIFY(db.isValid());
QVERIFY(db.isOpen());
// Closing database
db.close();
QVERIFY(!db.isOpen());
}
void PostgresqlTest::createTable_test()
{
// Prepare temporary path for database
const QString host = "192.168.1.94";
const QString databaseName = "serversdk";
const QString user = "postgres";
const QString password = "34rjkciea12";
serversdk::DatabaseManager db;
db.openPostgres(host, databaseName, user, password);
QVERIFY(db.isValid());
QVERIFY(db.isOpen());
Name names;
//Have to drop tabel
QSqlQuery dropQuery = db.execPlainSql("DROP TABLE names");
//QVERIFY(dropQuery.isValid());
// CREATE TABLE
serversdk::DatabaseUtils utils(&db, "PSQL");
QSqlQuery query = utils.createTable(&names, "names");
//QVERIFY(query.isValid());
// INSERT INTO TABLE
names.set_id(2);
names.set_name("Testname");
utils.insertIntoTable(&names, "names");
// SELECT FROM TABLE
QSqlQuery selectSQL = db.execPlainSql("SELECT * FROM names");
auto list = utils.tableFromQuery<Name>(selectSQL);
// ENSURE SAME
auto first = list.first();
// Problem is that fromListvariantMap returns shared_ptr of class DataTable
// Must subclass.
auto names_tab = std::dynamic_pointer_cast<Name>(first);
QCOMPARE(list.count(), 1);
QCOMPARE(names_tab->name(), QStringLiteral("Testname"));
QCOMPARE(list.first()->jsonData(), names.jsonData());
}
QTEST_APPLESS_MAIN(PostgresqlTest)
#include "tst_postgresql.moc"

View File

@ -0,0 +1,11 @@
QT += testlib
QT -= gui
CONFIG += qt console warn_on depend_includepath testcase
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += tst_postgresql.cpp
include(../../test.pri)

View File

@ -0,0 +1,182 @@
#include <QtTest>
#include "serversdk/databasemanager.h"
#include "serversdk/databaseutils.h"
#include "serversdk/core.h"
// add necessary includes here
class Name : public serversdk::DatabaseTable {
Q_OBJECT
TABLE_PROPERTY(QString, uuid, set_uuid, QString());
TABLE_PROPERTY(int, id, set_id, 0);
TABLE_PROPERTY(QString, name, set_name, "");
TABLE_PROPERTY(QString, vehicleName, set_vehicle_name, "Škodouliňkaťoučký");
};
class SqliteTest : public QObject
{
Q_OBJECT
public:
SqliteTest() = default;
private slots:
void createDb_test();
void createTable_test();
void updateTable_test();
void fail_updateTable_test();
};
void SqliteTest::createDb_test()
{
// Prepare temporary path for database
QTemporaryDir dir;
QVERIFY(dir.isValid());
QString dbpath = dir.path() + "/testdb.sqlite";
QVERIFY(!dbpath.isEmpty());
QVERIFY(!QFileInfo::exists(dbpath));
serversdk::DatabaseManager db;
db.openSQLite(dbpath);
QVERIFY(db.isValid());
QVERIFY(db.isOpen());
QVERIFY(QFileInfo::exists(dbpath));
// Closing database
db.close();
QVERIFY(!db.isOpen());
// Temporary dir cleanup
QVERIFY(dir.remove());
QVERIFY(!QFileInfo::exists(dbpath));
}
void SqliteTest::createTable_test()
{
// Prepare temporary path for database
QTemporaryDir dir;
QString dbpath = dir.path() + "/testdb.sqlite";
serversdk::DatabaseManager db;
db.openSQLite(dbpath);
QVERIFY(db.isOpen());
Name name_A, name_B;
// CREATE TABLE
serversdk::DatabaseUtils utils(&db);
QSqlQuery query = utils.createTable(&name_A, "names");
// INSERT INTO TABLE
const QString uuidA = "48f64fab-ba2a-40b5-8fbb-75361099dfb6";
const QString uuidB = "d70143ab-8c14-4ee0-85cb-f0d1b72df104";
name_A.set_uuid(uuidA);
name_A.set_id(2);
name_A.set_name("TestnameA");
name_B.set_uuid(uuidB);
name_B.set_id(2);
name_B.set_name("TestnameB");
utils.insertIntoTable(&name_A, "names");
utils.insertIntoTable(&name_B, "names");
// SELECT FROM TABLE
QSqlQuery selectSQL = db.execPlainSql("SELECT * FROM names");
auto list = utils.tableFromQuery<Name>(selectSQL);
// ENSURE SAME
QCOMPARE(list.count(), 2);
auto names_tab = list.first();
qDebug() << "Uuid" << names_tab->uuid();
QVERIFY(names_tab);
QCOMPARE(names_tab->name(), QStringLiteral("TestnameA"));
QCOMPARE(list.first()->jsonData(), name_A.jsonData());
}
void SqliteTest::updateTable_test()
{
// Prepare temporary path for database
QTemporaryDir dir;
QString dbpath = dir.path() + "/testdb.sqlite";
serversdk::DatabaseManager db;
db.openSQLite(dbpath);
QVERIFY(db.isOpen());
Name name_A, name_B;
// CREATE TABLE
serversdk::DatabaseUtils utils(&db);
QSqlQuery query = utils.createTable(&name_A, "names");
// INSERT INTO TABLE
const QString uuidA = "48f64fab-ba2a-40b5-8fbb-75361099dfb6";
const QString uuidB = "d70143ab-8c14-4ee0-85cb-f0d1b72df104";
name_A.set_uuid(uuidA);
name_A.set_id(2);
name_A.set_name("TestnameA");
name_B.set_uuid(uuidB);
name_B.set_id(2);
name_B.set_name("TestnameB");
QVERIFY2(utils.insertIntoTable(&name_A, "names").isActive(), "Successfull insert must return isActive sqlQuery");
QVERIFY2(utils.insertIntoTable(&name_B, "names").isActive(), "Successfull insert must return isActive sqlQuery");
// NOW UPDATING TABLE WITH name: NewName
name_A.set_name("TestnameA-new");
utils.updateTable(&name_A, "names");
name_B.set_name("TestnameB-new");
utils.updateTable(&name_B, "names");
// SELECT FROM TABLE
QSqlQuery selectSQL = db.execPlainSql("SELECT * FROM names");
auto list = utils.tableFromQuery<Name>(selectSQL);
QCOMPARE(list.count(), 2);
auto dbA = list.first();
auto dbB = list.last();
QCOMPARE(dbA->name(), name_A.name());
QCOMPARE(dbB->name(), name_B.name());
QCOMPARE(dbA->jsonData(), name_A.jsonData());
QCOMPARE(dbB->jsonData(), name_B.jsonData());
}
/** # Fail Update test
*
* Test isActive must be FALSE when not possible to update database table
*/
void SqliteTest::fail_updateTable_test()
{
// Prepare temporary path for database
QTemporaryDir dir;
QString dbpath = dir.path() + "/testdb.sqlite";
serversdk::DatabaseManager db;
db.openSQLite(dbpath);
QVERIFY(db.isOpen());
Name name_A, name_B;
// CREATE TABLE
serversdk::DatabaseUtils utils(&db);
QSqlQuery query = utils.createTable(&name_A, "names");
// INSERT INTO TABLE
const QString uuidA = "48f64fab-ba2a-40b5-8fbb-75361099dfb6";
name_A.set_uuid(uuidA);
name_A.set_id(2);
name_A.set_name("TestnameA");
QVERIFY2(!utils.insertIntoTable(&name_A, "nonexistenttable").isActive(), "Successfull insert must return isActive sqlQuery");
}
QTEST_APPLESS_MAIN(SqliteTest)
#include "tst_sqlite.moc"

View File

@ -0,0 +1,11 @@
QT += testlib
QT -= gui
CONFIG += qt console warn_on depend_includepath testcase
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += tst_sqlite.cpp
include(../../test.pri)

View File

@ -1,9 +1,16 @@
#include "tst_request.h"
#include <QtTest>
#include "serversdk/request.h"
namespace serversdk {
class RequestTest : public QObject
{
Q_OBJECT
private:
Q_SLOT void basicAuthorization_test();
};
void RequestTest::basicAuthorization_test()
{
QString expected = "Basic VGVzdGVyOlRlc3Q=";
@ -17,3 +24,5 @@ void RequestTest::basicAuthorization_test()
}
QTEST_APPLESS_MAIN(serversdk::RequestTest)
#include "tst_request.moc"

View File

@ -3,7 +3,6 @@ CONFIG += testcase # neccesary to have posibility to run: make check UnitTest
SOURCES += \
tst_request.cpp
HEADERS += \
tst_request.h
include(../../serversdkint.pri)
include(../../../serversdkint.pri)

View File

@ -0,0 +1,140 @@
#include <QtTest>
#include "serversdk/requestmanager.h"
#include "serversdk/databasetable.h"
#include "serversdk/core.h"
class LoginTable : public serversdk::DatabaseTable {
Q_OBJECT
TABLE_PROPERTY(QString, username, set_username, "");
TABLE_PROPERTY(QString, password, set_password, "")
};
class Token : public serversdk::DatabaseTable {
Q_OBJECT
TABLE_PROPERTY(QUuid, id, set_id, "");
public:
QString idWithoutCurlyBraces() {
return a_id.toString().mid(1, 36);
}
};
namespace serversdk {
class RequestManagerTest : public QObject
{
Q_OBJECT
public:
RequestManagerTest() {
qRegisterMetaType<std::shared_ptr<serversdk::Request>>("std::shared_ptr<Request>");
}
private:
Q_SLOT void colnodAuthorization_test();
Q_SLOT void colnodGetUpdatedTokens_test();
Q_SLOT void notConncetion_test();
std::shared_ptr<Request> colnodLoginRequest();
};
std::shared_ptr<Request> RequestManagerTest::colnodLoginRequest()
{
// Colnod
auto login = std::make_unique<LoginTable>();
login->set_username("spaceti");
login->set_password("E7FEA07DBC82FB9BA34B2E453E2EFE89F0583E7187543507EF11E84726071FE428445372F2E46B455EB0C79747C51E362F403CE39473A2A8C2DFA0CE5C9826C0");
auto loginRequest = std::make_shared<serversdk::Request>("POST", "https://192.168.1.3:8443/AaaManager/authSession", login->jsonData());
loginRequest->setUserName(login->username());
loginRequest->setPassword(login->password());
return loginRequest;
}
void RequestManagerTest::colnodAuthorization_test()
{
serversdk::RequestManager reqMan;
reqMan.setIgnoreSsl(true);
auto loginRequest = colnodLoginRequest();
QCOMPARE(loginRequest->basicAuthorization(), QStringLiteral("Basic c3BhY2V0aTpFN0ZFQTA3REJDODJGQjlCQTM0QjJFNDUzRTJFRkU4OUYwNTgzRTcxODc1NDM1MDdFRjExRTg0NzI2MDcxRkU0Mjg0NDUzNzJGMkU0NkI0NTVFQjBDNzk3NDdDNTFFMzYyRjQwM0NFMzk0NzNBMkE4QzJERkEwQ0U1Qzk4MjZDMA=="));
QSignalSpy loginFinished(loginRequest.get(), &serversdk::Request::finished);
QTest::ignoreMessage(QtDebugMsg, "\"SSL error:\" \"The host name did not match any of the valid hosts for this certificate\"");
reqMan.processRequest(loginRequest);
QVERIFY(loginFinished.wait());
QCOMPARE(loginFinished.count(), 1 );
auto data = loginRequest->responseData();
Token token;
QVERIFY(token.id().isNull()); // must be null
token.fromJson(data);
QVERIFY(!token.id().isNull()); // mut not be null ( authentication token )
}
void RequestManagerTest::colnodGetUpdatedTokens_test()
{
serversdk::RequestManager reqMan;
reqMan.setIgnoreSsl(true);
auto loginRequest = colnodLoginRequest();
QCOMPARE(loginRequest->basicAuthorization(), QStringLiteral("Basic c3BhY2V0aTpFN0ZFQTA3REJDODJGQjlCQTM0QjJFNDUzRTJFRkU4OUYwNTgzRTcxODc1NDM1MDdFRjExRTg0NzI2MDcxRkU0Mjg0NDUzNzJGMkU0NkI0NTVFQjBDNzk3NDdDNTFFMzYyRjQwM0NFMzk0NzNBMkE4QzJERkEwQ0U1Qzk4MjZDMA=="));
QSignalSpy spyFinished(loginRequest.get(), &serversdk::Request::finished);
QTest::ignoreMessage(QtDebugMsg, "\"SSL error:\" \"The host name did not match any of the valid hosts for this certificate\"");
reqMan.processRequest(loginRequest);
QVERIFY(spyFinished.wait());
QCOMPARE(spyFinished.count(), 1 );
auto data = loginRequest->responseData();
Token token;
QVERIFY(token.id().isNull()); // must be null
token.fromJson(data);
QVERIFY(!token.id().isNull()); // mut not be null ( authentication token )
QByteArray array = R"({"timestamp": 1570109859681})";
auto tokensRequest = std::make_shared<serversdk::Request>("POST", "https://192.168.1.3:8443/TokenManager/getTokensUpdated", array);
tokensRequest->setUserName(token.idWithoutCurlyBraces());
QSignalSpy tokensFinished(tokensRequest.get(), &serversdk::Request::finished);
reqMan.processRequest(tokensRequest);
QVERIFY(tokensRequest->networkReply());
QVERIFY(tokensFinished.wait());
QCOMPARE(tokensFinished.count(), 1 );
QVERIFY(tokensRequest->status() == 200);
//qDebug() << tokensRequest->responseData();
}
void RequestManagerTest::notConncetion_test()
{
serversdk::RequestManager reqMan;
reqMan.setIgnoreSsl(true);
auto tokensRequest = std::make_shared<serversdk::Request>("POST", "https://localhost:9999/TokenManager/getTokensUpdated", QByteArray("{}"));
//QTest::ignoreMessage(QtWarningMsg, QRegularExpression("POST.*error.*"));
reqMan.processRequest(tokensRequest);
QTest::ignoreMessage(QtWarningMsg, "POST https://localhost:9999/TokenManager/getTokensUpdated data: {} header: () reply error: QNetworkReply::NetworkError(ConnectionRefusedError)");
QVERIFY(tokensRequest->networkReply());
QSignalSpy tokensFinished(tokensRequest.get(), &serversdk::Request::finished);
QVERIFY(tokensFinished.wait());
QCOMPARE(tokensFinished.count(), 1 );
QCOMPARE(tokensRequest->status(), -1);
}
}
QTEST_GUILESS_MAIN(serversdk::RequestManagerTest)
#include "tst_requestmanager.moc"

View File

@ -0,0 +1,8 @@
QT += core sql network testlib
CONFIG += testcase # neccesary to have posibility to run: make check UnitTest
SOURCES += \
tst_requestmanager.cpp
include(../../../serversdkint.pri)

View File

@ -0,0 +1,11 @@
QT += testlib
QT -= gui
CONFIG += qt console warn_on depend_includepath testcase
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += tst_soaptest.cpp
include(../../test.pri)

View File

@ -0,0 +1,219 @@
#include <QtTest>
#include <QProcess>
#include "serversdk/requestmanager.h"
#include "serversdk/utils.h"
#include "serversdk/soaprequest.h"
#include "test.h"
namespace serversdk {
class SoapTest : public QObject
{
Q_OBJECT
public:
SoapTest();
private:
Q_SLOT void construct();
/*Q_SLOT*/ void geocode_soap();
/*Q_SLOT*/ void public_soap_xml();
/*Q_SLOT*/ void public_soap_test();
Q_SLOT void public_soap_test_data();
};
SoapTest::SoapTest()
{
qRegisterMetaType<std::shared_ptr<Request>>("std::shared_ptr<Request>");
}
void SoapTest::construct()
{
RequestManager soap;
QVERIFY(soap.mRequestMap.isEmpty());
QFileInfo info(Test::sourcePath(""));
QCOMPARE(info.fileName(), QStringLiteral("data"));
QCOMPARE(Test::sourceData("soap/LatLonListSubgrid-response.xml").count(), 1298);
//QCOMPARE(Utils::sourceData("test/cmd/LatLonListSubgrid-response.xml").count(), 1298);
}
void SoapTest::geocode_soap()
{
const QString soapAction = "https://geoservices.tamu.edu/GeocodeAddressNonParsed";
const QString host = "https://geoservices.tamu.edu";
const QString endpoint = "/Services/Geocode/WebService/GeocoderService_V04_01.asmx";
const QString url = host + endpoint;
QString xml = QString(R"(<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<GeocodeAddressNonParsed xmlns="https://geoservices.tamu.edu/">
<streetAddress>string</streetAddress>
<city>string</city>
<state>string</state>
<zip>string</zip>
<apiKey>string</apiKey>
<version>double</version>
<shouldCalculateCensus>boolean</shouldCalculateCensus>
<censusYear>Unknown or NineteenNinety or TwoThousand or TwoThousandTen or AllAvailable</censusYear>
<shouldReturnReferenceGeometry>boolean</shouldReturnReferenceGeometry>
<shouldNotStoreTransactionDetails>boolean</shouldNotStoreTransactionDetails>
</GeocodeAddressNonParsed>
</soap12:Body>
</soap12:Envelope>)");
QByteArray xmlRequest = xml.toUtf8();
// SOAP download
RequestManager soap;
auto response = std::make_shared<SoapRequest>(soapAction, url, xmlRequest);
QCOMPARE(response.use_count(), 1L );
soap.processRequest(response);
QVERIFY(soap.mRequestMap.count() == 1);
QSignalSpy spyFinished(&soap, &RequestManager::downloadFinished);
QVERIFY(spyFinished.wait());
//QCOMPARE(response.use_count(), 2L ); // this context + Soap
spyFinished.clear();
QCOMPARE(response.use_count(), 1L ); // this context + Soap
// Response
QByteArray xmlResponse = Test::sourceData("soap/bankapi-response.xml");
QVERIFY( ! xmlResponse.isEmpty());
QCOMPARE(response.use_count(), 1L ); // this context + Soap
QCOMPARE(response->responseData(), xmlResponse);
}
void SoapTest::public_soap_xml()
{
QString soapAction = "blz:getBank";
QString url = "http://www.thomas-bayer.com/axis2/services/BLZService";
QString xml = QString("<soapenv:Envelope "
"xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" "
"xmlns:blz=\"http://thomas-bayer.com/blz/\">"
"<soapenv:Header/>"
" <soapenv:Body>"
" <blz:getBank>"
" <blz:blz>%1</blz:blz>"
" </blz:getBank>"
"</soapenv:Body>"
"</soapenv:Envelope>").arg("10020200");
QByteArray xmlRequest = xml.toUtf8();
// SOAP download
RequestManager soap;
auto response = std::make_shared<SoapRequest>(soapAction, url, xmlRequest);
QCOMPARE(response.use_count(), 1L );
soap.processRequest(response);
QVERIFY(soap.mRequestMap.count() == 1);
QSignalSpy spyFinished(&soap, &RequestManager::downloadFinished);
QVERIFY(spyFinished.wait());
//QCOMPARE(response.use_count(), 2L ); // this context + Soap
spyFinished.clear();
QCOMPARE(response.use_count(), 1L ); // this context + Soap
// Response
QByteArray xmlResponse = Test::sourceData("soap/bankapi-response.xml");
QVERIFY( ! xmlResponse.isEmpty());
// QFile fileWrite(cmdDir() + "/tst_soap-response.xml");
// QVERIFY(fileWrite.open(QIODevice::WriteOnly));
// fileWrite.write(soap.data());
// fileWrite.close();
QCOMPARE(response.use_count(), 1L ); // this context + Soap
QCOMPARE(response->responseData(), xmlResponse);
}
void SoapTest::public_soap_test()
{
QFETCH(QString, soapAction);
QFETCH(QString, endpoint);
QFETCH(QString, xmlRequestFile);
QFETCH(QString, xmlResponseFile);
QFETCH(bool, soap12);
// Request
QByteArray xmlRequest = Test::sourceData(xmlRequestFile);
QVERIFY( ! xmlRequest.isEmpty());
// Response
QByteArray xmlResponse = Test::sourceData(xmlResponseFile);
QVERIFY( ! xmlResponse.isEmpty());
// RequestManagerrequest
auto req = std::make_shared<SoapRequest>(soapAction, endpoint, xmlRequest);
req->setSoapVersion12(soap12);
// RequestManagerdownload
RequestManager soap;
soap.processRequest(req);
QCOMPARE(req->status(), 0); // when not processed yet, status is 0
QSignalSpy progressSpy(&soap, &RequestManager::progresss);
QSignalSpy finishedSpy(&soap, &RequestManager::downloadFinished);
QVERIFY(finishedSpy.wait(20000));
QVERIFY(progressSpy.count());
QCOMPARE(req->status(), 200);
//Uncoment to write .xml response to cmd folder
// QFile fileWrite(Utils::sourcePath("test/cmd/tst_soap-response.xml"));
// QVERIFY(fileWrite.open(QIODevice::WriteOnly));
// fileWrite.write(req->responseData());
// fileWrite.close();
// QProcess process;
// process.start("hostname");
// process.waitForFinished(-1); // will wait forever until finished
QCOMPARE(req->responseData(), xmlResponse);
}
void SoapTest::public_soap_test_data()
{
QTest::addColumn<QString>("soapAction");
QTest::addColumn<QString>("endpoint");
QTest::addColumn<QString>("xmlRequestFile");
QTest::addColumn<QString>("xmlResponseFile");
QTest::addColumn<bool>("soap12");
QTest::addColumn<bool>("soap12");
// QTest::newRow("weather service")
// << "LatLonListSubgrid"
// << "https://graphical.weather.gov/xml/SOAP_server/ndfdXMLserver.php"
// << "soap/LatLonListSubgrid.xml"
// << "soap/LatLonListSubgrid-response.xml"
// << false;
QTest::newRow("bank service")
<< "blz:getBank"
<< "http://www.thomas-bayer.com/axis2/services/BLZService"
<< "soap/bankapi.xml"
<< "soap/bankapi-response.xml"
<< false;
QTest::newRow("bank service SOAP 1.2")
<< "blz:getBank"
<< "http://www.thomas-bayer.com/axis2/services/BLZService"
<< "soap/bankapi-1.2.xml"
<< "soap/bankapi-1.2-response.xml"
<< true; // SOAP 1.2
}
}
QTEST_MAIN(serversdk::SoapTest)
#include "tst_soaptest.moc"

View File

@ -0,0 +1,12 @@
QT += testlib
QT -= gui
CONFIG += qt console warn_on depend_includepath testcase
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += tst_utilstest.cpp
include(../../test.pri)

View File

@ -0,0 +1,37 @@
#include <QtTest>
#include <QDir>
#include "serversdk/utils.h"
#include "test.h"
class UtilsTest : public QObject
{
Q_OBJECT
public:
UtilsTest() = default;
private:
Q_SLOT void decompresTest();
};
void UtilsTest::decompresTest()
{
QByteArray expectedData = Test::sourceData("gzip.src");
int expectedSize = 895;
QCOMPARE( expectedData.count(), expectedSize);
QByteArray compressedData = Test::sourceData("gzip.compressed");
int compressedSize = 411;
QCOMPARE( compressedData.count(), compressedSize);
QByteArray decompressedData = serversdk::Utils::decompress(compressedData);
QCOMPARE(decompressedData.count(), expectedSize);
QCOMPARE(decompressedData, expectedData);
}
QTEST_MAIN(UtilsTest)
#include "tst_utilstest.moc"

BIN
test/data/gzip.compressed Normal file

Binary file not shown.

15
test/data/gzip.src Normal file
View File

@ -0,0 +1,15 @@
Příliš žluťoušký kůň
compresed using: gzip -c gzip.src > gzip.compressed
<?xml version="1.0" encoding="ISO-8859-1"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns5689:LatLonListSubgrid xmlns:ns5689="uri:DWMLgen">
<lowerLeftLatitude xsi:type="xsd:string">35.00</lowerLeftLatitude>
<lowerLeftLongitude xsi:type="xsd:string">-82.00</lowerLeftLongitude>
<upperRightLatitude xsi:type="xsd:string">35.5</upperRightLatitude>
<upperRightLongitude xsi:type="xsd:string">-81.50</upperRightLongitude>
<resolution xsi:type="xsd:string">20.0</resolution>
</ns5689:LatLonListSubgrid>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="ISO-8859-1"?><SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:LatLonListSubgridResponse xmlns:ns1="uri:DWMLgen"><listLatLonOut xsi:type="xsd:string">&lt;?xml version=&apos;1.0&apos;?&gt;&lt;dwml version=&apos;1.0&apos; xmlns:xsd=&apos;http://www.w3.org/2001/XMLSchema&apos; xmlns:xsi=&apos;http://www.w3.org/2001/XMLSchema-instance&apos; xsi:noNamespaceSchemaLocation=&apos;https://graphical.weather.gov/xml/DWMLgen/schema/DWML.xsd&apos;&gt;&lt;latLonList&gt;34.986638,-82.027411 35.120891,-82.011661 35.255084,-81.995872 35.389215,-81.980045 35.523285,-81.964180 34.973669,-81.863528 35.107911,-81.847581 35.242094,-81.831596 35.376216,-81.815571 35.510276,-81.799508 34.960536,-81.699684 35.094768,-81.683540 35.228941,-81.667358 35.363052,-81.651136 35.497102,-81.634876 34.947240,-81.535878 35.081463,-81.519539 35.215625,-81.503160 35.349726,-81.486741 35.483766,-81.470282&lt;/latLonList&gt;&lt;/dwml&gt;</listLatLonOut></ns1:LatLonListSubgridResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns5689:LatLonListSubgrid xmlns:ns5689="uri:DWMLgen">
<lowerLeftLatitude xsi:type="xsd:string">35.00</lowerLeftLatitude>
<lowerLeftLongitude xsi:type="xsd:string">-82.00</lowerLeftLongitude>
<upperRightLatitude xsi:type="xsd:string">35.5</upperRightLatitude>
<upperRightLongitude xsi:type="xsd:string">-81.50</upperRightLongitude>
<resolution xsi:type="xsd:string">20.0</resolution>
</ns5689:LatLonListSubgrid>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

View File

14
test/data/soap/bankapi-1.2.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
# @see https://stackoverflow.com/a/55352633/1917249
ACTION_YOU_WANT_TO_CALL="blz:getBank"
XMLFILENAME=bankapi-1.2.xml
URL_OF_THE_SOAP_WEB_SERVICE_ENDPOINT="http://www.thomas-bayer.com/axis2/services/BLZService"
curl -X POST $URL_OF_THE_SOAP_WEB_SERVICE_ENDPOINT \
-H 'Content-Type: application/soap+xml;action=$ACTION_YOU_WANT_TO_CALL' \
-H 'SOAPAction: $ACTION_YOU_WANT_TO_CALL' \
--data @$XMLFILENAME > bankapi-1.2-response.xml

View File

@ -0,0 +1,10 @@
<soapenv:Envelope
xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"
xmlns:blz="http://thomas-bayer.com/blz/">
<soapenv:Header/>
<soapenv:Body>
<blz:getBank>
<blz:blz>10020200</blz:blz>
</blz:getBank>
</soapenv:Body>
</soapenv:Envelope>

View File

@ -0,0 +1 @@
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body><ns1:getBankResponse xmlns:ns1="http://thomas-bayer.com/blz/"><ns1:details><ns1:bezeichnung>BHF-BANK</ns1:bezeichnung><ns1:bic>BHFBDEFF100</ns1:bic><ns1:ort>Berlin</ns1:ort><ns1:plz>10117</ns1:plz></ns1:details></ns1:getBankResponse></soapenv:Body></soapenv:Envelope>

14
test/data/soap/bankapi.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
# @see https://stackoverflow.com/a/55352633/1917249
ACTION_YOU_WANT_TO_CALL="blz:getBank"
XMLFILENAME=bankapi.xml
URL_OF_THE_SOAP_WEB_SERVICE_ENDPOINT="http://www.thomas-bayer.com/axis2/services/BLZService"
curl -X POST $URL_OF_THE_SOAP_WEB_SERVICE_ENDPOINT \
-H 'Content-Type: text/xml' \
-H 'SOAPAction: $ACTION_YOU_WANT_TO_CALL' \
--data @$XMLFILENAME > bankapi-response.xml

View File

@ -0,0 +1,10 @@
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:blz="http://thomas-bayer.com/blz/">
<soapenv:Header/>
<soapenv:Body>
<blz:getBank>
<blz:blz>10020200</blz:blz>
</blz:getBank>
</soapenv:Body>
</soapenv:Envelope>

34
test/test.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "test.h"
#include <QtCore>
/**
* @brief Return path to source directory
* @param subpath ( e.g. test/data )
* @return absolute path to desired subpath in source folder
*/
QString Test::sourcePath(const QString &subpath)
{
const QString path = QStringLiteral(SDK_TEST_PATH);
QDir dir(path);
return dir.absoluteFilePath(subpath);
}
/**
* @brief Return data from source directory
* @param subpath ( e.g. test/data/gizp.src )
* @return data of file - QByteArray
*/
QByteArray Test::sourceData(const QString &subpath)
{
// Get data from file
QString filePath = sourcePath(subpath);
if (! QFileInfo::exists(filePath)) {
qWarning() << "path does not exists:" << filePath;
return QByteArray();
}
QFile file(filePath);
if ( file.open(QIODevice::ReadOnly) )
return file.readAll();
return QByteArray();
}

View File

@ -1,19 +1,12 @@
#pragma once
#include "logger.h"
#include <QByteArray>
#include <QString>
namespace core {
class Compression
class Test
{
public:
static QByteArray decompress(const QByteArray &compressed);
static QString sourcePath(const QString &subpath);
static QByteArray sourceData(const QString &subpath);
};
} // namespace core

17
test/test.pri Normal file
View File

@ -0,0 +1,17 @@
include(../serversdkint.pri)
SDK_ROOT=$$PWD/..
SDK_TEST_PATH=$$SDK_ROOT/test/data
DEFINES += SDK_ROOT='\\"$$SDK_ROOT\\"'
DEFINES += SDK_TEST_PATH='\\"$$SDK_TEST_PATH\\"'
QT += testlib
INCLUDEPATH += $$PWD
HEADERS += \
$$PWD/test.h
SOURCES += \
$$PWD/test.cpp

View File

@ -1,5 +1,5 @@
TEMPLATE = subdirs
SUBDIRS += \
unittests
auto \

View File

@ -1,21 +0,0 @@
#pragma once
#include <QtTest/QtTest>
namespace serversdk {
class RequestTest : public QObject
{
Q_OBJECT
private:
// Default testcases ( not needed to declare it )
// Q_SLOT void initTestCase();
// Q_SLOT void cleanupTestCase();
// void wont_run(); // no Q_SLOT ( only slots will be runned within tests )
Q_SLOT void basicAuthorization_test();
};
} //