2016-08-04 15:59:46 +03:00
/*
* 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 .
*
*/
2022-11-28 13:57:32 +03:00
# include <QtTest>
# include <QTextCodec>
2016-08-04 15:59:46 +03:00
# include "syncenginetestutils.h"
2022-11-30 12:34:49 +03:00
# include "caseclashconflictsolver.h"
2023-07-18 12:19:39 +03:00
# include "configfile.h"
# include "propagatorjobs.h"
# include "syncengine.h"
2022-11-30 12:34:49 +03:00
2023-07-13 16:04:07 +03:00
# include <QFile>
2022-11-30 12:34:49 +03:00
# include <QtTest>
2016-08-04 15:59:46 +03:00
2024-02-01 17:48:40 +03:00
# include <filesystem>
2016-08-04 15:59:46 +03:00
using namespace OCC ;
2022-11-30 12:34:49 +03:00
namespace {
QStringList findCaseClashConflicts ( const FileInfo & dir )
2016-08-04 15:59:46 +03:00
{
2022-11-30 12:34:49 +03:00
QStringList conflicts ;
for ( const auto & item : dir . children ) {
if ( item . name . contains ( " (case clash from " ) ) {
conflicts . append ( item . path ( ) ) ;
}
}
return conflicts ;
}
bool expectConflict ( FileInfo state , const QString path )
{
PathComponents pathComponents ( path ) ;
auto base = state . find ( pathComponents . parentDirComponents ( ) ) ;
if ( ! base )
return false ;
for ( const auto & item : qAsConst ( base - > children ) ) {
if ( item . name . startsWith ( pathComponents . fileName ( ) ) & & item . name . contains ( " (case clash from " ) ) {
return true ;
}
2016-08-04 15:59:46 +03:00
}
return false ;
}
2022-11-30 12:34:49 +03:00
bool itemDidComplete ( const ItemCompletedSpy & spy , const QString & path )
2019-08-28 15:20:40 +03:00
{
2022-11-30 12:34:49 +03:00
if ( auto item = spy . findItem ( path ) ) {
return item - > _instruction ! = CSYNC_INSTRUCTION_NONE & & item - > _instruction ! = CSYNC_INSTRUCTION_UPDATE_METADATA ;
}
return false ;
2019-08-28 15:20:40 +03:00
}
2019-10-07 16:25:36 +03:00
bool itemDidCompleteSuccessfully ( const ItemCompletedSpy & spy , const QString & path )
2016-08-04 15:59:46 +03:00
{
2019-10-07 16:25:36 +03:00
if ( auto item = spy . findItem ( path ) ) {
2019-08-28 15:20:40 +03:00
return item - > _status = = SyncFileItem : : Success ;
2016-08-04 15:59:46 +03:00
}
return false ;
}
2021-09-09 15:29:06 +03:00
bool itemDidCompleteSuccessfullyWithExpectedRank ( const ItemCompletedSpy & spy , const QString & path , int rank )
{
if ( auto item = spy . findItemWithExpectedRank ( path , rank ) ) {
return item - > _status = = SyncFileItem : : Success ;
}
return false ;
}
2021-09-08 13:10:01 +03:00
int itemSuccessfullyCompletedGetRank ( const ItemCompletedSpy & spy , const QString & path )
{
auto itItem = std : : find_if ( spy . begin ( ) , spy . end ( ) , [ & path ] ( auto currentItem ) {
auto item = currentItem [ 0 ] . template value < OCC : : SyncFileItemPtr > ( ) ;
return item - > destination ( ) = = path ;
} ) ;
if ( itItem ! = spy . end ( ) ) {
return itItem - spy . begin ( ) ;
}
return - 1 ;
}
2022-11-30 12:34:49 +03:00
}
2016-08-04 15:59:46 +03:00
class TestSyncEngine : public QObject
{
Q_OBJECT
private slots :
2023-07-10 18:56:16 +03:00
void initTestCase ( )
{
2023-12-14 16:39:47 +03:00
Logger : : instance ( ) - > setLogFlush ( true ) ;
Logger : : instance ( ) - > setLogDebug ( true ) ;
2024-04-30 11:04:32 +03:00
QStandardPaths : : setTestModeEnabled ( true ) ;
2023-07-10 18:56:16 +03:00
}
2024-01-11 02:16:43 +03:00
void init ( )
{
QTextCodec : : setCodecForLocale ( QTextCodec : : codecForName ( " UTF-8 " ) ) ;
}
2016-08-04 15:59:46 +03:00
void testFileDownload ( ) {
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
2019-10-07 16:25:36 +03:00
ItemCompletedSpy completeSpy ( fakeFolder ) ;
2016-08-04 15:59:46 +03:00
fakeFolder . remoteModifier ( ) . insert ( " A/a0 " ) ;
fakeFolder . syncOnce ( ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " A/a0 " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
void testFileUpload ( ) {
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
2019-10-07 16:25:36 +03:00
ItemCompletedSpy completeSpy ( fakeFolder ) ;
2016-08-04 15:59:46 +03:00
fakeFolder . localModifier ( ) . insert ( " A/a0 " ) ;
fakeFolder . syncOnce ( ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " A/a0 " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
void testDirDownload ( ) {
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
2019-10-07 16:25:36 +03:00
ItemCompletedSpy completeSpy ( fakeFolder ) ;
2016-08-04 15:59:46 +03:00
fakeFolder . remoteModifier ( ) . mkdir ( " Y " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " Z " ) ;
fakeFolder . remoteModifier ( ) . insert ( " Z/d0 " ) ;
fakeFolder . syncOnce ( ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " Y " ) ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " Z " ) ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " Z/d0 " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
void testDirUpload ( ) {
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
2019-10-07 16:25:36 +03:00
ItemCompletedSpy completeSpy ( fakeFolder ) ;
2016-08-04 15:59:46 +03:00
fakeFolder . localModifier ( ) . mkdir ( " Y " ) ;
fakeFolder . localModifier ( ) . mkdir ( " Z " ) ;
fakeFolder . localModifier ( ) . insert ( " Z/d0 " ) ;
fakeFolder . syncOnce ( ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " Y " ) ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " Z " ) ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " Z/d0 " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
2021-09-09 15:29:06 +03:00
void testDirUploadWithDelayedAlgorithm ( ) {
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
2021-10-28 17:55:27 +03:00
fakeFolder . syncEngine ( ) . account ( ) - > setCapabilities ( { { " dav " , QVariantMap { { " bulkupload " , " 1.0 " } } } } ) ;
2021-09-09 15:29:06 +03:00
ItemCompletedSpy completeSpy ( fakeFolder ) ;
fakeFolder . localModifier ( ) . mkdir ( " Y " ) ;
fakeFolder . localModifier ( ) . insert ( " Y/d0 " ) ;
fakeFolder . localModifier ( ) . mkdir ( " Z " ) ;
fakeFolder . localModifier ( ) . insert ( " Z/d0 " ) ;
fakeFolder . localModifier ( ) . insert ( " A/a0 " ) ;
fakeFolder . localModifier ( ) . insert ( " B/b0 " ) ;
fakeFolder . localModifier ( ) . insert ( " r0 " ) ;
fakeFolder . localModifier ( ) . insert ( " r1 " ) ;
fakeFolder . syncOnce ( ) ;
QVERIFY ( itemDidCompleteSuccessfullyWithExpectedRank ( completeSpy , " Y " , 0 ) ) ;
QVERIFY ( itemDidCompleteSuccessfullyWithExpectedRank ( completeSpy , " Z " , 1 ) ) ;
2021-09-08 13:10:01 +03:00
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " Y/d0 " ) ) ;
QVERIFY ( itemSuccessfullyCompletedGetRank ( completeSpy , " Y/d0 " ) > 1 ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " Z/d0 " ) ) ;
QVERIFY ( itemSuccessfullyCompletedGetRank ( completeSpy , " Z/d0 " ) > 1 ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " A/a0 " ) ) ;
QVERIFY ( itemSuccessfullyCompletedGetRank ( completeSpy , " A/a0 " ) > 1 ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " B/b0 " ) ) ;
QVERIFY ( itemSuccessfullyCompletedGetRank ( completeSpy , " B/b0 " ) > 1 ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " r0 " ) ) ;
QVERIFY ( itemSuccessfullyCompletedGetRank ( completeSpy , " r0 " ) > 1 ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " r1 " ) ) ;
QVERIFY ( itemSuccessfullyCompletedGetRank ( completeSpy , " r1 " ) > 1 ) ;
2021-09-09 15:29:06 +03:00
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
2016-08-04 15:59:46 +03:00
void testLocalDelete ( ) {
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
2019-10-07 16:25:36 +03:00
ItemCompletedSpy completeSpy ( fakeFolder ) ;
2016-08-04 15:59:46 +03:00
fakeFolder . remoteModifier ( ) . remove ( " A/a1 " ) ;
fakeFolder . syncOnce ( ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " A/a1 " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
void testRemoteDelete ( ) {
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
2019-10-07 16:25:36 +03:00
ItemCompletedSpy completeSpy ( fakeFolder ) ;
2016-08-04 15:59:46 +03:00
fakeFolder . localModifier ( ) . remove ( " A/a1 " ) ;
fakeFolder . syncOnce ( ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " A/a1 " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
2022-02-17 19:18:35 +03:00
void testLocalDeleteWithReuploadForNewLocalFiles ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
// create folders hierarchy with some nested dirs and files
fakeFolder . localModifier ( ) . mkdir ( " A " ) ;
fakeFolder . localModifier ( ) . insert ( " A/existingfile_A.txt " , 100 ) ;
fakeFolder . localModifier ( ) . mkdir ( " A/B " ) ;
fakeFolder . localModifier ( ) . insert ( " A/B/existingfile_B.data " , 100 ) ;
fakeFolder . localModifier ( ) . mkdir ( " A/B/C " ) ;
fakeFolder . localModifier ( ) . mkdir ( " A/B/C/c1 " ) ;
fakeFolder . localModifier ( ) . mkdir ( " A/B/C/c1/c2 " ) ;
fakeFolder . localModifier ( ) . insert ( " A/B/C/c1/c2/existingfile_C2.md " , 100 ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
// make sure everything is uploaded
QVERIFY ( fakeFolder . currentRemoteState ( ) . find ( " A/B/C/c1/c2 " ) ) ;
QVERIFY ( fakeFolder . currentRemoteState ( ) . find ( " A/existingfile_A.txt " ) ) ;
QVERIFY ( fakeFolder . currentRemoteState ( ) . find ( " A/B/existingfile_B.data " ) ) ;
QVERIFY ( fakeFolder . currentRemoteState ( ) . find ( " A/B/C/c1/c2/existingfile_C2.md " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
// remove a folder "A" on the server
fakeFolder . remoteModifier ( ) . remove ( " A " ) ;
// put new files and folders into a local folder "A"
fakeFolder . localModifier ( ) . insert ( " A/B/C/c1/c2/newfile.txt " , 100 ) ;
fakeFolder . localModifier ( ) . insert ( " A/B/C/c1/c2/Readme.data " , 100 ) ;
fakeFolder . localModifier ( ) . mkdir ( " A/B/C/c1/c2/newfiles " ) ;
fakeFolder . localModifier ( ) . insert ( " A/B/C/c1/c2/newfiles/newfile.txt " , 100 ) ;
fakeFolder . localModifier ( ) . insert ( " A/B/C/c1/c2/newfiles/Readme.data " , 100 ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
// make sure new files and folders are uploaded (restored)
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " A/B/C/c1/c2 " ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " A/B/C/c1/c2/Readme.data " ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " A/B/C/c1/c2/newfiles/newfile.txt " ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " A/B/C/c1/c2/newfiles/Readme.data " ) ) ;
// and the old files are removed
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " A/existingfile_A.txt " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " A/B/existingfile_B.data " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " A/B/C/c1/c2/existingfile_C2.md " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
2016-08-04 15:59:46 +03:00
void testEmlLocalChecksum ( ) {
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . localModifier ( ) . insert ( " a1.eml " , 64 , ' A ' ) ;
fakeFolder . localModifier ( ) . insert ( " a2.eml " , 64 , ' A ' ) ;
fakeFolder . localModifier ( ) . insert ( " a3.eml " , 64 , ' A ' ) ;
2017-09-15 14:30:29 +03:00
fakeFolder . localModifier ( ) . insert ( " b3.txt " , 64 , ' A ' ) ;
2016-08-04 15:59:46 +03:00
// Upload and calculate the checksums
// fakeFolder.syncOnce();
fakeFolder . syncOnce ( ) ;
2017-09-15 14:30:29 +03:00
auto getDbChecksum = [ & ] ( QString path ) {
2017-09-13 20:02:38 +03:00
SyncJournalFileRecord record ;
2022-08-04 17:51:26 +03:00
[[maybe_unused]] const auto result = fakeFolder . syncJournal ( ) . getFileRecord ( path , & record ) ;
2017-09-15 14:30:29 +03:00
return record . _checksumHeader ;
} ;
// printf 'A%.0s' {1..64} | sha1sum -
QByteArray referenceChecksum ( " SHA1:30b86e44e6001403827a62c58b08893e77cf121f " ) ;
QCOMPARE ( getDbChecksum ( " a1.eml " ) , referenceChecksum ) ;
QCOMPARE ( getDbChecksum ( " a2.eml " ) , referenceChecksum ) ;
QCOMPARE ( getDbChecksum ( " a3.eml " ) , referenceChecksum ) ;
QCOMPARE ( getDbChecksum ( " b3.txt " ) , referenceChecksum ) ;
2019-10-07 16:25:36 +03:00
ItemCompletedSpy completeSpy ( fakeFolder ) ;
2016-08-04 15:59:46 +03:00
// Touch the file without changing the content, shouldn't upload
fakeFolder . localModifier ( ) . setContents ( " a1.eml " , ' A ' ) ;
// Change the content/size
fakeFolder . localModifier ( ) . setContents ( " a2.eml " , ' B ' ) ;
fakeFolder . localModifier ( ) . appendByte ( " a3.eml " ) ;
2017-09-15 14:30:29 +03:00
fakeFolder . localModifier ( ) . appendByte ( " b3.txt " ) ;
2016-08-04 15:59:46 +03:00
fakeFolder . syncOnce ( ) ;
2017-09-15 14:30:29 +03:00
QCOMPARE ( getDbChecksum ( " a1.eml " ) , referenceChecksum ) ;
QCOMPARE ( getDbChecksum ( " a2.eml " ) , QByteArray ( " SHA1:84951fc23a4dafd10020ac349da1f5530fa65949 " ) ) ;
QCOMPARE ( getDbChecksum ( " a3.eml " ) , QByteArray ( " SHA1:826b7e7a7af8a529ae1c7443c23bf185c0ad440c " ) ) ;
QCOMPARE ( getDbChecksum ( " b3.eml " ) , getDbChecksum ( " a3.txt " ) ) ;
2016-08-04 15:59:46 +03:00
QVERIFY ( ! itemDidComplete ( completeSpy , " a1.eml " ) ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " a2.eml " ) ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " a3.eml " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
2016-09-22 10:02:47 +03:00
2017-05-15 15:46:09 +03:00
void testSelectiveSyncBug ( ) {
// issue owncloud/enterprise#1965: files from selective-sync ignored
// folders are uploaded anyway is some circumstances.
FakeFolder fakeFolder { FileInfo { QString ( ) , {
FileInfo { QStringLiteral ( " parentFolder " ) , {
2017-09-14 16:50:13 +03:00
FileInfo { QStringLiteral ( " subFolderA " ) , {
2017-05-15 15:46:09 +03:00
{ QStringLiteral ( " fileA.txt " ) , 400 } ,
2017-06-06 17:00:41 +03:00
{ QStringLiteral ( " fileB.txt " ) , 400 , ' o ' } ,
FileInfo { QStringLiteral ( " subsubFolder " ) , {
{ QStringLiteral ( " fileC.txt " ) , 400 } ,
{ QStringLiteral ( " fileD.txt " ) , 400 , ' o ' }
} } ,
FileInfo { QStringLiteral ( " anotherFolder " ) , {
FileInfo { QStringLiteral ( " emptyFolder " ) , { } } ,
FileInfo { QStringLiteral ( " subsubFolder " ) , {
{ QStringLiteral ( " fileE.txt " ) , 400 } ,
{ QStringLiteral ( " fileF.txt " ) , 400 , ' o ' }
} }
} }
2017-09-14 16:50:13 +03:00
} } ,
FileInfo { QStringLiteral ( " subFolderB " ) , { } }
2017-05-15 15:46:09 +03:00
} }
} } } ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
auto expectedServerState = fakeFolder . currentRemoteState ( ) ;
2017-09-14 16:50:13 +03:00
// Remove subFolderA with selectiveSync:
2017-05-15 15:46:09 +03:00
fakeFolder . syncEngine ( ) . journal ( ) - > setSelectiveSyncList ( SyncJournalDb : : SelectiveSyncBlackList ,
2017-09-14 16:50:13 +03:00
{ " parentFolder/subFolderA/ " } ) ;
2019-02-13 16:18:54 +03:00
fakeFolder . syncEngine ( ) . journal ( ) - > schedulePathForRemoteDiscovery ( QByteArrayLiteral ( " parentFolder/subFolderA/ " ) ) ;
2018-02-07 15:05:41 +03:00
auto getEtag = [ & ] ( const QByteArray & file ) {
SyncJournalFileRecord rec ;
2022-08-04 17:51:26 +03:00
[[maybe_unused]] const auto result = fakeFolder . syncJournal ( ) . getFileRecord ( file , & rec ) ;
2018-02-07 15:05:41 +03:00
return rec . _etag ;
} ;
QVERIFY ( getEtag ( " parentFolder " ) = = " _invalid_ " ) ;
QVERIFY ( getEtag ( " parentFolder/subFolderA " ) = = " _invalid_ " ) ;
QVERIFY ( getEtag ( " parentFolder/subFolderA/subsubFolder " ) ! = " _invalid_ " ) ;
2017-05-15 15:46:09 +03:00
2017-06-06 17:00:41 +03:00
// But touch local file before the next sync, such that the local folder
2017-05-15 15:46:09 +03:00
// can't be removed
2017-09-14 16:50:13 +03:00
fakeFolder . localModifier ( ) . setContents ( " parentFolder/subFolderA/fileB.txt " , ' n ' ) ;
fakeFolder . localModifier ( ) . setContents ( " parentFolder/subFolderA/subsubFolder/fileD.txt " , ' n ' ) ;
fakeFolder . localModifier ( ) . setContents ( " parentFolder/subFolderA/anotherFolder/subsubFolder/fileF.txt " , ' n ' ) ;
2017-05-15 15:46:09 +03:00
// Several follow-up syncs don't change the state at all,
// in particular the remote state doesn't change and fileB.txt
// isn't uploaded.
for ( int i = 0 ; i < 3 ; + + i ) {
fakeFolder . syncOnce ( ) ;
{
// Nothing changed on the server
QCOMPARE ( fakeFolder . currentRemoteState ( ) , expectedServerState ) ;
// The local state should still have subFolderA
auto local = fakeFolder . currentLocalState ( ) ;
2017-09-14 16:50:13 +03:00
QVERIFY ( local . find ( " parentFolder/subFolderA " ) ) ;
QVERIFY ( ! local . find ( " parentFolder/subFolderA/fileA.txt " ) ) ;
QVERIFY ( local . find ( " parentFolder/subFolderA/fileB.txt " ) ) ;
QVERIFY ( ! local . find ( " parentFolder/subFolderA/subsubFolder/fileC.txt " ) ) ;
QVERIFY ( local . find ( " parentFolder/subFolderA/subsubFolder/fileD.txt " ) ) ;
QVERIFY ( ! local . find ( " parentFolder/subFolderA/anotherFolder/subsubFolder/fileE.txt " ) ) ;
QVERIFY ( local . find ( " parentFolder/subFolderA/anotherFolder/subsubFolder/fileF.txt " ) ) ;
QVERIFY ( ! local . find ( " parentFolder/subFolderA/anotherFolder/emptyFolder " ) ) ;
QVERIFY ( local . find ( " parentFolder/subFolderB " ) ) ;
2017-05-15 15:46:09 +03:00
}
}
}
2017-01-26 12:03:22 +03:00
void abortAfterFailedMkdir ( ) {
FakeFolder fakeFolder { FileInfo { } } ;
2023-02-17 19:06:35 +03:00
QSignalSpy finishedSpy ( & fakeFolder . syncEngine ( ) , & SyncEngine : : finished ) ;
2017-01-26 12:03:22 +03:00
fakeFolder . serverErrorPaths ( ) . append ( " NewFolder " ) ;
fakeFolder . localModifier ( ) . mkdir ( " NewFolder " ) ;
// This should be aborted and would otherwise fail in FileInfo::create.
fakeFolder . localModifier ( ) . insert ( " NewFolder/NewFile " ) ;
fakeFolder . syncOnce ( ) ;
QCOMPARE ( finishedSpy . size ( ) , 1 ) ;
QCOMPARE ( finishedSpy . first ( ) . first ( ) . toBool ( ) , false ) ;
}
2017-03-06 15:07:08 +03:00
2019-09-23 16:52:57 +03:00
/** Verify that an incompletely propagated directory doesn't have the server's
* etag stored in the database yet . */
void testDirEtagAfterIncompleteSync ( ) {
FakeFolder fakeFolder { FileInfo { } } ;
2023-02-17 19:06:35 +03:00
QSignalSpy finishedSpy ( & fakeFolder . syncEngine ( ) , & SyncEngine : : finished ) ;
2019-09-23 16:52:57 +03:00
fakeFolder . serverErrorPaths ( ) . append ( " NewFolder/foo " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " NewFolder " ) ;
fakeFolder . remoteModifier ( ) . insert ( " NewFolder/foo " ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
SyncJournalFileRecord rec ;
2022-08-04 17:51:26 +03:00
QVERIFY ( fakeFolder . syncJournal ( ) . getFileRecord ( QByteArrayLiteral ( " NewFolder " ) , & rec ) & & rec . isValid ( ) ) ;
2019-09-23 16:52:57 +03:00
QCOMPARE ( rec . _etag , QByteArrayLiteral ( " _invalid_ " ) ) ;
QVERIFY ( ! rec . _fileId . isEmpty ( ) ) ;
}
2017-03-06 15:07:08 +03:00
void testDirDownloadWithError ( ) {
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
2019-10-07 16:25:36 +03:00
ItemCompletedSpy completeSpy ( fakeFolder ) ;
2017-03-06 15:07:08 +03:00
fakeFolder . remoteModifier ( ) . mkdir ( " Y " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " Y/Z " ) ;
fakeFolder . remoteModifier ( ) . insert ( " Y/Z/d0 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " Y/Z/d1 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " Y/Z/d2 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " Y/Z/d3 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " Y/Z/d4 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " Y/Z/d5 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " Y/Z/d6 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " Y/Z/d7 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " Y/Z/d8 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " Y/Z/d9 " ) ;
2018-06-20 16:46:58 +03:00
fakeFolder . serverErrorPaths ( ) . append ( " Y/Z/d2 " , 503 ) ;
fakeFolder . serverErrorPaths ( ) . append ( " Y/Z/d3 " , 503 ) ;
2017-03-06 15:07:08 +03:00
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
QCoreApplication : : processEvents ( ) ; // should not crash
QSet < QString > seen ;
for ( const QList < QVariant > & args : completeSpy ) {
auto item = args [ 0 ] . value < SyncFileItemPtr > ( ) ;
2017-08-24 18:31:46 +03:00
qDebug ( ) < < item - > _file < < item - > isDirectory ( ) < < item - > _status ;
2017-03-06 15:07:08 +03:00
QVERIFY ( ! seen . contains ( item - > _file ) ) ; // signal only sent once per item
seen . insert ( item - > _file ) ;
2017-03-09 12:35:58 +03:00
if ( item - > _file = = " Y/Z/d2 " ) {
2018-06-20 16:46:58 +03:00
QVERIFY ( item - > _status = = SyncFileItem : : NormalError ) ;
} else if ( item - > _file = = " Y/Z/d3 " ) {
2017-03-09 12:35:58 +03:00
QVERIFY ( item - > _status ! = SyncFileItem : : Success ) ;
2018-06-20 16:46:58 +03:00
} else if ( ! item - > isDirectory ( ) ) {
QVERIFY ( item - > _status = = SyncFileItem : : Success ) ;
2017-03-06 15:07:08 +03:00
}
}
}
2017-11-16 14:35:40 +03:00
void testFakeConflict_data ( )
{
QTest : : addColumn < bool > ( " sameMtime " ) ;
QTest : : addColumn < QByteArray > ( " checksums " ) ;
QTest : : addColumn < int > ( " expectedGET " ) ;
QTest : : newRow ( " Same mtime, but no server checksum -> ignored in reconcile " )
< < true < < QByteArray ( )
< < 0 ;
QTest : : newRow ( " Same mtime, weak server checksum differ -> downloaded " )
< < true < < QByteArray ( " Adler32:bad " )
< < 1 ;
QTest : : newRow ( " Same mtime, matching weak checksum -> skipped " )
< < true < < QByteArray ( " Adler32:2a2010d " )
< < 0 ;
QTest : : newRow ( " Same mtime, strong server checksum differ -> downloaded " )
< < true < < QByteArray ( " SHA1:bad " )
< < 1 ;
QTest : : newRow ( " Same mtime, matching strong checksum -> skipped " )
< < true < < QByteArray ( " SHA1:56900fb1d337cf7237ff766276b9c1e8ce507427 " )
< < 0 ;
QTest : : newRow ( " mtime changed, but no server checksum -> download " )
< < false < < QByteArray ( )
< < 1 ;
QTest : : newRow ( " mtime changed, weak checksum match -> download anyway " )
< < false < < QByteArray ( " Adler32:2a2010d " )
< < 1 ;
QTest : : newRow ( " mtime changed, strong checksum match -> skip " )
< < false < < QByteArray ( " SHA1:56900fb1d337cf7237ff766276b9c1e8ce507427 " )
< < 0 ;
}
2017-06-14 13:14:46 +03:00
void testFakeConflict ( )
{
2017-11-16 14:35:40 +03:00
QFETCH ( bool , sameMtime ) ;
QFETCH ( QByteArray , checksums ) ;
QFETCH ( int , expectedGET ) ;
2017-06-14 13:14:46 +03:00
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
int nGET = 0 ;
2017-12-11 19:03:24 +03:00
fakeFolder . setServerOverride ( [ & ] ( QNetworkAccessManager : : Operation op , const QNetworkRequest & , QIODevice * ) {
2017-06-14 13:14:46 +03:00
if ( op = = QNetworkAccessManager : : GetOperation )
+ + nGET ;
return nullptr ;
} ) ;
// For directly editing the remote checksum
2021-01-19 16:28:04 +03:00
auto & remoteInfo = fakeFolder . remoteModifier ( ) ;
2017-06-14 13:14:46 +03:00
// Base mtime with no ms content (filesystem is seconds only)
2017-09-25 12:49:11 +03:00
auto mtime = QDateTime : : currentDateTimeUtc ( ) . addDays ( - 4 ) ;
2017-06-14 13:14:46 +03:00
mtime . setMSecsSinceEpoch ( mtime . toMSecsSinceEpoch ( ) / 1000 * 1000 ) ;
fakeFolder . localModifier ( ) . setContents ( " A/a1 " , ' C ' ) ;
fakeFolder . localModifier ( ) . setModTime ( " A/a1 " , mtime ) ;
fakeFolder . remoteModifier ( ) . setContents ( " A/a1 " , ' C ' ) ;
2017-11-16 14:35:40 +03:00
if ( ! sameMtime )
mtime = mtime . addDays ( 1 ) ;
2017-06-14 13:14:46 +03:00
fakeFolder . remoteModifier ( ) . setModTime ( " A/a1 " , mtime ) ;
2017-11-16 14:35:40 +03:00
remoteInfo . find ( " A/a1 " ) - > checksums = checksums ;
2017-06-14 13:14:46 +03:00
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2017-11-16 14:35:40 +03:00
QCOMPARE ( nGET , expectedGET ) ;
2017-06-14 13:14:46 +03:00
2017-11-16 12:13:21 +03:00
// check that mtime in journal and filesystem agree
QString a1path = fakeFolder . localPath ( ) + " A/a1 " ;
SyncJournalFileRecord a1record ;
2022-08-04 17:51:26 +03:00
QVERIFY ( fakeFolder . syncJournal ( ) . getFileRecord ( QByteArray ( " A/a1 " ) , & a1record ) ) ;
2017-11-16 12:13:21 +03:00
QCOMPARE ( a1record . _modtime , ( qint64 ) FileSystem : : getModTime ( a1path ) ) ;
2017-06-14 13:14:46 +03:00
// Extra sync reads from db, no difference
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2017-11-16 14:35:40 +03:00
QCOMPARE ( nGET , expectedGET ) ;
2017-06-14 13:14:46 +03:00
}
2017-06-28 12:15:22 +03:00
/**
* Checks whether SyncFileItems have the expected properties before start
* of propagation .
*/
void testSyncFileItemProperties ( )
{
2017-09-25 12:49:11 +03:00
auto initialMtime = QDateTime : : currentDateTimeUtc ( ) . addDays ( - 7 ) ;
auto changedMtime = QDateTime : : currentDateTimeUtc ( ) . addDays ( - 4 ) ;
auto changedMtime2 = QDateTime : : currentDateTimeUtc ( ) . addDays ( - 3 ) ;
2017-06-28 12:15:22 +03:00
// Base mtime with no ms content (filesystem is seconds only)
initialMtime . setMSecsSinceEpoch ( initialMtime . toMSecsSinceEpoch ( ) / 1000 * 1000 ) ;
changedMtime . setMSecsSinceEpoch ( changedMtime . toMSecsSinceEpoch ( ) / 1000 * 1000 ) ;
changedMtime2 . setMSecsSinceEpoch ( changedMtime2 . toMSecsSinceEpoch ( ) / 1000 * 1000 ) ;
2017-07-04 10:57:22 +03:00
// Ensure the initial mtimes are as expected
auto initialFileInfo = FileInfo : : A12_B12_C12_S12 ( ) ;
initialFileInfo . setModTime ( " A/a1 " , initialMtime ) ;
initialFileInfo . setModTime ( " B/b1 " , initialMtime ) ;
initialFileInfo . setModTime ( " C/c1 " , initialMtime ) ;
FakeFolder fakeFolder { initialFileInfo } ;
2017-06-28 12:15:22 +03:00
// upload a
fakeFolder . localModifier ( ) . appendByte ( " A/a1 " ) ;
fakeFolder . localModifier ( ) . setModTime ( " A/a1 " , changedMtime ) ;
// download b
fakeFolder . remoteModifier ( ) . appendByte ( " B/b1 " ) ;
fakeFolder . remoteModifier ( ) . setModTime ( " B/b1 " , changedMtime ) ;
// conflict c
fakeFolder . localModifier ( ) . appendByte ( " C/c1 " ) ;
fakeFolder . localModifier ( ) . appendByte ( " C/c1 " ) ;
fakeFolder . localModifier ( ) . setModTime ( " C/c1 " , changedMtime ) ;
fakeFolder . remoteModifier ( ) . appendByte ( " C/c1 " ) ;
fakeFolder . remoteModifier ( ) . setModTime ( " C/c1 " , changedMtime2 ) ;
connect ( & fakeFolder . syncEngine ( ) , & SyncEngine : : aboutToPropagate , [ & ] ( SyncFileItemVector & items ) {
SyncFileItemPtr a1 , b1 , c1 ;
for ( auto & item : items ) {
if ( item - > _file = = " A/a1 " )
a1 = item ;
if ( item - > _file = = " B/b1 " )
b1 = item ;
if ( item - > _file = = " C/c1 " )
c1 = item ;
}
// a1: should have local size and modtime
QVERIFY ( a1 ) ;
QCOMPARE ( a1 - > _instruction , CSYNC_INSTRUCTION_SYNC ) ;
QCOMPARE ( a1 - > _direction , SyncFileItem : : Up ) ;
2019-02-13 12:15:33 +03:00
QCOMPARE ( a1 - > _size , qint64 ( 5 ) ) ;
2017-06-28 12:15:22 +03:00
QCOMPARE ( Utility : : qDateTimeFromTime_t ( a1 - > _modtime ) , changedMtime ) ;
2019-02-13 12:15:33 +03:00
QCOMPARE ( a1 - > _previousSize , qint64 ( 4 ) ) ;
2017-08-24 18:07:22 +03:00
QCOMPARE ( Utility : : qDateTimeFromTime_t ( a1 - > _previousModtime ) , initialMtime ) ;
2017-06-28 12:15:22 +03:00
// b2: should have remote size and modtime
QVERIFY ( b1 ) ;
QCOMPARE ( b1 - > _instruction , CSYNC_INSTRUCTION_SYNC ) ;
QCOMPARE ( b1 - > _direction , SyncFileItem : : Down ) ;
2019-02-13 12:15:33 +03:00
QCOMPARE ( b1 - > _size , qint64 ( 17 ) ) ;
2017-06-28 12:15:22 +03:00
QCOMPARE ( Utility : : qDateTimeFromTime_t ( b1 - > _modtime ) , changedMtime ) ;
2019-02-13 12:15:33 +03:00
QCOMPARE ( b1 - > _previousSize , qint64 ( 16 ) ) ;
2017-08-24 18:07:22 +03:00
QCOMPARE ( Utility : : qDateTimeFromTime_t ( b1 - > _previousModtime ) , initialMtime ) ;
2017-06-28 12:15:22 +03:00
// c1: conflicts are downloads, so remote size and modtime
QVERIFY ( c1 ) ;
QCOMPARE ( c1 - > _instruction , CSYNC_INSTRUCTION_CONFLICT ) ;
QCOMPARE ( c1 - > _direction , SyncFileItem : : None ) ;
2019-02-13 12:15:33 +03:00
QCOMPARE ( c1 - > _size , qint64 ( 25 ) ) ;
2017-06-28 12:15:22 +03:00
QCOMPARE ( Utility : : qDateTimeFromTime_t ( c1 - > _modtime ) , changedMtime2 ) ;
2019-02-13 12:15:33 +03:00
QCOMPARE ( c1 - > _previousSize , qint64 ( 26 ) ) ;
2017-08-24 18:07:22 +03:00
QCOMPARE ( Utility : : qDateTimeFromTime_t ( c1 - > _previousModtime ) , changedMtime ) ;
2017-06-28 12:15:22 +03:00
} ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
}
2017-07-12 10:58:15 +03:00
/**
* Checks whether subsequent large uploads are skipped after a 507 error
*/
void testInsufficientRemoteStorage ( )
{
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
// Disable parallel uploads
SyncOptions syncOptions ;
2018-10-12 15:44:33 +03:00
syncOptions . _parallelNetworkJobs = 0 ;
2017-07-12 10:58:15 +03:00
fakeFolder . syncEngine ( ) . setSyncOptions ( syncOptions ) ;
// Produce an error based on upload size
int remoteQuota = 1000 ;
int n507 = 0 , nPUT = 0 ;
2018-10-05 20:03:08 +03:00
QObject parent ;
2021-09-08 13:10:01 +03:00
fakeFolder . setServerOverride ( [ & ] ( QNetworkAccessManager : : Operation op , const QNetworkRequest & request , QIODevice * outgoingData ) - > QNetworkReply * {
Q_UNUSED ( outgoingData )
2017-07-12 10:58:15 +03:00
if ( op = = QNetworkAccessManager : : PutOperation ) {
nPUT + + ;
if ( request . rawHeader ( " OC-Total-Length " ) . toInt ( ) > remoteQuota ) {
n507 + + ;
2018-10-05 20:03:08 +03:00
return new FakeErrorReply ( op , request , & parent , 507 ) ;
2017-07-12 10:58:15 +03:00
}
}
return nullptr ;
} ) ;
fakeFolder . localModifier ( ) . insert ( " A/big " , 800 ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( nPUT , 1 ) ;
QCOMPARE ( n507 , 0 ) ;
nPUT = 0 ;
fakeFolder . localModifier ( ) . insert ( " A/big1 " , 500 ) ; // ok
fakeFolder . localModifier ( ) . insert ( " A/big2 " , 1200 ) ; // 507 (quota guess now 1199)
fakeFolder . localModifier ( ) . insert ( " A/big3 " , 1200 ) ; // skipped
fakeFolder . localModifier ( ) . insert ( " A/big4 " , 1500 ) ; // skipped
fakeFolder . localModifier ( ) . insert ( " A/big5 " , 1100 ) ; // 507 (quota guess now 1099)
fakeFolder . localModifier ( ) . insert ( " A/big6 " , 900 ) ; // ok (quota guess now 199)
fakeFolder . localModifier ( ) . insert ( " A/big7 " , 200 ) ; // skipped
fakeFolder . localModifier ( ) . insert ( " A/big8 " , 199 ) ; // ok (quota guess now 0)
fakeFolder . localModifier ( ) . insert ( " B/big8 " , 1150 ) ; // 507
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( nPUT , 6 ) ;
QCOMPARE ( n507 , 3 ) ;
}
2017-10-05 12:39:35 +03:00
2017-10-10 14:24:41 +03:00
// Checks whether downloads with bad checksums are accepted
void testChecksumValidation ( )
2017-10-05 12:39:35 +03:00
{
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
2017-10-10 14:24:41 +03:00
QObject parent ;
2017-10-05 12:39:35 +03:00
2017-10-10 14:24:41 +03:00
QByteArray checksumValue ;
2021-12-20 15:59:08 +03:00
QByteArray checksumValueRecalculated ;
2017-10-10 14:24:41 +03:00
QByteArray contentMd5Value ;
2021-12-20 15:59:08 +03:00
bool isChecksumRecalculateSupported = false ;
2017-10-10 14:24:41 +03:00
2017-12-11 19:03:24 +03:00
fakeFolder . setServerOverride ( [ & ] ( QNetworkAccessManager : : Operation op , const QNetworkRequest & request , QIODevice * ) - > QNetworkReply * {
2017-10-10 14:24:41 +03:00
if ( op = = QNetworkAccessManager : : GetOperation ) {
auto reply = new FakeGetReply ( fakeFolder . remoteModifier ( ) , op , request , & parent ) ;
if ( ! checksumValue . isNull ( ) )
2021-12-20 15:59:08 +03:00
reply - > setRawHeader ( OCC : : checkSumHeaderC , checksumValue ) ;
2017-10-10 14:24:41 +03:00
if ( ! contentMd5Value . isNull ( ) )
2021-12-20 15:59:08 +03:00
reply - > setRawHeader ( OCC : : contentMd5HeaderC , contentMd5Value ) ;
2017-10-10 14:24:41 +03:00
return reply ;
2021-12-20 15:59:08 +03:00
} else if ( op = = QNetworkAccessManager : : CustomOperation ) {
2021-12-23 13:07:02 +03:00
if ( request . hasRawHeader ( OCC : : checksumRecalculateOnServerHeaderC ) ) {
2021-12-20 15:59:08 +03:00
if ( ! isChecksumRecalculateSupported ) {
return new FakeErrorReply ( op , request , & parent , 402 ) ;
}
auto reply = new FakeGetReply ( fakeFolder . remoteModifier ( ) , op , request , & parent ) ;
reply - > setRawHeader ( OCC : : checkSumHeaderC , checksumValueRecalculated ) ;
return reply ;
}
2017-10-10 14:24:41 +03:00
}
2017-10-05 12:39:35 +03:00
return nullptr ;
} ) ;
2017-10-10 14:24:41 +03:00
// Basic case
fakeFolder . remoteModifier ( ) . create ( " A/a3 " , 16 , ' A ' ) ;
2017-10-05 12:39:35 +03:00
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2017-10-10 14:24:41 +03:00
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
// Bad OC-Checksum
checksumValue = " SHA1:bad " ;
fakeFolder . remoteModifier ( ) . create ( " A/a4 " , 16 , ' A ' ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
2017-10-05 12:39:35 +03:00
2021-12-20 15:59:08 +03:00
const QByteArray matchedSha1Checksum ( QByteArrayLiteral ( " SHA1:19b1928d58a2030d08023f3d7054516dbc186f20 " ) ) ;
const QByteArray mismatchedSha1Checksum ( matchedSha1Checksum . chopped ( 1 ) ) ;
2017-10-10 14:24:41 +03:00
// Good OC-Checksum
2021-12-20 15:59:08 +03:00
checksumValue = matchedSha1Checksum ; // printf 'A%.0s' {1..16} | sha1sum -
2017-10-05 12:39:35 +03:00
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2017-10-10 14:24:41 +03:00
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
checksumValue = QByteArray ( ) ;
// Bad Content-MD5
contentMd5Value = " bad " ;
fakeFolder . remoteModifier ( ) . create ( " A/a5 " , 16 , ' A ' ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
2017-10-05 12:39:35 +03:00
2017-10-10 14:24:41 +03:00
// Good Content-MD5
contentMd5Value = " d8a73157ce10cd94a91c2079fc9a92c8 " ; // printf 'A%.0s' {1..16} | md5sum -
2017-10-05 12:39:35 +03:00
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2017-10-10 14:24:41 +03:00
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
2017-11-20 10:18:52 +03:00
// Invalid OC-Checksum is ignored
2017-10-10 14:24:41 +03:00
checksumValue = " garbage " ;
// contentMd5Value is still good
fakeFolder . remoteModifier ( ) . create ( " A/a6 " , 16 , ' A ' ) ;
2017-11-20 10:18:52 +03:00
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
contentMd5Value = " bad " ;
fakeFolder . remoteModifier ( ) . create ( " A/a7 " , 16 , ' A ' ) ;
2017-10-10 14:24:41 +03:00
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
2017-11-20 10:18:52 +03:00
contentMd5Value . clear ( ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
// OC-Checksum contains Unsupported checksums
checksumValue = " Unsupported:XXXX SHA1:invalid Invalid:XxX " ;
fakeFolder . remoteModifier ( ) . create ( " A/a8 " , 16 , ' A ' ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ; // Since the supported SHA1 checksum is invalid, no download
checksumValue = " Unsupported:XXXX SHA1:19b1928d58a2030d08023f3d7054516dbc186f20 Invalid:XxX " ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ; // The supported SHA1 checksum is valid now, so the file are downloaded
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
2021-12-20 15:59:08 +03:00
// Begin Test mismatch recalculation---------------------------------------------------------------------------------
const auto prevServerVersion = fakeFolder . account ( ) - > serverVersion ( ) ;
fakeFolder . account ( ) - > setServerVersion ( QString ( " %1.0.0 " ) . arg ( fakeFolder . account ( ) - > checksumRecalculateServerVersionMinSupportedMajor ( ) ) ) ;
// Mismatched OC-Checksum and X-Recalculate-Hash is not supported -> sync must fail
isChecksumRecalculateSupported = false ;
checksumValue = mismatchedSha1Checksum ;
checksumValueRecalculated = matchedSha1Checksum ;
fakeFolder . remoteModifier ( ) . create ( " A/a9 " , 16 , ' A ' ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
// Mismatched OC-Checksum and X-Recalculate-Hash is supported, but, recalculated checksum is again mismatched -> sync must fail
isChecksumRecalculateSupported = true ;
checksumValue = mismatchedSha1Checksum ;
checksumValueRecalculated = mismatchedSha1Checksum ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
// Mismatched OC-Checksum and X-Recalculate-Hash is supported, and, recalculated checksum is a match -> sync must succeed
isChecksumRecalculateSupported = true ;
checksumValue = mismatchedSha1Checksum ;
checksumValueRecalculated = matchedSha1Checksum ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
checksumValue = QByteArray ( ) ;
fakeFolder . account ( ) - > setServerVersion ( prevServerVersion ) ;
// End Test mismatch recalculation-----------------------------------------------------------------------------------
2017-10-10 14:24:41 +03:00
}
2017-10-13 15:56:40 +03:00
// Tests the behavior of invalid filename detection
void testInvalidFilenameRegex ( )
{
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
2017-12-15 16:34:36 +03:00
# ifndef Q_OS_WIN // We can't have local file with these character
2017-10-13 15:56:40 +03:00
// For current servers, no characters are forbidden
fakeFolder . syncEngine ( ) . account ( ) - > setServerVersion ( " 10.0.0 " ) ;
fakeFolder . localModifier ( ) . insert ( " A/ \\ :?* \" <>|.txt " ) ;
2017-10-05 12:39:35 +03:00
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2017-10-13 15:56:40 +03:00
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
// For legacy servers, some characters were forbidden by the client
fakeFolder . syncEngine ( ) . account ( ) - > setServerVersion ( " 8.0.0 " ) ;
fakeFolder . localModifier ( ) . insert ( " B/ \\ :?* \" <>|.txt " ) ;
2017-10-05 12:39:35 +03:00
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2017-10-13 15:56:40 +03:00
QVERIFY ( ! fakeFolder . currentRemoteState ( ) . find ( " B/ \\ :?* \" <>|.txt " ) ) ;
2017-12-15 16:34:36 +03:00
# endif
2017-10-05 12:39:35 +03:00
2017-10-13 15:56:40 +03:00
// We can override that by setting the capability
fakeFolder . syncEngine ( ) . account ( ) - > setCapabilities ( { { " dav " , QVariantMap { { " invalidFilenameRegex " , " " } } } } ) ;
2017-10-05 12:39:35 +03:00
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2017-10-13 15:56:40 +03:00
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
// Check that new servers also accept the capability
fakeFolder . syncEngine ( ) . account ( ) - > setServerVersion ( " 10.0.0 " ) ;
fakeFolder . syncEngine ( ) . account ( ) - > setCapabilities ( { { " dav " , QVariantMap { { " invalidFilenameRegex " , " my[fgh]ile " } } } } ) ;
fakeFolder . localModifier ( ) . insert ( " C/myfile.txt " ) ;
2017-10-05 12:39:35 +03:00
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2017-10-13 15:56:40 +03:00
QVERIFY ( ! fakeFolder . currentRemoteState ( ) . find ( " C/myfile.txt " ) ) ;
2017-10-05 12:39:35 +03:00
}
2017-09-14 17:43:23 +03:00
2017-11-08 13:56:31 +03:00
void testDiscoveryHiddenFile ( )
{
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
// We can't depend on currentLocalState for hidden files since
// it should rightfully skip things like download temporaries
auto localFileExists = [ & ] ( QString name ) {
return QFileInfo ( fakeFolder . localPath ( ) + name ) . exists ( ) ;
} ;
fakeFolder . syncEngine ( ) . setIgnoreHiddenFiles ( true ) ;
fakeFolder . remoteModifier ( ) . insert ( " A/.hidden " ) ;
fakeFolder . localModifier ( ) . insert ( " B/.hidden " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( ! localFileExists ( " A/.hidden " ) ) ;
QVERIFY ( ! fakeFolder . currentRemoteState ( ) . find ( " B/.hidden " ) ) ;
fakeFolder . syncEngine ( ) . setIgnoreHiddenFiles ( false ) ;
fakeFolder . syncJournal ( ) . forceRemoteDiscoveryNextSync ( ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( localFileExists ( " A/.hidden " ) ) ;
QVERIFY ( fakeFolder . currentRemoteState ( ) . find ( " B/.hidden " ) ) ;
}
2018-01-10 13:40:02 +03:00
void testNoLocalEncoding ( )
{
auto utf8Locale = QTextCodec : : codecForLocale ( ) ;
if ( utf8Locale - > mibEnum ( ) ! = 106 ) {
QSKIP ( " Test only works for UTF8 locale " ) ;
}
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
// Utf8 locale can sync both
fakeFolder . remoteModifier ( ) . insert ( " A/tößt " ) ;
fakeFolder . remoteModifier ( ) . insert ( " A/t𠜎t " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " A/tößt " ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " A/t𠜎t " ) ) ;
2018-02-20 12:15:59 +03:00
# if !defined(Q_OS_MAC) && !defined(Q_OS_WIN)
2024-02-01 17:48:40 +03:00
try {
// Try again with a locale that can represent ö but not 𠜎 (4-byte utf8).
QTextCodec : : setCodecForLocale ( QTextCodec : : codecForName ( " ISO-8859-15 " ) ) ;
QVERIFY ( QTextCodec : : codecForLocale ( ) - > mibEnum ( ) = = 111 ) ;
fakeFolder . remoteModifier ( ) . insert ( " B/tößt " ) ;
fakeFolder . remoteModifier ( ) . insert ( " B/t𠜎t " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " B/tößt " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " B/t𠜎t " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " B/t?t " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " B/t??t " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " B/t???t " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " B/t????t " ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . currentRemoteState ( ) . find ( " B/tößt " ) ) ;
QVERIFY ( fakeFolder . currentRemoteState ( ) . find ( " B/t𠜎t " ) ) ;
// Try again with plain ascii
QTextCodec : : setCodecForLocale ( QTextCodec : : codecForName ( " ASCII " ) ) ;
QVERIFY ( QTextCodec : : codecForLocale ( ) - > mibEnum ( ) = = 3 ) ;
fakeFolder . remoteModifier ( ) . insert ( " C/tößt " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " C/tößt " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " C/t??t " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " C/t????t " ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . currentRemoteState ( ) . find ( " C/tößt " ) ) ;
}
catch ( const std : : filesystem : : filesystem_error & e )
{
qCritical ( ) < < e . what ( ) < < e . path1 ( ) . c_str ( ) < < e . path2 ( ) . c_str ( ) < < e . code ( ) . message ( ) . c_str ( ) ;
}
2024-01-11 02:16:43 +03:00
QTextCodec : : setCodecForLocale ( utf8Locale ) ;
2018-02-20 12:15:59 +03:00
# endif
2018-01-10 13:40:02 +03:00
}
2018-10-08 13:04:54 +03:00
// Aborting has had bugs when there are parallel upload jobs
void testUploadV1Multiabort ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
SyncOptions options ;
options . _initialChunkSize = 10 ;
2023-07-28 20:04:17 +03:00
options . setMaxChunkSize ( 10 ) ;
options . setMinChunkSize ( 10 ) ;
2018-10-08 13:04:54 +03:00
fakeFolder . syncEngine ( ) . setSyncOptions ( options ) ;
QObject parent ;
int nPUT = 0 ;
fakeFolder . setServerOverride ( [ & ] ( QNetworkAccessManager : : Operation op , const QNetworkRequest & request , QIODevice * ) - > QNetworkReply * {
if ( op = = QNetworkAccessManager : : PutOperation ) {
+ + nPUT ;
return new FakeHangingReply ( op , request , & parent ) ;
}
return nullptr ;
} ) ;
fakeFolder . localModifier ( ) . insert ( " file " , 100 , ' W ' ) ;
QTimer : : singleShot ( 100 , & fakeFolder . syncEngine ( ) , [ & ] ( ) { fakeFolder . syncEngine ( ) . abort ( ) ; } ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( nPUT , 3 ) ;
}
2019-02-11 15:25:56 +03:00
# ifndef Q_OS_WIN
void testPropagatePermissions ( )
{
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
auto perm = QFileDevice : : Permission ( 0x7704 ) ; // user/owner: rwx, group: r, other: -
QFile : : setPermissions ( fakeFolder . localPath ( ) + " A/a1 " , perm ) ;
QFile : : setPermissions ( fakeFolder . localPath ( ) + " A/a2 " , perm ) ;
fakeFolder . syncOnce ( ) ; // get the metadata-only change out of the way
fakeFolder . remoteModifier ( ) . appendByte ( " A/a1 " ) ;
fakeFolder . remoteModifier ( ) . appendByte ( " A/a2 " ) ;
fakeFolder . localModifier ( ) . appendByte ( " A/a2 " ) ;
fakeFolder . localModifier ( ) . appendByte ( " A/a2 " ) ;
fakeFolder . syncOnce ( ) ; // perms should be preserved
QCOMPARE ( QFileInfo ( fakeFolder . localPath ( ) + " A/a1 " ) . permissions ( ) , perm ) ;
QCOMPARE ( QFileInfo ( fakeFolder . localPath ( ) + " A/a2 " ) . permissions ( ) , perm ) ;
2019-02-13 13:17:51 +03:00
auto conflictName = fakeFolder . syncJournal ( ) . conflictRecord ( fakeFolder . syncJournal ( ) . conflictRecordPaths ( ) . first ( ) ) . path ;
QVERIFY ( conflictName . contains ( " A/a2 " ) ) ;
QCOMPARE ( QFileInfo ( fakeFolder . localPath ( ) + conflictName ) . permissions ( ) , perm ) ;
2019-02-11 15:25:56 +03:00
}
# endif
2019-03-29 12:16:09 +03:00
2019-09-09 16:41:57 +03:00
void testEmptyLocalButHasRemote ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . remoteModifier ( ) . mkdir ( " foo " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " foo " ) ) ;
}
2019-03-29 12:16:09 +03:00
// Check that server mtime is set on directories on initial propagation
void testDirectoryInitialMtime ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . remoteModifier ( ) . mkdir ( " foo " ) ;
fakeFolder . remoteModifier ( ) . insert ( " foo/bar " ) ;
auto datetime = QDateTime : : currentDateTime ( ) ;
datetime . setSecsSinceEpoch ( datetime . toSecsSinceEpoch ( ) ) ; // wipe ms
fakeFolder . remoteModifier ( ) . find ( " foo " ) - > lastModified = datetime ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
QCOMPARE ( QFileInfo ( fakeFolder . localPath ( ) + " foo " ) . lastModified ( ) , datetime ) ;
}
2021-09-08 13:10:01 +03:00
2023-07-13 16:04:07 +03:00
// A local file should not be modified after upload to server if nothing has changed.
void testLocalFileInitialMtime ( )
{
constexpr auto fooFolder = " foo/ " ;
constexpr auto barFile = " foo/bar " ;
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . localModifier ( ) . mkdir ( fooFolder ) ;
fakeFolder . localModifier ( ) . insert ( barFile ) ;
const auto localDiskFileModifier = dynamic_cast < DiskFileModifier & > ( fakeFolder . localModifier ( ) ) ;
const auto localFile = localDiskFileModifier . find ( barFile ) ;
const auto localFileInfo = QFileInfo ( localFile ) ;
const auto expectedMtime = localFileInfo . metadataChangeTime ( ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
const auto currentMtime = localFileInfo . metadataChangeTime ( ) ;
QCOMPARE ( currentMtime , expectedMtime ) ;
}
2021-09-08 13:10:01 +03:00
/**
* Checks whether subsequent large uploads are skipped after a 507 error
*/
void testErrorsWithBulkUpload ( )
{
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
fakeFolder . syncEngine ( ) . account ( ) - > setCapabilities ( { { " dav " , QVariantMap { { " bulkupload " , " 1.0 " } } } } ) ;
// Disable parallel uploads
SyncOptions syncOptions ;
syncOptions . _parallelNetworkJobs = 0 ;
fakeFolder . syncEngine ( ) . setSyncOptions ( syncOptions ) ;
int nPUT = 0 ;
int nPOST = 0 ;
fakeFolder . setServerOverride ( [ & ] ( QNetworkAccessManager : : Operation op , const QNetworkRequest & request , QIODevice * outgoingData ) - > QNetworkReply * {
auto contentType = request . header ( QNetworkRequest : : ContentTypeHeader ) . toString ( ) ;
if ( op = = QNetworkAccessManager : : PostOperation ) {
+ + nPOST ;
if ( contentType . startsWith ( QStringLiteral ( " multipart/related; boundary= " ) ) ) {
auto jsonReplyObject = fakeFolder . forEachReplyPart ( outgoingData , contentType , [ ] ( const QMap < QString , QByteArray > & allHeaders ) - > QJsonObject {
auto reply = QJsonObject { } ;
const auto fileName = allHeaders [ QStringLiteral ( " X-File-Path " ) ] ;
if ( fileName . endsWith ( " A/big2 " ) | |
fileName . endsWith ( " A/big3 " ) | |
fileName . endsWith ( " A/big4 " ) | |
fileName . endsWith ( " A/big5 " ) | |
fileName . endsWith ( " A/big7 " ) | |
fileName . endsWith ( " B/big8 " ) ) {
reply . insert ( QStringLiteral ( " error " ) , true ) ;
reply . insert ( QStringLiteral ( " etag " ) , { } ) ;
return reply ;
} else {
reply . insert ( QStringLiteral ( " error " ) , false ) ;
reply . insert ( QStringLiteral ( " etag " ) , { } ) ;
}
return reply ;
} ) ;
if ( jsonReplyObject . size ( ) ) {
auto jsonReply = QJsonDocument { } ;
jsonReply . setObject ( jsonReplyObject ) ;
return new FakeJsonErrorReply { op , request , this , 200 , jsonReply } ;
}
return nullptr ;
}
} else if ( op = = QNetworkAccessManager : : PutOperation ) {
+ + nPUT ;
const auto fileName = getFilePathFromUrl ( request . url ( ) ) ;
if ( fileName . endsWith ( " A/big2 " ) | |
fileName . endsWith ( " A/big3 " ) | |
fileName . endsWith ( " A/big4 " ) | |
fileName . endsWith ( " A/big5 " ) | |
fileName . endsWith ( " A/big7 " ) | |
fileName . endsWith ( " B/big8 " ) ) {
return new FakeErrorReply ( op , request , this , 412 ) ;
}
return nullptr ;
}
return nullptr ;
} ) ;
fakeFolder . localModifier ( ) . insert ( " A/big " , 1 ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( nPUT , 0 ) ;
QCOMPARE ( nPOST , 1 ) ;
nPUT = 0 ;
nPOST = 0 ;
fakeFolder . localModifier ( ) . insert ( " A/big1 " , 1 ) ; // ok
fakeFolder . localModifier ( ) . insert ( " A/big2 " , 1 ) ; // ko
fakeFolder . localModifier ( ) . insert ( " A/big3 " , 1 ) ; // ko
fakeFolder . localModifier ( ) . insert ( " A/big4 " , 1 ) ; // ko
fakeFolder . localModifier ( ) . insert ( " A/big5 " , 1 ) ; // ko
fakeFolder . localModifier ( ) . insert ( " A/big6 " , 1 ) ; // ok
fakeFolder . localModifier ( ) . insert ( " A/big7 " , 1 ) ; // ko
fakeFolder . localModifier ( ) . insert ( " A/big8 " , 1 ) ; // ok
fakeFolder . localModifier ( ) . insert ( " B/big8 " , 1 ) ; // ko
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( nPUT , 0 ) ;
QCOMPARE ( nPOST , 1 ) ;
nPUT = 0 ;
nPOST = 0 ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( nPUT , 6 ) ;
QCOMPARE ( nPOST , 0 ) ;
}
2022-02-10 18:40:15 +03:00
/**
* Checks whether subsequent large uploads are skipped after a 507 error
*/
void testNetworkErrorsWithBulkUpload ( )
{
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
fakeFolder . syncEngine ( ) . account ( ) - > setCapabilities ( { { " dav " , QVariantMap { { " bulkupload " , " 1.0 " } } } } ) ;
// Disable parallel uploads
SyncOptions syncOptions ;
syncOptions . _parallelNetworkJobs = 0 ;
fakeFolder . syncEngine ( ) . setSyncOptions ( syncOptions ) ;
int nPUT = 0 ;
int nPOST = 0 ;
fakeFolder . setServerOverride ( [ & ] ( QNetworkAccessManager : : Operation op , const QNetworkRequest & request , QIODevice * ) - > QNetworkReply * {
auto contentType = request . header ( QNetworkRequest : : ContentTypeHeader ) . toString ( ) ;
if ( op = = QNetworkAccessManager : : PostOperation ) {
+ + nPOST ;
if ( contentType . startsWith ( QStringLiteral ( " multipart/related; boundary= " ) ) ) {
return new FakeErrorReply ( op , request , this , 400 ) ;
}
return nullptr ;
} else if ( op = = QNetworkAccessManager : : PutOperation ) {
+ + nPUT ;
}
return nullptr ;
} ) ;
fakeFolder . localModifier ( ) . insert ( " A/big1 " , 1 ) ;
fakeFolder . localModifier ( ) . insert ( " A/big2 " , 1 ) ;
fakeFolder . localModifier ( ) . insert ( " A/big3 " , 1 ) ;
fakeFolder . localModifier ( ) . insert ( " A/big4 " , 1 ) ;
fakeFolder . localModifier ( ) . insert ( " A/big5 " , 1 ) ;
fakeFolder . localModifier ( ) . insert ( " A/big6 " , 1 ) ;
fakeFolder . localModifier ( ) . insert ( " A/big7 " , 1 ) ;
fakeFolder . localModifier ( ) . insert ( " A/big8 " , 1 ) ;
fakeFolder . localModifier ( ) . insert ( " B/big8 " , 1 ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( nPUT , 0 ) ;
QCOMPARE ( nPOST , 1 ) ;
nPUT = 0 ;
nPOST = 0 ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( nPUT , 9 ) ;
QCOMPARE ( nPOST , 0 ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
2022-02-18 11:50:00 +03:00
void testRemoteMoveFailedInsufficientStorageLocalMoveRolledBack ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
// create a big shared folder with some files
fakeFolder . remoteModifier ( ) . mkdir ( " big_shared_folder " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " big_shared_folder/shared_files " ) ;
fakeFolder . remoteModifier ( ) . insert ( " big_shared_folder/shared_files/big_shared_file_A.data " , 1000 ) ;
fakeFolder . remoteModifier ( ) . insert ( " big_shared_folder/shared_files/big_shared_file_B.data " , 1000 ) ;
// make sure big shared folder is synced
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " big_shared_folder/shared_files/big_shared_file_A.data " ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " big_shared_folder/shared_files/big_shared_file_B.data " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
// try to move from a big shared folder to your own folder
fakeFolder . localModifier ( ) . mkdir ( " own_folder " ) ;
fakeFolder . localModifier ( ) . rename (
" big_shared_folder/shared_files/big_shared_file_A.data " , " own_folder/big_shared_file_A.data " ) ;
fakeFolder . localModifier ( ) . rename (
" big_shared_folder/shared_files/big_shared_file_B.data " , " own_folder/big_shared_file_B.data " ) ;
// emulate server MOVE 507 error
QObject parent ;
fakeFolder . setServerOverride ( [ & ] ( QNetworkAccessManager : : Operation op , const QNetworkRequest & request ,
QIODevice * outgoingData ) - > QNetworkReply * {
Q_UNUSED ( outgoingData )
if ( op = = QNetworkAccessManager : : CustomOperation
& & request . attribute ( QNetworkRequest : : CustomVerbAttribute ) . toString ( ) = = QStringLiteral ( " MOVE " ) ) {
return new FakeErrorReply ( op , request , & parent , 507 ) ;
}
return nullptr ;
} ) ;
2023-06-02 14:55:08 +03:00
// make sure the first sync fails and files get restored to original folder
2022-02-18 11:50:00 +03:00
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " big_shared_folder/shared_files/big_shared_file_A.data " ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " big_shared_folder/shared_files/big_shared_file_B.data " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " own_folder/big_shared_file_A.data " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " own_folder/big_shared_file_B.data " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
void testRemoteMoveFailedForbiddenLocalMoveRolledBack ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
// create a big shared folder with some files
fakeFolder . remoteModifier ( ) . mkdir ( " big_shared_folder " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " big_shared_folder/shared_files " ) ;
fakeFolder . remoteModifier ( ) . insert ( " big_shared_folder/shared_files/big_shared_file_A.data " , 1000 ) ;
fakeFolder . remoteModifier ( ) . insert ( " big_shared_folder/shared_files/big_shared_file_B.data " , 1000 ) ;
// make sure big shared folder is synced
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " big_shared_folder/shared_files/big_shared_file_A.data " ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " big_shared_folder/shared_files/big_shared_file_B.data " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
// try to move from a big shared folder to your own folder
fakeFolder . localModifier ( ) . mkdir ( " own_folder " ) ;
fakeFolder . localModifier ( ) . rename (
" big_shared_folder/shared_files/big_shared_file_A.data " , " own_folder/big_shared_file_A.data " ) ;
fakeFolder . localModifier ( ) . rename (
" big_shared_folder/shared_files/big_shared_file_B.data " , " own_folder/big_shared_file_B.data " ) ;
// emulate server MOVE 507 error
QObject parent ;
fakeFolder . setServerOverride ( [ & ] ( QNetworkAccessManager : : Operation op , const QNetworkRequest & request ,
QIODevice * outgoingData ) - > QNetworkReply * {
Q_UNUSED ( outgoingData )
auto attributeCustomVerb = request . attribute ( QNetworkRequest : : CustomVerbAttribute ) . toString ( ) ;
if ( op = = QNetworkAccessManager : : CustomOperation
& & request . attribute ( QNetworkRequest : : CustomVerbAttribute ) . toString ( ) = = QStringLiteral ( " MOVE " ) ) {
return new FakeErrorReply ( op , request , & parent , 403 ) ;
}
return nullptr ;
} ) ;
2023-06-02 14:55:08 +03:00
// make sure the first sync fails and files get restored to original folder
2022-02-18 11:50:00 +03:00
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " big_shared_folder/shared_files/big_shared_file_A.data " ) ) ;
QVERIFY ( fakeFolder . currentLocalState ( ) . find ( " big_shared_folder/shared_files/big_shared_file_B.data " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " own_folder/big_shared_file_A.data " ) ) ;
QVERIFY ( ! fakeFolder . currentLocalState ( ) . find ( " own_folder/big_shared_file_B.data " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
2022-03-03 12:53:50 +03:00
void testFolderWithFilesInError ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . setServerOverride ( [ & ] ( QNetworkAccessManager : : Operation op , const QNetworkRequest & request , QIODevice * outgoingData ) - > QNetworkReply * {
Q_UNUSED ( outgoingData )
if ( op = = QNetworkAccessManager : : GetOperation ) {
const auto fileName = getFilePathFromUrl ( request . url ( ) ) ;
if ( fileName = = QStringLiteral ( " aaa/subfolder/foo " ) ) {
return new FakeErrorReply ( op , request , & fakeFolder . syncEngine ( ) , 403 ) ;
}
}
return nullptr ;
} ) ;
fakeFolder . remoteModifier ( ) . mkdir ( QStringLiteral ( " aaa " ) ) ;
fakeFolder . remoteModifier ( ) . mkdir ( QStringLiteral ( " aaa/subfolder " ) ) ;
fakeFolder . remoteModifier ( ) . insert ( QStringLiteral ( " aaa/subfolder/bar " ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
fakeFolder . remoteModifier ( ) . insert ( QStringLiteral ( " aaa/subfolder/foo " ) ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
}
2022-03-03 18:46:14 +03:00
void testInvalidMtimeRecoveryAtStart ( )
{
constexpr auto INVALID_MTIME = 0 ;
constexpr auto CURRENT_MTIME = 1646057277 ;
FakeFolder fakeFolder { FileInfo { } } ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
const QString fooFileRootFolder ( " foo " ) ;
const QString barFileRootFolder ( " bar " ) ;
const QString fooFileSubFolder ( " subfolder/foo " ) ;
const QString barFileSubFolder ( " subfolder/bar " ) ;
const QString fooFileAaaSubFolder ( " aaa/subfolder/foo " ) ;
const QString barFileAaaSubFolder ( " aaa/subfolder/bar " ) ;
fakeFolder . remoteModifier ( ) . insert ( fooFileRootFolder ) ;
fakeFolder . remoteModifier ( ) . insert ( barFileRootFolder ) ;
fakeFolder . remoteModifier ( ) . mkdir ( QStringLiteral ( " subfolder " ) ) ;
fakeFolder . remoteModifier ( ) . insert ( fooFileSubFolder ) ;
fakeFolder . remoteModifier ( ) . insert ( barFileSubFolder ) ;
fakeFolder . remoteModifier ( ) . mkdir ( QStringLiteral ( " aaa " ) ) ;
fakeFolder . remoteModifier ( ) . mkdir ( QStringLiteral ( " aaa/subfolder " ) ) ;
fakeFolder . remoteModifier ( ) . insert ( fooFileAaaSubFolder ) ;
fakeFolder . remoteModifier ( ) . setModTime ( fooFileAaaSubFolder , QDateTime : : fromSecsSinceEpoch ( INVALID_MTIME ) ) ;
fakeFolder . remoteModifier ( ) . insert ( barFileAaaSubFolder ) ;
fakeFolder . remoteModifier ( ) . setModTime ( barFileAaaSubFolder , QDateTime : : fromSecsSinceEpoch ( INVALID_MTIME ) ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
fakeFolder . remoteModifier ( ) . setModTimeKeepEtag ( fooFileAaaSubFolder , QDateTime : : fromSecsSinceEpoch ( CURRENT_MTIME ) ) ;
fakeFolder . remoteModifier ( ) . setModTimeKeepEtag ( barFileAaaSubFolder , QDateTime : : fromSecsSinceEpoch ( CURRENT_MTIME ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
auto expectedState = fakeFolder . currentLocalState ( ) ;
QCOMPARE ( fakeFolder . currentRemoteState ( ) , expectedState ) ;
}
void testInvalidMtimeRecovery ( )
{
constexpr auto INVALID_MTIME = 0 ;
constexpr auto CURRENT_MTIME = 1646057277 ;
FakeFolder fakeFolder { FileInfo { } } ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
const QString fooFileRootFolder ( " foo " ) ;
const QString barFileRootFolder ( " bar " ) ;
const QString fooFileSubFolder ( " subfolder/foo " ) ;
const QString barFileSubFolder ( " subfolder/bar " ) ;
const QString fooFileAaaSubFolder ( " aaa/subfolder/foo " ) ;
const QString barFileAaaSubFolder ( " aaa/subfolder/bar " ) ;
fakeFolder . remoteModifier ( ) . insert ( fooFileRootFolder ) ;
fakeFolder . remoteModifier ( ) . insert ( barFileRootFolder ) ;
fakeFolder . remoteModifier ( ) . mkdir ( QStringLiteral ( " subfolder " ) ) ;
fakeFolder . remoteModifier ( ) . insert ( fooFileSubFolder ) ;
fakeFolder . remoteModifier ( ) . insert ( barFileSubFolder ) ;
fakeFolder . remoteModifier ( ) . mkdir ( QStringLiteral ( " aaa " ) ) ;
fakeFolder . remoteModifier ( ) . mkdir ( QStringLiteral ( " aaa/subfolder " ) ) ;
fakeFolder . remoteModifier ( ) . insert ( fooFileAaaSubFolder ) ;
fakeFolder . remoteModifier ( ) . insert ( barFileAaaSubFolder ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
fakeFolder . remoteModifier ( ) . setModTime ( fooFileAaaSubFolder , QDateTime : : fromSecsSinceEpoch ( INVALID_MTIME ) ) ;
fakeFolder . remoteModifier ( ) . setModTime ( barFileAaaSubFolder , QDateTime : : fromSecsSinceEpoch ( INVALID_MTIME ) ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
QVERIFY ( ! fakeFolder . syncOnce ( ) ) ;
fakeFolder . remoteModifier ( ) . setModTimeKeepEtag ( fooFileAaaSubFolder , QDateTime : : fromSecsSinceEpoch ( CURRENT_MTIME ) ) ;
fakeFolder . remoteModifier ( ) . setModTimeKeepEtag ( barFileAaaSubFolder , QDateTime : : fromSecsSinceEpoch ( CURRENT_MTIME ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
auto expectedState = fakeFolder . currentLocalState ( ) ;
QCOMPARE ( fakeFolder . currentRemoteState ( ) , expectedState ) ;
}
2022-11-18 17:09:08 +03:00
void testServerUpdatingMTimeShouldNotCreateConflicts ( )
{
constexpr auto testFile = " test.txt " ;
constexpr auto CURRENT_MTIME = 1646057277 ;
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . remoteModifier ( ) . insert ( testFile ) ;
fakeFolder . remoteModifier ( ) . setModTimeKeepEtag ( testFile , QDateTime : : fromSecsSinceEpoch ( CURRENT_MTIME - 2 ) ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2022-12-13 16:41:07 +03:00
auto localState = fakeFolder . currentLocalState ( ) ;
QCOMPARE ( localState , fakeFolder . currentRemoteState ( ) ) ;
2022-11-18 17:09:08 +03:00
QCOMPARE ( printDbData ( fakeFolder . dbState ( ) ) , printDbData ( fakeFolder . currentRemoteState ( ) ) ) ;
2022-12-13 16:41:07 +03:00
const auto fileFirstSync = localState . find ( testFile ) ;
2022-11-18 17:09:08 +03:00
QVERIFY ( fileFirstSync ) ;
QCOMPARE ( fileFirstSync - > lastModified . toSecsSinceEpoch ( ) , CURRENT_MTIME - 2 ) ;
fakeFolder . remoteModifier ( ) . setModTimeKeepEtag ( testFile , QDateTime : : fromSecsSinceEpoch ( CURRENT_MTIME - 1 ) ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : FilesystemOnly ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2022-12-13 16:41:07 +03:00
localState = fakeFolder . currentLocalState ( ) ;
2022-11-18 17:09:08 +03:00
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
QCOMPARE ( printDbData ( fakeFolder . dbState ( ) ) , printDbData ( fakeFolder . currentRemoteState ( ) ) ) ;
2022-12-13 16:41:07 +03:00
const auto fileSecondSync = localState . find ( testFile ) ;
2022-11-18 17:09:08 +03:00
QVERIFY ( fileSecondSync ) ;
QCOMPARE ( fileSecondSync - > lastModified . toSecsSinceEpoch ( ) , CURRENT_MTIME - 1 ) ;
fakeFolder . remoteModifier ( ) . setModTime ( testFile , QDateTime : : fromSecsSinceEpoch ( CURRENT_MTIME ) ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : FilesystemOnly ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2022-12-13 16:41:07 +03:00
localState = fakeFolder . currentLocalState ( ) ;
QCOMPARE ( localState , fakeFolder . currentRemoteState ( ) ) ;
2022-11-18 17:09:08 +03:00
QCOMPARE ( printDbData ( fakeFolder . dbState ( ) ) , printDbData ( fakeFolder . currentRemoteState ( ) ) ) ;
2022-12-13 16:41:07 +03:00
const auto fileThirdSync = localState . find ( testFile ) ;
2022-11-18 17:09:08 +03:00
QVERIFY ( fileThirdSync ) ;
QCOMPARE ( fileThirdSync - > lastModified . toSecsSinceEpoch ( ) , CURRENT_MTIME ) ;
}
2022-11-28 18:43:29 +03:00
void testFolderRemovalWithCaseClash ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . remoteModifier ( ) . mkdir ( " A " ) ;
2022-11-28 19:06:57 +03:00
fakeFolder . remoteModifier ( ) . mkdir ( " toDelete " ) ;
2022-11-28 18:43:29 +03:00
fakeFolder . remoteModifier ( ) . insert ( " A/file " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
fakeFolder . remoteModifier ( ) . insert ( " A/FILE " ) ;
2022-11-28 19:06:57 +03:00
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2022-11-28 18:43:29 +03:00
2022-11-28 19:06:57 +03:00
fakeFolder . remoteModifier ( ) . mkdir ( " a " ) ;
fakeFolder . remoteModifier ( ) . remove ( " toDelete " ) ;
2022-11-28 18:43:29 +03:00
2022-11-28 19:06:57 +03:00
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
auto folderA = fakeFolder . currentLocalState ( ) . find ( " toDelete " ) ;
2022-11-28 18:43:29 +03:00
QCOMPARE ( folderA , nullptr ) ;
}
2022-11-30 12:34:49 +03:00
void testServer_caseClash_createConflict ( )
{
constexpr auto testLowerCaseFile = " test " ;
constexpr auto testUpperCaseFile = " TEST " ;
# if defined Q_OS_LINUX
constexpr auto shouldHaveCaseClashConflict = false ;
# else
constexpr auto shouldHaveCaseClashConflict = true ;
# endif
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . remoteModifier ( ) . insert ( " otherFile.txt " ) ;
fakeFolder . remoteModifier ( ) . insert ( testLowerCaseFile ) ;
fakeFolder . remoteModifier ( ) . insert ( testUpperCaseFile ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
auto conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
const auto hasConflict = expectConflict ( fakeFolder . currentLocalState ( ) , testLowerCaseFile ) ;
QCOMPARE ( hasConflict , shouldHaveCaseClashConflict ? true : false ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
}
void testServer_subFolderCaseClash_createConflict ( )
{
constexpr auto testLowerCaseFile = " a/b/test " ;
constexpr auto testUpperCaseFile = " a/b/TEST " ;
# if defined Q_OS_LINUX
constexpr auto shouldHaveCaseClashConflict = false ;
# else
constexpr auto shouldHaveCaseClashConflict = true ;
# endif
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . remoteModifier ( ) . mkdir ( " a " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " a/b " ) ;
fakeFolder . remoteModifier ( ) . insert ( " a/b/otherFile.txt " ) ;
fakeFolder . remoteModifier ( ) . insert ( testLowerCaseFile ) ;
fakeFolder . remoteModifier ( ) . insert ( testUpperCaseFile ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
auto conflicts = findCaseClashConflicts ( * fakeFolder . currentLocalState ( ) . find ( " a/b " ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
const auto hasConflict = expectConflict ( fakeFolder . currentLocalState ( ) , testLowerCaseFile ) ;
QCOMPARE ( hasConflict , shouldHaveCaseClashConflict ? true : false ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
conflicts = findCaseClashConflicts ( * fakeFolder . currentLocalState ( ) . find ( " a/b " ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
}
void testServer_caseClash_createConflictOnMove ( )
{
constexpr auto testLowerCaseFile = " test " ;
constexpr auto testUpperCaseFile = " TEST2 " ;
constexpr auto testUpperCaseFileAfterMove = " TEST " ;
# if defined Q_OS_LINUX
constexpr auto shouldHaveCaseClashConflict = false ;
# else
constexpr auto shouldHaveCaseClashConflict = true ;
# endif
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . remoteModifier ( ) . insert ( " otherFile.txt " ) ;
fakeFolder . remoteModifier ( ) . insert ( testLowerCaseFile ) ;
fakeFolder . remoteModifier ( ) . insert ( testUpperCaseFile ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
auto conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , 0 ) ;
const auto hasConflict = expectConflict ( fakeFolder . currentLocalState ( ) , testLowerCaseFile ) ;
QCOMPARE ( hasConflict , false ) ;
fakeFolder . remoteModifier ( ) . rename ( testUpperCaseFile , testUpperCaseFileAfterMove ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
const auto hasConflictAfterMove = expectConflict ( fakeFolder . currentLocalState ( ) , testUpperCaseFileAfterMove ) ;
QCOMPARE ( hasConflictAfterMove , shouldHaveCaseClashConflict ? true : false ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
}
void testServer_subFolderCaseClash_createConflictOnMove ( )
{
constexpr auto testLowerCaseFile = " a/b/test " ;
constexpr auto testUpperCaseFile = " a/b/TEST2 " ;
constexpr auto testUpperCaseFileAfterMove = " a/b/TEST " ;
# if defined Q_OS_LINUX
constexpr auto shouldHaveCaseClashConflict = false ;
# else
constexpr auto shouldHaveCaseClashConflict = true ;
# endif
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . remoteModifier ( ) . mkdir ( " a " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " a/b " ) ;
fakeFolder . remoteModifier ( ) . insert ( " a/b/otherFile.txt " ) ;
fakeFolder . remoteModifier ( ) . insert ( testLowerCaseFile ) ;
fakeFolder . remoteModifier ( ) . insert ( testUpperCaseFile ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
auto conflicts = findCaseClashConflicts ( * fakeFolder . currentLocalState ( ) . find ( " a/b " ) ) ;
QCOMPARE ( conflicts . size ( ) , 0 ) ;
const auto hasConflict = expectConflict ( fakeFolder . currentLocalState ( ) , testLowerCaseFile ) ;
QCOMPARE ( hasConflict , false ) ;
fakeFolder . remoteModifier ( ) . rename ( testUpperCaseFile , testUpperCaseFileAfterMove ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
conflicts = findCaseClashConflicts ( * fakeFolder . currentLocalState ( ) . find ( " a/b " ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
const auto hasConflictAfterMove = expectConflict ( fakeFolder . currentLocalState ( ) , testUpperCaseFileAfterMove ) ;
QCOMPARE ( hasConflictAfterMove , shouldHaveCaseClashConflict ? true : false ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
conflicts = findCaseClashConflicts ( * fakeFolder . currentLocalState ( ) . find ( " a/b " ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
}
void testServer_caseClash_createConflictAndSolveIt ( )
{
constexpr auto testLowerCaseFile = " test " ;
constexpr auto testUpperCaseFile = " TEST " ;
# if defined Q_OS_LINUX
constexpr auto shouldHaveCaseClashConflict = false ;
# else
constexpr auto shouldHaveCaseClashConflict = true ;
# endif
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . remoteModifier ( ) . insert ( " otherFile.txt " ) ;
fakeFolder . remoteModifier ( ) . insert ( testLowerCaseFile ) ;
fakeFolder . remoteModifier ( ) . insert ( testUpperCaseFile ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
auto conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
const auto hasConflict = expectConflict ( fakeFolder . currentLocalState ( ) , testLowerCaseFile ) ;
QCOMPARE ( hasConflict , shouldHaveCaseClashConflict ? true : false ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
if ( shouldHaveCaseClashConflict ) {
const auto conflictFileName = QString { conflicts . constFirst ( ) } ;
qDebug ( ) < < conflictFileName ;
CaseClashConflictSolver conflictSolver ( fakeFolder . localPath ( ) + testLowerCaseFile ,
fakeFolder . localPath ( ) + conflictFileName ,
QStringLiteral ( " / " ) ,
fakeFolder . localPath ( ) ,
fakeFolder . account ( ) ,
& fakeFolder . syncJournal ( ) ) ;
QSignalSpy conflictSolverDone ( & conflictSolver , & CaseClashConflictSolver : : done ) ;
QSignalSpy conflictSolverFailed ( & conflictSolver , & CaseClashConflictSolver : : failed ) ;
conflictSolver . solveConflict ( " test2 " ) ;
QVERIFY ( conflictSolverDone . wait ( ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , 0 ) ;
}
}
void testServer_subFolderCaseClash_createConflictAndSolveIt ( )
{
constexpr auto testLowerCaseFile = " a/b/test " ;
constexpr auto testUpperCaseFile = " a/b/TEST " ;
# if defined Q_OS_LINUX
constexpr auto shouldHaveCaseClashConflict = false ;
# else
constexpr auto shouldHaveCaseClashConflict = true ;
# endif
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . remoteModifier ( ) . mkdir ( " a " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " a/b " ) ;
fakeFolder . remoteModifier ( ) . insert ( " a/b/otherFile.txt " ) ;
fakeFolder . remoteModifier ( ) . insert ( testLowerCaseFile ) ;
fakeFolder . remoteModifier ( ) . insert ( testUpperCaseFile ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
auto conflicts = findCaseClashConflicts ( * fakeFolder . currentLocalState ( ) . find ( " a/b " ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
const auto hasConflict = expectConflict ( fakeFolder . currentLocalState ( ) , testLowerCaseFile ) ;
QCOMPARE ( hasConflict , shouldHaveCaseClashConflict ? true : false ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
conflicts = findCaseClashConflicts ( * fakeFolder . currentLocalState ( ) . find ( " a/b " ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
if ( shouldHaveCaseClashConflict ) {
CaseClashConflictSolver conflictSolver ( fakeFolder . localPath ( ) + testLowerCaseFile ,
fakeFolder . localPath ( ) + conflicts . constFirst ( ) ,
QStringLiteral ( " / " ) ,
fakeFolder . localPath ( ) ,
fakeFolder . account ( ) ,
& fakeFolder . syncJournal ( ) ) ;
QSignalSpy conflictSolverDone ( & conflictSolver , & CaseClashConflictSolver : : done ) ;
QSignalSpy conflictSolverFailed ( & conflictSolver , & CaseClashConflictSolver : : failed ) ;
conflictSolver . solveConflict ( " a/b/test2 " ) ;
QVERIFY ( conflictSolverDone . wait ( ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
conflicts = findCaseClashConflicts ( * fakeFolder . currentLocalState ( ) . find ( " a/b " ) ) ;
QCOMPARE ( conflicts . size ( ) , 0 ) ;
}
}
2023-06-07 19:47:24 +03:00
void testServer_caseClash_createConflict_thenRemoveOneRemoteFile ( )
{
constexpr auto testLowerCaseFile = " test " ;
constexpr auto testUpperCaseFile = " TEST " ;
# if defined Q_OS_LINUX
constexpr auto shouldHaveCaseClashConflict = false ;
# else
constexpr auto shouldHaveCaseClashConflict = true ;
# endif
FakeFolder fakeFolder { FileInfo { } } ;
fakeFolder . remoteModifier ( ) . insert ( " otherFile.txt " ) ;
fakeFolder . remoteModifier ( ) . insert ( testLowerCaseFile ) ;
fakeFolder . remoteModifier ( ) . insert ( testUpperCaseFile ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
auto conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
const auto hasConflict = expectConflict ( fakeFolder . currentLocalState ( ) , testLowerCaseFile ) ;
QCOMPARE ( hasConflict , shouldHaveCaseClashConflict ? true : false ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
// remove (UPPERCASE) file
fakeFolder . remoteModifier ( ) . remove ( testUpperCaseFile ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
// make sure we got no conflicts now (conflicted copy gets removed)
conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , 0 ) ;
// insert (UPPERCASE) file back
fakeFolder . remoteModifier ( ) . insert ( testUpperCaseFile ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2023-09-16 22:42:20 +03:00
// we must get conflicts
2023-06-07 19:47:24 +03:00
conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
// now remove (lowercase) file
fakeFolder . remoteModifier ( ) . remove ( testLowerCaseFile ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
// make sure we got no conflicts now (conflicted copy gets removed)
conflicts = findCaseClashConflicts ( fakeFolder . currentLocalState ( ) ) ;
QCOMPARE ( conflicts . size ( ) , 0 ) ;
2023-09-17 12:09:02 +03:00
// remove the other file
2023-06-07 19:47:24 +03:00
fakeFolder . remoteModifier ( ) . remove ( testUpperCaseFile ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
}
2023-07-18 12:19:39 +03:00
2024-04-22 00:48:01 +03:00
void testServer_caseClash_createDiverseConflictsInsideOneFolderAndSolveThem ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
const QStringList conflictsFolderPathComponents = { " Documents " , " DiverseConflicts " } ;
QString diverseConflictsFolderPath ;
for ( const auto & conflictsFolderPathComponent : conflictsFolderPathComponents ) {
if ( diverseConflictsFolderPath . isEmpty ( ) ) {
diverseConflictsFolderPath + = conflictsFolderPathComponent ;
} else {
diverseConflictsFolderPath + = " / " + conflictsFolderPathComponent ;
}
fakeFolder . remoteModifier ( ) . mkdir ( diverseConflictsFolderPath ) ;
}
constexpr auto testLowerCaseFile = " testfile " ;
constexpr auto testUpperCaseFile = " TESTFILE " ;
constexpr auto testLowerCaseFolder = " testfolder " ;
constexpr auto testUpperCaseFolder = " TESTFOLDER " ;
constexpr auto testInvalidCharFolder = " Really? " ;
fakeFolder . remoteModifier ( ) . insert ( diverseConflictsFolderPath + " / " + testLowerCaseFile ) ;
fakeFolder . remoteModifier ( ) . insert ( diverseConflictsFolderPath + " / " + testUpperCaseFile ) ;
fakeFolder . remoteModifier ( ) . mkdir ( diverseConflictsFolderPath + " / " + testLowerCaseFolder ) ;
fakeFolder . remoteModifier ( ) . mkdir ( diverseConflictsFolderPath + " / " + testUpperCaseFolder ) ;
fakeFolder . remoteModifier ( ) . mkdir ( diverseConflictsFolderPath + " / " + testInvalidCharFolder ) ;
# if defined Q_OS_LINUX
constexpr auto shouldHaveCaseClashConflict = false ;
# else
constexpr auto shouldHaveCaseClashConflict = true ;
# endif
2024-04-22 22:44:49 +03:00
if ( shouldHaveCaseClashConflict ) {
ItemCompletedSpy completeSpy ( fakeFolder ) ;
2024-04-22 00:48:01 +03:00
2024-04-22 22:44:49 +03:00
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2024-04-22 00:48:01 +03:00
2024-04-22 22:44:49 +03:00
// verify the parent of a folder where caseclash and invalidchar conflicts were found, has corresponding flags set (conflict info must get
// propagated to the very top)
const auto diverseConflictsFolderParent = completeSpy . findItem ( conflictsFolderPathComponents . first ( ) ) ;
QVERIFY ( diverseConflictsFolderParent ) ;
QVERIFY ( diverseConflictsFolderParent - > _isAnyCaseClashChild ) ;
QVERIFY ( diverseConflictsFolderParent - > _isAnyInvalidCharChild ) ;
completeSpy . clear ( ) ;
2024-04-22 00:48:01 +03:00
2024-04-22 22:44:49 +03:00
auto diverseConflictsFolderInfo = fakeFolder . currentLocalState ( ) . findRecursive ( diverseConflictsFolderPath ) ;
QVERIFY ( ! diverseConflictsFolderInfo . name . isEmpty ( ) ) ;
2024-04-22 00:48:01 +03:00
2024-04-22 22:44:49 +03:00
auto conflictsFile = findCaseClashConflicts ( diverseConflictsFolderInfo ) ;
QCOMPARE ( conflictsFile . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
const auto hasFileCaseClashConflict = expectConflict ( diverseConflictsFolderInfo , testLowerCaseFile ) ;
QCOMPARE ( hasFileCaseClashConflict , shouldHaveCaseClashConflict ? true : false ) ;
2024-04-22 00:48:01 +03:00
2024-04-22 22:44:49 +03:00
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
2024-04-22 00:48:01 +03:00
2024-04-22 22:44:49 +03:00
diverseConflictsFolderInfo = fakeFolder . currentLocalState ( ) . findRecursive ( diverseConflictsFolderPath ) ;
QVERIFY ( ! diverseConflictsFolderInfo . name . isEmpty ( ) ) ;
2024-04-22 00:48:01 +03:00
2024-04-22 22:44:49 +03:00
conflictsFile = findCaseClashConflicts ( diverseConflictsFolderInfo ) ;
QCOMPARE ( conflictsFile . size ( ) , shouldHaveCaseClashConflict ? 1 : 0 ) ;
2024-04-22 00:48:01 +03:00
const auto conflictFileName = QString { conflictsFile . constFirst ( ) } ;
qDebug ( ) < < conflictFileName ;
CaseClashConflictSolver conflictSolver ( fakeFolder . localPath ( ) + diverseConflictsFolderPath + " / " + testLowerCaseFile ,
fakeFolder . localPath ( ) + conflictFileName ,
QStringLiteral ( " / " ) ,
fakeFolder . localPath ( ) ,
fakeFolder . account ( ) ,
& fakeFolder . syncJournal ( ) ) ;
QSignalSpy conflictSolverDone ( & conflictSolver , & CaseClashConflictSolver : : done ) ;
conflictSolver . solveConflict ( " testfile2 " ) ;
QVERIFY ( conflictSolverDone . wait ( ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
diverseConflictsFolderInfo = fakeFolder . currentLocalState ( ) . findRecursive ( diverseConflictsFolderPath ) ;
QVERIFY ( ! diverseConflictsFolderInfo . name . isEmpty ( ) ) ;
conflictsFile = findCaseClashConflicts ( diverseConflictsFolderInfo ) ;
QCOMPARE ( conflictsFile . size ( ) , 0 ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
// After solving file conflict, verify that we did not lose conflict detection of caseclash and invalidchar for folders
for ( auto it = completeSpy . begin ( ) ; it ! = completeSpy . end ( ) ; + + it ) {
auto item = ( * it ) . first ( ) . value < OCC : : SyncFileItemPtr > ( ) ;
item = nullptr ;
}
auto invalidFilenameConflictFolderItem = completeSpy . findItem ( diverseConflictsFolderPath + " / " + testInvalidCharFolder ) ;
QVERIFY ( invalidFilenameConflictFolderItem ) ;
QVERIFY ( invalidFilenameConflictFolderItem - > _status = = SyncFileItem : : FileNameInvalid ) ;
auto caseClashConflictFolderItemLower = completeSpy . findItem ( diverseConflictsFolderPath + " / " + testLowerCaseFolder ) ;
auto caseClashConflictFolderItemUpper = completeSpy . findItem ( diverseConflictsFolderPath + " / " + testUpperCaseFolder ) ;
completeSpy . clear ( ) ;
2024-04-22 22:44:49 +03:00
// we always create UPPERCASE folder in current syncengine logic for now and then create a conflict for a lowercase folder, but this may change, so
// keep this check more future proof
2024-04-22 00:48:01 +03:00
const auto upperOrLowerCaseFolderCaseClashFound =
( caseClashConflictFolderItemLower & & caseClashConflictFolderItemLower - > _status = = SyncFileItem : : FileNameClash )
| | ( caseClashConflictFolderItemUpper & & caseClashConflictFolderItemUpper - > _status = = SyncFileItem : : FileNameClash ) ;
QVERIFY ( upperOrLowerCaseFolderCaseClashFound ) ;
// solve case clash folders conflict
CaseClashConflictSolver conflictSolverCaseClashForFolder ( fakeFolder . localPath ( ) + diverseConflictsFolderPath + " / " + testLowerCaseFolder ,
2024-04-22 22:44:49 +03:00
fakeFolder . localPath ( ) + diverseConflictsFolderPath + " / " + testUpperCaseFolder ,
QStringLiteral ( " / " ) ,
fakeFolder . localPath ( ) ,
fakeFolder . account ( ) ,
& fakeFolder . syncJournal ( ) ) ;
2024-04-22 00:48:01 +03:00
QSignalSpy conflictSolverCaseClashForFolderDone ( & conflictSolverCaseClashForFolder , & CaseClashConflictSolver : : done ) ;
conflictSolverCaseClashForFolder . solveConflict ( " testfolder1 " ) ;
QVERIFY ( conflictSolverCaseClashForFolderDone . wait ( ) ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
// verify no case clash conflicts folder items are found
caseClashConflictFolderItemLower = completeSpy . findItem ( diverseConflictsFolderPath + " / " + testLowerCaseFolder ) ;
caseClashConflictFolderItemUpper = completeSpy . findItem ( diverseConflictsFolderPath + " / " + testUpperCaseFolder ) ;
QVERIFY ( ( ! caseClashConflictFolderItemLower | | caseClashConflictFolderItemLower - > _file . isEmpty ( ) )
& & ( ! caseClashConflictFolderItemUpper | | caseClashConflictFolderItemUpper - > _file . isEmpty ( ) ) ) ;
// veriy invalid filename conflict folder item is still present
invalidFilenameConflictFolderItem = completeSpy . findItem ( diverseConflictsFolderPath + " / " + testInvalidCharFolder ) ;
completeSpy . clear ( ) ;
QVERIFY ( invalidFilenameConflictFolderItem ) ;
QVERIFY ( invalidFilenameConflictFolderItem - > _status = = SyncFileItem : : FileNameInvalid ) ;
}
}
2023-07-18 12:19:39 +03:00
void testExistingFolderBecameBig ( )
{
constexpr auto testFolder = " folder " ;
constexpr auto testSmallFile = " folder/small_file.txt " ;
constexpr auto testLargeFile = " folder/large_file.txt " ;
QTemporaryDir dir ;
ConfigFile : : setConfDir ( dir . path ( ) ) ; // we don't want to pollute the user's config file
auto config = ConfigFile ( ) ;
config . setNotifyExistingFoldersOverLimit ( true ) ;
FakeFolder fakeFolder { FileInfo { } } ;
QSignalSpy spy ( & fakeFolder . syncEngine ( ) , & SyncEngine : : existingFolderNowBig ) ;
auto syncOptions = fakeFolder . syncEngine ( ) . syncOptions ( ) ;
syncOptions . _newBigFolderSizeLimit = 128 ; // 128 bytes
fakeFolder . syncEngine ( ) . setSyncOptions ( syncOptions ) ;
fakeFolder . remoteModifier ( ) . mkdir ( testFolder ) ;
fakeFolder . remoteModifier ( ) . insert ( testSmallFile , 64 ) ;
fakeFolder . syncEngine ( ) . setLocalDiscoveryOptions ( OCC : : LocalDiscoveryStyle : : DatabaseAndFilesystem ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( spy . count ( ) , 0 ) ;
fakeFolder . remoteModifier ( ) . insert ( testLargeFile , 256 ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( spy . count ( ) , 1 ) ;
}
2023-08-14 20:43:31 +03:00
void testFileDownloadWithUnicodeCharacterInName ( ) {
FakeFolder fakeFolder { FileInfo : : A12_B12_C12_S12 ( ) } ;
ItemCompletedSpy completeSpy ( fakeFolder ) ;
fakeFolder . remoteModifier ( ) . insert ( " A/abcdęfg.txt " ) ;
fakeFolder . syncOnce ( ) ;
QVERIFY ( itemDidCompleteSuccessfully ( completeSpy , " A/abcdęfg.txt " ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
2023-12-13 13:35:45 +03:00
void testRemoteTypeChangeExistingLocalMustGetRemoved ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
// test file change to directory on remote
fakeFolder . remoteModifier ( ) . mkdir ( " a " ) ;
fakeFolder . remoteModifier ( ) . insert ( " a/TESTFILE " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
fakeFolder . remoteModifier ( ) . remove ( " a/TESTFILE " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " a/TESTFILE " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
// test directory change to file on remote
fakeFolder . remoteModifier ( ) . mkdir ( " a/TESTDIR " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
fakeFolder . remoteModifier ( ) . remove ( " a/TESTDIR " ) ;
fakeFolder . remoteModifier ( ) . insert ( " a/TESTDIR " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
}
2024-06-25 23:12:14 +03:00
void testRemoveAllFilesWithNextcloudCmd ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
auto nextcloudCmdSyncOptions = fakeFolder . syncEngine ( ) . syncOptions ( ) ;
nextcloudCmdSyncOptions . setIsCmd ( true ) ;
fakeFolder . syncEngine ( ) . setSyncOptions ( nextcloudCmdSyncOptions ) ;
ConfigFile ( ) . setPromptDeleteFiles ( true ) ;
QSignalSpy displayDialogSignal ( & fakeFolder . syncEngine ( ) , & SyncEngine : : aboutToRemoveAllFiles ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " folder " ) ;
fakeFolder . remoteModifier ( ) . insert ( " folder/file1 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " folder/file2 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " folder/file3 " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " folder2 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " file1 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " file2 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " file3 " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
fakeFolder . remoteModifier ( ) . remove ( " folder " ) ;
fakeFolder . remoteModifier ( ) . remove ( " folder2 " ) ;
fakeFolder . remoteModifier ( ) . remove ( " file1 " ) ;
fakeFolder . remoteModifier ( ) . remove ( " file2 " ) ;
fakeFolder . remoteModifier ( ) . remove ( " file3 " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
// the signal to display the dialog should not be emitted
QCOMPARE ( displayDialogSignal . count ( ) , 0 ) ;
QCOMPARE ( fakeFolder . remoteModifier ( ) . find ( " folder " ) , nullptr ) ;
QCOMPARE ( fakeFolder . remoteModifier ( ) . find ( " folder2 " ) , nullptr ) ;
QCOMPARE ( fakeFolder . remoteModifier ( ) . find ( " file1 " ) , nullptr ) ;
}
void testRemoveAllFilesWithoutNextcloudCmd ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
auto nextcloudCmdSyncOptions = fakeFolder . syncEngine ( ) . syncOptions ( ) ;
nextcloudCmdSyncOptions . setIsCmd ( false ) ;
fakeFolder . syncEngine ( ) . setSyncOptions ( nextcloudCmdSyncOptions ) ;
ConfigFile ( ) . setPromptDeleteFiles ( true ) ;
QSignalSpy displayDialogSignal ( & fakeFolder . syncEngine ( ) , & SyncEngine : : aboutToRemoveAllFiles ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " folder " ) ;
fakeFolder . remoteModifier ( ) . insert ( " folder/file1 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " folder/file2 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " folder/file3 " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " folder2 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " file1 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " file2 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " file3 " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
QCOMPARE ( fakeFolder . currentLocalState ( ) , fakeFolder . currentRemoteState ( ) ) ;
fakeFolder . remoteModifier ( ) . remove ( " folder " ) ;
fakeFolder . remoteModifier ( ) . remove ( " folder2 " ) ;
fakeFolder . remoteModifier ( ) . remove ( " file1 " ) ;
fakeFolder . remoteModifier ( ) . remove ( " file2 " ) ;
fakeFolder . remoteModifier ( ) . remove ( " file3 " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
// the signal to show the dialog should be emitted
QCOMPARE ( displayDialogSignal . count ( ) , 1 ) ;
QCOMPARE ( fakeFolder . remoteModifier ( ) . find ( " folder " ) , nullptr ) ;
QCOMPARE ( fakeFolder . remoteModifier ( ) . find ( " folder2 " ) , nullptr ) ;
QCOMPARE ( fakeFolder . remoteModifier ( ) . find ( " file1 " ) , nullptr ) ;
}
2024-10-08 15:46:59 +03:00
void testSyncReadOnlyLnkWindowsShortcuts ( )
{
FakeFolder fakeFolder { FileInfo { } } ;
auto nextcloudCmdSyncOptions = fakeFolder . syncEngine ( ) . syncOptions ( ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " abcdefabcdefabcdefabcdefabcdefabcd " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd/123abcdefabcdef1 " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd/123abcdefabcdef1/123123abcdef123 abcdef1 " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd/123abcdefabcdef1/123123abcdef123 abcdef1/12abcabc " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd/123abcdefabcdef1/123123abcdef123 abcdef1/12abcabc/12abcabd " ) ;
fakeFolder . remoteModifier ( ) . insert ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd/123abcdefabcdef1/123123abcdef123 abcdef1/12abcabc/12abcabd/this is a long long long long long long long long long long long long long long long long l.docx - Sh.lnk " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " folder " ) ;
fakeFolder . remoteModifier ( ) . insert ( " folder/file1.lnk " ) ;
fakeFolder . remoteModifier ( ) . insert ( " folder/file2.lnk " ) ;
fakeFolder . remoteModifier ( ) . insert ( " folder/file3.lnk " ) ;
fakeFolder . remoteModifier ( ) . mkdir ( " folder2 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " file1 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " file2 " ) ;
fakeFolder . remoteModifier ( ) . insert ( " file3 " ) ;
fakeFolder . remoteModifier ( ) . find ( " folder " ) - > permissions = RemotePermissions : : fromServerString ( " DNVS " ) ;
fakeFolder . remoteModifier ( ) . find ( " folder/file1.lnk " ) - > permissions = RemotePermissions : : fromServerString ( " S " ) ;
fakeFolder . remoteModifier ( ) . find ( " folder/file2.lnk " ) - > permissions = RemotePermissions : : fromServerString ( " S " ) ;
fakeFolder . remoteModifier ( ) . find ( " folder/file3.lnk " ) - > permissions = RemotePermissions : : fromServerString ( " S " ) ;
fakeFolder . remoteModifier ( ) . find ( " abcdefabcdefabcdefabcdefabcdefabcd " ) - > permissions = RemotePermissions : : fromServerString ( " DNVS " ) ;
fakeFolder . remoteModifier ( ) . find ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a " ) - > permissions = RemotePermissions : : fromServerString ( " DNVS " ) ;
fakeFolder . remoteModifier ( ) . find ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef " ) - > permissions = RemotePermissions : : fromServerString ( " DNVS " ) ;
fakeFolder . remoteModifier ( ) . find ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd " ) - > permissions = RemotePermissions : : fromServerString ( " DNVS " ) ;
fakeFolder . remoteModifier ( ) . find ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd/123abcdefabcdef1 " ) - > permissions = RemotePermissions : : fromServerString ( " DNVS " ) ;
fakeFolder . remoteModifier ( ) . find ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd/123abcdefabcdef1/123123abcdef123 abcdef1 " ) - > permissions = RemotePermissions : : fromServerString ( " DNVS " ) ;
fakeFolder . remoteModifier ( ) . find ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd/123abcdefabcdef1/123123abcdef123 abcdef1/12abcabc " ) - > permissions = RemotePermissions : : fromServerString ( " DNVS " ) ;
fakeFolder . remoteModifier ( ) . find ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd/123abcdefabcdef1/123123abcdef123 abcdef1/12abcabc/12abcabd " ) - > permissions = RemotePermissions : : fromServerString ( " DNVS " ) ;
fakeFolder . remoteModifier ( ) . find ( " abcdefabcdefabcdefabcdefabcdefabcd/abcdef abcdef abcdef a/abcdef abcdef/abcdef acbdef abcd/123abcdefabcdef1/123123abcdef123 abcdef1/12abcabc/12abcabd/this is a long long long long long long long long long long long long long long long long l.docx - Sh.lnk " ) - > permissions = RemotePermissions : : fromServerString ( " S " ) ;
QVERIFY ( fakeFolder . syncOnce ( ) ) ;
}
2016-08-04 15:59:46 +03:00
} ;
QTEST_GUILESS_MAIN ( TestSyncEngine )
# include "testsyncengine.moc"