test_deltafilewrapper.cpp 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879
  1. /***************************************************************************
  2. test_deltafilewrapper.h
  3. -----------------------
  4. begin : Apr 2020
  5. copyright : (C) 2020 by Ivan Ivanov
  6. email : ivan@opengis.ch
  7. ***************************************************************************/
  8. /***************************************************************************
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. ***************************************************************************/
  16. #define QFIELDTEST_MAIN
  17. #include "deltafilewrapper.h"
  18. #include "qfield.h"
  19. #include "utils/fileutils.h"
  20. #include "utils/qfieldcloudutils.h"
  21. #include "qgsvectorlayerjoininfo.h"
  22. #include "qgssettings.h"
  23. #include "qgsapplication.h"
  24. #include "catch2.h"
  25. #include <qgsproject.h>
  26. #include <QFileInfo>
  27. QT_BEGIN_NAMESPACE
  28. std::ostream &operator << ( std::ostream &os, const QJsonDocument &value )
  29. {
  30. os << value.toJson().constData();
  31. return os;
  32. }
  33. QT_END_NAMESPACE
  34. QJsonArray normalizeDeltasSchema( const QJsonArray &deltasJson )
  35. {
  36. QStringList localLayerIds;
  37. QStringList sourceLayerIds;
  38. QJsonArray deltas;
  39. // normalize layerIds
  40. for ( const QJsonValue &v : deltasJson )
  41. {
  42. QJsonObject deltaItem = v.toObject();
  43. const QString localLayerId = deltaItem.value( QStringLiteral( "localLayerId" ) ).toString();
  44. const QString sourceLayerId = deltaItem.value( QStringLiteral( "sourceLayerId" ) ).toString();
  45. if ( !localLayerIds.contains( localLayerId ) )
  46. localLayerIds.append( localLayerId );
  47. if ( !sourceLayerIds.contains( sourceLayerId ) )
  48. sourceLayerIds.append( sourceLayerId );
  49. int localLayerIdx = localLayerIds.indexOf( localLayerId ) + 1;
  50. int sourceLayerIdx = sourceLayerIds.indexOf( sourceLayerId ) + 1;
  51. deltaItem.insert( QStringLiteral( "localLayerId" ), QStringLiteral( "dummyLayerIdL%1" ).arg( localLayerIdx ) );
  52. deltaItem.insert( QStringLiteral( "sourceLayerId" ), QStringLiteral( "dummyLayerIdS%1" ).arg( sourceLayerIdx ) );
  53. deltaItem.insert( QStringLiteral( "uuid" ), QStringLiteral( "11111111-1111-1111-1111-111111111111" ) );
  54. deltas.append( deltaItem );
  55. }
  56. return deltas;
  57. }
  58. QJsonArray normalizeFilesSchema( const QJsonArray &filesJson )
  59. {
  60. QJsonArray files;
  61. // normalize file names
  62. int i = 0;
  63. while ( i < filesJson.size() )
  64. files.append( QStringLiteral( "file%1.jpg" ).arg( i++ ) );
  65. return files;
  66. }
  67. /**
  68. * Normalized the random part of the delta file JSON schema to static values.
  69. * "id" - "11111111-1111-1111-1111-111111111111"
  70. * "project" - "projectId"
  71. *
  72. * @param json - JSON string
  73. * @return QJsonDocument normalized JSON document. NULL document if the input is invalid.
  74. */
  75. QJsonDocument normalizeSchema( const QString &json )
  76. {
  77. QJsonDocument doc = QJsonDocument::fromJson( json.toUtf8() );
  78. if ( doc.isNull() )
  79. return doc;
  80. QJsonObject o = doc.object();
  81. QJsonArray deltas;
  82. if ( o.value( QStringLiteral( "version" ) ).toString() != DeltaFormatVersion )
  83. return QJsonDocument();
  84. if ( o.value( QStringLiteral( "project" ) ).toString().size() == 0 )
  85. return QJsonDocument();
  86. if ( QUuid::fromString( o.value( QStringLiteral( "id" ) ).toString() ).isNull() )
  87. return QJsonDocument();
  88. if ( !o.value( QStringLiteral( "deltas" ) ).isArray() )
  89. return QJsonDocument();
  90. if ( !o.value( QStringLiteral( "files" ) ).isArray() )
  91. return QJsonDocument();
  92. // normalize non-constant values
  93. o.insert( QStringLiteral( "id" ), QStringLiteral( "11111111-1111-1111-1111-111111111111" ) );
  94. o.insert( QStringLiteral( "project" ), QStringLiteral( "projectId" ) );
  95. o.insert( QStringLiteral( "deltas" ), normalizeDeltasSchema( o.value( QStringLiteral( "deltas" ) ).toArray() ) );
  96. o.insert( QStringLiteral( "files" ), normalizeFilesSchema( o.value( QStringLiteral( "files" ) ).toArray() ) );
  97. return QJsonDocument( o );
  98. }
  99. QJsonArray getDeltasArray( const QString &json )
  100. {
  101. return normalizeSchema( json.toUtf8() )
  102. .object()
  103. .value( QStringLiteral( "deltas" ) )
  104. .toArray();
  105. }
  106. TEST_CASE( "Delta File Wrapper" )
  107. {
  108. QgsProject *project = QgsProject::instance();
  109. QTemporaryDir settingsDir;
  110. QTemporaryFile tmpDeltaFile;
  111. QTemporaryDir workDir;
  112. REQUIRE( settingsDir.isValid() );
  113. REQUIRE( tmpDeltaFile.open() );
  114. REQUIRE( QDir( settingsDir.path() ).mkpath( QStringLiteral( "cloud_projects/TEST_PROJECT_ID" ) ) );
  115. QDir projectDir( QStringLiteral( "%1/cloud_projects/TEST_PROJECT_ID" ).arg( settingsDir.path() ) );
  116. QFieldCloudUtils::setLocalCloudDirectory( settingsDir.path() );
  117. QFile projectFile( QStringLiteral( "%1/%2" ).arg( projectDir.path(), QStringLiteral( "project.qgs" ) ) );
  118. QFile attachmentFile( QStringLiteral( "%1/%2" ).arg( projectDir.path(), QStringLiteral( "attachment.jpg" ) ) );
  119. REQUIRE( projectFile.open( QIODevice::WriteOnly ) );
  120. REQUIRE( projectFile.flush() );
  121. project->setFileName( projectFile.fileName() );
  122. const char *fileContents = "кирилица"; // SHA 256 71055d022f50027387eae32426a1857d6e2fa2d416d64753b63470db7f00f239
  123. REQUIRE( attachmentFile.open( QIODevice::ReadWrite ) );
  124. REQUIRE( attachmentFile.write( fileContents ) );
  125. REQUIRE( attachmentFile.flush() );
  126. QString attachmentFileName = attachmentFile.fileName();
  127. QString attachmentFileChecksum = FileUtils::fileChecksum( attachmentFileName ).toHex();
  128. std::unique_ptr<QgsVectorLayer> layer = std::make_unique<QgsVectorLayer>( QStringLiteral( "Point?crs=EPSG:3857&field=fid:integer&field=int:integer&field=dbl:double&field=str:string&field=attachment:string" ), QStringLiteral( "layer_name" ), QStringLiteral( "memory" ) );
  129. layer->setEditorWidgetSetup( layer->fields().indexFromName( QStringLiteral( "attachment" ) ), QgsEditorWidgetSetup( QStringLiteral( "ExternalResource" ), QVariantMap() ) );
  130. std::unique_ptr<QgsVectorLayer> joinedLayer = std::make_unique<QgsVectorLayer>( QStringLiteral( "NoGeometry?field=fid:integer&field=number_field:integer" ), QStringLiteral( "joined_layer" ), QStringLiteral( "memory" ) );
  131. REQUIRE( layer->isValid() );
  132. REQUIRE( joinedLayer->isValid() );
  133. REQUIRE( project->addMapLayer( layer.get(), false, false ) );
  134. REQUIRE( project->addMapLayer( joinedLayer.get(), false, false ) );
  135. QFieldCloudUtils::setProjectSetting( QStringLiteral( "TEST_PROJECT_ID" ), QStringLiteral( "lastLocalExportId" ), QStringLiteral( "22222222-2222-2222-2222-222222222222" ) );
  136. QFieldCloudUtils::setProjectSetting( QStringLiteral( "TEST_PROJECT_ID" ), QStringLiteral( "lastExportId" ), QStringLiteral( "33333333-3333-3333-3333-333333333333" ) );
  137. QgsFeature f( layer->fields(), 1 );
  138. f.setAttribute( QStringLiteral( "fid" ), 1 );
  139. f.setAttribute( QStringLiteral( "dbl" ), 3.14 );
  140. f.setAttribute( QStringLiteral( "int" ), 42 );
  141. f.setAttribute( QStringLiteral( "str" ), QStringLiteral( "stringy" ) );
  142. f.setAttribute( QStringLiteral( "attachment" ), attachmentFileName );
  143. REQUIRE( layer->startEditing() );
  144. REQUIRE( layer->addFeature( f ) );
  145. REQUIRE( layer->commitChanges() );
  146. QgsFeature jf1( joinedLayer->fields() );
  147. jf1.setAttribute( QStringLiteral( "fid" ), 1 );
  148. jf1.setAttribute( QStringLiteral( "number_field" ), 100 );
  149. REQUIRE( joinedLayer->startEditing() );
  150. REQUIRE( joinedLayer->addFeature( jf1 ) );
  151. REQUIRE( joinedLayer->commitChanges() );
  152. #if 0
  153. // TODO enable this code once we have a single delta pointer stored per project and passed to the layer observer.
  154. // Now both the qfieldcloudprojects model (Read only) and the layer observer (Read/Write) create their pointers to the deltafilewrapper
  155. SECTION( "NoMoreThanOneInstance" )
  156. {
  157. QString fileName( wrappedDeltaFilePath );
  158. DeltaFileWrapper dfw1( project, fileName );
  159. REQUIRE( dfw1.errorType() == DeltaFileWrapper::ErrorTypes::NoError );
  160. DeltaFileWrapper dfw2( project, fileName );
  161. REQUIRE( dfw2.errorType() == DeltaFileWrapper::ErrorTypes::LockError );
  162. }
  163. #endif
  164. SECTION( "No error with existing file" )
  165. {
  166. QString correctExistingContents = QStringLiteral( R""""(
  167. {
  168. "deltas":[],
  169. "files":[],
  170. "id":"11111111-1111-1111-1111-111111111111",
  171. "project":"projectId",
  172. "version":"1.0"
  173. }
  174. )"""" );
  175. REQUIRE( tmpDeltaFile.write( correctExistingContents.toUtf8() ) );
  176. tmpDeltaFile.flush();
  177. DeltaFileWrapper correctExistingDfw( project, tmpDeltaFile.fileName() );
  178. REQUIRE( correctExistingDfw.errorType() == DeltaFileWrapper::ErrorTypes::NoError );
  179. QJsonDocument correctExistingDoc = normalizeSchema( correctExistingDfw.toString() );
  180. REQUIRE( !correctExistingDoc.isNull() );
  181. REQUIRE( correctExistingDoc == QJsonDocument::fromJson( correctExistingContents.toUtf8() ) );
  182. }
  183. SECTION( "NoErrorNonExistingFile" )
  184. {
  185. QString fileName( workDir.filePath( QUuid::createUuid().toString() ) );
  186. DeltaFileWrapper dfw( project, fileName );
  187. REQUIRE( dfw.errorType() == DeltaFileWrapper::ErrorTypes::NoError );
  188. REQUIRE( QFileInfo::exists( fileName ) );
  189. DeltaFileWrapper validNonexistingFileCheckDfw( project, fileName );
  190. QFile deltaFile( fileName );
  191. REQUIRE( deltaFile.open( QIODevice::ReadOnly ) );
  192. QJsonDocument fileContents = normalizeSchema( deltaFile.readAll() );
  193. REQUIRE( !fileContents.isNull() );
  194. QJsonDocument expectedDoc = QJsonDocument::fromJson( R""""(
  195. {
  196. "deltas": [],
  197. "files":[],
  198. "id":"11111111-1111-1111-1111-111111111111",
  199. "project": "projectId",
  200. "version": "1.0"
  201. }
  202. )"""" );
  203. REQUIRE( fileContents == expectedDoc );
  204. }
  205. SECTION( "ErrorInvalidName" )
  206. {
  207. DeltaFileWrapper dfw( project, "" );
  208. REQUIRE( dfw.errorType() == DeltaFileWrapper::ErrorTypes::IOError );
  209. }
  210. SECTION( "ErrorInvalidJsonParse" )
  211. {
  212. REQUIRE( tmpDeltaFile.write( R""""( asd )"""" ) );
  213. tmpDeltaFile.flush();
  214. DeltaFileWrapper dfw( project, tmpDeltaFile.fileName() );
  215. REQUIRE( dfw.errorType() == DeltaFileWrapper::ErrorTypes::JsonParseError );
  216. }
  217. SECTION( "ErrorJsonFormatVersionType" )
  218. {
  219. REQUIRE( tmpDeltaFile.write( R""""({"version":5,"files":[],"id":"11111111-1111-1111-1111-111111111111","project":"projectId","deltas":[]})"""" ) );
  220. tmpDeltaFile.flush();
  221. DeltaFileWrapper dfw( project, tmpDeltaFile.fileName() );
  222. REQUIRE( dfw.errorType() == DeltaFileWrapper::ErrorTypes::JsonFormatVersionError );
  223. }
  224. SECTION( "ErrorJsonFormatVersionEmpty" )
  225. {
  226. REQUIRE( tmpDeltaFile.write( R""""({"version":"","files":[],"id":"11111111-1111-1111-1111-111111111111","project":"projectId","deltas":[]})"""" ) );
  227. tmpDeltaFile.flush();
  228. DeltaFileWrapper emptyVersionDfw( project, tmpDeltaFile.fileName() );
  229. REQUIRE( emptyVersionDfw.errorType() == DeltaFileWrapper::ErrorTypes::JsonFormatVersionError );
  230. }
  231. SECTION( "ErrorJsonFormatVersionValue" )
  232. {
  233. REQUIRE( tmpDeltaFile.write( R""""({"version":"2.0","files":[],"id":"11111111-1111-1111-1111-111111111111","project":"projectId","deltas":[]})"""" ) );
  234. tmpDeltaFile.flush();
  235. DeltaFileWrapper wrongVersionNumberDfw( project, tmpDeltaFile.fileName() );
  236. REQUIRE( wrongVersionNumberDfw.errorType() == DeltaFileWrapper::ErrorTypes::JsonIncompatibleVersionError );
  237. }
  238. SECTION( "ErrorJsonFormatIdType" )
  239. {
  240. REQUIRE( tmpDeltaFile.write( R""""({"version":"2.0","files":[],"id": 5,"project":"projectId","deltas":[]})"""" ) );
  241. tmpDeltaFile.flush();
  242. DeltaFileWrapper wrongIdTypeDfw( project, tmpDeltaFile.fileName() );
  243. REQUIRE( wrongIdTypeDfw.errorType() == DeltaFileWrapper::ErrorTypes::JsonFormatIdError );
  244. }
  245. SECTION( "ErrorJsonFormatIdEmpty" )
  246. {
  247. REQUIRE( tmpDeltaFile.write( R""""({"version":"2.0","files":[],"id": "","project":"projectId","deltas":[]})"""" ) );
  248. tmpDeltaFile.flush();
  249. DeltaFileWrapper emptyIdDfw( project, tmpDeltaFile.fileName() );
  250. REQUIRE( emptyIdDfw.errorType() == DeltaFileWrapper::ErrorTypes::JsonFormatIdError );
  251. }
  252. SECTION( "ErrorJsonFormatProjectIdType" )
  253. {
  254. REQUIRE( tmpDeltaFile.write( R""""({"version":"2.0","files":[],"id": "11111111-1111-1111-1111-111111111111","project":5,"deltas":[]})"""" ) );
  255. tmpDeltaFile.flush();
  256. DeltaFileWrapper wrongProjectIdTypeDfw( project, tmpDeltaFile.fileName() );
  257. REQUIRE( wrongProjectIdTypeDfw.errorType() == DeltaFileWrapper::ErrorTypes::JsonFormatProjectIdError );
  258. }
  259. SECTION( "ErrorJsonFormatProjectIdEmpty" )
  260. {
  261. REQUIRE( tmpDeltaFile.write( R""""({"version":"2.0","files":[],"id": "11111111-1111-1111-1111-111111111111","project":"","deltas":[]})"""" ) );
  262. tmpDeltaFile.flush();
  263. DeltaFileWrapper emptyProjectIdDfw( project, tmpDeltaFile.fileName() );
  264. REQUIRE( emptyProjectIdDfw.errorType() == DeltaFileWrapper::ErrorTypes::JsonFormatProjectIdError );
  265. }
  266. SECTION( "ErrorJsonFormatDeltasType" )
  267. {
  268. REQUIRE( tmpDeltaFile.write( R""""({"version":"2.0","files":[],"id": "11111111-1111-1111-1111-111111111111","project":"projectId","deltas":{}})"""" ) );
  269. tmpDeltaFile.flush();
  270. DeltaFileWrapper wrongDeltasTypeDfw( project, tmpDeltaFile.fileName() );
  271. REQUIRE( wrongDeltasTypeDfw.errorType() == DeltaFileWrapper::ErrorTypes::JsonFormatDeltasError );
  272. }
  273. SECTION( "FileName" )
  274. {
  275. QString fileName( QFileInfo( workDir.filePath( QUuid::createUuid().toString() ) ).absoluteFilePath() );
  276. DeltaFileWrapper dfw( project, fileName );
  277. REQUIRE( dfw.fileName() == fileName );
  278. }
  279. SECTION( "Id" )
  280. {
  281. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  282. REQUIRE( !QUuid::fromString( dfw.id() ).isNull() );
  283. }
  284. SECTION( "Reset" )
  285. {
  286. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  287. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), QgsFeature() );
  288. REQUIRE( getDeltasArray( dfw.toString() ).size() == 1 );
  289. dfw.reset();
  290. REQUIRE( getDeltasArray( dfw.toString() ).size() == 0 );
  291. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), QgsFeature() );
  292. REQUIRE( getDeltasArray( dfw.toString() ).size() == 1 );
  293. }
  294. SECTION( "ResetId" )
  295. {
  296. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  297. REQUIRE( getDeltasArray( dfw.toString() ).size() == 0 );
  298. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), QgsFeature() );
  299. const QString dfwId = dfw.id();
  300. dfw.resetId();
  301. REQUIRE( getDeltasArray( dfw.toString() ).size() == 1 );
  302. REQUIRE( dfwId != dfw.id() );
  303. }
  304. SECTION( "ToString" )
  305. {
  306. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  307. QgsFields fields;
  308. fields.append( QgsField( "fid", QVariant::Int, "integer" ) );
  309. QgsFeature f1( fields, 100 );
  310. f1.setAttribute( "fid", 100 );
  311. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f1 );
  312. QgsFeature f2( fields, 101 );
  313. f2.setAttribute( "fid", 101 );
  314. dfw.addDelete( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f2 );
  315. QJsonDocument doc = normalizeSchema( dfw.toString() );
  316. REQUIRE( !doc.isNull() );
  317. QJsonDocument expectedDoc = QJsonDocument::fromJson( R""""(
  318. {
  319. "deltas": [
  320. {
  321. "uuid": "11111111-1111-1111-1111-111111111111",
  322. "clientId": "22222222-2222-2222-2222-222222222222",
  323. "exportId": "33333333-3333-3333-3333-333333333333",
  324. "localLayerId": "dummyLayerIdL1",
  325. "localPk": "100",
  326. "sourceLayerId": "dummyLayerIdS1",
  327. "sourcePk": "100",
  328. "method": "create",
  329. "new": {
  330. "attributes": {
  331. "fid": 100
  332. },
  333. "geometry": null
  334. }
  335. },
  336. {
  337. "uuid": "11111111-1111-1111-1111-111111111111",
  338. "clientId": "22222222-2222-2222-2222-222222222222",
  339. "exportId": "33333333-3333-3333-3333-333333333333",
  340. "localLayerId": "dummyLayerIdL1",
  341. "localPk": "101",
  342. "sourceLayerId": "dummyLayerIdS1",
  343. "sourcePk": "101",
  344. "method": "delete",
  345. "old": {
  346. "attributes": {
  347. "fid": 101
  348. },
  349. "geometry": null
  350. }
  351. }
  352. ],
  353. "files":[],
  354. "id": "11111111-1111-1111-1111-111111111111",
  355. "project": "projectId",
  356. "version": "1.0"
  357. }
  358. )"""" );
  359. REQUIRE( doc == expectedDoc );
  360. }
  361. SECTION( "ToJson" )
  362. {
  363. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  364. QgsFields fields;
  365. fields.append( QgsField( "fid", QVariant::Int, "integer" ) );
  366. QgsFeature f1( fields, 100 );
  367. f1.setAttribute( "fid", 100 );
  368. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f1 );
  369. QgsFeature f2( fields, 101 );
  370. f2.setAttribute( "fid", 101 );
  371. dfw.addDelete( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f2 );
  372. QJsonDocument doc = normalizeSchema( QString( dfw.toJson() ) );
  373. REQUIRE( !doc.isNull() );
  374. QJsonDocument expectedDoc = QJsonDocument::fromJson( R""""(
  375. {
  376. "deltas": [
  377. {
  378. "uuid": "11111111-1111-1111-1111-111111111111",
  379. "clientId": "22222222-2222-2222-2222-222222222222",
  380. "exportId": "33333333-3333-3333-3333-333333333333",
  381. "localLayerId": "dummyLayerIdL1",
  382. "localPk": "100",
  383. "sourceLayerId": "dummyLayerIdS1",
  384. "sourcePk": "100",
  385. "method": "create",
  386. "new": {
  387. "attributes": {
  388. "fid": 100
  389. },
  390. "geometry": null
  391. }
  392. },
  393. {
  394. "uuid": "11111111-1111-1111-1111-111111111111",
  395. "clientId": "22222222-2222-2222-2222-222222222222",
  396. "exportId": "33333333-3333-3333-3333-333333333333",
  397. "localLayerId": "dummyLayerIdL1",
  398. "localPk": "101",
  399. "sourceLayerId": "dummyLayerIdS1",
  400. "sourcePk": "101",
  401. "method": "delete",
  402. "old": {
  403. "attributes": {
  404. "fid": 101
  405. },
  406. "geometry": null
  407. }
  408. }
  409. ],
  410. "files":[],
  411. "id": "11111111-1111-1111-1111-111111111111",
  412. "project": "projectId",
  413. "version": "1.0"
  414. }
  415. )"""" );
  416. REQUIRE( doc == expectedDoc );
  417. }
  418. SECTION( "ProjectId" )
  419. {
  420. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  421. REQUIRE( dfw.projectId() == QStringLiteral( "TEST_PROJECT_ID" ) );
  422. }
  423. SECTION( "IsDirty" )
  424. {
  425. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  426. REQUIRE( dfw.isDirty() == false );
  427. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), QgsFeature() );
  428. REQUIRE( dfw.isDirty() == true );
  429. REQUIRE( dfw.toFile() );
  430. REQUIRE( dfw.isDirty() == false );
  431. dfw.reset();
  432. REQUIRE( dfw.isDirty() == true );
  433. }
  434. SECTION( "Count" )
  435. {
  436. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  437. REQUIRE( dfw.count() == 0 );
  438. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), QgsFeature() );
  439. REQUIRE( dfw.count() == 1 );
  440. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), QgsFeature() );
  441. REQUIRE( dfw.count() == 2 );
  442. }
  443. SECTION( "Deltas" )
  444. {
  445. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  446. REQUIRE( QJsonDocument( dfw.deltas() ) == QJsonDocument::fromJson( "[]" ) );
  447. QgsFields fields;
  448. fields.append( QgsField( "fid", QVariant::Int, "integer" ) );
  449. QgsFeature f1( fields, 100 );
  450. f1.setAttribute( "fid", 100 );
  451. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f1 );
  452. QJsonDocument expectedDoc = QJsonDocument::fromJson( R""""(
  453. [
  454. {
  455. "uuid": "11111111-1111-1111-1111-111111111111",
  456. "clientId": "22222222-2222-2222-2222-222222222222",
  457. "exportId": "33333333-3333-3333-3333-333333333333",
  458. "localLayerId": "dummyLayerIdL1",
  459. "localPk": "100",
  460. "sourceLayerId": "dummyLayerIdS1",
  461. "sourcePk": "100",
  462. "method": "create",
  463. "new": {
  464. "attributes": {
  465. "fid": 100
  466. },
  467. "geometry": null
  468. }
  469. }
  470. ]
  471. )"""" );
  472. REQUIRE( QJsonDocument( normalizeDeltasSchema( dfw.deltas() ) ) == expectedDoc );
  473. QgsFeature f2( fields, 101 );
  474. f2.setAttribute( "fid", 101 );
  475. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f2 );
  476. expectedDoc = QJsonDocument::fromJson( R""""(
  477. [
  478. {
  479. "uuid": "11111111-1111-1111-1111-111111111111",
  480. "clientId": "22222222-2222-2222-2222-222222222222",
  481. "exportId": "33333333-3333-3333-3333-333333333333",
  482. "localLayerId": "dummyLayerIdL1",
  483. "localPk": "100",
  484. "sourceLayerId": "dummyLayerIdS1",
  485. "sourcePk": "100",
  486. "method": "create",
  487. "new": {
  488. "attributes": {
  489. "fid": 100
  490. },
  491. "geometry": null
  492. }
  493. },
  494. {
  495. "uuid": "11111111-1111-1111-1111-111111111111",
  496. "clientId": "22222222-2222-2222-2222-222222222222",
  497. "exportId": "33333333-3333-3333-3333-333333333333",
  498. "localLayerId": "dummyLayerIdL1",
  499. "localPk": "101",
  500. "sourceLayerId": "dummyLayerIdS1",
  501. "sourcePk": "101",
  502. "method": "create",
  503. "new": {
  504. "attributes": {
  505. "fid": 101
  506. },
  507. "geometry": null
  508. }
  509. }
  510. ]
  511. )"""" );
  512. REQUIRE( QJsonDocument( normalizeDeltasSchema( dfw.deltas() ) ) == expectedDoc );
  513. }
  514. SECTION( "ToFile" )
  515. {
  516. QString fileName = workDir.filePath( QUuid::createUuid().toString() );
  517. DeltaFileWrapper dfw1( project, fileName );
  518. dfw1.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), QgsFeature() );
  519. REQUIRE( !dfw1.hasError() );
  520. REQUIRE( getDeltasArray( dfw1.toString() ).size() == 1 );
  521. REQUIRE( dfw1.toFile() );
  522. REQUIRE( getDeltasArray( dfw1.toString() ).size() == 1 );
  523. QFile deltaFile( fileName );
  524. REQUIRE( deltaFile.open( QIODevice::ReadOnly ) );
  525. REQUIRE( getDeltasArray( deltaFile.readAll() ).size() == 1 );
  526. }
  527. SECTION( "Append" )
  528. {
  529. DeltaFileWrapper dfw1( project, workDir.filePath( QUuid::createUuid().toString() ) );
  530. DeltaFileWrapper dfw2( project, workDir.filePath( QUuid::createUuid().toString() ) );
  531. dfw1.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), QgsFeature( QgsFields(), 100 ) );
  532. dfw2.append( &dfw1 );
  533. REQUIRE( dfw2.count() == 1 );
  534. }
  535. SECTION( "AttachmentFieldNames" )
  536. {
  537. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  538. QStringList attachmentFields = dfw.attachmentFieldNames( project, layer->id() );
  539. REQUIRE( attachmentFields == QStringList( { QStringLiteral( "attachment" ) } ) );
  540. }
  541. SECTION( "AttachmentFileNames" )
  542. {
  543. QTemporaryFile deltaFile;
  544. REQUIRE( deltaFile.open() );
  545. REQUIRE( deltaFile.write( QStringLiteral( R""""(
  546. {
  547. "deltas": [
  548. {
  549. "uuid": "11111111-1111-1111-1111-111111111111",
  550. "clientId": "22222222-2222-2222-2222-222222222222",
  551. "exportId": "33333333-3333-3333-3333-333333333333",
  552. "localLayerId": "%1",
  553. "localPk": "100",
  554. "sourceLayerId": "%1",
  555. "sourcePk": "100",
  556. "method": "create",
  557. "new": {
  558. "attributes": {
  559. "attachment": "FILE1.jpg",
  560. "dbl": 3.14,
  561. "int": 42,
  562. "str": "stringy"
  563. },
  564. "files_sha256": {
  565. "FILE1.jpg": null
  566. },
  567. "geometry": null
  568. }
  569. },
  570. {
  571. "uuid": "11111111-1111-1111-1111-111111111111",
  572. "clientId": "22222222-2222-2222-2222-222222222222",
  573. "exportId": "33333333-3333-3333-3333-333333333333",
  574. "localLayerId": "%1",
  575. "localPk": "102",
  576. "sourceLayerId": "%1",
  577. "sourcePk": "102",
  578. "method": "create",
  579. "new": {
  580. "attributes": {
  581. "attachment": "FILE2.jpg",
  582. "dbl": null,
  583. "int": null,
  584. "str": null
  585. },
  586. "files_sha256": {
  587. "FILE2.jpg": null
  588. },
  589. "geometry": null
  590. }
  591. },
  592. {
  593. "uuid": "11111111-1111-1111-1111-111111111111",
  594. "clientId": "22222222-2222-2222-2222-222222222222",
  595. "exportId": "33333333-3333-3333-3333-333333333333",
  596. "localLayerId": "%1",
  597. "localPk": "102",
  598. "sourceLayerId": "%1",
  599. "sourcePk": "102",
  600. "method": "patch",
  601. "new": {
  602. "attributes": {
  603. "attachment": "FILE3.jpg"
  604. },
  605. "files_sha256": {
  606. "FILE3.jpg": null
  607. },
  608. "geometry": null
  609. },
  610. "old": {
  611. "attributes": {
  612. "attachment": "FILE2.jpg"
  613. },
  614. "files_sha256": {
  615. "FILE2.jpg": null
  616. },
  617. "geometry": null
  618. }
  619. }
  620. ],
  621. "files": [],
  622. "id": "11111111-1111-1111-1111-111111111111",
  623. "project": "projectId",
  624. "version": "1.0"
  625. }
  626. )"""" )
  627. .arg( layer->id() )
  628. .toUtf8() ) );
  629. REQUIRE( deltaFile.flush() );
  630. DeltaFileWrapper dfw( project, deltaFile.fileName() );
  631. REQUIRE( !dfw.hasError() );
  632. QMap<QString, QString> attachmentFileNames = dfw.attachmentFileNames();
  633. QMap<QString, QString> expectedAttachmentFileNames(
  634. {
  635. { "FILE1.jpg", "" },
  636. { "FILE3.jpg", "" } } );
  637. REQUIRE( attachmentFileNames == expectedAttachmentFileNames );
  638. }
  639. SECTION( "AddCreate" )
  640. {
  641. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  642. QgsFeature f( layer->fields(), 100 );
  643. f.setAttribute( QStringLiteral( "fid" ), 100 );
  644. f.setAttribute( QStringLiteral( "dbl" ), 3.14 );
  645. f.setAttribute( QStringLiteral( "int" ), 42 );
  646. f.setAttribute( QStringLiteral( "str" ), QStringLiteral( "stringy" ) );
  647. f.setAttribute( QStringLiteral( "attachment" ), attachmentFileName );
  648. // Check if creates delta of a feature with a geometry and existing attachment
  649. f.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  650. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f );
  651. QJsonDocument expectedDoc = QJsonDocument::fromJson( QStringLiteral( R""""(
  652. [
  653. {
  654. "uuid": "11111111-1111-1111-1111-111111111111",
  655. "clientId": "22222222-2222-2222-2222-222222222222",
  656. "exportId": "33333333-3333-3333-3333-333333333333",
  657. "localLayerId": "dummyLayerIdL1",
  658. "localPk": "100",
  659. "sourceLayerId": "dummyLayerIdS1",
  660. "sourcePk": "100",
  661. "method": "create",
  662. "new": {
  663. "attributes": {
  664. "attachment": "%1",
  665. "dbl": 3.14,
  666. "fid": 100,
  667. "int": 42,
  668. "str": "stringy"
  669. },
  670. "files_sha256": {
  671. "%1": "%2"
  672. },
  673. "geometry": "Point (25.96569999999999823 43.83559999999999945)"
  674. }
  675. }
  676. ]
  677. )"""" )
  678. .arg( attachmentFileName, attachmentFileChecksum )
  679. .toUtf8() );
  680. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == expectedDoc );
  681. // Check if creates delta of a feature with a NULL geometry and non existant attachment.
  682. // NOTE this is the same as calling f clearGeometry()
  683. dfw.reset();
  684. f.setGeometry( QgsGeometry() );
  685. f.setAttribute( QStringLiteral( "attachment" ), workDir.filePath( QUuid::createUuid().toString() ) );
  686. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f );
  687. expectedDoc = QJsonDocument::fromJson( QStringLiteral( R""""(
  688. [
  689. {
  690. "uuid": "11111111-1111-1111-1111-111111111111",
  691. "clientId": "22222222-2222-2222-2222-222222222222",
  692. "exportId": "33333333-3333-3333-3333-333333333333",
  693. "localLayerId": "dummyLayerIdL1",
  694. "localPk": "100",
  695. "sourceLayerId": "dummyLayerIdS1",
  696. "sourcePk": "100",
  697. "method": "create",
  698. "new": {
  699. "attributes": {
  700. "attachment": "%1",
  701. "dbl": 3.14,
  702. "fid": 100,
  703. "int": 42,
  704. "str": "stringy"
  705. },
  706. "files_sha256": {
  707. "%1": null
  708. },
  709. "geometry": null
  710. }
  711. }
  712. ]
  713. )"""" )
  714. .arg( f.attribute( QStringLiteral( "attachment" ) ).toString() )
  715. .toUtf8() );
  716. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == expectedDoc );
  717. // Check if creates delta of a feature without attributes
  718. dfw.reset();
  719. QgsFields fields;
  720. fields.append( QgsField( QStringLiteral( "fid" ), QVariant::Int ) );
  721. QgsFeature f1( fields, 101 );
  722. f1.setAttribute( QStringLiteral( "fid" ), 101 );
  723. f1.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  724. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f1 );
  725. expectedDoc = QJsonDocument::fromJson( R""""(
  726. [
  727. {
  728. "uuid": "11111111-1111-1111-1111-111111111111",
  729. "clientId": "22222222-2222-2222-2222-222222222222",
  730. "exportId": "33333333-3333-3333-3333-333333333333",
  731. "localLayerId": "dummyLayerIdL1",
  732. "localPk": "101",
  733. "sourceLayerId": "dummyLayerIdS1",
  734. "sourcePk": "101",
  735. "method": "create",
  736. "new": {
  737. "attributes":{
  738. "fid": 101
  739. },
  740. "geometry": "Point (25.96569999999999823 43.83559999999999945)"
  741. }
  742. }
  743. ]
  744. )"""" );
  745. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == expectedDoc );
  746. }
  747. SECTION( "AddPatch" )
  748. {
  749. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  750. QgsFeature oldFeature( layer->fields(), 100 );
  751. oldFeature.setAttribute( QStringLiteral( "dbl" ), 3.14 );
  752. oldFeature.setAttribute( QStringLiteral( "int" ), 42 );
  753. oldFeature.setAttribute( QStringLiteral( "fid" ), 100 );
  754. oldFeature.setAttribute( QStringLiteral( "str" ), QStringLiteral( "stringy" ) );
  755. oldFeature.setAttribute( QStringLiteral( "attachment" ), QString() );
  756. QgsFeature newFeature( layer->fields(), 100 );
  757. newFeature.setAttribute( QStringLiteral( "dbl" ), 9.81 );
  758. newFeature.setAttribute( QStringLiteral( "int" ), 680 );
  759. newFeature.setAttribute( QStringLiteral( "fid" ), 100 );
  760. newFeature.setAttribute( QStringLiteral( "str" ), QStringLiteral( "pingy" ) );
  761. newFeature.setAttribute( QStringLiteral( "attachment" ), attachmentFileName );
  762. // Patch both the attributes with existing attachment and the geometry
  763. oldFeature.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  764. newFeature.setGeometry( QgsGeometry( new QgsPoint( 23.398819, 41.7672147 ) ) );
  765. dfw.addPatch( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), oldFeature, newFeature );
  766. QJsonDocument expectedDoc = QJsonDocument::fromJson( QStringLiteral( R""""(
  767. [
  768. {
  769. "uuid": "11111111-1111-1111-1111-111111111111",
  770. "clientId": "22222222-2222-2222-2222-222222222222",
  771. "exportId": "33333333-3333-3333-3333-333333333333",
  772. "localLayerId": "dummyLayerIdL1",
  773. "localPk": "100",
  774. "sourceLayerId": "dummyLayerIdS1",
  775. "sourcePk": "100",
  776. "method": "patch",
  777. "new": {
  778. "attributes": {
  779. "attachment": "%1",
  780. "dbl": 9.81,
  781. "int": 680,
  782. "str": "pingy"
  783. },
  784. "files_sha256": {
  785. "%1": "%2"
  786. },
  787. "geometry": "Point (23.39881899999999959 41.7672146999999967)"
  788. },
  789. "old": {
  790. "attributes": {
  791. "attachment": null,
  792. "dbl": 3.14,
  793. "int": 42,
  794. "str": "stringy"
  795. },
  796. "geometry": "Point (25.96569999999999823 43.83559999999999945)"
  797. }
  798. }
  799. ]
  800. )"""" )
  801. .arg( attachmentFileName, attachmentFileChecksum )
  802. .toUtf8() ) ;
  803. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == expectedDoc );
  804. // Patch attributes only with non existing attachnment
  805. dfw.reset();
  806. newFeature.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  807. newFeature.setAttribute( QStringLiteral( "attachment" ), workDir.filePath( QUuid::createUuid().toString() ) );
  808. oldFeature.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  809. dfw.addPatch( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), oldFeature, newFeature );
  810. expectedDoc = QJsonDocument::fromJson( QStringLiteral( R""""(
  811. [
  812. {
  813. "uuid": "11111111-1111-1111-1111-111111111111",
  814. "clientId": "22222222-2222-2222-2222-222222222222",
  815. "exportId": "33333333-3333-3333-3333-333333333333",
  816. "localLayerId": "dummyLayerIdL1",
  817. "localPk": "100",
  818. "sourceLayerId": "dummyLayerIdS1",
  819. "sourcePk": "100",
  820. "method": "patch",
  821. "new": {
  822. "attributes": {
  823. "attachment": "%1",
  824. "dbl": 9.81,
  825. "int": 680,
  826. "str": "pingy"
  827. },
  828. "files_sha256": {
  829. "%1": null
  830. }
  831. },
  832. "old": {
  833. "attributes": {
  834. "attachment": null,
  835. "dbl": 3.14,
  836. "int": 42,
  837. "str": "stringy"
  838. }
  839. }
  840. }
  841. ]
  842. )"""" )
  843. .arg( newFeature.attribute( "attachment" ).toString() )
  844. .toUtf8() ) ;
  845. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == expectedDoc );
  846. // Patch feature without geometry on attributes only with non existant attachment
  847. dfw.reset();
  848. newFeature.setGeometry( QgsGeometry() );
  849. oldFeature.setGeometry( QgsGeometry() );
  850. dfw.addPatch( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), oldFeature, newFeature );
  851. // Patch geometry only
  852. dfw.reset();
  853. newFeature.setAttribute( QStringLiteral( "dbl" ), 3.14 );
  854. newFeature.setAttribute( QStringLiteral( "int" ), 42 );
  855. newFeature.setAttribute( QStringLiteral( "fid" ), 100 );
  856. newFeature.setAttribute( QStringLiteral( "str" ), QStringLiteral( "stringy" ) );
  857. newFeature.setAttribute( QStringLiteral( "attachment" ), QVariant() );
  858. newFeature.setGeometry( QgsGeometry( new QgsPoint( 23.398819, 41.7672147 ) ) );
  859. oldFeature.setAttribute( QStringLiteral( "attachment" ), QVariant() );
  860. oldFeature.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  861. dfw.addPatch( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), oldFeature, newFeature );
  862. expectedDoc = QJsonDocument::fromJson( R""""(
  863. [
  864. {
  865. "uuid": "11111111-1111-1111-1111-111111111111",
  866. "clientId": "22222222-2222-2222-2222-222222222222",
  867. "exportId": "33333333-3333-3333-3333-333333333333",
  868. "localLayerId": "dummyLayerIdL1",
  869. "localPk": "100",
  870. "sourceLayerId": "dummyLayerIdS1",
  871. "sourcePk": "100",
  872. "method": "patch",
  873. "new": {
  874. "geometry": "Point (23.39881899999999959 41.7672146999999967)"
  875. },
  876. "old": {
  877. "geometry": "Point (25.96569999999999823 43.83559999999999945)"
  878. }
  879. }
  880. ]
  881. )"""" );
  882. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == expectedDoc );
  883. // Do not patch equal features
  884. dfw.reset();
  885. oldFeature.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  886. newFeature.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  887. dfw.addPatch( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), oldFeature, newFeature );
  888. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == QJsonDocument::fromJson( "[]" ) );
  889. }
  890. SECTION( "AddDeleteWithStringPk" )
  891. {
  892. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  893. QgsFeature f( layer->fields(), 100 );
  894. f.setAttribute( QStringLiteral( "fid" ), 100 );
  895. f.setAttribute( QStringLiteral( "dbl" ), 3.14 );
  896. f.setAttribute( QStringLiteral( "int" ), 42 );
  897. f.setAttribute( QStringLiteral( "str" ), QStringLiteral( "stringy" ) );
  898. f.setAttribute( QStringLiteral( "attachment" ), workDir.filePath( QUuid::createUuid().toString() ) );
  899. f.setGeometry( QgsGeometry() );
  900. dfw.addDelete( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "str" ), f );
  901. QJsonDocument expectedDoc = QJsonDocument::fromJson( QStringLiteral( R""""(
  902. [
  903. {
  904. "uuid": "11111111-1111-1111-1111-111111111111",
  905. "clientId": "22222222-2222-2222-2222-222222222222",
  906. "exportId": "33333333-3333-3333-3333-333333333333",
  907. "localLayerId": "dummyLayerIdL1",
  908. "localPk": "100",
  909. "sourceLayerId": "dummyLayerIdS1",
  910. "sourcePk": "stringy",
  911. "method": "delete",
  912. "old": {
  913. "attributes": {
  914. "attachment": "%1",
  915. "dbl": 3.14,
  916. "fid": 100,
  917. "int": 42,
  918. "str": "stringy"
  919. },
  920. "files_sha256": {
  921. "%1": null
  922. },
  923. "geometry": null
  924. }
  925. }
  926. ]
  927. )"""" )
  928. .arg( f.attribute( QStringLiteral( "attachment" ) ).toString() )
  929. .toUtf8() ) ;
  930. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == expectedDoc );
  931. }
  932. SECTION( "AddDelete" )
  933. {
  934. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  935. QgsFeature f( layer->fields(), 100 );
  936. f.setAttribute( QStringLiteral( "fid" ), 100 );
  937. f.setAttribute( QStringLiteral( "dbl" ), 3.14 );
  938. f.setAttribute( QStringLiteral( "int" ), 42 );
  939. f.setAttribute( QStringLiteral( "str" ), QStringLiteral( "stringy" ) );
  940. f.setAttribute( QStringLiteral( "attachment" ), attachmentFileName );
  941. // Check if creates delta of a feature with a geometry and existant attachment.
  942. f.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  943. // ? why this is not working, as QgsPoint is QgsAbstractGeometry and there is example in the docs? https://qgis.org/api/classQgsFeature.html#a14dcfc99b476b613c21b8c35840ff388
  944. // f.setGeometry( QgsPoint( 25.9657, 43.8356 ) );
  945. dfw.addDelete( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f );
  946. QJsonDocument expectedDoc = QJsonDocument::fromJson( QStringLiteral( R""""(
  947. [
  948. {
  949. "uuid": "11111111-1111-1111-1111-111111111111",
  950. "clientId": "22222222-2222-2222-2222-222222222222",
  951. "exportId": "33333333-3333-3333-3333-333333333333",
  952. "localLayerId": "dummyLayerIdL1",
  953. "localPk": "100",
  954. "sourceLayerId": "dummyLayerIdS1",
  955. "sourcePk": "100",
  956. "method": "delete",
  957. "old": {
  958. "attributes": {
  959. "attachment": "%1",
  960. "dbl": 3.14,
  961. "fid": 100,
  962. "int": 42,
  963. "str": "stringy"
  964. },
  965. "files_sha256": {
  966. "%1": "%2"
  967. },
  968. "geometry": "Point (25.96569999999999823 43.83559999999999945)"
  969. }
  970. }
  971. ]
  972. )"""" )
  973. .arg( attachmentFileName, attachmentFileChecksum )
  974. .toUtf8() );
  975. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == expectedDoc );
  976. // Check if creates delta of a feature with a NULL geometry and non existant attachment.
  977. // NOTE this is the same as calling f clearGeometry()
  978. dfw.reset();
  979. f.setGeometry( QgsGeometry() );
  980. f.setAttribute( QStringLiteral( "attachment" ), workDir.filePath( QUuid::createUuid().toString() ) );
  981. dfw.addDelete( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f );
  982. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == QJsonDocument::fromJson( QStringLiteral( R""""(
  983. [
  984. {
  985. "uuid": "11111111-1111-1111-1111-111111111111",
  986. "clientId": "22222222-2222-2222-2222-222222222222",
  987. "exportId": "33333333-3333-3333-3333-333333333333",
  988. "localLayerId": "dummyLayerIdL1",
  989. "localPk": "100",
  990. "sourceLayerId": "dummyLayerIdS1",
  991. "sourcePk": "100",
  992. "method": "delete",
  993. "old": {
  994. "attributes": {
  995. "attachment": "%1",
  996. "dbl": 3.14,
  997. "fid": 100,
  998. "int": 42,
  999. "str": "stringy"
  1000. },
  1001. "files_sha256": {
  1002. "%1": null
  1003. },
  1004. "geometry": null
  1005. }
  1006. }
  1007. ]
  1008. )"""" )
  1009. .arg( f.attribute( QStringLiteral( "attachment" ) ).toString() )
  1010. .toUtf8() ) );
  1011. // Check if creates delta of a feature without attributes
  1012. dfw.reset();
  1013. QgsFields fields;
  1014. fields.append( QgsField( QStringLiteral( "fid" ), QVariant::Int ) );
  1015. QgsFeature f1( fields, 101 );
  1016. f1.setAttribute( QStringLiteral( "fid" ), 101 );
  1017. f1.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  1018. dfw.addDelete( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f1 );
  1019. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == QJsonDocument::fromJson( R""""(
  1020. [
  1021. {
  1022. "uuid": "11111111-1111-1111-1111-111111111111",
  1023. "clientId": "22222222-2222-2222-2222-222222222222",
  1024. "exportId": "33333333-3333-3333-3333-333333333333",
  1025. "localLayerId": "dummyLayerIdL1",
  1026. "localPk": "101",
  1027. "sourceLayerId": "dummyLayerIdS1",
  1028. "sourcePk": "101",
  1029. "method": "delete",
  1030. "old": {
  1031. "attributes": {
  1032. "fid": 101
  1033. },
  1034. "geometry": "Point (25.96569999999999823 43.83559999999999945)"
  1035. }
  1036. }
  1037. ]
  1038. )"""" ) );
  1039. }
  1040. SECTION( "MultipleDeltaAdd" )
  1041. {
  1042. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  1043. QgsFields fields;
  1044. fields.append( QgsField( "dbl", QVariant::Double, "double" ) );
  1045. fields.append( QgsField( "int", QVariant::Int, "integer" ) );
  1046. fields.append( QgsField( "fid", QVariant::Int, "integer" ) );
  1047. fields.append( QgsField( "str", QVariant::String, "text" ) );
  1048. QgsFeature f1( fields, 100 );
  1049. f1.setAttribute( QStringLiteral( "dbl" ), 3.14 );
  1050. f1.setAttribute( QStringLiteral( "int" ), 42 );
  1051. f1.setAttribute( QStringLiteral( "fid" ), 100 );
  1052. f1.setAttribute( QStringLiteral( "str" ), QStringLiteral( "stringy" ) );
  1053. QgsFields fields2;
  1054. fields2.append( QgsField( "fid", QVariant::Int, "integer" ) );
  1055. QgsFeature f2( fields2, 101 );
  1056. f2.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  1057. f2.setAttribute( QStringLiteral( "fid" ), 101 );
  1058. QgsFeature f3( fields, 102 );
  1059. f3.setAttribute( QStringLiteral( "fid" ), 102 );
  1060. dfw.addCreate( QStringLiteral( "dummyLayerId1" ), QStringLiteral( "dummyLayerId1" ), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f1 );
  1061. dfw.addDelete( QStringLiteral( "dummyLayerId2" ), QStringLiteral( "dummyLayerId2" ), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f2 );
  1062. dfw.addDelete( QStringLiteral( "dummyLayerId1" ), QStringLiteral( "dummyLayerId1" ), QStringLiteral( "fid" ), QStringLiteral( "fid" ), f3 );
  1063. QJsonDocument doc = normalizeSchema( dfw.toString() );
  1064. REQUIRE( !doc.isNull() );
  1065. REQUIRE( doc == QJsonDocument::fromJson( R""""(
  1066. {
  1067. "deltas": [
  1068. {
  1069. "uuid": "11111111-1111-1111-1111-111111111111",
  1070. "clientId": "22222222-2222-2222-2222-222222222222",
  1071. "exportId": "33333333-3333-3333-3333-333333333333",
  1072. "localLayerId": "dummyLayerIdL1",
  1073. "localPk": "100",
  1074. "sourceLayerId": "dummyLayerIdS1",
  1075. "sourcePk": "100",
  1076. "method": "create",
  1077. "new": {
  1078. "attributes": {
  1079. "dbl": 3.14,
  1080. "fid": 100,
  1081. "int": 42,
  1082. "str": "stringy"
  1083. },
  1084. "geometry": null
  1085. }
  1086. },
  1087. {
  1088. "uuid": "11111111-1111-1111-1111-111111111111",
  1089. "clientId": "22222222-2222-2222-2222-222222222222",
  1090. "exportId": "33333333-3333-3333-3333-333333333333",
  1091. "localLayerId": "dummyLayerIdL2",
  1092. "localPk": "101",
  1093. "sourceLayerId": "dummyLayerIdS2",
  1094. "sourcePk": "101",
  1095. "method": "delete",
  1096. "old": {
  1097. "attributes": {
  1098. "fid": 101
  1099. },
  1100. "geometry": "Point (25.96569999999999823 43.83559999999999945)"
  1101. }
  1102. },
  1103. {
  1104. "uuid": "11111111-1111-1111-1111-111111111111",
  1105. "clientId": "22222222-2222-2222-2222-222222222222",
  1106. "exportId": "33333333-3333-3333-3333-333333333333",
  1107. "localLayerId": "dummyLayerIdL1",
  1108. "localPk": "102",
  1109. "sourceLayerId": "dummyLayerIdS1",
  1110. "sourcePk": "102",
  1111. "method": "delete",
  1112. "old": {
  1113. "attributes": {
  1114. "dbl": null,
  1115. "fid": 102,
  1116. "int": null,
  1117. "str": null
  1118. },
  1119. "geometry": null
  1120. }
  1121. }
  1122. ],
  1123. "files":[],
  1124. "id": "11111111-1111-1111-1111-111111111111",
  1125. "project": "projectId",
  1126. "version": "1.0"
  1127. }
  1128. )"""" ) );
  1129. }
  1130. SECTION( "Apply" )
  1131. {
  1132. QTemporaryFile deltaFile;
  1133. REQUIRE( deltaFile.open() );
  1134. REQUIRE( deltaFile.write( QStringLiteral( R""""(
  1135. {
  1136. "deltas": [
  1137. {
  1138. "uuid": "11111111-1111-1111-1111-111111111111",
  1139. "clientId": "22222222-2222-2222-2222-222222222222",
  1140. "exportId": "33333333-3333-3333-3333-333333333333",
  1141. "localLayerId": "%1",
  1142. "localPk": "100",
  1143. "sourceLayerId": "%1",
  1144. "sourcePk": "100",
  1145. "method": "create",
  1146. "new": {
  1147. "attributes": {
  1148. "attachment": "FILE1.jpg",
  1149. "dbl": 3.14,
  1150. "fid": 100,
  1151. "int": 42,
  1152. "str": "stringy"
  1153. },
  1154. "files_sha256": {
  1155. "FILE1.jpg": null
  1156. },
  1157. "geometry": null
  1158. }
  1159. },
  1160. {
  1161. "uuid": "11111111-1111-1111-1111-111111111111",
  1162. "clientId": "22222222-2222-2222-2222-222222222222",
  1163. "exportId": "33333333-3333-3333-3333-333333333333",
  1164. "localLayerId": "%1",
  1165. "localPk": "102",
  1166. "sourceLayerId": "%1",
  1167. "sourcePk": "102",
  1168. "method": "create",
  1169. "new": {
  1170. "attributes": {
  1171. "attachment": "FILE2.jpg",
  1172. "dbl": null,
  1173. "fid": 102,
  1174. "int": null,
  1175. "str": null
  1176. },
  1177. "files_sha256": {
  1178. "FILE2.jpg": null
  1179. },
  1180. "geometry": null
  1181. }
  1182. },
  1183. {
  1184. "uuid": "11111111-1111-1111-1111-111111111111",
  1185. "clientId": "22222222-2222-2222-2222-222222222222",
  1186. "exportId": "33333333-3333-3333-3333-333333333333",
  1187. "localLayerId": "%1",
  1188. "localPk": "102",
  1189. "sourceLayerId": "%1",
  1190. "sourcePk": "102",
  1191. "method": "patch",
  1192. "new": {
  1193. "attributes": {
  1194. "attachment": "FILE3.jpg"
  1195. },
  1196. "files_sha256": {
  1197. "FILE3.jpg": null
  1198. },
  1199. "geometry": null
  1200. },
  1201. "old": {
  1202. "attributes": {
  1203. "attachment": "FILE2.jpg"
  1204. },
  1205. "files_sha256": {
  1206. "FILE2.jpg": null
  1207. },
  1208. "geometry": null
  1209. }
  1210. },
  1211. {
  1212. "uuid": "11111111-1111-1111-1111-111111111111",
  1213. "clientId": "22222222-2222-2222-2222-222222222222",
  1214. "exportId": "33333333-3333-3333-3333-333333333333",
  1215. "localLayerId": "%1",
  1216. "localPk": "1",
  1217. "sourceLayerId": "%1",
  1218. "sourcePk": "1",
  1219. "method": "delete",
  1220. "old": {
  1221. "attachment": "%2",
  1222. "dbl": 3.14,
  1223. "fid": 1,
  1224. "int": 42,
  1225. "str": "stringy"
  1226. }
  1227. }
  1228. ],
  1229. "files": [],
  1230. "id": "11111111-1111-1111-1111-111111111111",
  1231. "project": "projectId",
  1232. "version": "1.0"
  1233. }
  1234. )"""" )
  1235. .arg( layer->id(), attachmentFileName )
  1236. .toUtf8() ) );
  1237. REQUIRE( deltaFile.flush() );
  1238. DeltaFileWrapper dfw( project, deltaFile.fileName() );
  1239. // make sure there is a single feature with id 1
  1240. QgsFeature f0;
  1241. qApp->processEvents();
  1242. QgsFeatureIterator it0 = layer->getFeatures( QgsFeatureRequest( QgsExpression( " fid = 1 " ) ) );
  1243. REQUIRE( it0.nextFeature( f0 ) );
  1244. REQUIRE( layer->featureCount() == 1 );
  1245. REQUIRE( dfw.apply() );
  1246. REQUIRE( layer->featureCount() == 2 );
  1247. QgsFeature f1;
  1248. QgsFeatureIterator it1 = layer->getFeatures( QgsFeatureRequest( QgsExpression( " fid = 100 " ) ) );
  1249. REQUIRE( it1.nextFeature( f1 ) );
  1250. REQUIRE( f1.isValid() );
  1251. REQUIRE( f1.attribute( QStringLiteral( "int" ) ) == 42 );
  1252. REQUIRE( f1.attribute( QStringLiteral( "dbl" ) ) == 3.14 );
  1253. REQUIRE( f1.attribute( QStringLiteral( "str" ) ) == QStringLiteral( "stringy" ) );
  1254. REQUIRE( f1.attribute( QStringLiteral( "attachment" ) ) == QStringLiteral( "FILE1.jpg" ) );
  1255. REQUIRE( !it1.nextFeature( f1 ) );
  1256. QgsFeature f2;
  1257. QgsFeatureIterator it2 = layer->getFeatures( QgsFeatureRequest( QgsExpression( " fid = 102 " ) ) );
  1258. REQUIRE( it2.nextFeature( f2 ) );
  1259. REQUIRE( f2.isValid() );
  1260. REQUIRE( f2.attribute( QStringLiteral( "attachment" ) ).toString() == QStringLiteral( "FILE3.jpg" ) );
  1261. REQUIRE( !it2.nextFeature( f2 ) );
  1262. QgsFeature f3;
  1263. QgsFeatureIterator it3 = layer->getFeatures( QgsFeatureRequest( QgsExpression( " fid = 1 " ) ) );
  1264. REQUIRE( !it3.nextFeature( f3 ) );
  1265. }
  1266. SECTION( "ApplyReversed" )
  1267. {
  1268. QTemporaryFile deltaFile;
  1269. REQUIRE( deltaFile.open() );
  1270. REQUIRE( deltaFile.write( QStringLiteral( R""""(
  1271. {
  1272. "deltas": [
  1273. {
  1274. "uuid": "11111111-1111-1111-1111-111111111111",
  1275. "clientId": "22222222-2222-2222-2222-222222222222",
  1276. "exportId": "33333333-3333-3333-3333-333333333333",
  1277. "localLayerId": "%1",
  1278. "localPk": "100",
  1279. "sourceLayerId": "%1",
  1280. "sourcePk": "100",
  1281. "method": "create",
  1282. "new": {
  1283. "attributes": {
  1284. "attachment": "FILE1.jpg",
  1285. "dbl": 3.14,
  1286. "fid": 100,
  1287. "int": 42,
  1288. "str": "stringy"
  1289. },
  1290. "files_sha256": {
  1291. "FILE1.jpg": null
  1292. },
  1293. "geometry": null
  1294. }
  1295. },
  1296. {
  1297. "uuid": "11111111-1111-1111-1111-111111111111",
  1298. "clientId": "22222222-2222-2222-2222-222222222222",
  1299. "exportId": "33333333-3333-3333-3333-333333333333",
  1300. "localLayerId": "%1",
  1301. "localPk": "102",
  1302. "sourceLayerId": "%1",
  1303. "sourcePk": "102",
  1304. "method": "create",
  1305. "new": {
  1306. "attributes": {
  1307. "attachment": "FILE2.jpg",
  1308. "dbl": null,
  1309. "fid": 102,
  1310. "int": null,
  1311. "str": null
  1312. },
  1313. "files_sha256": {
  1314. "FILE2.jpg": null
  1315. },
  1316. "geometry": null
  1317. }
  1318. },
  1319. {
  1320. "uuid": "11111111-1111-1111-1111-111111111111",
  1321. "clientId": "22222222-2222-2222-2222-222222222222",
  1322. "exportId": "33333333-3333-3333-3333-333333333333",
  1323. "localLayerId": "%1",
  1324. "localPk": "102",
  1325. "sourceLayerId": "%1",
  1326. "sourcePk": "102",
  1327. "method": "patch",
  1328. "new": {
  1329. "attributes": {
  1330. "attachment": "FILE3.jpg"
  1331. },
  1332. "files_sha256": {
  1333. "FILE3.jpg": null
  1334. },
  1335. "geometry": null
  1336. },
  1337. "old": {
  1338. "attributes": {
  1339. "attachment": "FILE2.jpg"
  1340. },
  1341. "files_sha256": {
  1342. "FILE2.jpg": null
  1343. },
  1344. "geometry": null
  1345. }
  1346. },
  1347. {
  1348. "uuid": "11111111-1111-1111-1111-111111111111",
  1349. "clientId": "22222222-2222-2222-2222-222222222222",
  1350. "exportId": "33333333-3333-3333-3333-333333333333",
  1351. "localLayerId": "%1",
  1352. "localPk": "1",
  1353. "sourceLayerId": "%1",
  1354. "sourcePk": "1",
  1355. "method": "delete",
  1356. "old": {
  1357. "attributes": {
  1358. "attachment": "%2",
  1359. "dbl": 3.14,
  1360. "fid": 1,
  1361. "int": 42,
  1362. "str": "stringy"
  1363. },
  1364. "files_sha256": {
  1365. "%2": "%3"
  1366. }
  1367. }
  1368. }
  1369. ],
  1370. "files": [],
  1371. "id": "11111111-1111-1111-1111-111111111111",
  1372. "project": "projectId",
  1373. "version": "1.0"
  1374. }
  1375. )"""" )
  1376. .arg( layer->id(), attachmentFileName, attachmentFileChecksum )
  1377. .toUtf8() ) );
  1378. REQUIRE( deltaFile.flush() );
  1379. DeltaFileWrapper dfw( project, deltaFile.fileName() );
  1380. // make sure there is a single feature with id 1
  1381. QgsFeature f0;
  1382. QgsFeatureIterator it0 = layer->getFeatures( QgsFeatureRequest( QgsExpression( " fid = 1 " ) ) );
  1383. REQUIRE( it0.nextFeature( f0 ) );
  1384. REQUIRE( layer->featureCount() == 1 );
  1385. REQUIRE( dfw.apply() );
  1386. REQUIRE( layer->featureCount() == 2 );
  1387. QgsFeature f1;
  1388. QgsFeatureIterator it1 = layer->getFeatures( QgsFeatureRequest( QgsExpression( " fid = 100 " ) ) );
  1389. REQUIRE( it1.nextFeature( f1 ) );
  1390. REQUIRE( f1.isValid() );
  1391. REQUIRE( f1.attribute( QStringLiteral( "int" ) ) == 42 );
  1392. REQUIRE( f1.attribute( QStringLiteral( "dbl" ) ) == 3.14 );
  1393. REQUIRE( f1.attribute( QStringLiteral( "str" ) ) == QStringLiteral( "stringy" ) );
  1394. REQUIRE( f1.attribute( QStringLiteral( "attachment" ) ) == QStringLiteral( "FILE1.jpg" ) );
  1395. REQUIRE( !it1.nextFeature( f1 ) );
  1396. QgsFeature f2;
  1397. QgsFeatureIterator it2 = layer->getFeatures( QgsFeatureRequest( QgsExpression( " fid = 102 " ) ) );
  1398. REQUIRE( it2.nextFeature( f2 ) );
  1399. REQUIRE( f2.isValid() );
  1400. REQUIRE( f2.attribute( QStringLiteral( "attachment" ) ).toString() == QStringLiteral( "FILE3.jpg" ) );
  1401. REQUIRE( !it2.nextFeature( f2 ) );
  1402. QgsFeature f3;
  1403. QgsFeatureIterator it3 = layer->getFeatures( QgsFeatureRequest( QgsExpression( " fid = 1 " ) ) );
  1404. REQUIRE( !it3.nextFeature( f3 ) );
  1405. // ^^^ the same as apply above
  1406. dfw.applyReversed();
  1407. QgsFeature f4;
  1408. QgsFeatureIterator it4 = layer->getFeatures( QgsFeatureRequest( QgsExpression( " fid = 100 OR fid = 102 " ) ) );
  1409. REQUIRE( !it4.nextFeature( f4 ) );
  1410. QgsFeature f5;
  1411. QgsFeatureIterator it5 = layer->getFeatures( QgsFeatureRequest( QgsExpression( " fid = 1 " ) ) );
  1412. REQUIRE( it5.nextFeature( f5 ) );
  1413. REQUIRE( f5.isValid() );
  1414. REQUIRE( f5.attribute( QStringLiteral( "fid" ) ) == 1 );
  1415. REQUIRE( f5.attribute( QStringLiteral( "int" ) ) == 42 );
  1416. REQUIRE( f5.attribute( QStringLiteral( "dbl" ) ) == 3.14 );
  1417. REQUIRE( f5.attribute( QStringLiteral( "str" ) ) == QStringLiteral( "stringy" ) );
  1418. REQUIRE( f5.attribute( QStringLiteral( "attachment" ) ) == attachmentFileName );
  1419. REQUIRE( !it5.nextFeature( f5 ) );
  1420. }
  1421. SECTION( "AddCreateWithJoinedLayer" )
  1422. {
  1423. QgsVectorLayerJoinInfo ji;
  1424. ji.setTargetFieldName( QStringLiteral( "fid" ) );
  1425. ji.setJoinLayer( joinedLayer.get() );
  1426. ji.setJoinFieldName( QStringLiteral( "fid" ) );
  1427. ji.setPrefix( QString( "" ) );
  1428. ji.setEditable( false );
  1429. REQUIRE( layer->addJoin( ji ) );
  1430. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  1431. QgsFeature f( layer->fields(), 2 );
  1432. f.setAttribute( QStringLiteral( "fid" ), 2 );
  1433. f.setAttribute( QStringLiteral( "dbl" ), 3.14 );
  1434. f.setAttribute( QStringLiteral( "int" ), 42 );
  1435. f.setAttribute( QStringLiteral( "str" ), QStringLiteral( "stringy" ) );
  1436. f.setAttribute( QStringLiteral( "attachment" ), attachmentFileName );
  1437. REQUIRE( f.isValid() );
  1438. REQUIRE( layer->startEditing() );
  1439. REQUIRE( layer->addFeature( f ) );
  1440. REQUIRE( layer->commitChanges() );
  1441. QgsFeature savedFeat = layer->getFeature( 2 );
  1442. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), savedFeat );
  1443. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == QJsonDocument::fromJson( QStringLiteral( R""""(
  1444. [
  1445. {
  1446. "uuid": "11111111-1111-1111-1111-111111111111",
  1447. "clientId": "22222222-2222-2222-2222-222222222222",
  1448. "exportId": "33333333-3333-3333-3333-333333333333",
  1449. "localLayerId": "dummyLayerIdL1",
  1450. "localPk": "2",
  1451. "sourceLayerId": "dummyLayerIdS1",
  1452. "sourcePk": "2",
  1453. "method": "create",
  1454. "new": {
  1455. "attributes": {
  1456. "attachment": "%1",
  1457. "dbl": 3.14,
  1458. "fid": 2,
  1459. "int": 42,
  1460. "str": "stringy"
  1461. },
  1462. "files_sha256": {
  1463. "%1": "%2"
  1464. },
  1465. "geometry": null
  1466. }
  1467. }
  1468. ]
  1469. )"""" )
  1470. .arg( attachmentFileName, attachmentFileChecksum )
  1471. .toUtf8() ) );
  1472. }
  1473. SECTION( "AddCreateWithExpressionField" )
  1474. {
  1475. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  1476. QgsFields fields = layer->fields();
  1477. QgsField expressionField( QStringLiteral( "expression" ), QVariant::String, QStringLiteral( "text" ) );
  1478. QgsFeature f( fields, 2 );
  1479. f.setAttribute( QStringLiteral( "fid" ), 2 );
  1480. f.setAttribute( QStringLiteral( "dbl" ), 3.14 );
  1481. f.setAttribute( QStringLiteral( "int" ), 42 );
  1482. f.setAttribute( QStringLiteral( "str" ), QStringLiteral( "stringy" ) );
  1483. f.setAttribute( QStringLiteral( "attachment" ), attachmentFileName );
  1484. REQUIRE( f.isValid() );
  1485. REQUIRE( layer->startEditing() );
  1486. REQUIRE( layer->addFeature( f ) );
  1487. REQUIRE( layer->commitChanges() );
  1488. REQUIRE( layer->addExpressionField( QStringLiteral( " UPPER( str ) " ), expressionField ) >= 0 );
  1489. QgsFeature savedFeat = layer->getFeature( 2 );
  1490. dfw.addCreate( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), savedFeat );
  1491. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == QJsonDocument::fromJson( QStringLiteral( R""""(
  1492. [
  1493. {
  1494. "uuid": "11111111-1111-1111-1111-111111111111",
  1495. "clientId": "22222222-2222-2222-2222-222222222222",
  1496. "exportId": "33333333-3333-3333-3333-333333333333",
  1497. "localLayerId": "dummyLayerIdL1",
  1498. "localPk": "2",
  1499. "sourceLayerId": "dummyLayerIdS1",
  1500. "sourcePk": "2",
  1501. "method": "create",
  1502. "new": {
  1503. "attributes": {
  1504. "attachment": "%1",
  1505. "dbl": 3.14,
  1506. "fid": 2,
  1507. "int": 42,
  1508. "str": "stringy"
  1509. },
  1510. "files_sha256": {
  1511. "%1": "%2"
  1512. },
  1513. "geometry": null
  1514. }
  1515. }
  1516. ]
  1517. )"""" )
  1518. .arg( attachmentFileName, attachmentFileChecksum )
  1519. .toUtf8() ) );
  1520. }
  1521. SECTION( "AddPatchWithJoinedLayer" )
  1522. {
  1523. QgsVectorLayerJoinInfo ji;
  1524. ji.setTargetFieldName( QStringLiteral( "fid" ) );
  1525. ji.setJoinLayer( joinedLayer.get() );
  1526. ji.setJoinFieldName( QStringLiteral( "fid" ) );
  1527. ji.setPrefix( QString( "" ) );
  1528. ji.setEditable( false );
  1529. REQUIRE( layer->addJoin( ji ) );
  1530. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  1531. QgsFields fields = layer->fields();
  1532. QgsField expressionField( QStringLiteral( "expression" ), QVariant::String, QStringLiteral( "text" ) );
  1533. QgsFeature oldFeature = layer->getFeature( 1 );
  1534. QgsFeature newFeature = layer->getFeature( 1 );
  1535. REQUIRE( newFeature.setAttribute( QStringLiteral( "dbl" ), 9.81 ) );
  1536. REQUIRE( newFeature.setAttribute( QStringLiteral( "int" ), 680 ) );
  1537. REQUIRE( newFeature.setAttribute( QStringLiteral( "str" ), QStringLiteral( "pingy" ) ) );
  1538. // Patch both the attributes with existing attachment and the geometry
  1539. oldFeature.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  1540. newFeature.setGeometry( QgsGeometry( new QgsPoint( 23.398819, 41.7672147 ) ) );
  1541. dfw.addPatch( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), oldFeature, newFeature );
  1542. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == QJsonDocument::fromJson( QStringLiteral( R""""(
  1543. [
  1544. {
  1545. "uuid": "11111111-1111-1111-1111-111111111111",
  1546. "clientId": "22222222-2222-2222-2222-222222222222",
  1547. "exportId": "33333333-3333-3333-3333-333333333333",
  1548. "localLayerId": "dummyLayerIdL1",
  1549. "localPk": "1",
  1550. "sourceLayerId": "dummyLayerIdS1",
  1551. "sourcePk": "1",
  1552. "method": "patch",
  1553. "new": {
  1554. "attributes": {
  1555. "dbl": 9.81,
  1556. "int": 680,
  1557. "str": "pingy"
  1558. },
  1559. "geometry": "Point (23.39881899999999959 41.7672146999999967)"
  1560. },
  1561. "old": {
  1562. "attributes": {
  1563. "dbl": 3.14,
  1564. "int": 42,
  1565. "str": "stringy"
  1566. },
  1567. "geometry": "Point (25.96569999999999823 43.83559999999999945)"
  1568. }
  1569. }
  1570. ]
  1571. )"""" )
  1572. .toUtf8() ) );
  1573. }
  1574. SECTION( "AddPatchWithExpressionField" )
  1575. {
  1576. DeltaFileWrapper dfw( project, workDir.filePath( QUuid::createUuid().toString() ) );
  1577. QgsFields fields = layer->fields();
  1578. QgsField expressionField( QStringLiteral( "expression" ), QVariant::String, QStringLiteral( "text" ) );
  1579. // this is the easiest way to create an expression field
  1580. REQUIRE( layer->addExpressionField( QStringLiteral( " UPPER( str ) " ), expressionField ) >= 0 );
  1581. QgsFeature oldFeature = layer->getFeature( 1 );
  1582. QgsFeature newFeature = layer->getFeature( 1 );
  1583. REQUIRE( newFeature.setAttribute( QStringLiteral( "dbl" ), 9.81 ) );
  1584. REQUIRE( newFeature.setAttribute( QStringLiteral( "int" ), 680 ) );
  1585. REQUIRE( newFeature.setAttribute( QStringLiteral( "str" ), QStringLiteral( "pingy" ) ) );
  1586. // Patch both the attributes with existing attachment and the geometry
  1587. oldFeature.setGeometry( QgsGeometry( new QgsPoint( 25.9657, 43.8356 ) ) );
  1588. newFeature.setGeometry( QgsGeometry( new QgsPoint( 23.398819, 41.7672147 ) ) );
  1589. dfw.addPatch( layer->id(), layer->id(), QStringLiteral( "fid" ), QStringLiteral( "fid" ), oldFeature, newFeature );
  1590. REQUIRE( QJsonDocument( getDeltasArray( dfw.toString() ) ) == QJsonDocument::fromJson( QStringLiteral( R""""(
  1591. [
  1592. {
  1593. "uuid": "11111111-1111-1111-1111-111111111111",
  1594. "clientId": "22222222-2222-2222-2222-222222222222",
  1595. "exportId": "33333333-3333-3333-3333-333333333333",
  1596. "localLayerId": "dummyLayerIdL1",
  1597. "localPk": "1",
  1598. "sourceLayerId": "dummyLayerIdS1",
  1599. "sourcePk": "1",
  1600. "method": "patch",
  1601. "new": {
  1602. "attributes": {
  1603. "dbl": 9.81,
  1604. "int": 680,
  1605. "str": "pingy"
  1606. },
  1607. "geometry": "Point (23.39881899999999959 41.7672146999999967)"
  1608. },
  1609. "old": {
  1610. "attributes": {
  1611. "dbl": 3.14,
  1612. "int": 42,
  1613. "str": "stringy"
  1614. },
  1615. "geometry": "Point (25.96569999999999823 43.83559999999999945)"
  1616. }
  1617. }
  1618. ]
  1619. )"""" )
  1620. .toUtf8() ) );
  1621. }
  1622. project->removeMapLayer( layer.get() );
  1623. project->removeMapLayer( joinedLayer.get() );
  1624. }