44using MiniLcm . SyncHelpers ;
55using MiniLcm . Tests . AutoFakerHelpers ;
66using Soenneker . Utils . AutoBogus ;
7- using Soenneker . Utils . AutoBogus . Config ;
87
98namespace FwLiteProjectSync . Tests ;
109
@@ -29,7 +28,7 @@ public async Task CanSyncRandomEntries()
2928 ..after . Senses
3029 ] ) ] ;
3130
32- await EntrySync . Sync ( createdEntry , after , Api ) ;
31+ await EntrySync . SyncFull ( createdEntry , after , Api ) ;
3332 var actual = await Api . GetEntry ( after . Id ) ;
3433 actual . Should ( ) . NotBeNull ( ) ;
3534 actual . Should ( ) . BeEquivalentTo ( after , options => options
@@ -92,7 +91,7 @@ public async Task CanChangeComplexFormViaSync_Components()
9291 complexFormAfter . Components [ 0 ] . ComponentEntryId = component2 . Id ;
9392 complexFormAfter . Components [ 0 ] . ComponentHeadword = component2 . Headword ( ) ;
9493
95- await EntrySync . Sync ( complexForm , complexFormAfter , Api ) ;
94+ await EntrySync . SyncFull ( complexForm , complexFormAfter , Api ) ;
9695
9796 var actual = await Api . GetEntry ( complexFormAfter . Id ) ;
9897 actual . Should ( ) . NotBeNull ( ) ;
@@ -125,7 +124,7 @@ public async Task CanChangeComplexFormViaSync_ComplexForms()
125124 componentAter . ComplexForms [ 0 ] . ComplexFormEntryId = complexForm2 . Id ;
126125 componentAter . ComplexForms [ 0 ] . ComplexFormHeadword = complexForm2 . Headword ( ) ;
127126
128- await EntrySync . Sync ( component , componentAter , Api ) ;
127+ await EntrySync . SyncFull ( component , componentAter , Api ) ;
129128
130129 var actual = await Api . GetEntry ( componentAter . Id ) ;
131130 actual . Should ( ) . NotBeNull ( ) ;
@@ -140,7 +139,7 @@ public async Task CanChangeComplexFormTypeViaSync()
140139 var entry = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "complexForm1" } } } ) ;
141140 var after = entry . Copy ( ) ;
142141 after . ComplexFormTypes = [ complexFormType ] ;
143- await EntrySync . Sync ( entry , after , Api ) ;
142+ await EntrySync . SyncFull ( entry , after , Api ) ;
144143
145144 var actual = await Api . GetEntry ( after . Id ) ;
146145 actual . Should ( ) . NotBeNull ( ) ;
@@ -172,14 +171,14 @@ public async Task CanInsertComplexFormComponentViaSync(bool componentThenComplex
172171 // this results in 2 crdt changes:
173172 // (1) add complex-form (i.e. implicitly add component)
174173 // (2) move component to the right place
175- await EntrySync . Sync ( [ newComponentBefore , complexFormBefore ] , [ newComponentAfter , complexFormAfter ] , Api ) ;
174+ await EntrySync . SyncFull ( [ newComponentBefore , complexFormBefore ] , [ newComponentAfter , complexFormAfter ] , Api ) ;
176175 }
177176 else
178177 {
179178 // this results in 1 crdt change:
180179 // the component is added in the right place
181180 // (adding the complex-form becomes a no-op, because it already exists and a BetweenPosition is not specified)
182- await EntrySync . Sync ( [ complexFormBefore , newComponentBefore ] , [ complexFormAfter , newComponentAfter ] , Api ) ;
181+ await EntrySync . SyncFull ( [ complexFormBefore , newComponentBefore ] , [ complexFormAfter , newComponentAfter ] , Api ) ;
183182 }
184183
185184 // assert
@@ -210,7 +209,7 @@ public async Task CanSyncNewEntryReferencedByExistingEntry()
210209 newEntry . Components . Add ( newComplexFormComponent ) ;
211210
212211 // act
213- await EntrySync . Sync ( [ existingEntryBefore ] , [ existingEntryAfter , newEntry ] , Api ) ;
212+ await EntrySync . SyncFull ( [ existingEntryBefore ] , [ existingEntryAfter , newEntry ] , Api ) ;
214213
215214 // assert
216215 var actualExistingEntry = await Api . GetEntry ( existingEntryAfter . Id ) ;
@@ -224,4 +223,202 @@ public async Task CanSyncNewEntryReferencedByExistingEntry()
224223 . For ( e => e . Components ) . Exclude ( c => c . Id )
225224 . For ( e => e . Components ) . Exclude ( c => c . Order ) ) ;
226225 }
226+
227+ [ Fact ]
228+ public async Task CanSyncNewComplexFormComponentReferencingNewSense ( )
229+ {
230+ // arrange
231+ // - before
232+ var complexFormEntryBefore = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "complex-form" } } } ) ;
233+ var componentEntryBefore = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "component" } } } ) ;
234+
235+ // - after
236+ var complexFormEntryAfter = complexFormEntryBefore . Copy ( ) ;
237+ var componentEntryAfter = componentEntryBefore . Copy ( ) ;
238+ var senseId = Guid . NewGuid ( ) ;
239+ componentEntryAfter . Senses = [ new Sense ( ) { Id = senseId , EntryId = componentEntryAfter . Id } ] ;
240+
241+ var component = ComplexFormComponent . FromEntries ( complexFormEntryAfter , componentEntryAfter , senseId ) ;
242+ complexFormEntryAfter . Components . Add ( component ) ;
243+ componentEntryAfter . ComplexForms . Add ( component ) ;
244+
245+ // act
246+ await EntrySync . SyncFull (
247+ // note: the entry with the added sense is at the end of the list
248+ [ complexFormEntryBefore , componentEntryBefore ] ,
249+ [ complexFormEntryAfter , componentEntryAfter ] ,
250+ Api ) ;
251+
252+ // assert
253+ var actualComplexFormEntry = await Api . GetEntry ( complexFormEntryAfter . Id ) ;
254+ actualComplexFormEntry . Should ( ) . BeEquivalentTo ( complexFormEntryAfter ,
255+ options => SyncTests . SyncExclusions ( options )
256+ . Excluding ( e => e . ComplexFormTypes ) // LibLcm automatically creates a complex form type. Should we?
257+ . WithStrictOrdering ( ) ) ;
258+
259+ var actualComponentEntry = await Api . GetEntry ( componentEntryAfter . Id ) ;
260+ actualComponentEntry . Should ( ) . BeEquivalentTo ( componentEntryAfter ,
261+ options => SyncTests . SyncExclusions ( options ) . WithStrictOrdering ( ) ) ;
262+ }
263+
264+ [ Fact ]
265+ public async Task SyncWithoutComplexFormsAndComponents_CorrectlySyncsUpdatedEntries ( )
266+ {
267+ // Arrange
268+ // - before
269+ var componentBefore = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "component" } } } ) ;
270+
271+ // - after
272+ var componentAfter = componentBefore . Copy ( ) ;
273+ componentAfter . LexemeForm [ "en" ] = "component updated" ;
274+ var complexForm = new Entry ( )
275+ {
276+ Id = Guid . NewGuid ( ) ,
277+ LexemeForm = { { "en" , "complex form" } }
278+ } ;
279+ var complexFormComponent = ComplexFormComponent . FromEntries ( complexForm , componentAfter ) ;
280+ componentAfter . ComplexForms . Add ( complexFormComponent ) ;
281+ complexForm . Components . Add ( complexFormComponent ) ;
282+
283+ // act
284+ var ( changes , added ) = await EntrySync . SyncWithoutComplexFormsAndComponents ( [ componentBefore ] , [ componentAfter , complexForm ] , Api ) ;
285+ added . Should ( ) . HaveCount ( 1 ) ;
286+ var addedComplexForm = added . First ( ) ;
287+
288+ // assert
289+ var actualComponent = await Api . GetEntry ( componentAfter . Id ) ;
290+ actualComponent . Should ( ) . BeEquivalentTo ( componentAfter ,
291+ options => options . Excluding ( e => e . ComplexForms ) ) ;
292+ actualComponent . ComplexForms . Should ( ) . BeEmpty ( ) ;
293+
294+ var actualComplexForm = await Api . GetEntry ( complexForm . Id ) ;
295+ addedComplexForm . Should ( ) . BeEquivalentTo ( actualComplexForm ) ;
296+ actualComplexForm . Should ( ) . BeEquivalentTo ( complexForm ,
297+ options => options . Excluding ( e => e . Components ) ) ;
298+ actualComplexForm . Components . Should ( ) . BeEmpty ( ) ;
299+ }
300+
301+ [ Fact ]
302+ public async Task SyncWithoutComplexFormsAndComponents_CorrectlySyncsAddedEntries ( )
303+ {
304+ // Arrange
305+ // - after
306+ var component = new Entry ( )
307+ {
308+ Id = Guid . NewGuid ( ) ,
309+ LexemeForm = { { "en" , "component" } }
310+ } ;
311+ var complexForm = new Entry ( )
312+ {
313+ Id = Guid . NewGuid ( ) ,
314+ LexemeForm = { { "en" , "complex form" } }
315+ } ;
316+ var complexFormComponent = ComplexFormComponent . FromEntries ( complexForm , component ) ;
317+ component . ComplexForms . Add ( complexFormComponent ) ;
318+ complexForm . Components . Add ( complexFormComponent ) ;
319+
320+ // act
321+ var ( _, added ) = await EntrySync . SyncWithoutComplexFormsAndComponents ( [ ] , [ component , complexForm ] , Api ) ;
322+ added . Should ( ) . HaveCount ( 2 ) ;
323+ var addedComponent = added . ElementAt ( 0 ) ;
324+ var addedComplexForm = added . ElementAt ( 1 ) ;
325+
326+ // assert
327+ var actualComponent = await Api . GetEntry ( component . Id ) ;
328+ addedComponent . Should ( ) . BeEquivalentTo ( actualComponent ) ;
329+ actualComponent . Should ( ) . BeEquivalentTo ( component ,
330+ options => options . Excluding ( e => e . ComplexForms ) ) ;
331+ actualComponent . ComplexForms . Should ( ) . BeEmpty ( ) ;
332+
333+ var actualComplexForm = await Api . GetEntry ( complexForm . Id ) ;
334+ addedComplexForm . Should ( ) . BeEquivalentTo ( actualComplexForm ) ;
335+ actualComplexForm . Should ( ) . BeEquivalentTo ( complexForm ,
336+ options => options . Excluding ( e => e . Components ) ) ;
337+ actualComplexForm . Components . Should ( ) . BeEmpty ( ) ;
338+ }
339+
340+ [ Fact ]
341+ public async Task SyncComplexFormsAndComponents_CorrectlySyncsUpdatedEntries ( )
342+ {
343+ // Arrange
344+ // - before
345+ var componentBefore = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "component" } } } ) ;
346+ var complexFormBefore = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "complex form" } } } ) ;
347+
348+ // - after
349+ var componentAfter = componentBefore . Copy ( ) ;
350+ componentAfter . LexemeForm [ "en" ] = "component updated" ;
351+ var complexFormAfter = complexFormBefore . Copy ( ) ;
352+ complexFormAfter . LexemeForm [ "en" ] = "complex form updated" ;
353+ var complexFormComponent = ComplexFormComponent . FromEntries ( complexFormAfter , componentAfter ) ;
354+ componentAfter . ComplexForms . Add ( complexFormComponent ) ;
355+ complexFormAfter . Components . Add ( complexFormComponent ) ;
356+
357+ // act
358+ await EntrySync . SyncComplexFormsAndComponents ( [ componentBefore , complexFormBefore ] , [ componentAfter , complexFormAfter ] , Api ) ;
359+
360+ // assert
361+ var actualComponent = await Api . GetEntry ( componentAfter . Id ) ;
362+ actualComponent . Should ( ) . NotBeNull ( ) ;
363+
364+ // complex forms were synced
365+ actualComponent . ComplexForms . Should ( ) . NotBeEmpty ( ) ;
366+ actualComponent . ComplexForms . Should ( ) . BeEquivalentTo ( componentAfter . ComplexForms , options
367+ => options . Excluding ( c => c . Id )
368+ . Excluding ( c => c . Order )
369+ // The lexeme-form/headword wasn't synced so it doesn't match the "after" version
370+ . Excluding ( c => c . ComplexFormHeadword )
371+ . Excluding ( c => c . ComponentHeadword ) ) ;
372+
373+ var actualComplexForm = await Api . GetEntry ( complexFormAfter . Id ) ;
374+ actualComplexForm . Should ( ) . NotBeNull ( ) ;
375+ // components were synced
376+ actualComplexForm . Components . Should ( ) . NotBeEmpty ( ) ;
377+ actualComplexForm . Components . Should ( ) . BeEquivalentTo ( complexFormAfter . Components , options
378+ => options . Excluding ( c => c . Id )
379+ . Excluding ( c => c . Order )
380+ // The lexeme-form/headword wasn't synced so it doesn't match the "after" version
381+ . Excluding ( c => c . ComplexFormHeadword )
382+ . Excluding ( c => c . ComponentHeadword ) ) ;
383+
384+ // Lexeme form was not synced
385+ actualComponent . LexemeForm . Should ( ) . BeEquivalentTo ( componentBefore . LexemeForm ) ;
386+ actualComponent . LexemeForm . Should ( ) . NotBeEquivalentTo ( componentAfter . LexemeForm ) ;
387+ actualComplexForm . LexemeForm . Should ( ) . BeEquivalentTo ( complexFormBefore . LexemeForm ) ;
388+ actualComplexForm . LexemeForm . Should ( ) . NotBeEquivalentTo ( complexFormAfter . LexemeForm ) ;
389+ }
390+
391+ [ Fact ]
392+ public async Task SyncComplexFormsAndComponents_ThrowsExceptionIfEntryNotInBefore ( )
393+ {
394+ // Arrange
395+ var component = new Entry ( ) { Id = Guid . NewGuid ( ) } ;
396+ var complexForm = new Entry ( ) { Id = Guid . NewGuid ( ) } ;
397+ var complexFormComponent = ComplexFormComponent . FromEntries ( complexForm , component ) ;
398+ component . ComplexForms . Add ( complexFormComponent ) ;
399+ complexForm . Components . Add ( complexFormComponent ) ;
400+
401+ // Act
402+ var act = ( ) => EntrySync . SyncComplexFormsAndComponents ( [ ] , [ component , complexForm ] , Api ) ;
403+
404+ // Assert
405+ await act . Should ( ) . ThrowAsync < InvalidOperationException > ( ) ;
406+ }
407+
408+ [ Fact ]
409+ public async Task SyncComplexFormsAndComponents_ThrowsExceptionIfEntryNotInAfter ( )
410+ {
411+ // Arrange
412+ var component = new Entry ( ) { Id = Guid . NewGuid ( ) } ;
413+ var complexForm = new Entry ( ) { Id = Guid . NewGuid ( ) } ;
414+ var complexFormComponent = ComplexFormComponent . FromEntries ( complexForm , component ) ;
415+ component . ComplexForms . Add ( complexFormComponent ) ;
416+ complexForm . Components . Add ( complexFormComponent ) ;
417+
418+ // Act
419+ var act = ( ) => EntrySync . SyncComplexFormsAndComponents ( [ component , complexForm ] , [ ] , Api ) ;
420+
421+ // Assert
422+ await act . Should ( ) . ThrowAsync < InvalidOperationException > ( ) ;
423+ }
227424}
0 commit comments