From ab22277e7b5dc63f3094c5843c5526a176297713 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 6 Oct 2026 19:44:56 +0200 Subject: [PATCH] Test avatar originals, selected lossy crops and storage limits --- tests/test_member_files.c | 6 +++- tools/chatgui/tests/test_avatars.cpp | 41 ++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/tests/test_member_files.c b/tests/test_member_files.c index fa383ff8..8a23874a 100644 --- a/tests/test_member_files.c +++ b/tests/test_member_files.c @@ -59,7 +59,11 @@ static void avatar_storage_test(const char* directory) { memset(q.upload, 2, 16); run(s, 55, 77, MF_BEGIN_WRITE, MF_EXISTS); memcpy(q.version, version, 16); run(s, 55, 77, MF_BEGIN_WRITE, MF_EXISTS); memset(q.version, 0, 16); q.hash[0] ^= 1; run(s, 55, 77, MF_BEGIN_WRITE, MF_HASH_MISMATCH); q.hash[0] ^= 1; - q.size = MF_AVATAR_MAX_BYTES + 1; run(s, 55, 77, MF_BEGIN_WRITE, MF_INVALID_RANGE); + q.size = MF_AVATAR_ORIGINAL_MAX_BYTES + 1; run(s, 55, 77, MF_BEGIN_WRITE, MF_INVALID_RANGE); + snprintf(q.path, sizeof(q.path), "avatars/%s.json", hash); + q.size = MF_AVATAR_MANIFEST_MAX_BYTES + 1; run(s, 55, 77, MF_BEGIN_WRITE, MF_INVALID_RANGE); + EXPECT(mf_avatar_manifest_valid(q.path + 8), "manifest name accepted"); + snprintf(q.path, sizeof(q.path), "avatars/%s.png", hash); q.size = 0; run(s, 55, 77, MF_BEGIN_WRITE, MF_INVALID_RANGE); memcpy(q.version, version, 16); snprintf(q.target, sizeof(q.target), "renamed.png"); run(s, 55, 77, MF_RENAME, MF_INVALID_PATH); snprintf(q.path, sizeof(q.path), "Avatars/%s.png", hash); run(s, 55, 77, MF_BEGIN_WRITE, MF_INVALID_PATH); diff --git a/tools/chatgui/tests/test_avatars.cpp b/tools/chatgui/tests/test_avatars.cpp index 04c53a35..21ecddbd 100644 --- a/tools/chatgui/tests/test_avatars.cpp +++ b/tools/chatgui/tests/test_avatars.cpp @@ -65,7 +65,11 @@ private slots: 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"; + const QString path = directory.path() + '/' + prepared.hash + ".json"; + QFile original(input), stored(directory.path() + '/' + prepared.record.original); + QVERIFY(original.open(QIODevice::ReadOnly)); QVERIFY(stored.open(QIODevice::ReadOnly)); QCOMPARE(stored.readAll(),original.readAll()); + QFile webp(directory.path() + '/' + prepared.record.preview); QVERIFY(webp.open(QIODevice::ReadOnly)); + const QByteArray encoded=webp.readAll(); QCOMPARE(encoded.left(4),QByteArray("RIFF")); QVERIFY(encoded.contains("VP8 ")); 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(); @@ -75,11 +79,25 @@ private slots: 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)); + QVERIFY(!AvatarCache::publish(bad, directory.path(), hash + ".webp").error.isEmpty()); 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 selectedSquarePreservesOriginal() { + QTemporaryDir directory; QVERIFY(directory.isValid()); + QImage source(800,400,QImage::Format_RGB32); source.fill(Qt::blue); + QPainter painter(&source); painter.fillRect(QRect(0,0,400,400),Qt::red); painter.end(); + const QString input=directory.path()+"/source.png"; QVERIFY(source.save(input)); + const auto prepared=AvatarCache::prepare(input,directory.path(),QRectF(0,0,0.5,1)); + QVERIFY2(prepared.error.isEmpty(),qPrintable(prepared.error)); QCOMPARE(prepared.record.crop,QRectF(0,0,0.5,1)); + const auto loaded=AvatarCache::load(directory.path()+'/'+prepared.hash+".json",prepared.hash); + QVERIFY2(loaded.error.isEmpty(),qPrintable(loaded.error)); QVERIFY(loaded.image.pixelColor(128,128).red()>240); + const auto full=AvatarCache::readObject(directory.path()+'/'+prepared.record.original,prepared.record.original); + QCOMPARE(full.image,source); + QVERIFY(!AvatarCache::prepare(input,directory.path(),QRectF(0.7,0,0.5,1)).error.isEmpty()); + } + void uploadReplicateCacheRestartAndRemove() { debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); debug_set_category_level(DEBUG_CATEGORY_CHAT_SYNC, DEBUG_LEVEL_DEBUG); @@ -125,6 +143,12 @@ private slots: 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)); + const QString root=directory.path()+"/storage/"+channel.channelId+'/'+QString::number(testInstance->node_id)+"/avatars/"; + const auto bundle=AvatarCache::readObject(root+member.avatar_file,member.avatar_file); + QVERIFY2(bundle.error.isEmpty(),qPrintable(bundle.error)); + QFile original(source), uploaded(root+bundle.record.original); + QVERIFY(original.open(QIODevice::ReadOnly)); QVERIFY(uploaded.open(QIODevice::ReadOnly)); QCOMPARE(uploaded.readAll(),original.readAll()); + QVERIFY(QFile::exists(root+bundle.record.preview)); 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; @@ -149,7 +173,7 @@ private slots: 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)); + QVERIFY(manager.image(channel.channelId, testInstance->node_id).pixelColor(128,128).blue()>240); } uasync_poll(ua, 0); } @@ -169,13 +193,20 @@ private slots: 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); + return !current.isNull() && current.pixelColor(128,128).red()>240; }; 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)); + for (const auto& channel : channels) QVERIFY(manager.image(channel.channelId,testInstance->node_id).pixelColor(128,128).red()>240); + manager.openGallery(channels[0].channelId,testInstance->node_id); + QTRY_COMPARE_WITH_TIMEOUT(manager.gallery().size(),2,5000); + const QString selected=manager.galleryCurrent(); + QTRY_VERIFY_WITH_TIMEOUT(!manager.galleryImage(selected,true).isNull(),10000); + QCOMPARE(manager.galleryImage(selected,true).size(),QSize(300,200)); + QVERIFY(manager.galleryImage(selected,true).pixelColor(0,0).red()>240); + manager.closeGallery(); manager.clearDefault(); for (const auto& channel : channels) { uint8_t wire[CHAT_MEMBER_DISPLAY_SIZE];