2
0
mirror of https://github.com/ars3niy/tdlib-purple synced 2025-08-30 13:37:45 +00:00

Added a real test case with login sequence

This commit is contained in:
Arseniy Lartsev
2020-05-06 20:19:26 +02:00
parent 37a03df751
commit df08fa12f9
10 changed files with 1413 additions and 22 deletions

View File

@@ -44,6 +44,14 @@ Copy the .so to libpurple plugins directory.
It's good to have telegram-purple installed as well since its icon is used at the moment.
## Regression test
Build google test library and `make install` it somewhere
Run cmake with '-DGTEST_PATH=/path/to/gtest'
`make run-tests` or `test/tests` or `valgrind test/tests`
## GPL compatibility: building tdlib with OpenSSL 3.0
OpenSSL versions prior to 3.0 branch have license with advertisement clause, making it incompatible with GPL. If this is a concern, a possible solution is to build with OpenSSL 3.0 which uses Apache 2.0 license.

View File

@@ -275,8 +275,9 @@ void PurpleTdClient::setPurpleConnectionInProgress()
purple_debug_misc(config::pluginId, "Connection in progress\n");
PurpleConnection *gc = purple_account_get_connection(m_account);
if (PURPLE_CONNECTION_IS_CONNECTED(gc))
purple_blist_remove_account(m_account);
purple_connection_set_state (gc, PURPLE_CONNECTING);
purple_blist_remove_account(m_account);
purple_connection_update_progress(gc, "Connecting", 1, 3);
}

View File

