<commit_before>/*
   Copyright (C) 2018 <NAME> <<EMAIL>>

   This file is a part of TelegramQt library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

 */

#include <QObject>

#ifndef FAST_PASS
#define TEST_TIMEOUT 10000
#endif

#include "AccountStorage.hpp"
#include "Client.hpp"
#include "ClientSettings.hpp"
#include "ConnectionApi.hpp"
#include "DataStorage.hpp"
#include "Utils.hpp"
#include "TelegramNamespace.hpp"
#include "CAppInformation.hpp"

#include "Operations/ClientAuthOperation.hpp"

#include "ContactsApi.hpp"
#include "CTcpTransport.hpp"
#include "CTelegramTransport.hpp"
#include "DcConfiguration.hpp"

// Server
#include "TelegramServer.hpp"
#include "RemoteClientConnection.hpp"
#include "TelegramServerUser.hpp"
#include "ServerRpcLayer.hpp"
#include "Session.hpp"
#include "LocalCluster.hpp"

#include <QTest>
#include <QSignalSpy>
#include <QDebug>
#include <QRegularExpression>

#include "keys_data.hpp"
#include "TestAuthProvider.hpp"
#include "TestClientUtils.hpp"
#include "TestServerUtils.hpp"
#include "TestUserData.hpp"
#include "TestUtils.hpp"

using namespace Telegram;

static const UserData c_userWithPassword = []() {
    UserData userData;
    userData.dcId = 1;
    userData.setName(QStringLiteral("First"), QStringLiteral("Last"));
    userData.phoneNumber = QStringLiteral("123<PASSWORD>");
    userData.password = QByteArrayLiteral("<PASSWORD>");
    userData.setPasswordSalt(QByteArrayLiteral("<PASSWORD>"));
    return userData;
}();

class tst_ConnectionApi : public QObject
{
    Q_OBJECT
public:
    explicit tst_ConnectionApi(QObject *parent = nullptr);

private slots:
    void initTestCase();
    void cleanupTestCase();
    void testClientConnection_data();
    void testClientConnection();
    void registrationAuthError();
    void reconnect();
};

tst_ConnectionApi::tst_ConnectionApi(QObject *parent) :
    QObject(parent)
{
}

void tst_ConnectionApi::initTestCase()
{
    qRegisterMetaType<UserData>();
    QVERIFY(TestKeyData::initKeyFiles());

#ifdef FAST_PASS
    if (!qEnvironmentVariableIsSet(BaseTcpTransport::timeoutEnvironmentVariableName())) {
        qputenv(BaseTcpTransport::timeoutEnvironmentVariableName(), QByteArrayLiteral("50"));
    }
    if (!qEnvironmentVariableIsSet(Client::ConnectionApi::reconnectionIntervalsEnvironmentVariableName())) {
        qputenv(Client::ConnectionApi::reconnectionIntervalsEnvironmentVariableName(), QByteArrayLiteral("0,5,5,30,30,60"));
    }
#endif
}

void tst_ConnectionApi::cleanupTestCase()
{
    QVERIFY(TestKeyData::cleanupKeyFiles());
}

void tst_ConnectionApi::testClientConnection_data()
{
    QTest::addColumn<Telegram::Client::Settings::SessionType>("sessionType");
    QTest::addColumn<UserData>("userData");
    QTest::addColumn<DcOption>("clientDcOption");
    QTest::addColumn<bool>("waitForPhoneRequest");
    UserData userOnDc1 = c_userWithPassword;
    userOnDc1.dcId = 1;
    UserData userOnDc2 = c_userWithPassword;
    userOnDc2.dcId = 2;
    UserData user2OnDc2 = c_userWithPassword;
    user2OnDc2.unsetPassword();
    user2OnDc2.dcId = 2;
    constexpr bool c_waitForPhoneRequest = true;
    constexpr bool c_dontWaitForPhoneRequest = false;

    DcOption opt = c_localDcOptions.first();

    QTest::newRow("Abridged")
            << Client::Settings::SessionType::Abridged
            << userOnDc1
            << opt
            << c_dontWaitForPhoneRequest;

    QTest::newRow("Obfuscated")
            << Client::Settings::SessionType::Obfuscated
            << userOnDc1
            << opt
            << c_dontWaitForPhoneRequest;

    QTest::newRow("Abridged with migration")
            << Client::Settings::SessionType::Abridged
            << userOnDc2
            << opt
            << c_dontWaitForPhoneRequest;

    QTest::newRow("Abridged with migration (wait for phone requested)")
            << Client::Settings::SessionType::Abridged
            << userOnDc2
            << opt
            << c_waitForPhoneRequest;

    QTest::newRow("Obfuscated with migration")
            << Client::Settings::SessionType::Obfuscated
            << userOnDc2
            << opt
            << c_dontWaitForPhoneRequest;

    opt.id = 0;
    QTest::newRow("Migration from unknown dc (with password)")
            << Client::Settings::SessionType::Obfuscated
            << userOnDc2
            << opt
            << c_dontWaitForPhoneRequest;

    QTest::newRow("Migration from unknown dc, no password")
            << Client::Settings::SessionType::Obfuscated
            << user2OnDc2
            << opt
            << c_dontWaitForPhoneRequest;
}

