185 lines
9.7 KiB
C++
185 lines
9.7 KiB
C++
/* Настоящие JOIN/OFFER-диалоги: тексты ошибок, восстановление UI и корреляция ответов.
|
|
* Подменён только bridge: тест управляет отказом submit и асинхронным результатом без сетевого подключения. */
|
|
#include "../src/joindialog.h"
|
|
#include "../src/inviteby.h"
|
|
#include "../transport/gui_bridge.h"
|
|
#include "../transport/utun_node.h"
|
|
#include "../../../lib/debug_config.h"
|
|
#include "../../../src/chat/chat_sync.h"
|
|
#include "../../../src/chat/invite_link.h"
|
|
#include "../../../src/transport_layer/secure_channel.h"
|
|
|
|
#include <QtTest>
|
|
#include <QTranslator>
|
|
#include <memory>
|
|
|
|
static gui_connect_result_fn result_callback;
|
|
static int submit_result;
|
|
static uint64_t next_request, last_request;
|
|
static bool last_offer;
|
|
|
|
void gui_bridge_set_connect_result_cb(gui_connect_result_fn callback) { result_callback = callback; }
|
|
int gui_bridge_join_link(const char*, size_t, uint64_t* request) {
|
|
last_offer = false;
|
|
if (!submit_result) *request = last_request = ++next_request;
|
|
return submit_result;
|
|
}
|
|
int gui_bridge_offer_link(uint64_t, const char*, size_t, uint64_t* request) {
|
|
last_offer = true;
|
|
if (!submit_result) *request = last_request = ++next_request;
|
|
return submit_result;
|
|
}
|
|
QString UtunNode::nodeIdHex() const { return QStringLiteral("1"); }
|
|
|
|
class JoinDialogsTest : public QObject {
|
|
Q_OBJECT
|
|
QTranslator m_translator;
|
|
QString m_link;
|
|
uint64_t m_peer = 0;
|
|
|
|
std::unique_ptr<QDialog> makeDialog(bool offer) {
|
|
if (offer) return std::make_unique<InviteByLinkDialog>(QStringLiteral("77"), nullptr);
|
|
return std::make_unique<JoinDialog>(nullptr, nullptr);
|
|
}
|
|
|
|
private slots:
|
|
void initTestCase() {
|
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_DEBUG);
|
|
InviteData invite = {};
|
|
invite.channelId = 42; invite.join_key = 123; invite.pubkey[0] = 1; invite.addrCount = 1;
|
|
invite.addrs[0].family = 4; invite.addrs[0].address[0] = 127; invite.addrs[0].address[3] = 1;
|
|
invite.addrs[0].proto = INVITE_PROTO_TCP; invite.addrs[0].port = 1333;
|
|
char link[INVITE_LINK_MAX];
|
|
QVERIFY(invite_link_encode(&invite, nullptr, link, sizeof(link)) > 0);
|
|
m_link = QString::fromUtf8(link); m_peer = sc_derive_node_id_from_pubkey(invite.pubkey);
|
|
}
|
|
|
|
void cleanup() {
|
|
qApp->removeTranslator(&m_translator);
|
|
submit_result = 0;
|
|
QVERIFY(!result_callback);
|
|
}
|
|
|
|
void resultErrors_data() {
|
|
QTest::addColumn<bool>("offer"); QTest::addColumn<bool>("immediate");
|
|
QTest::addColumn<QString>("language"); QTest::addColumn<int>("result"); QTest::addColumn<QString>("message");
|
|
struct Case { int result; const char* english; const char* russian; };
|
|
const Case cases[] = {
|
|
{CHAT_CONNECT_ERROR, "Could not complete the request.", "Не удалось выполнить запрос."},
|
|
{CHAT_CONNECT_INVALID, "Invalid request or chat service unavailable.", "Некорректный запрос или сервис чата недоступен."},
|
|
{CHAT_CONNECT_TIMEOUT, "The request timed out.", "Истекло время ожидания запроса."},
|
|
{CHAT_CONNECT_BUSY, "Another join or invitation is in progress. Try again later.",
|
|
"Уже выполняется вход или приглашение. Повторите попытку позже."},
|
|
{CHAT_CONNECT_CANCELLED, "The request was cancelled.", "Запрос отменён."},
|
|
{CHAT_CONNECT_AUTH, "The server access key is invalid or has changed.", "Ключ доступа к серверу неверен или был изменён."},
|
|
{12345, "Could not complete the request.", "Не удалось выполнить запрос."}
|
|
};
|
|
for (bool offer : {false, true}) for (bool immediate : {false, true})
|
|
for (const QString& language : {QStringLiteral("en"), QStringLiteral("ru")}) for (const auto& c : cases) {
|
|
/* TIMEOUT и неизвестный код приходят только в завершении, не в отказе запуска. */
|
|
if (immediate && c.result > 0) continue;
|
|
const QString name = QStringLiteral("%1-%2-%3-%4").arg(offer ? "offer" : "join")
|
|
.arg(immediate ? "submit" : "callback").arg(language).arg(c.result);
|
|
QTest::newRow(qPrintable(name)) << offer << immediate << language << c.result
|
|
<< QString::fromUtf8(language == QStringLiteral("ru") ? c.russian : c.english);
|
|
}
|
|
}
|
|
|
|
void resultErrors() {
|
|
QFETCH(bool, offer); QFETCH(bool, immediate); QFETCH(QString, language); QFETCH(int, result); QFETCH(QString, message);
|
|
QVERIFY(m_translator.load(QStringLiteral(":/translations/chatgui_") + language + QStringLiteral(".qm")));
|
|
QVERIFY(qApp->installTranslator(&m_translator));
|
|
auto dialog = makeDialog(offer);
|
|
auto* edit = dialog->findChild<QLineEdit*>();
|
|
auto* action = dialog->findChild<QPushButton*>(offer ? "inviteSend" : "joinConnect");
|
|
auto* status = dialog->findChild<QLabel*>(offer ? "inviteStatus" : "joinStatus");
|
|
QVERIFY(edit && action && status && result_callback);
|
|
dialog->show(); QTRY_COMPARE(QApplication::activeModalWidget(), dialog.get());
|
|
edit->setText(m_link); submit_result = immediate ? result : 0;
|
|
QTest::mouseClick(action, Qt::LeftButton);
|
|
QCOMPARE(last_offer, offer);
|
|
if (!immediate) {
|
|
QVERIFY(!action->isEnabled());
|
|
result_callback(m_peer, offer ? 77 : 42, result, last_request);
|
|
}
|
|
const QString prefix = QCoreApplication::translate("ChatGui", offer ? "Error: %1" : "Connection failed: %1");
|
|
QCOMPARE(status->text(), prefix.arg(message));
|
|
QVERIFY(dialog->isVisible() && action->isEnabled() && !edit->isReadOnly());
|
|
QCOMPARE(edit->text(), m_link);
|
|
}
|
|
|
|
void serverLinkStartsOfferAndCannotStartLocalJoin() {
|
|
InviteData server = {}; server.kind = INVITE_KIND_SERVER; server.pubkey[0] = 1; server.addrCount = 1;
|
|
memset(server.add_key, 0x57, sizeof(server.add_key));
|
|
server.addrs[0].family = 4; server.addrs[0].proto = INVITE_PROTO_TCP; server.addrs[0].port = 1333;
|
|
server.addrs[0].address[0] = 127; server.addrs[0].address[3] = 1;
|
|
char buffer[INVITE_LINK_MAX]; QVERIFY(invite_link_encode(&server, nullptr, buffer, sizeof(buffer)) > 0);
|
|
const QString link = QString::fromUtf8(buffer);
|
|
{
|
|
InviteByLinkDialog dialog(QStringLiteral("77"), nullptr, nullptr, link);
|
|
auto* action = dialog.findChild<QPushButton*>("inviteSend");
|
|
dialog.show(); QTRY_COMPARE(QApplication::activeModalWidget(), &dialog);
|
|
QTest::mouseClick(action, Qt::LeftButton); QVERIFY(last_offer); QVERIFY(!action->isEnabled());
|
|
result_callback(m_peer, 77, CHAT_CONNECT_AUTH, last_request); QVERIFY(action->isEnabled());
|
|
}
|
|
{
|
|
const uint64_t before = last_request;
|
|
JoinDialog dialog(nullptr, nullptr);
|
|
dialog.findChild<QLineEdit*>()->setText(link);
|
|
QTest::mouseClick(dialog.findChild<QPushButton*>("joinConnect"), Qt::LeftButton);
|
|
QCOMPARE(last_request, before);
|
|
QVERIFY(dialog.findChild<QLabel*>("joinStatus")->text().contains("Select a channel"));
|
|
}
|
|
}
|
|
|
|
void errorSurvivesPendingPreview_data() {
|
|
QTest::addColumn<bool>("accepted");
|
|
QTest::newRow("core-result") << true;
|
|
QTest::newRow("validation") << false;
|
|
}
|
|
|
|
void errorSurvivesPendingPreview() {
|
|
QFETCH(bool, accepted);
|
|
JoinDialog dialog(nullptr, nullptr);
|
|
auto* edit = dialog.findChild<QLineEdit*>();
|
|
auto* action = dialog.findChild<QPushButton*>("joinConnect");
|
|
auto* status = dialog.findChild<QLabel*>("joinStatus");
|
|
dialog.show(); QTRY_COMPARE(QApplication::activeModalWidget(), &dialog);
|
|
edit->setText(accepted ? m_link : QStringLiteral("utun://invalid")); QTest::mouseClick(action, Qt::LeftButton);
|
|
if (accepted) result_callback(m_peer, 42, CHAT_CONNECT_ERROR, last_request);
|
|
const QString error = status->text();
|
|
QVERIFY(!error.isEmpty());
|
|
QTest::qWait(500);
|
|
QCOMPARE(status->text(), error);
|
|
}
|
|
|
|
void retryAndSuccess_data() {
|
|
QTest::addColumn<bool>("offer");
|
|
QTest::newRow("join") << false; QTest::newRow("offer") << true;
|
|
}
|
|
|
|
void retryAndSuccess() {
|
|
QFETCH(bool, offer);
|
|
auto dialog = makeDialog(offer);
|
|
auto* edit = dialog->findChild<QLineEdit*>();
|
|
auto* action = dialog->findChild<QPushButton*>(offer ? "inviteSend" : "joinConnect");
|
|
QVERIFY(edit && action && result_callback);
|
|
QSignalSpy finished(dialog.get(), &QDialog::finished);
|
|
dialog->show(); QTRY_COMPARE(QApplication::activeModalWidget(), dialog.get());
|
|
edit->setText(m_link); QTest::mouseClick(action, Qt::LeftButton);
|
|
const uint64_t previous = last_request;
|
|
result_callback(m_peer, offer ? 77 : 42, CHAT_CONNECT_BUSY, previous);
|
|
QVERIFY(action->isEnabled());
|
|
QTest::mouseClick(action, Qt::LeftButton); QVERIFY(last_request != previous);
|
|
result_callback(m_peer, offer ? 77 : 42, 0, previous);
|
|
result_callback(m_peer + 1, offer ? 77 : 42, 0, last_request);
|
|
QVERIFY(dialog->isVisible() && !action->isEnabled()); QCOMPARE(finished.size(), 0);
|
|
result_callback(m_peer, offer ? 77 : 42, 0, last_request);
|
|
QTRY_VERIFY(!dialog->isVisible());
|
|
QCOMPARE(finished.size(), 1); QCOMPARE(finished.first().first().toInt(), int(QDialog::Accepted));
|
|
}
|
|
};
|
|
|
|
QTEST_MAIN(JoinDialogsTest)
|
|
#include "test_join_dialogs.moc"
|