@@ -8,6 +8,7 @@ add_executable(tests EXCLUDE_FROM_ALL
testsuite.cpp
test-transceiver.cpp
libpurple-mock.cpp
printout.cpp
../tdlib-purple.cpp
../td-client.cpp

View File

@@ -64,7 +64,7 @@ void purple_account_destroy(PurpleAccount *account)
{
free(account->username);
free(account->alias);
free(account);
delete account;
}
void purple_blist_add_account(PurpleAccount *account)
@@ -89,7 +89,7 @@ void purple_blist_remove_buddy(PurpleBuddy *buddy)
// TODO add event
free(buddy->name);
free(buddy->alias);
free(buddy);
delete buddy;
}
const char *purple_buddy_get_alias_only(PurpleBuddy *buddy)
@@ -176,7 +176,7 @@ PurpleConversation *purple_conversation_new(PurpleConversationType type,
void purple_conversation_destroy(PurpleConversation *conv)
{
free(conv->name);
free(conv);
delete conv;
}
void purple_conversation_write(PurpleConversation *conv, const char *who,
@@ -309,7 +309,7 @@ void purple_xfer_unref(PurpleXfer *xfer)
{
if (--xfer->ref == 0) {
free(xfer->who);
free(xfer);
delete xfer;
}
}

1219
test/printout.cpp Normal file

File diff suppressed because it is too large Load Diff

9
test/printout.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef _PRINTOUT_H
#define _PRINTOUT_H
#include <td/telegram/td_api.hpp>
std::string requestToString(const td::td_api::Function &req);
std::string responseToString(const td::td_api::Object &object);
#endif

View File

@@ -1,17 +1,15 @@
#include "test-transceiver.h"
#include "printout.h"
#include <gtest/gtest.h>
static std::string requestToString(const td::td_api::Function &req)
{
return "whatever";
}
using namespace td::td_api;
void TestTransceiver::send(td::Client::Request &&request)
{
m_requests.push(std::move(request));
}
void TestTransceiver::verifyRequest(const td::td_api::Function &request)
void TestTransceiver::verifyRequest(const Function &request)
{
m_lastRequestIds.clear();
verifyRequestImpl(request);
@@ -20,10 +18,10 @@ void TestTransceiver::verifyRequest(const td::td_api::Function &request)
verifyNoRequests();
}
void TestTransceiver::verifyRequests(const std::vector<td::td_api::Function> &requests)
void TestTransceiver::verifyRequests(const std::vector<Function> &requests)
{
m_lastRequestIds.clear();
for (const td::td_api::Function &req: requests) {
for (const Function &req: requests) {
verifyRequestImpl(req);
m_lastRequestIds.push_back(m_requests.front().id);
m_requests.pop();
@@ -31,12 +29,67 @@ void TestTransceiver::verifyRequests(const std::vector<td::td_api::Function> &re
verifyNoRequests();
}
void TestTransceiver::verifyRequestImpl(const td::td_api::Function &request)
static void compare(const Function &actual, const Function &expected)
{
}
static void compare(const setTdlibParameters &actual, const setTdlibParameters &expected)
{
EXPECT_EQ(expected.parameters_->database_directory_, actual.parameters_->database_directory_);
}
static void compare(const checkDatabaseEncryptionKey &actual, const checkDatabaseEncryptionKey &expected)
{
EXPECT_EQ(expected.encryption_key_, actual.encryption_key_);
}
static void compare(const setAuthenticationPhoneNumber &actual, const setAuthenticationPhoneNumber &expected)
{
EXPECT_EQ(expected.phone_number_, actual.phone_number_);
EXPECT_TRUE((expected.settings_ != nullptr) == (actual.settings_ != nullptr));
}
static void compareRequests(const Function &actual, const Function &expected)
{
EXPECT_EQ(expected.get_id(), actual.get_id()) << "Wrong request type: expected " << requestToString(expected);
#define C(class) case class::ID: \
compare(static_cast<const class &>(actual), static_cast<const class &>(expected)); \
break;
switch (actual.get_id()) {
C(setTdlibParameters)
C(checkDatabaseEncryptionKey)
C(setAuthenticationPhoneNumber)
default:
compare(actual, expected);
break;
}
}
void TestTransceiver::verifyRequestImpl(const Function &request)
{
EXPECT_FALSE(m_requests.empty()) << "Missing request: expected " << requestToString(request);
std::cout << "Received request " << m_requests.front().id << ": " << requestToString(*m_requests.front().function) << "\n";
compareRequests(*m_requests.front().function, request);
}
void TestTransceiver::verifyNoRequests()
{
EXPECT_TRUE(m_requests.empty()) << "Unexpected request: " << requestToString(*m_requests.front().function);
}
void TestTransceiver::update(object_ptr<Object> object)
{
std::cout << "Sending update: " << responseToString(*object) << "\n";
receive({0, std::move(object)});
}
void TestTransceiver::reply(object_ptr<Object> object)
{
EXPECT_GE(1u, m_lastRequestIds.size()) << "No requests to reply to";
std::cout << "Replying to request " << m_lastRequestIds.front() << ": " << responseToString(*object) << "\n";
receive({m_lastRequestIds.front(), std::move(object)});
m_lastRequestIds.erase(m_lastRequestIds.begin());
}

View File

@@ -11,6 +11,8 @@ public:
void verifyRequest(const td::td_api::Function &request);
void verifyRequests(const std::vector<td::td_api::Function> &requests);
void verifyNoRequests();
void update(td::td_api::object_ptr<td::td_api::Object> object);
void reply(td::td_api::object_ptr<td::td_api::Object> object);
private:
std::queue<td::Client::Request> m_requests;
std::vector<uint64_t> m_lastRequestIds;

View File

@@ -2,32 +2,128 @@
#include "tdlib-purple.h"
#include <gtest/gtest.h>
using namespace td::td_api;
class CommTest: public testing::Test {
public:
CommTest();
~CommTest();
private:
const std::string phoneNumber = "1234567";
const std::string phoneNumber = "1234567";
const int selfId = 1;
const std::string selfFirstName = "Isaac";
const std::string selfLastName = "Newton";
TestTransceiver tgl;
PurplePlugin purplePlugin;
PurpleAccount *account;
PurpleConnection *connection;
protected:
void SetUp() override;
void TearDown() override;
void login();
};
CommTest::CommTest()
{
tgprpl_set_test_backend(&tgl);
purple_init_plugin(&purplePlugin);
account = purple_account_new(phoneNumber.c_str(), NULL);
((PurplePluginProtocolInfo *)purplePlugin.info->extra_info)->login(account);
}
CommTest::~CommTest()
void CommTest::SetUp()
{
account = purple_account_new(phoneNumber.c_str(), NULL);
connection = new PurpleConnection;
account->gc = connection;
}
void CommTest::TearDown()
{
tgl.verifyNoRequests();
if (purple_connection_get_protocol_data(connection))
((PurplePluginProtocolInfo *)purplePlugin.info->extra_info)->close(connection);
delete connection;
purple_account_destroy(account);
}
TEST_F(CommTest, dummy)
void CommTest::login()
{
EXPECT_EQ(4, 2*2);
((PurplePluginProtocolInfo *)purplePlugin.info->extra_info)->login(account);
tgl.update(make_object<updateAuthorizationState>(make_object<authorizationStateWaitTdlibParameters>()));
tgl.verifyRequest(setTdlibParameters(make_object<tdlibParameters>(
false,
std::string(purple_user_dir()) + G_DIR_SEPARATOR_S +
"tdlib" + G_DIR_SEPARATOR_S + phoneNumber,
"",
false,
false,
false,
false,
0,
"",
"",
"",
"",
"",
false,
false
)));
tgl.reply(make_object<ok>());
// TODO: what if is_encrypted = false?
tgl.update(make_object<updateAuthorizationState>(make_object<authorizationStateWaitEncryptionKey>(true)));
tgl.verifyRequest(checkDatabaseEncryptionKey(""));
tgl.reply(make_object<ok>());
tgl.update(make_object<updateAuthorizationState>(make_object<authorizationStateWaitPhoneNumber>()));
tgl.verifyRequest(setAuthenticationPhoneNumber(phoneNumber, nullptr));
tgl.reply(make_object<ok>());
tgl.update(make_object<updateAuthorizationState>(make_object<authorizationStateReady>()));
tgl.verifyNoRequests();
tgl.update(make_object<updateConnectionState>(make_object<connectionStateConnecting>()));
tgl.verifyNoRequests();
// TODO: verify purple_connection_set_state and purple_connection_update_progress
tgl.update(make_object<updateConnectionState>(make_object<connectionStateUpdating>()));
tgl.verifyNoRequests();
// TODO: verify purple_connection_update_progress
tgl.update(make_object<updateConnectionState>(make_object<connectionStateReady>()));
tgl.verifyRequest(getContacts());
tgl.update(make_object<updateUser>(make_object<user>(
selfId,
selfFirstName,
selfLastName,
"",
phoneNumber,
make_object<userStatusOffline>(),
nullptr,
false,
false,
false,
false,
"",
false,
true,
make_object<userTypeRegular>(),
""
)));
tgl.verifyNoRequests();
// TODO: test sending some users and chats
tgl.reply(make_object<users>());
tgl.verifyRequest(getChats());
// TODO: test sending some chats
tgl.reply(make_object<chats>());
// TODO: verfy purple_account_set_alias
}
TEST_F(CommTest, login)
{
login();
}

View File

@@ -65,8 +65,10 @@ TdTransceiver::TdTransceiver(PurpleTdClient *owner, UpdateCb updateCb, ITranscei
TdTransceiver::~TdTransceiver()
{
m_stopThread = true;
m_impl->m_client->send({UINT64_MAX, td::td_api::make_object<td::td_api::close>()});
m_pollThread.join();
if (!m_testBackend) {
m_impl->m_client->send({UINT64_MAX, td::td_api::make_object<td::td_api::close>()});
m_pollThread.join();
}
// Orphan m_impl - if the background thread generated idle callbacks while we were waiting for
// it to quit, those callbacks will be called after this destructor return (doing nothing, as