nextcloud-desktop/test/testallfilesdeleted.cpp
Olivier Goffart 08e4ba3363
Data-Fingerprint: Fix backup detection when fingerprint is empty
Add a test to test the data fingerprint feature make me realize it was broken.
The code was relying in the distinction between empty and null QByteArray,
but this was a bad idea as this difference is lost when going through QString.
2018-09-10 20:44:03 +02:00

227 lines
9.4 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 <QtTest>
#include "syncenginetestutils.h"
#include <syncengine.h>
using namespace OCC;
static void changeAllFileId(FileInfo &info) {
info.fileId = generateFileId();
if (!info.isDir)
return;
info.etag = generateEtag();
for (auto it = info.children.begin(); it != info.children.end(); ++it) {
changeAllFileId(*it);
}
}
/*
* This test ensure that the SyncEngine::aboutToRemoveAllFiles is correctly called and that when
* we the user choose to remove all files SyncJournalDb::clearFileTable makes works as expected
*/
class TestAllFilesDeleted : public QObject
{
Q_OBJECT
private slots:
void testAllFilesDeletedKeep_data()
{
QTest::addColumn<bool>("deleteOnRemote");
QTest::newRow("local") << false;
QTest::newRow("remote") << true;
}
/*
* In this test, all files are deleted in the client, or the server, and we simulate
* that the users press "keep"
*/
void testAllFilesDeletedKeep()
{
QFETCH(bool, deleteOnRemote);
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
//Just set a blacklist so we can check it is still there. This directory does not exists but
// that does not matter for our purposes.
QStringList selectiveSyncBlackList = { "Q/" };
fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
selectiveSyncBlackList);
auto initialState = fakeFolder.currentLocalState();
int aboutToRemoveAllFilesCalled = 0;
QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
[&](SyncFileItem::Direction dir, bool *cancel) {
QCOMPARE(aboutToRemoveAllFilesCalled, 0);
aboutToRemoveAllFilesCalled++;
QCOMPARE(dir, deleteOnRemote ? SyncFileItem::Down : SyncFileItem::Up);
*cancel = true;
fakeFolder.syncEngine().journal()->clearFileTable(); // That's what Folder is doing
});
auto &modifier = deleteOnRemote ? fakeFolder.remoteModifier() : fakeFolder.localModifier();
for (const auto &s : fakeFolder.currentRemoteState().children.keys())
modifier.remove(s);
QVERIFY(!fakeFolder.syncOnce()); // Should fail because we cancel the sync
QCOMPARE(aboutToRemoveAllFilesCalled, 1);
// Next sync should recover all files
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), initialState);
QCOMPARE(fakeFolder.currentRemoteState(), initialState);
// The selective sync blacklist should be not have been deleted.
bool ok = true;
QCOMPARE(fakeFolder.syncEngine().journal()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok),
selectiveSyncBlackList);
}
void testAllFilesDeletedDelete_data()
{
testAllFilesDeletedKeep_data();
}
/*
* This test is like the previous one but we simulate that the user presses "delete"
*/
void testAllFilesDeletedDelete()
{
QFETCH(bool, deleteOnRemote);
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
int aboutToRemoveAllFilesCalled = 0;
QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
[&](SyncFileItem::Direction dir, bool *cancel) {
QCOMPARE(aboutToRemoveAllFilesCalled, 0);
aboutToRemoveAllFilesCalled++;
QCOMPARE(dir, deleteOnRemote ? SyncFileItem::Down : SyncFileItem::Up);
*cancel = false;
});
auto &modifier = deleteOnRemote ? fakeFolder.remoteModifier() : fakeFolder.localModifier();
for (const auto &s : fakeFolder.currentRemoteState().children.keys())
modifier.remove(s);
QVERIFY(fakeFolder.syncOnce()); // Should succeed, and all files must then be deleted
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(fakeFolder.currentLocalState().children.count(), 0);
// Try another sync to be sure.
QVERIFY(fakeFolder.syncOnce()); // Should succeed (doing nothing)
QCOMPARE(aboutToRemoveAllFilesCalled, 1); // should not have been called.
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(fakeFolder.currentLocalState().children.count(), 0);
}
void testNotDeleteMetaDataChange() {
/**
* This test make sure that we don't popup a file deleted message if all the metadata have
* been updated (for example when the server is upgraded or something)
**/
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
// We never remove all files.
QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
[&] { QVERIFY(false); });
QVERIFY(fakeFolder.syncOnce());
for (const auto &s : fakeFolder.currentRemoteState().children.keys())
fakeFolder.syncJournal().avoidRenamesOnNextSync(s); // clears all the fileid and inodes.
fakeFolder.localModifier().remove("A/a1");
auto expectedState = fakeFolder.currentLocalState();
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), expectedState);
QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
fakeFolder.remoteModifier().remove("B/b1");
changeAllFileId(fakeFolder.remoteModifier());
expectedState = fakeFolder.currentRemoteState();
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), expectedState);
QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
}
void testDataFingetPrint_data()
{
QTest::addColumn<bool>("hasInitialFingerPrint");
QTest::newRow("initial finger print") << true;
QTest::newRow("no initial finger print") << false;
}
void testDataFingetPrint()
{
QFETCH(bool, hasInitialFingerPrint);
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
fakeFolder.remoteModifier().setContents("C/c1", 'N');
fakeFolder.remoteModifier().setModTime("C/c1", QDateTime::currentDateTimeUtc().addDays(-2));
fakeFolder.remoteModifier().remove("C/c2");
if (hasInitialFingerPrint) {
fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>initial_finger_print</oc:data-fingerprint>";
} else {
//Server support finger print, but none is set.
fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint></oc:data-fingerprint>";
}
QVERIFY(fakeFolder.syncOnce());
// First sync, we did not change the finger print, so the file should be downloaded as normal
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->contentChar, 'N');
QVERIFY(!fakeFolder.currentRemoteState().find("C/c2"));
/* Simulate a backup restoration */
// A/a1 is an old file
fakeFolder.remoteModifier().setContents("A/a1", 'O');
fakeFolder.remoteModifier().setModTime("A/a1", QDateTime::currentDateTimeUtc().addDays(-2));
// B/b1 did not exist at the time of the backup
fakeFolder.remoteModifier().remove("B/b1");
// B/b2 was uploaded by another user in the mean time.
fakeFolder.remoteModifier().setContents("B/b2", 'N');
fakeFolder.remoteModifier().setModTime("B/b2", QDateTime::currentDateTimeUtc().addDays(2));
// C/c3 was removed since we made the backup
fakeFolder.remoteModifier().insert("C/c3_removed");
// C/c4 was moved to A/a2 since we made the backup
fakeFolder.remoteModifier().rename("A/a2", "C/old_a2_location");
// The admin sets the data-fingerprint property
fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>new_finger_print</oc:data-fingerprint>";
QVERIFY(fakeFolder.syncOnce());
auto currentState = fakeFolder.currentLocalState();
// Altough the local file is kept as a conflict, the server file is downloaded
QCOMPARE(currentState.find("A/a1")->contentChar, 'O');
auto conflict = findConflict(currentState, "A/a1");
QVERIFY(conflict);
QCOMPARE(conflict->contentChar, 'W');
fakeFolder.localModifier().remove(conflict->path());
// b1 was restored (re-uploaded)
QVERIFY(currentState.find("B/b1"));
// b2 has the new content (was not restored), since its mode time goes forward in time
QCOMPARE(currentState.find("B/b2")->contentChar, 'N');
conflict = findConflict(currentState, "B/b2");
QVERIFY(conflict); // Just to be sure, we kept the old file in a conflict
QCOMPARE(conflict->contentChar, 'W');
fakeFolder.localModifier().remove(conflict->path());
// We actually do not remove files that technically should have been removed (we don't want data-loss)
QVERIFY(currentState.find("C/c3_removed"));
QVERIFY(currentState.find("C/old_a2_location"));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
};
QTEST_GUILESS_MAIN(TestAllFilesDeleted)
#include "testallfilesdeleted.moc"