From 4527a6399eb1d43dba9c9feb47d571212b0ee142 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 18 Apr 2025 20:09:15 +0200 Subject: [PATCH] Add QHttpServer-based mock server This should allow for creating tests more easily than the python-based server, since we can poke at it from C++ code. The idea is that each test creates the things it needs (rooms in a certain state, etc) programmatically instead of through the json files we use for the other tests. This allows us to adapt the test data to each test as needed, without having to copy it around a lot. --- CMakeLists.txt | 3 +- autotests/CMakeLists.txt | 6 +- autotests/actionstest.cpp | 47 +++++- autotests/data/localhost.crt | 20 +++ autotests/data/localhost.key | 28 ++++ autotests/server.cpp | 235 ++++++++++++++++++++++++++++++ autotests/server.h | 33 +++++ src/libneochat/accountmanager.cpp | 8 +- 8 files changed, 365 insertions(+), 15 deletions(-) create mode 100644 autotests/data/localhost.crt create mode 100644 autotests/data/localhost.key create mode 100644 autotests/server.cpp create mode 100644 autotests/server.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d3efaf289..918672aa5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,6 +164,7 @@ endif() if(ANDROID) find_package(Sqlite3) + set(BUILD_TESTING FALSE) endif() ki18n_install(po) @@ -178,7 +179,7 @@ add_definitions(-DQT_NO_FOREACH) add_subdirectory(src) if (BUILD_TESTING) - find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test) + find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test HttpServer) add_subdirectory(autotests) # add_subdirectory(appiumtests) if (NOT ANDROID) diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index b1d12d5eb..1e7bd31cf 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -3,6 +3,10 @@ enable_testing() +add_library(neochat_server STATIC server.cpp) + +target_link_libraries(neochat_server PUBLIC Qt::HttpServer QuotientQt6) + add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" ) ecm_add_test( @@ -85,6 +89,6 @@ ecm_add_test( ecm_add_test( actionstest.cpp - LINK_LIBRARIES neochat Qt::Test + LINK_LIBRARIES neochat Qt::Test neochat_server TEST_NAME actionstest ) diff --git a/autotests/actionstest.cpp b/autotests/actionstest.cpp index ec6f45fb4..9b9775bea 100644 --- a/autotests/actionstest.cpp +++ b/autotests/actionstest.cpp @@ -6,9 +6,11 @@ #include #include +#include "accountmanager.h" #include "chatbarcache.h" #include "models/actionsmodel.h" +#include "server.h" #include "testutils.h" using namespace Quotient; @@ -21,10 +23,12 @@ class ActionsTest : public QObject private: Connection *connection = nullptr; - TestUtils::TestRoom *room = nullptr; + NeoChatRoom *room = nullptr; void expectMessage(const QString &actionName, const QString &args, MessageType::Type type, const QString &message); + Server server; + private Q_SLOTS: void initTestCase(); void testActions(); @@ -34,8 +38,23 @@ private Q_SLOTS: void ActionsTest::initTestCase() { - connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org")); - room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-min-sync.json")); + Connection::setRoomType(); + server.start(); + KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat")); + auto accountManager = new AccountManager(true); + QSignalSpy spy(accountManager, &AccountManager::connectionAdded); + connection = accountManager->accounts()->front(); + auto roomId = server.createRoom(u"@user:localhost:1234"_s); + server.inviteUser(roomId, u"@invited:example.com"_s); + server.banUser(roomId, u"@banned:example.com"_s); + server.joinUser(roomId, u"@example:example.com"_s); + + QSignalSpy syncSpy(connection, &Connection::syncDone); + // We need to wait for two syncs, as the next one won't have the changes yet + QVERIFY(syncSpy.wait()); + QVERIFY(syncSpy.wait()); + room = dynamic_cast(connection->room(roomId)); + QVERIFY(room); } void ActionsTest::testActions_data() @@ -90,7 +109,7 @@ static ActionsModel::Action findAction(const QString &name) void ActionsTest::expectMessage(const QString &actionName, const QString &args, MessageType::Type type, const QString &message) { auto action = findAction(actionName); - QSignalSpy spy(room, &TestUtils::TestRoom::showMessage); + QSignalSpy spy(room, &NeoChatRoom::showMessage); auto result = action.handle(args, room, nullptr); auto expected = QVariantList {type, message}; auto signal = spy.takeFirst(); @@ -106,14 +125,26 @@ void ActionsTest::testInvite() QCOMPARE(room->memberState(u"@banned:example.com"_s), Membership::Ban); expectMessage(u"invite"_s, connection->userId(), MessageType::Positive, u"You are already in this room."_s); QCOMPARE(room->memberState(connection->userId()), Membership::Join); - expectMessage(u"invite"_s, u"@example:example.org"_s, MessageType::Information, u"@example:example.org is already in this room."_s); - QCOMPARE(room->memberState(u"@example:example.org"_s), Membership::Join); + expectMessage(u"invite"_s, u"@example:example.com"_s, MessageType::Information, u"@example:example.com is already in this room."_s); + QCOMPARE(room->memberState(u"@example:example.com"_s), Membership::Join); QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Leave); expectMessage(u"invite"_s, u"@user:example.com"_s, MessageType::Positive, u"@user:example.com was invited into this room."_s); - //TODO mock server, wait for invite state to change - //TODO QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Invite); + QSignalSpy spy(room, &NeoChatRoom::changed); + QVERIFY(spy.wait()); + + auto tries = 0; + + while (room->memberState(u"@user:example.com"_s) != Membership::Invite) { + QVERIFY(spy.wait()); + tries += 1; + if (tries > 3) { + QVERIFY(false); + } + } + + QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Invite); } QTEST_MAIN(ActionsTest) diff --git a/autotests/data/localhost.crt b/autotests/data/localhost.crt new file mode 100644 index 000000000..35309c919 --- /dev/null +++ b/autotests/data/localhost.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUXbyWfTfcvVLrVB1qx36pW/7IkwMwDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNDEyMjQxNTAxMDNaGA8yNTcyMDcy +NDE1MDEwM1owQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc +MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKlxZ540TQ1uUDAR7ZJ9ue0PzcD2dPmblIIddyekvZS59V7X +drhamclXpHE2EelR87Sexst0BaHH/jmrHwxCtwbeXHZ8ueJHkGHJ5DLZCCiwfG+Q +gml7wlSXxXz37vie2tdlZh2yJSM8yvLAYceHb2zOskaGvul7ZITIS0JrPc3o6VZk ++MYGkYtA2JfUsv3jH4oQbxOf7RXqhWNAXbB+3hlwRBwMIdyoBNK6YS9QSrTeS9jj +UqgO5QmaQZOVvpaPf1Y/rHHLd2Qa6+a/cCJ1sr2biagb75AihpQFsK/oy6D1PP70 +zTe7hPWn/efEpmtCV7CQ8ti4cRu0Kjy0T8grtCsCAwEAAaMhMB8wHQYDVR0OBBYE +FIFlylzwADNLfgTDNkhFeFelaEDxMA0GCSqGSIb3DQEBCwUAA4IBAQBQ2rw4GLIU +v+GY7Qru9LttkrQPd2bZXKxDMd/jT+wjmMVtqS4MAsCuDYwaYLjU1aWyqy0mN+lY +A17kD0VjBNBy45sYqkZveY0ks8mCScBemtrIDmjz2tiueecBIEASwEPBOZgv5/MV +cz864FiChF+2r8Zl8bhycGy9DEpRjzYKvIQWSDHQ3zpuh3iBnjfoieLHWX2kKCpk +ouS3V6485rHNCWsZT5IcCwfBFQkOuWRJpIazpz4AfwZh1TK9+bgiKA5EyZjSNrKw +xGQSpMSTRQMB0/FOCL/AixhN9unVFUViqUcdtSfoHE1VyBHv9kDT/cYms/Xl4B0t +/ZSQJ/D/Km1+ +-----END CERTIFICATE----- diff --git a/autotests/data/localhost.key b/autotests/data/localhost.key new file mode 100644 index 000000000..a60535528 --- /dev/null +++ b/autotests/data/localhost.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCpcWeeNE0NblAw +Ee2SfbntD83A9nT5m5SCHXcnpL2UufVe13a4WpnJV6RxNhHpUfO0nsbLdAWhx/45 +qx8MQrcG3lx2fLniR5BhyeQy2QgosHxvkIJpe8JUl8V89+74ntrXZWYdsiUjPMry +wGHHh29szrJGhr7pe2SEyEtCaz3N6OlWZPjGBpGLQNiX1LL94x+KEG8Tn+0V6oVj +QF2wft4ZcEQcDCHcqATSumEvUEq03kvY41KoDuUJmkGTlb6Wj39WP6xxy3dkGuvm +v3AidbK9m4moG++QIoaUBbCv6Mug9Tz+9M03u4T1p/3nxKZrQlewkPLYuHEbtCo8 +tE/IK7QrAgMBAAECggEAH9qmeKrra2F4KLlOGNKS//qPGz4Z+ozhi95/NpA1Zb7Z +3pUSCBFcROo5i2D3WA4kiymoRLpQjrv60puVcCggoWVvK4VCKsR6Y6/hOx/q9T9M +fWrE4ZC3FVEc+uPfZJT0nja9TkrdyXSV0LITD8Ap1eI7yJ9vR5R/bqj64QcpLMrU +QeoQIy1oTMR+qdjj33duyRwBZU3Yf8FRB2iW6OILZ8hzFo1jngec7dph9a1RK4e0 +mEPdc9ywsKlDM7P0Y7zdmjar5XtQn87GiwNhz23f1fzCC2axLtOW0Xm4e4Qumehb +WrIi6Vfq8IWMglU7QrBJ7iR0Ls+XoKA5GxomV2IJZQKBgQDoIkOl5YGPQ3iGR+WK +e5/2Ml4G/uURzYiOlzSsyfoPXyO4EI2BJd5HkH+EvfgRx4xKkxUZRJdzR7llYPl8 +BFYcFitvhO8SbD0mNAB5YW7f+3v1pgEN2umzoKd389Zx5WqTZ7YB1VG5RN/Q1JJL +2JM0Xgamq2vNtx3roRPxDBeW7QKBgQC63R/bmACJbgIzfaVBX4Zie3NQG0/Hf+gF +LnBwUmQDZOR7MY+kSiIUVMn3NuZRiCSCFBVwApruyK8r535JCibTVm5PWjvhFddY +LgaPOCKGlm9TLScjoH1pErYgG3uJ4nXeRfXhg4mco6EkrC7RzQywrd0VDoqpuc1Y +EKfEsYk8dwKBgE+mSh3nNOBKX1V73+f3aTiZqaeu2DyWkG+UtE9BclrJ40Cp9VPG +AZH+o7KRWEgJdzqzYv7riSfWCWgesRv7hOxYMwktzLY+i3DLUQpVAy05ZhwwnJX7 +ckrfKfc/pGoqNLplUI8qecMfPciy14vMwR2r0Y5orTHFzi9mcqg35PQ1AoGAW2LX +OLq+0HdHhk0Va8I+450CSRQCUUvhed87SANTPEG0Z/dWC3/h6NWKrGdh/k+5oxAV +Z+EuSkdFPBCLt0bKtCKZ8h7sF+lplotz08kdQXsC2MfFU2wiySdIgK1QHp/tCxZl +6LM+sqdnoJrAjwRcB3AQJkMlV1ox7ba/hbdZqYMCgYBS6+JUXSSASpm5ZHd32a8m +xwryEZ7H6Hek6lvMHdxmwoKat5dCavxw64nrtyeeGZpg1W3zLLyamF9x/8kMyr6y +KKvtBfJ5sCvAbt80o9Pbs6R3yDB3AKiD3s3PQK7lol1nhE/8IbsF2r8JEQVcYd/k +oBzkl7MrMyLhhaCqSxwqQQ== +-----END PRIVATE KEY----- diff --git a/autotests/server.cpp b/autotests/server.cpp new file mode 100644 index 000000000..e9598a907 --- /dev/null +++ b/autotests/server.cpp @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: 2025 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "server.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace Qt::Literals::StringLiterals; + +QString generateEventId() +{ + return u"$"_s + QString::fromLatin1(QCryptographicHash::hash(QUuid::createUuid().toString().toLatin1(), QCryptographicHash::Sha1).toBase64()); +} + +QString generateRoomId() +{ + return u"!%1:localhost:1234"_s + .arg(QString::fromLatin1(QCryptographicHash::hash(QUuid::createUuid().toString().toLatin1(), QCryptographicHash::Sha1).toBase64())) + .replace(u'/', QChar()); +} + +Server::Server() +{ +} + +void Server::start() +{ + QObject::connect(Quotient::NetworkAccessManager::instance(), + &QNetworkAccessManager::sslErrors, + Quotient::NetworkAccessManager::instance(), + [](QNetworkReply *reply) { + reply->ignoreSslErrors(); + }); + m_server.route(u"/.well-known/matrix/client"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) { + responder.write(QJsonDocument(QJsonObject{ + {u"m.homeserver"_s, QJsonObject{{u"base_url"_s, u"https://localhost:1234"_s}}}, + }), + QHttpServerResponder::StatusCode::Ok); + }); + m_server.route(u"/_matrix/client/versions"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) { + responder.write(QJsonDocument(QJsonObject{ + {u"versions"_s, + QJsonArray{ + u"v1.0"_s, + u"v1.1"_s, + u"v1.2"_s, + u"v1.3"_s, + u"v1.4"_s, + u"v1.5"_s, + u"v1.6"_s, + u"v1.7"_s, + u"v1.8"_s, + u"v1.9"_s, + u"v1.10"_s, + u"v1.11"_s, + u"v1.12"_s, + u"v1.13"_s, + }}, + }), + QHttpServerResponder::StatusCode::Ok); + }); + m_server.route(u"/_matrix/client/v3/capabilities"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) { + responder.write( + QJsonDocument(QJsonObject{{u"capabilities"_s, + QJsonObject{ + {u"m.room_versions"_s, QJsonObject{{u"m.available"_s, QJsonObject{{u"1"_s, u"stable"_s}}}, {u"default"_s, u"1"_s}}}, + }}}), + QHttpServerResponder::StatusCode::Ok); + }); + m_server.route(u"/_matrix/client/v3/account/whoami"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) { + responder.write(QJsonDocument(QJsonObject{ + {u"device_id"_s, u"device_id_1234"_s}, + {u"user_id"_s, u"@user:localhost:1234"_s}, + }), + QHttpServerResponder::StatusCode::Ok); + }); + + m_server.route(u"/_matrix/client/v3/login"_s, QHttpServerRequest::Method::Post, [](QHttpServerResponder &responder) { + // TODO + // if data["identifier"]["user"] != "user" or data["password"] != "1234": + // abort(403) + responder.write(QJsonDocument(QJsonObject{ + {u"access_token"_s, u"token_login"_s}, + {u"device_id"_s, u"device_1234"_s}, + {u"user_id"_s, u"@user:localhost:1234"_s}, + }), + QHttpServerResponder::StatusCode::Ok); + }); + + m_server.route(u"/_matrix/client/v3/login"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) { + responder.write(QJsonDocument(QJsonObject{ + {u"flows"_s, QJsonArray{QJsonObject{{u"type"_s, u"m.login.password"_s}}}}, + }), + QHttpServerResponder::StatusCode::Ok); + }); + + m_server.route(u"/_matrix/client/v3/rooms//invite"_s, + QHttpServerRequest::Method::Post, + [this](const QString &roomId, QHttpServerResponder &responder, const QHttpServerRequest &request) { + m_invitedUsers[roomId] += QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString(); + responder.write(QJsonDocument(QJsonObject{}), QHttpServerResponder::StatusCode::Ok); + }); + + m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, [this](QHttpServerResponder &responder) { + QMap stateEvents; + + for (const auto &[roomId, matrixId] : m_roomsToCreate) { + stateEvents[roomId] += QJsonObject{ + {u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}}, + {u"event_id"_s, generateEventId()}, + {u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()}, + {u"room_id"_s, roomId}, + {u"sender"_s, matrixId}, + {u"state_key"_s, QString()}, + {u"type"_s, u"m.room.create"_s}, + {u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}}, + }; + stateEvents[roomId] += QJsonObject{ + {u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}}, + {u"event_id"_s, generateEventId()}, + {u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()}, + {u"room_id"_s, roomId}, + {u"sender"_s, matrixId}, + {u"state_key"_s, matrixId}, + {u"type"_s, u"m.room.member"_s}, + {u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}}, + }; + } + m_roomsToCreate.clear(); + for (const auto &roomId : m_invitedUsers.keys()) { + const auto &values = m_invitedUsers[roomId]; + for (const auto &value : values) { + stateEvents[roomId] += QJsonObject{ + {u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}}, + {u"event_id"_s, generateEventId()}, + {u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()}, + {u"room_id"_s, roomId}, + {u"sender"_s, u"@user:localhost:1234"_s}, + {u"state_key"_s, value}, + {u"type"_s, u"m.room.member"_s}, + {u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}}, + }; + } + } + m_invitedUsers.clear(); + + for (const auto &roomId : m_bannedUsers.keys()) { + const auto &values = m_bannedUsers[roomId]; + for (const auto &value : values) { + stateEvents[roomId] += QJsonObject{ + {u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}}, + {u"event_id"_s, generateEventId()}, + {u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()}, + {u"room_id"_s, roomId}, + {u"sender"_s, u"@user:localhost:1234"_s}, + {u"state_key"_s, value}, + {u"type"_s, u"m.room.member"_s}, + {u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}}, + }; + } + } + m_bannedUsers.clear(); + + for (const auto &roomId : m_joinedUsers.keys()) { + const auto &values = m_joinedUsers[roomId]; + for (const auto &value : values) { + stateEvents[roomId] += QJsonObject{ + {u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}}, + {u"event_id"_s, generateEventId()}, + {u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()}, + {u"room_id"_s, roomId}, + {u"sender"_s, u"@user:localhost:1234"_s}, + {u"state_key"_s, value}, + {u"type"_s, u"m.room.member"_s}, + {u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}}, + }; + } + } + m_joinedUsers.clear(); + + QJsonObject rooms; + for (const auto &roomId : stateEvents.keys()) { + rooms[roomId] = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents[roomId]}}}}; + } + + responder.write(QJsonDocument(QJsonObject{{u"rooms"_s, QJsonObject{{u"join"_s, rooms}}}}), QHttpServerResponder::StatusCode::Ok); + }); + + QSslConfiguration config; + QFile key(QStringLiteral(DATA_DIR) + u"/localhost.key"_s); + key.open(QFile::ReadOnly); + config.setPrivateKey(QSslKey(&key, QSsl::Rsa)); + config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(DATA_DIR) + u"/localhost.crt"_s).front()); + m_sslServer.setSslConfiguration(config); + if (!m_sslServer.listen(QHostAddress::LocalHost, 1234) || !m_server.bind(&m_sslServer)) { + qFatal() << "Server failed to listen on a port."; + return; + } else { + qWarning() << "Server listening"; + } +} + +QString Server::createRoom(const QString &matrixId) +{ + auto roomId = generateRoomId(); + m_roomsToCreate += {roomId, matrixId}; + return roomId; +} + +void Server::inviteUser(const QString &roomId, const QString &matrixId) +{ + m_invitedUsers[roomId] += matrixId; +} + +void Server::banUser(const QString &roomId, const QString &matrixId) +{ + m_bannedUsers[roomId] += matrixId; +} + +void Server::joinUser(const QString &roomId, const QString &matrixId) +{ + m_joinedUsers[roomId] += matrixId; +} diff --git a/autotests/server.h b/autotests/server.h new file mode 100644 index 000000000..c9a4e1d6c --- /dev/null +++ b/autotests/server.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include +#include + +class Server +{ +public: + Server(); + + void start(); + + /** + * Create a room and place the user with id matrixId in it. + * Returns the room's id + */ + QString createRoom(const QString &matrixId); + + void inviteUser(const QString &roomId, const QString &matrixId); + void banUser(const QString &roomId, const QString &matrixId); + void joinUser(const QString &roomId, const QString &matrixId); + +private: + QHttpServer m_server; + QSslServer m_sslServer; + + QHash> m_invitedUsers; + QHash> m_bannedUsers; + QHash> m_joinedUsers; + + QList> m_roomsToCreate; +}; diff --git a/src/libneochat/accountmanager.cpp b/src/libneochat/accountmanager.cpp index 97db00586..763f7a504 100644 --- a/src/libneochat/accountmanager.cpp +++ b/src/libneochat/accountmanager.cpp @@ -23,12 +23,10 @@ AccountManager::AccountManager(bool testMode, QObject *parent) loadAccountsFromCache(); }); } else { - auto c = new NeoChatConnection(this); + auto c = new NeoChatConnection(QUrl(u"https://localhost:1234"_s), this); c->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s); - connect(c, &NeoChatConnection::connected, this, [c, this]() { - m_accountRegistry->add(c); - c->syncLoop(); - }); + m_accountRegistry->add(c); + c->syncLoop(); } }