/* Настоящие 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 #include #include 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 makeDialog(bool offer) { if (offer) return std::make_unique(QStringLiteral("77"), nullptr); return std::make_unique(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("offer"); QTest::addColumn("immediate"); QTest::addColumn("language"); QTest::addColumn("result"); QTest::addColumn("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.", "Запрос отменён."}, {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(); auto* action = dialog->findChild(offer ? "inviteSend" : "joinConnect"); auto* status = dialog->findChild(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 errorSurvivesPendingPreview_data() { QTest::addColumn("accepted"); QTest::newRow("core-result") << true; QTest::newRow("validation") << false; } void errorSurvivesPendingPreview() { QFETCH(bool, accepted); JoinDialog dialog(nullptr, nullptr); auto* edit = dialog.findChild(); auto* action = dialog.findChild("joinConnect"); auto* status = dialog.findChild("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("offer"); QTest::newRow("join") << false; QTest::newRow("offer") << true; } void retryAndSuccess() { QFETCH(bool, offer); auto dialog = makeDialog(offer); auto* edit = dialog->findChild(); auto* action = dialog->findChild(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"