Files
QSfera/Desktop/test/testsyncjournaldb.cpp
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

306 lines
11 KiB
C++

/*
* This software is in the public domain, furnished "as is", without technical
* support, and with no warranty, express or implied, as to its usefulness for
* any purpose.
* */
#include "folderman.h"
#include "syncfileitem.h"
#include <QtTest>
#include "common/syncjournaldb.h"
#include "common/syncjournalfilerecord.h"
#include "testutils/testutils.h"
using namespace Qt::Literals::StringLiterals;
using namespace OCC;
class TestSyncJournalDB : public QObject
{
Q_OBJECT
QTemporaryDir _tempDir;
public:
TestSyncJournalDB()
: _db((_tempDir.path() + QStringLiteral("/sync.db")))
{
QVERIFY(_tempDir.isValid());
}
qint64 dropMsecs(const QDateTime &time)
{
return Utility::qDateTimeToTime_t(time);
}
private Q_SLOTS:
void initTestCase()
{
}
void cleanupTestCase()
{
const QString file = _db.databaseFilePath();
QFile::remove(file);
}
void testFileRecord()
{
QVERIFY(!_db.getFileRecord(u"nonexistant"_s).isValid());
auto item = TestUtils::dummyItem(u"foo"_s);
// Use a value that exceeds uint32 and isn't representable by the
// signed int being cast to uint64 either (like uint64::max would be)
item._inode = std::numeric_limits<quint32>::max() + 12ull;
item._modtime = dropMsecs(QDateTime::currentDateTime());
item._type = ItemTypeDirectory;
item._etag = u"789789"_s;
item._fileId = "abcd"_ba;
item._remotePerm = RemotePermissions::fromDbValue("RW");
item._size = 213089055;
item._checksumHeader = "MD5:mychecksum";
const auto record = [&] { return SyncJournalFileRecord::fromSyncFileItem(item); };
QVERIFY(_db.setFileRecord(record()));
SyncJournalFileRecord storedRecord = _db.getFileRecord(u"foo"_s);
QVERIFY(storedRecord.isValid());
QVERIFY(storedRecord == record());
// Update checksum
item._checksumHeader = "ADLER32:newchecksum";
_db.updateFileRecordChecksum(QStringLiteral("foo"), "newchecksum", CheckSums::fromByteArray("Adler32"));
storedRecord = _db.getFileRecord(u"foo"_s);
QVERIFY(storedRecord.isValid());
QVERIFY(storedRecord == record());
// Update metadata
item._modtime = dropMsecs(QDateTime::currentDateTime().addDays(1));
// try a value that only fits uint64, not int64
item._inode = std::numeric_limits<quint64>::max() - std::numeric_limits<quint32>::max() - 1;
item._type = ItemTypeFile;
item._etag = u"789FFF"_s;
item._fileId = "efg"_ba;
item._remotePerm = RemotePermissions::fromDbValue("NV");
item._size = 289055;
_db.setFileRecord(record());
storedRecord = _db.getFileRecord(u"foo"_s);
QVERIFY(storedRecord.isValid());
QVERIFY(storedRecord == record());
QVERIFY(_db.deleteFileRecord(u"foo"_s));
storedRecord = _db.getFileRecord(u"foo"_s);
QVERIFY(!storedRecord.isValid());
}
void testFileRecordChecksum()
{
// Try with and without a checksum
{
auto item = TestUtils::dummyItem(u"foo-checksum"_s);
item._checksumHeader = "MD5:mychecksum"_ba;
const auto record = SyncJournalFileRecord::fromSyncFileItem(item);
QVERIFY(_db.setFileRecord(record));
SyncJournalFileRecord storedRecord = _db.getFileRecord(item.destination());
QVERIFY(storedRecord.isValid());
QCOMPARE(storedRecord.path(), record.path());
QCOMPARE(storedRecord.remotePerm(), record.remotePerm());
QCOMPARE(storedRecord.checksumHeader(), record.checksumHeader());
// qDebug()<< u"OOOOO " << storedRecord._modtime.toTime_t() << record._modtime.toTime_t();
// Attention: compare time_t types here, as QDateTime seem to maintain
// milliseconds internally, which disappear in sqlite. Go for full seconds here.
QVERIFY(storedRecord.modtime() == record.modtime());
QVERIFY(storedRecord == record);
}
{
auto item = TestUtils::dummyItem(u"foo-nochecksum"_s);
item._remotePerm = RemotePermissions::fromDbValue("RW");
const auto record = SyncJournalFileRecord::fromSyncFileItem(item);
QVERIFY(_db.setFileRecord(record));
SyncJournalFileRecord storedRecord = _db.getFileRecord(item.destination());
QVERIFY(storedRecord.isValid());
QVERIFY(storedRecord == record);
}
}
void testDownloadInfo()
{
typedef SyncJournalDb::DownloadInfo Info;
Info record = _db.getDownloadInfo(QStringLiteral("nonexistant"));
QVERIFY(!record._valid);
record._errorCount = 5;
record._etag = "ABCDEF";
record._valid = true;
record._tmpfile = QStringLiteral("/tmp/foo");
_db.setDownloadInfo(QStringLiteral("foo"), record);
Info storedRecord = _db.getDownloadInfo(QStringLiteral("foo"));
QVERIFY(storedRecord == record);
_db.setDownloadInfo(QStringLiteral("foo"), Info());
Info wipedRecord = _db.getDownloadInfo(QStringLiteral("foo"));
QVERIFY(!wipedRecord._valid);
}
void testUploadInfo()
{
typedef SyncJournalDb::UploadInfo Info;
Info record = _db.getUploadInfo(QStringLiteral("nonexistant"));
QVERIFY(!record._valid);
record._size = 12894789147;
record._modtime = dropMsecs(QDateTime::currentDateTime());
record._valid = true;
record._url = QUrl(QStringLiteral("http://example.com/foo"));
_db.setUploadInfo(QStringLiteral("foo"), record);
Info storedRecord = _db.getUploadInfo(QStringLiteral("foo"));
QVERIFY(storedRecord == record);
_db.setUploadInfo(QStringLiteral("foo"), Info());
Info wipedRecord = _db.getUploadInfo(QStringLiteral("foo"));
QVERIFY(!wipedRecord._valid);
}
void testConflictRecord()
{
ConflictRecord record;
record.path = "abc";
record.baseFileId = "def";
record.baseModtime = 1234;
record.baseEtag = "ghi";
QVERIFY(!_db.conflictRecord(record.path).isValid());
_db.setConflictRecord(record);
auto newRecord = _db.conflictRecord(record.path);
QVERIFY(newRecord.isValid());
QCOMPARE(newRecord.path, record.path);
QCOMPARE(newRecord.baseFileId, record.baseFileId);
QCOMPARE(newRecord.baseModtime, record.baseModtime);
QCOMPARE(newRecord.baseEtag, record.baseEtag);
_db.deleteConflictRecord(record.path);
QVERIFY(!_db.conflictRecord(record.path).isValid());
}
void testAvoidReadFromDbOnNextSync()
{
auto invalidEtag = u"_invalid_"_s;
auto initialEtag = u"etag"_s;
auto makeEntry = [&](const QAnyStringView &path, ItemType type) {
auto item = TestUtils::dummyItem(path.toString());
item._type = type;
item._etag = initialEtag;
item._remotePerm = RemotePermissions::fromDbValue("RW");
_db.setFileRecord(SyncJournalFileRecord::fromSyncFileItem(item));
};
auto getEtag = [&](const QAnyStringView &path) { return _db.getFileRecord(path.toString()).etag(); };
const auto dirType = ItemTypeDirectory;
const auto fileType = ItemTypeFile;
makeEntry("foodir", dirType);
makeEntry("otherdir", dirType);
makeEntry("foo%", dirType); // wildcards don't apply
makeEntry("foodi_", dirType); // wildcards don't apply
makeEntry("foodir/file", fileType);
makeEntry("foodir/subdir", dirType);
makeEntry("foodir/subdir/file", fileType);
makeEntry("foodir/otherdir", dirType);
makeEntry("fo", dirType); // prefix, but does not match
makeEntry("foodir/sub", dirType); // prefix, but does not match
makeEntry("foodir/subdir/subsubdir", dirType);
makeEntry("foodir/subdir/subsubdir/file", fileType);
makeEntry("foodir/subdir/otherdir", dirType);
_db.schedulePathForRemoteDiscovery(QByteArray("foodir/subdir"));
// Direct effects of parent directories being set to _invalid_
QCOMPARE(getEtag("foodir"), invalidEtag);
QCOMPARE(getEtag("foodir/subdir"), invalidEtag);
QCOMPARE(getEtag("foodir/subdir/subsubdir"), initialEtag);
QCOMPARE(getEtag("foodir/file"), initialEtag);
QCOMPARE(getEtag("foodir/subdir/file"), initialEtag);
QCOMPARE(getEtag("foodir/subdir/subsubdir/file"), initialEtag);
QCOMPARE(getEtag("fo"), initialEtag);
QCOMPARE(getEtag("foo%"), initialEtag);
QCOMPARE(getEtag("foodi_"), initialEtag);
QCOMPARE(getEtag("otherdir"), initialEtag);
QCOMPARE(getEtag("foodir/otherdir"), initialEtag);
QCOMPARE(getEtag("foodir/sub"), initialEtag);
QCOMPARE(getEtag("foodir/subdir/otherdir"), initialEtag);
// Indirect effects: setFileRecord() calls filter etags
initialEtag = u"etag2"_s;
makeEntry("foodir", dirType);
QCOMPARE(getEtag("foodir"), invalidEtag);
makeEntry("foodir/subdir", dirType);
QCOMPARE(getEtag("foodir/subdir"), invalidEtag);
makeEntry("foodir/subdir/subsubdir", dirType);
QCOMPARE(getEtag("foodir/subdir/subsubdir"), initialEtag);
makeEntry("fo", dirType);
QCOMPARE(getEtag("fo"), initialEtag);
makeEntry("foodir/sub", dirType);
QCOMPARE(getEtag("foodir/sub"), initialEtag);
}
void testRecursiveDelete()
{
auto makeEntry = [&](const QAnyStringView &path) {
auto item = TestUtils::dummyItem(path.toString());
item._remotePerm = RemotePermissions::fromDbValue("RW");
_db.setFileRecord(SyncJournalFileRecord::fromSyncFileItem(item));
};
QStringList elements{u"foo"_s, u"foo/file"_s, u"bar"_s, u"moo"_s, u"moo/file"_s, u"foo%bar"_s, u"foo bla bar/file"_s, u"fo_"_s, u"fo_/file"_s};
for (const auto &elem : std::as_const(elements))
makeEntry(elem);
auto checkElements = [&]() {
bool ok = true;
for (const auto &elem : std::as_const(elements)) {
SyncJournalFileRecord record = _db.getFileRecord(elem);
if (!record.isValid()) {
qWarning() << u"Missing record: " << elem;
ok = false;
}
}
return ok;
};
_db.deleteFileRecord(QStringLiteral("moo"), true);
elements.removeAll(u"moo"_s);
elements.removeAll(u"moo/file"_s);
QVERIFY(checkElements());
_db.deleteFileRecord(u"fo_"_s, true);
elements.removeAll(u"fo_"_s);
elements.removeAll(u"fo_/file"_s);
QVERIFY(checkElements());
_db.deleteFileRecord(u"foo%bar"_s, true);
elements.removeAll(u"foo%bar"_s);
QVERIFY(checkElements());
}
private:
SyncJournalDb _db;
};
QTEST_GUILESS_MAIN(TestSyncJournalDB)
#include "testsyncjournaldb.moc"