You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

192 lines
13 KiB

/* Настоящие core/storage, Qt worker и кеш: два канала, публикация после COMMIT и restart. */
#include "../src/avatars/avatarmanager.h"
#include "../db/db_manager.h"
#include "../transport/gui_bridge.h"
#include <QtTest>
#include <QTemporaryDir>
#include <QFile>
#include <QCryptographicHash>
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 "routing_layer/topo_node_sqlite.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;
static QSet<quint64> downloadIds;
static QHash<quint64, int> finished;
static bool deferDownload;
static QByteArray delayedDownload;
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++; downloadIds.insert(((mf_transfer_request*)arg)->id);
}
uasync_call_soon(testInstance->ua, arg, fn);
}
static void avatarEvent(UTUN_INSTANCE*, int type, const uint8_t* data, int length) {
DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "avatar test: event type=%d bytes=%d", type, 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) {
if (length == 51 && data[50] == 2) {
finished[mf_get64(data)] = data[48] | (int(data[49]) << 8);
if (deferDownload && downloadIds.contains(mf_get64(data)) && !finished[mf_get64(data)]) {
delayedDownload = QByteArray((const char*)data, length); return;
}
}
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);
debug_set_category_level(DEBUG_CATEGORY_CHAT_SYNC, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_MEDIA, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_SYS, DEBUG_LEVEL_ERROR);
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, avatarEvent);
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);
QSignalSpy unchangedServers(&manager, &AvatarManager::serversChanged);
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);
manager.onMembers(channel.channelId, 1, wire, sizeof(wire), false);
}
QCOMPARE(unchangedServers.count(), 0);
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());
QVERIFY(!manager.image("pm:test", testInstance->node_id).isNull());
cached = directory.path() + "/avatar_cache/" + QString::number(testInstance->node_id, 16) + '/' + member.avatar_file;
}
auto* limited = (mf_transfer_request*)u_calloc(1, sizeof(mf_transfer_request)); QVERIFY(limited);
limited->inst = testInstance; limited->id = 41; limited->group = channels[0].channelId.toULongLong();
limited->server = limited->owner = testInstance->node_id; limited->max_bytes = 1;
const QByteArray remote = "avatars/" + QFileInfo(cached).fileName().toLatin1();
const QString rejected = directory.path() + "/rejected.png";
const QByteArray local = rejected.toUtf8(); memcpy(limited->remote, remote.constData(), remote.size());
memcpy(limited->local, local.constData(), local.size()); gui_bridge_post_uasync_fn(member_files_gui_transfer, limited);
QTRY_VERIFY_WITH_TIMEOUT(finished.contains(41), 5000); QCOMPARE(finished.take(41), int(MF_INVALID_RANGE));
QVERIFY(!QFile::exists(rejected));
chat_core_update_my_name(testInstance, "quote\"\\name"); chat_core_sync_my_addresses(testInstance);
for (const auto& channel : channels) {
char profile[MS_USERINFO_MAX + 1], name[128]; uint8_t signature[64]; uint64_t timestamp;
QCOMPARE(chat_core_get_my_profile(testInstance, channel.channelId.toLatin1().constData(), profile, sizeof(profile), signature, &timestamp), 0);
QCOMPARE(json_flat_get(profile, "name", name, sizeof(name)), 0); QCOMPARE(QString::fromLatin1(name), QString("quote\"\\name"));
uint8_t join[64], signedBytes[MS_USERINFO_MAX + 73]; uint64_t joinTimestamp;
QCOMPARE(topo_node_sqlite_member_get_join(testInstance->topo_sqlite_db, channel.channelId.toLatin1().constData(), testInstance->node_id, join, &joinTimestamp), 0);
int length = member_sync_build_update_msg(join, timestamp, profile, signedBytes, sizeof(signedBytes)); QVERIFY(length > 0);
QCOMPARE(sc_ed25519_verify(testInstance->my_ed25519_pubkey, signedBytes, length, signature), SC_OK);
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();
deferDownload = true; delayedDownload.clear();
QTRY_VERIFY_WITH_TIMEOUT((manager.image(channels[0].channelId, testInstance->node_id), !delayedDownload.isEmpty()), 10000);
QCOMPARE(downloads, 1u);
const QString replacement = directory.path() + "/replacement.png"; image.fill(Qt::red); QVERIFY(image.save(replacement));
manager.uploadDefault(replacement, testInstance->node_id);
auto isRed = [&manager, &db](const QString& channel) {
const QImage current = manager.image(channel, db.myNodeId());
return !current.isNull() && current.pixelColor(128, 128) == QColor(Qt::red);
};
for (const auto& channel : channels)
QTRY_VERIFY_WITH_TIMEOUT(isRed(channel.channelId), 10000);
deferDownload = false; manager.onTransfer((const uint8_t*)delayedDownload.constData(), delayedDownload.size());
QTRY_VERIFY_WITH_TIMEOUT(QFile::exists(cached), 5000);
for (const auto& channel : channels) QCOMPARE(manager.image(channel.channelId, testInstance->node_id).pixelColor(128, 128), QColor(Qt::red));
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, 0);
}
};
QTEST_MAIN(AvatarTest)
#include "test_avatars.moc"