diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index 29f6839a..d9c16236 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -273,6 +273,11 @@ target_link_libraries(test_member_files_browser PRIVATE ${QT_LIBS} ${QT_TEST_LIB add_test(NAME test_member_files_browser COMMAND test_member_files_browser) set_tests_properties(test_member_files_browser PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 30) +add_executable(test_avatars tests/test_avatars.cpp src/avatars/avatarcache.cpp src/avatars/avatarmanager.cpp db/db_manager.cpp) +target_link_libraries(test_avatars PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun pthread) +add_test(NAME test_avatars COMMAND test_avatars) +set_tests_properties(test_avatars PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 60) + add_executable(test_gui_language tests/test_gui_language.cpp src/networksettingspage.cpp transport/node_config.cpp transport/config_updater.cpp "${CMAKE_CURRENT_BINARY_DIR}/translations.qrc") add_dependencies(test_gui_language chatgui_translations) diff --git a/tools/chatgui/tests/test_avatars.cpp b/tools/chatgui/tests/test_avatars.cpp new file mode 100644 index 00000000..ce6e1731 --- /dev/null +++ b/tools/chatgui/tests/test_avatars.cpp @@ -0,0 +1,137 @@ +/* Настоящие core/storage, Qt worker и кеш: два канала, публикация после COMMIT и restart. */ +#include "../src/avatars/avatarmanager.h" +#include "../db/db_manager.h" +#include "../transport/gui_bridge.h" +#include +#include +#include +#include +extern "C" { +#include "utun_instance.h" +#include "chat/chat_core.h" +#include "chat/chat_event.h" +#include "chat/chat_member.h" +#include "chat/member_sync.h" +#include "routing_layer/topo_group.h" +#include "member_files/member_files.h" +#include "json_flat.h" +#include "mem.h" +#include "debug_config.h" +} +static UTUN_INSTANCE* testInstance; +static unsigned downloads; +extern "C" int gui_bridge_is_uasync_ready(void) { return testInstance != nullptr; } +extern "C" UTUN_INSTANCE* gui_bridge_get_inst(void) { return testInstance; } +extern "C" void gui_bridge_post_uasync_fn(void (*fn)(void*), void* arg) { + if (fn == member_files_gui_transfer && !((mf_transfer_request*)arg)->upload) downloads++; + uasync_call_soon(testInstance->ua, arg, fn); +} +static void event(UTUN_INSTANCE*, int type, const uint8_t* data, int length) { + auto* manager = AvatarManager::instance(); if (!manager) return; + if (type == CHAT_EVT_MEMBER_FILES_RESPONSE) manager->onResponse(data, length); + if (type == CHAT_EVT_MEMBER_FILES_TRANSFER) manager->onTransfer(data, length); + if (type == CHAT_EVT_AVATAR_RESULT) manager->onPublished(data, length); + if (type == CHAT_EVT_MEMBER_UPDATED && length >= 2 + data[0] + CHAT_MEMBER_DISPLAY_SIZE) + manager->onMembers(QString::fromLatin1((const char*)data + 1, data[0]), 1, data + 2 + data[0], CHAT_MEMBER_DISPLAY_SIZE, false); + if (type == CHAT_EVT_MEMBER_LIST && length >= 3 + data[0]) { + uint16_t count; memcpy(&count, data + 1 + data[0], 2); + manager->onMembers(QString::fromLatin1((const char*)data + 1, data[0]), count, data + 3 + data[0], length - 3 - data[0]); + } +} + +class AvatarTest : public QObject { + Q_OBJECT +private slots: + void cacheValidation() { + QTemporaryDir directory; QVERIFY(directory.isValid()); + QVERIFY(AvatarCache::initialize(directory.path())); + QImage source(600, 400, QImage::Format_RGB32); source.fill(Qt::red); + const QString input = directory.path() + "/input.jpg"; QVERIFY(source.save(input)); + const auto prepared = AvatarCache::prepare(input, directory.path()); + QVERIFY2(!prepared.image.isNull(), qPrintable(prepared.error)); QCOMPARE(prepared.image.size(), QSize(256, 256)); + const QString path = directory.path() + '/' + prepared.hash + ".png"; + QVERIFY(!AvatarCache::load(path, prepared.hash).image.isNull()); + QVERIFY(AvatarCache::load(path, QString(64, '0')).image.isNull()); + QFile corrupt(path); QVERIFY(corrupt.open(QIODevice::Append)); QCOMPARE(corrupt.write("x"), 1LL); corrupt.close(); + QVERIFY(AvatarCache::load(path, prepared.hash).image.isNull()); + QImage nonSquare(32, 64, QImage::Format_RGB32); nonSquare.fill(Qt::green); + QByteArray bytes; QBuffer buffer(&bytes); QVERIFY(buffer.open(QIODevice::WriteOnly)); QVERIFY(nonSquare.save(&buffer, "PNG")); + const QString hash = QCryptographicHash::hash(bytes, QCryptographicHash::Sha256).toHex(); + const QString bad = directory.path() + "/bad.download"; QFile output(bad); QVERIFY(output.open(QIODevice::WriteOnly)); + QCOMPARE(output.write(bytes), qint64(bytes.size())); output.close(); + QVERIFY(AvatarCache::publish(bad, directory.path(), hash).image.isNull()); QVERIFY(!QFile::exists(bad)); + QFile partial(directory.path() + "/x.png.utun-123.part"); QVERIFY(partial.open(QIODevice::WriteOnly)); partial.close(); + QVERIFY(AvatarCache::initialize(directory.path())); QVERIFY(!partial.exists()); + } + + void uploadReplicateCacheRestartAndRemove() { + debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); + QTemporaryDir directory; QVERIFY(directory.isValid()); + UASYNC* ua = uasync_create(); QVERIFY(ua); + const QByteArray config = "[global]\nmy_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n" + "my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n" + "[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\nmember_files_path=" + directory.path().toUtf8() + "/storage\n"; + testInstance = utun_instance_create_from_str(ua, config.constData()); QVERIFY(testInstance); + QVERIFY(utun_core_start(testInstance) == 0); QVERIFY(chat_service_start(testInstance) == 0); + chat_core_create_channel_auto(testInstance, "one"); chat_core_create_channel_auto(testInstance, "two"); + DbManager db; db.setDb(testInstance->topo_sqlite_db); db.setMyNodeId(testInstance->node_id); db.setDbPath(directory.path()); + const auto channels = db.getChannels(); QCOMPARE(channels.size(), 2); + for (const auto& channel : channels) { + auto* tags = chat_member_tags_load(testInstance, channel.channelId.toLatin1().constData(), testInstance->node_id); QVERIFY(tags); + QCOMPARE(chat_member_tags_set(tags, "storage", "yes"), 0); QCOMPARE(chat_member_tags_commit(testInstance, tags), 0); + chat_member_tags_free(tags); + } + QTimer poll; connect(&poll, &QTimer::timeout, this, [ua] { uasync_poll(ua, 0); }); poll.start(1); + chat_event_set_handler(testInstance, event); + const QString source = directory.path() + "/photo.png"; QImage image(300, 200, QImage::Format_RGB32); image.fill(Qt::blue); + QVERIFY(image.save(source)); + QString cached; + { + AvatarManager manager(&db); manager.initialize(); + QTRY_COMPARE_WITH_TIMEOUT(manager.servers().size(), 1, 5000); + manager.uploadDefault(source, testInstance->node_id); + QTRY_VERIFY_WITH_TIMEOUT(!manager.defaultImage().isNull(), 5000); + for (const auto& channel : channels) { + uint8_t wire[CHAT_MEMBER_DISPLAY_SIZE]; + QTRY_VERIFY_WITH_TIMEOUT(chat_core_get_single_member(testInstance, channel.channelId.toLatin1().constData(), testInstance->node_id, wire) == 0 && + mf_get64(wire + 79) == testInstance->node_id, 10000); + chat_member_display member{}; chat_core_deserialize_member(wire, &member); + QVERIFY(mf_avatar_file_valid(member.avatar_file)); + QVERIFY(QFile::exists(directory.path() + "/storage/" + channel.channelId + '/' + QString::number(testInstance->node_id) + + "/avatars/" + member.avatar_file)); + QVERIFY(!manager.image(channel.channelId, testInstance->node_id).isNull()); + cached = directory.path() + "/avatar_cache/" + QString::number(testInstance->node_id, 16) + '/' + member.avatar_file; + } + chat_core_update_my_name(testInstance, "quote\"\\name"); chat_core_sync_my_addresses(testInstance); + for (const auto& channel : channels) { + uint8_t wire[CHAT_MEMBER_DISPLAY_SIZE]; QCOMPARE(chat_core_get_single_member(testInstance, channel.channelId.toLatin1().constData(), testInstance->node_id, wire), 0); + QCOMPARE(mf_get64(wire + 79), testInstance->node_id); + QCOMPARE(manager.image(channel.channelId, testInstance->node_id).pixelColor(128, 128), QColor(Qt::blue)); + } + uasync_poll(ua, 0); + } + downloads = 0; + { + AvatarManager manager(&db); manager.initialize(); + QTRY_VERIFY_WITH_TIMEOUT(!manager.image(channels[0].channelId, testInstance->node_id).isNull(), 5000); + QCOMPARE(downloads, 0u); + } + QVERIFY(QFile::remove(cached)); downloads = 0; + { + AvatarManager manager(&db); manager.initialize(); + QTRY_VERIFY_WITH_TIMEOUT(!manager.image(channels[0].channelId, testInstance->node_id).isNull(), 10000); + QVERIFY(downloads == 1); QVERIFY(QFile::exists(cached)); + manager.clearDefault(); + for (const auto& channel : channels) { + uint8_t wire[CHAT_MEMBER_DISPLAY_SIZE]; + QTRY_VERIFY_WITH_TIMEOUT(chat_core_get_single_member(testInstance, channel.channelId.toLatin1().constData(), testInstance->node_id, wire) == 0 && + mf_get64(wire + 79) == 0, 5000); + QVERIFY(manager.image(channel.channelId, testInstance->node_id).isNull()); + } + } + poll.stop(); chat_event_set_handler(testInstance, nullptr); db.setDb(nullptr); + utun_instance_destroy(testInstance); testInstance = nullptr; uasync_destroy(ua); + } +}; +QTEST_MAIN(AvatarTest) +#include "test_avatars.moc"