void tst_ConnectionApi::testClientConnection()
{
    QFETCH(Telegram::Client::Settings::SessionType, sessionType);
    QFETCH(UserData, userData);
    QFETCH(DcOption, clientDcOption);
    QFETCH(bool, waitForPhoneRequest);

    const RsaKey publicKey = RsaKey::fromFile(TestKeyData::publicKeyFileName());
    QVERIFY2(publicKey.isValid(), "Unable to read public RSA key");
    const RsaKey privateKey = RsaKey::fromFile(TestKeyData::privateKeyFileName());
    QVERIFY2(privateKey.isPrivate(), "Unable to read private RSA key");

    Test::AuthProvider authProvider;
    Telegram::Server::LocalCluster cluster;
    cluster.setAuthorizationProvider(&authProvider);
    cluster.setServerPrivateRsaKey(privateKey);
    cluster.setServerConfiguration(c_localDcConfiguration);
    QVERIFY(cluster.start());

    Server::ServerApi *server = cluster.getServerApiInstance(userData.dcId);
    QVERIFY(server);

    Server::LocalUser *user = tryAddUser(&cluster, userData);
    QVERIFY(user);

    Client::Client client;
    setupClientHelper(&client, userData, publicKey, clientDcOption, sessionType);
    Client::ConnectionApi *connectionApi = client.connectionApi();

    QSignalSpy clientConnectionStatusSpy(connectionApi, &Client::ConnectionApi::statusChanged);
    QCOMPARE(connectionApi->status(), Telegram::Client::ConnectionApi::StatusDisconnected);

    // --- Sign in ---
    Client::AuthOperation *signInOperation = connectionApi->startAuthentication();
    {
        QSignalSpy serverAuthCodeSpy(&authProvider, &Test::AuthProvider::codeSent);
        QSignalSpy authPhoneSpy(signInOperation, &Client::AuthOperation::phoneNumberRequired);
        QSignalSpy authCodeSpy(signInOperation, &Client::AuthOperation::authCodeRequired);

        if (waitForPhoneRequest) {
            TRY_VERIFY(!authPhoneSpy.isEmpty());
            QCOMPARE(authPhoneSpy.count(), 1);
            signInOperation->submitPhoneNumber(userData.phoneNumber);
            QCOMPARE(connectionApi->status(), Telegram::Client::ConnectionApi::StatusWaitForAuthentication);
        } else {
            signInOperation->setPhoneNumber(userData.phoneNumber);
            TRY_COMPARE(connectionApi->status(), Telegram::Client::ConnectionApi::StatusWaitForAuthentication);
        }<commit_msg>Tests/ConnectionApi: Use a custom DC Configuration with Media Options<commit_after>/*
   Copyright (C) 2018 <NAME> <<EMAIL>>

   This file is a part of TelegramQt library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

 */

#include <QObject>

#ifndef FAST_PASS
#define TEST_TIMEOUT 10000
#endif

#include "AccountStorage.hpp"
#include "Client.hpp"
#include "ClientSettings.hpp"
#include "ConnectionApi.hpp"
#include "DataStorage.hpp"
#include "Utils.hpp"
#include "TelegramNamespace.hpp"
#include "CAppInformation.hpp"

#include "Operations/ClientAuthOperation.hpp"

#include "ContactsApi.hpp"
#include "CTcpTransport.hpp"
#include "CTelegramTransport.hpp"
#include "DcConfiguration.hpp"

// Server
#include "TelegramServer.hpp"
#include "RemoteClientConnection.hpp"
#include "TelegramServerUser.hpp"
#include "ServerRpcLayer.hpp"
#include "Session.hpp"
#include "LocalCluster.hpp"

#include <QTest>
#include <QSignalSpy>
#include <QDebug>
#include <QRegularExpression>

#include "keys_data.hpp"
#include "TestAuthProvider.hpp"
#include "TestClientUtils.hpp"
#include "TestServerUtils.hpp"
#include "TestUserData.hpp"
#include "TestUtils.hpp"

using namespace Telegram;

static const UserData c_userWithPassword = []() {
    UserData userData;
    userData.dcId = 1;
    userData.setName(QStringLiteral("First"), QStringLiteral("Last"));
    userData.phoneNumber = QStringLiteral("123456");
    userData.password = QByteArrayLiteral("<PASSWORD>");
    userData.setPasswordSalt(QByteArrayLiteral("<PASSWORD>"));
    return userData;
}();

static const Telegram::DcConfiguration c_customDcConfiguration = []() {
    Telegram::DcConfiguration configuration;
    {
        Telegram::DcOption opt(QStringLiteral("127.0.0.11"), 11441, 1);
        configuration.dcOptions.append(opt);
    }
    {
        Telegram::DcOption opt(QStringLiteral("127.0.0.111"), 11441, 1);
        opt.flags |= Telegram::DcOption::IsStatic;
        configuration.dcOptions.append(opt);
    }
    {
        Telegram::DcOption opt(QStringLiteral("127.0.0.111"), 11442, 1);
        opt.flags |= Telegram::DcOption::MediaOnly;
        configuration.dcOptions.append(opt);
    }

    {
        Telegram::DcOption opt(QStringLiteral("127.0.0.112"), 11441, 2);
        opt.flags |= Telegram::DcOption::IsStatic;
        configuration.dcOptions.append(opt);
    }
    {
        Telegram::DcOption opt(QStringLiteral("127.0.0.112"), 11442, 2);
        opt.flags |= Telegram::DcOption::MediaOnly;
        configuration.dcOptions.append(opt);
    }

    {
        Telegram::DcOption opt(QStringLiteral("127.0.0.13"), 11441, 3);
        configuration.dcOptions.append(opt);
    }

    return configuration;
}();

class tst_ConnectionApi : public QObject
{
    Q_OBJECT
public:
    explicit tst_ConnectionApi(QObject *parent = nullptr);

private slots:
    void initTestCase();
    void cleanupTestCase();
    void testClientConnection_data();
    void testClientConnection();
    void registrationAuthError();
    void reconnect();
};

tst_ConnectionApi::tst_ConnectionApi(QObject *parent) :
    QObject(parent)
{
}

void tst_ConnectionApi::initTestCase()
{
    qRegisterMetaType<UserData>();
    QVERIFY(TestKeyData::initKeyFiles());

#ifdef FAST_PASS
    if (!qEnvironmentVariableIsSet(BaseTcpTransport::timeoutEnvironmentVariableName())) {
        qputenv(BaseTcpTransport::timeoutEnvironmentVariableName(), QByteArrayLiteral("50"));
    }
    if (!qEnvironmentVariableIsSet(Client::ConnectionApi::reconnectionIntervalsEnvironmentVariableName())) {
        qputenv(Client::ConnectionApi::reconnectionIntervalsEnvironmentVariableName(), QByteArrayLiteral("0,5,5,30,30,60"));
    }
#endif
}

void tst_ConnectionApi::cleanupTestCase()
{
    QVERIFY(TestKeyData::cleanupKeyFiles());
}

void tst_ConnectionApi::testClientConnection_data()
{
    QTest::addColumn<Telegram::Client::Settings::SessionType>("sessionType");
    QTest::addColumn<UserData>("userData");
    QTest::addColumn<DcOption>("clientDcOption");
    QTest::addColumn<bool>("waitForPhoneRequest");
    UserData userOnDc1 = c_userWithPassword;
    userOnDc1.dcId = 1;
    UserData userOnDc2 = c_userWithPassword;
    userOnDc2.dcId = 2;
    UserData user2OnDc2 = c_userWithPassword;
    user2OnDc2.unsetPassword();
    user2OnDc2.dcId = 2;
    constexpr bool c_waitForPhoneRequest = true;
    constexpr bool c_dontWaitForPhoneRequest = false;

    DcOption opt = c_localDcOptions.first();

    QTest::newRow("Abridged")
            << Client::Settings::SessionType::Abridged
            << userOnDc1
            << opt
            << c_dontWaitForPhoneRequest;

    QTest::newRow("Obfuscated")
            << Client::Settings::SessionType::Obfuscated
            << userOnDc1
            << opt
            << c_dontWaitForPhoneRequest;

    QTest::newRow("Abridged with migration")
            << Client::Settings::SessionType::Abridged
            << userOnDc2
            << opt
            << c_dontWaitForPhoneRequest;

    QTest::newRow("Abridged with migration (wait for phone requested)")
            << Client::Settings::SessionType::Abridged
            << userOnDc2
            << opt
            << c_waitForPhoneRequest;

    QTest::newRow("Obfuscated with migration")
            << Client::Settings::SessionType::Obfuscated
            << userOnDc2
            << opt
            << c_dontWaitForPhoneRequest;

    opt.id = 0;
    QTest::newRow("Migration from unknown dc (with password)")
            << Client::Settings::SessionType::Obfuscated
            << userOnDc2
            << opt
            << c_dontWaitForPhoneRequest;

    QTest::newRow("Migration from unknown dc, no password")
            << Client::Settings::SessionType::Obfuscated
            << user2OnDc2
            << opt
            << c_dontWaitForPhoneRequest;
}

void tst_ConnectionApi::testClientConnection()
{
    QFETCH(Telegram::Client::Settings::SessionType, sessionType);
    QFETCH(UserData, userData);
    QFETCH(DcOption, clientDcOption);
    QFETCH(bool, waitForPhoneRequest);

    const RsaKey publicKey = RsaKey::fromFile(TestKeyData::publicKeyFileName());
    QVERIFY2(publicKey.isValid(), "Unable to read public RSA key");
    const RsaKey privateKey = RsaKey::fromFile(TestKeyData::privateKeyFileName());
    QVERIFY2(privateKey.isPrivate(), "Unable to read private RSA key");

    Test::AuthProvider authProvider;
    Telegram::Server::LocalCluster cluster;
    cluster.setAuthorizationProvider(&authProvider);
    cluster.setServerPrivateRsaKey(privateKey);
    cluster.setServerConfiguration(c_customDcConfiguration);
    QVERIFY(cluster.start());

    Server::ServerApi *server = cluster.getServerApiInstance(userData.dcId);
    QVERIFY(server);

    Server::LocalUser *user = tryAddUser(&cluster, userData);
    QVERIFY(user);

    Client::Client client;
    setupClientHelper(&client, userData, publicKey, clientDcOption, sessionType);
    Client::ConnectionApi *connectionApi = client.connectionApi();

    QSignalSpy clientConnectionStatusSpy(connectionApi, &Client::ConnectionApi::statusChanged);
    QCOMPARE(connectionApi->status(), Telegram::Client::ConnectionApi::StatusDisconnected);

    // --- Sign in ---
    Client::AuthOperation *signInOperation = connectionApi->startAuthentication();
    {
        QSignalSpy serverAuthCodeSpy(&authProvider, &Test::AuthProvider::codeSent);
        QSignalSpy authPhoneSpy(signInOperation, &Client::AuthOperation::phoneNumberRequired);
        QSignalSpy authCodeSpy(signInOperation, &Client::AuthOperation::authCodeRequired);

        if (waitForPhoneRequest) {
            TRY_VERIFY(!authPhoneSpy.isEmpty());
            QCOMPARE(authPhoneSpy.count(), 1);
            signInOperation->submitPhoneNumber(userData.phoneNumber);
            QCOMPARE(connectionApi->status(), Telegram::Client::ConnectionApi::StatusWaitForAuthentication);
        } else {
            signInOperation->setPhoneNumber(userData.phoneNumber);
            TRY_COMPARE(connectionApi->status(), Telegram::Client::ConnectionApi::StatusWaitForAuthentication);
        }
{'fraction_non_alphanumeric': 0.11572024788162388, 'fraction_numerical': 0.011824965220690528, 'mean_word_length': 4.154823989569752, 'pattern_counts': {'":': 0, '<': 146, '<?xml version=': 0, '>': 46, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 6, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2226685', 'n_tokens_mistral': 4747, 'n_tokens_neox': 4615, 'n_words': 868}