@@ -24,6 +24,35 @@ public void Setup()
2424 _wordService = new WordService ( _wordRepo ) ;
2525 }
2626
27+ [ Test ]
28+ public void TestImportWordsDoesNotChangeTimestamps ( )
29+ {
30+ const string existingCreated = "existing-created" ;
31+ const string existingModified = "existing-modified" ;
32+
33+ var importedWords = _wordService . ImportWords ( [
34+ new Word { ProjectId = ProjId } ,
35+ new Word { ProjectId = ProjId , Created = existingCreated , Modified = existingModified } ,
36+ ] ) . Result ;
37+
38+ Assert . That ( importedWords , Has . Count . EqualTo ( 2 ) ) ;
39+ Assert . That ( importedWords [ 0 ] . Created , Is . Not . Empty ) ;
40+ Assert . That ( importedWords [ 0 ] . Modified , Is . Not . Empty ) ;
41+ Assert . That ( importedWords [ 1 ] . Created , Is . EqualTo ( existingCreated ) ) ;
42+ Assert . That ( importedWords [ 1 ] . Modified , Is . EqualTo ( existingModified ) ) ;
43+ Assert . That ( _wordRepo . GetAllFrontier ( ProjId ) . Result , Has . Count . EqualTo ( 2 ) ) ;
44+ }
45+
46+ [ Test ]
47+ public void TestImportWordsEmptyInputReturnsEmptyAndDoesNotChangeRepo ( )
48+ {
49+ var importedWords = _wordService . ImportWords ( [ ] ) . Result ;
50+
51+ Assert . That ( importedWords , Is . Empty ) ;
52+ Assert . That ( _wordRepo . GetAllFrontier ( ProjId ) . Result , Is . Empty ) ;
53+ Assert . That ( _wordRepo . GetAllWords ( ProjId ) . Result , Is . Empty ) ;
54+ }
55+
2756 [ Test ]
2857 public void TestCreateAddsUserId ( )
2958 {
@@ -39,6 +68,28 @@ public void TestCreateDoesNotAddDuplicateUserId()
3968 Assert . That ( word . EditedBy , Has . Count . EqualTo ( 1 ) ) ;
4069 }
4170
71+ [ Test ]
72+ public void TestCreateBlankUserIdDoesNotAppendEditedBy ( )
73+ {
74+ var word = _wordService . Create ( "" , new Word { EditedBy = [ "other" ] } ) . Result ;
75+
76+ Assert . That ( word . EditedBy , Has . Count . EqualTo ( 1 ) ) ;
77+ Assert . That ( word . EditedBy . Last ( ) , Is . EqualTo ( "other" ) ) ;
78+ }
79+
80+ [ Test ]
81+ public void TestCreatePreservesCreatedAndUpdatesModified ( )
82+ {
83+ const string existingCreated = "existing-created" ;
84+ const string existingModified = "existing-modified" ;
85+
86+ var createdWord = _wordService . Create ( UserId ,
87+ new Word { ProjectId = ProjId , Created = existingCreated , Modified = existingModified } ) . Result ;
88+
89+ Assert . That ( createdWord . Created , Is . EqualTo ( existingCreated ) ) ;
90+ Assert . That ( createdWord . Modified , Is . Not . EqualTo ( existingModified ) ) ;
91+ }
92+
4293 [ Test ]
4394 public void TestDeleteAudioBadInput ( )
4495 {
@@ -48,10 +99,8 @@ public void TestDeleteAudioBadInput()
4899 Assert . That ( _wordService . DeleteAudio ( "non-proj-id" , UserId , wordInFrontier . Id , fileName ) . Result , Is . Null ) ;
49100 Assert . That ( _wordService . DeleteAudio ( ProjId , UserId , "non-word-id" , fileName ) . Result , Is . Null ) ;
50101
51- var ex = Assert . Throws < AggregateException > ( ( ) =>
52- {
53- _ = _wordService . DeleteAudio ( ProjId , UserId , wordInFrontier . Id , "non-file-name" ) . Result ;
54- } ) ;
102+ var ex = Assert . Throws < AggregateException > (
103+ ( ) => _wordService . DeleteAudio ( ProjId , UserId , wordInFrontier . Id , "non-file-name" ) . Wait ( ) ) ;
55104 Assert . That ( ex ? . InnerException , Is . InstanceOf < ArgumentException > ( ) ) ;
56105 }
57106
@@ -126,6 +175,22 @@ public void TestDeleteFrontierWordCopiesToWordsAndRemovesFrontier()
126175 Assert . That ( _wordRepo . GetAllFrontier ( ProjId ) . Result , Is . Empty ) ;
127176 }
128177
178+ [ Test ]
179+ public void TestDeleteFrontierWordPreservesHistoryAndAppendsDeletedId ( )
180+ {
181+ var word = _wordRepo . Create ( new Word { ProjectId = ProjId , History = [ "older-1" , "older-2" ] } ) . Result ;
182+
183+ var deletedId = _wordService . DeleteFrontierWord ( ProjId , UserId , word . Id ) . Result ;
184+ Assert . That ( deletedId , Is . Not . Null ) ;
185+ var deletedWord = _wordRepo . GetWord ( ProjId , deletedId ) . Result ;
186+ var expectedHistoryPrefix = new [ ] { "older-1" , "older-2" } ;
187+
188+ Assert . That ( deletedWord , Is . Not . Null ) ;
189+ Assert . That ( deletedWord . History , Has . Count . EqualTo ( 3 ) ) ;
190+ Assert . That ( deletedWord . History . Take ( 2 ) , Is . EqualTo ( expectedHistoryPrefix ) ) ;
191+ Assert . That ( deletedWord . History . Last ( ) , Is . EqualTo ( word . Id ) ) ;
192+ }
193+
129194 [ Test ]
130195 public void TestRestoreFrontierWordAlreadyInFrontierThrows ( )
131196 {
@@ -137,10 +202,20 @@ public void TestRestoreFrontierWordAlreadyInFrontierThrows()
137202 Throws . TypeOf < AggregateException > ( ) ) ;
138203 }
139204
205+ [ Test ]
206+ public void TestRestoreFrontierWordDeletedWordThrows ( )
207+ {
208+ var deletedWord = _wordRepo . Add ( new Word { ProjectId = ProjId , Accessibility = Status . Deleted } ) . Result ;
209+
210+ var ex = Assert . Throws < AggregateException > (
211+ ( ) => _wordService . RestoreFrontierWord ( ProjId , deletedWord . Id ) . Wait ( ) ) ;
212+ Assert . That ( ex ? . InnerException , Is . InstanceOf < ArgumentException > ( ) ) ;
213+ }
214+
140215 [ Test ]
141216 public void TestRestoreFrontierWordMissingWordReturnsFalse ( )
142217 {
143- var word = _wordRepo . Add ( new Word { ProjectId = ProjId } ) . Result ;
218+ _wordRepo . Add ( new Word { ProjectId = ProjId } ) . Wait ( ) ;
144219
145220 var result = _wordService . RestoreFrontierWord ( ProjId , "NotAnId" ) . Result ;
146221
@@ -206,6 +281,19 @@ public void TestUpdateUsingCitationForm()
206281 Assert . That ( vernUpdate . UsingCitationForm , Is . False ) ;
207282 }
208283
284+ [ Test ]
285+ public void TestUpdateDoesNotDuplicateExistingHistoryId ( )
286+ {
287+ var word = _wordRepo . Create ( new Word { ProjectId = ProjId } ) . Result ;
288+ var oldId = word . Id ;
289+ word . History . Add ( oldId ) ;
290+
291+ var updatedWord = _wordService . Update ( UserId , word ) . Result ;
292+
293+ Assert . That ( updatedWord , Is . Not . Null ) ;
294+ Assert . That ( updatedWord . History . Count ( id => id == oldId ) , Is . EqualTo ( 1 ) ) ;
295+ }
296+
209297 [ Test ]
210298 public void TestFindContainingWordNoFrontier ( )
211299 {
@@ -291,5 +379,137 @@ public void TestFindContainingWordSameVernEmptySensesSameDoms()
291379 var dupId = _wordService . FindContainingWord ( newWord ) . Result ;
292380 Assert . That ( dupId , Is . EqualTo ( oldWord . Id ) ) ;
293381 }
382+
383+ [ Test ]
384+ public void TestFindContainingWordIgnoresWordsNotInFrontier ( )
385+ {
386+ var oldWordInWordsOnly = Util . RandomWord ( ProjId ) ;
387+ oldWordInWordsOnly = _wordRepo . Add ( oldWordInWordsOnly ) . Result ;
388+
389+ var newWord = Util . RandomWord ( ProjId ) ;
390+ newWord . Vernacular = oldWordInWordsOnly . Vernacular ;
391+ newWord . Senses = oldWordInWordsOnly . Senses . Select ( s => s . Clone ( ) ) . ToList ( ) ;
392+
393+ var dupId = _wordService . FindContainingWord ( newWord ) . Result ;
394+
395+ Assert . That ( dupId , Is . Null ) ;
396+ }
397+
398+ [ Test ]
399+ public void TestMergeReplaceFrontierUpdatesAndDeletes ( )
400+ {
401+ var childToReplace = _wordRepo . Create ( Util . RandomWord ( ProjId ) ) . Result ;
402+ var childToDelete = _wordRepo . Create ( Util . RandomWord ( ProjId ) ) . Result ;
403+ var parent = Util . RandomWord ( ProjId ) ;
404+ parent . Id = childToReplace . Id ;
405+ parent . Vernacular = "merged-vern" ;
406+
407+ var mergedParents = _wordService
408+ . MergeReplaceFrontier ( ProjId , UserId , [ parent ] , [ childToReplace . Id , childToDelete . Id ] ) . Result ;
409+
410+ Assert . That ( mergedParents , Is . Not . Null ) ;
411+ Assert . That ( mergedParents , Has . Count . EqualTo ( 1 ) ) ;
412+
413+ var mergedParent = mergedParents . Single ( ) ;
414+ Assert . That ( mergedParent . Id , Is . Not . EqualTo ( childToReplace . Id ) ) ;
415+ Assert . That ( mergedParent . History . Last ( ) , Is . EqualTo ( childToReplace . Id ) ) ;
416+ Assert . That ( mergedParent . EditedBy . Last ( ) , Is . EqualTo ( UserId ) ) ;
417+
418+ var frontier = _wordRepo . GetAllFrontier ( ProjId ) . Result ;
419+ Assert . That ( frontier , Has . Count . EqualTo ( 1 ) ) ;
420+ Assert . That ( frontier . Single ( ) . Id , Is . EqualTo ( mergedParent . Id ) ) ;
421+
422+ var deletedCopy = _wordRepo . GetAllWords ( ProjId ) . Result
423+ . Find ( w => w . Accessibility == Status . Deleted && w . History . Contains ( childToDelete . Id ) ) ;
424+ Assert . That ( deletedCopy , Is . Not . Null ) ;
425+ Assert . That ( deletedCopy . EditedBy . Last ( ) , Is . EqualTo ( UserId ) ) ;
426+ }
427+
428+ [ Test ]
429+ public void TestMergeReplaceFrontierWrongProjectThrows ( )
430+ {
431+ var parent = Util . RandomWord ( "other-project" ) ;
432+
433+ var ex = Assert . Throws < AggregateException > (
434+ ( ) => _wordService . MergeReplaceFrontier ( ProjId , UserId , [ parent ] , [ ] ) . Wait ( ) ) ;
435+ Assert . That ( ex ? . InnerException , Is . InstanceOf < ArgumentException > ( ) ) ;
436+ }
437+
438+ [ Test ]
439+ public void TestMergeReplaceFrontierDeleteOnlyReturnsEmpty ( )
440+ {
441+ var kid1 = _wordRepo . Create ( new Word { ProjectId = ProjId } ) . Result ;
442+ var kid2 = _wordRepo . Create ( new Word { ProjectId = ProjId } ) . Result ;
443+
444+ var mergedParents = _wordService . MergeReplaceFrontier ( ProjId , UserId , [ ] , [ kid1 . Id , kid2 . Id ] ) . Result ;
445+
446+ Assert . That ( mergedParents , Is . Not . Null ) ;
447+ Assert . That ( mergedParents , Is . Empty ) ;
448+ Assert . That ( _wordRepo . GetAllFrontier ( ProjId ) . Result , Is . Empty ) ;
449+
450+ var allWords = _wordRepo . GetAllWords ( ProjId ) . Result ;
451+ Assert . That ( allWords . Any ( w => w . Accessibility == Status . Deleted && w . History . Contains ( kid1 . Id ) ) , Is . True ) ;
452+ Assert . That ( allWords . Any ( w => w . Accessibility == Status . Deleted && w . History . Contains ( kid2 . Id ) ) , Is . True ) ;
453+ }
454+
455+ [ Test ]
456+ public void TestRevertMergeReplaceFrontierDeletesAndRestores ( )
457+ {
458+ var wordToRestore = _wordRepo . Add ( new Word { ProjectId = ProjId } ) . Result ;
459+ var frontierWordToDelete = _wordRepo . Create ( new Word { ProjectId = ProjId } ) . Result ;
460+
461+ var result = _wordService . RevertMergeReplaceFrontier (
462+ ProjId , UserId , [ wordToRestore . Id ] , [ frontierWordToDelete . Id ] ) . Result ;
463+
464+ Assert . That ( result , Is . True ) ;
465+ Assert . That ( _wordRepo . IsInFrontier ( ProjId , wordToRestore . Id ) . Result , Is . True ) ;
466+ Assert . That ( _wordRepo . IsInFrontier ( ProjId , frontierWordToDelete . Id ) . Result , Is . False ) ;
467+
468+ var deletedCopy = _wordRepo . GetAllWords ( ProjId ) . Result
469+ . Find ( w => w . Accessibility == Status . Deleted && w . History . Contains ( frontierWordToDelete . Id ) ) ;
470+ Assert . That ( deletedCopy , Is . Not . Null ) ;
471+ Assert . That ( deletedCopy . EditedBy . Last ( ) , Is . EqualTo ( UserId ) ) ;
472+ }
473+
474+ [ Test ]
475+ public void TestRevertMergeReplaceFrontierMissingRestoreReturnsFalse ( )
476+ {
477+ var frontierWordToDelete = _wordRepo . Create ( new Word { ProjectId = ProjId } ) . Result ;
478+
479+ var result = _wordService . RevertMergeReplaceFrontier (
480+ ProjId , UserId , [ "missing-id" ] , [ frontierWordToDelete . Id ] ) . Result ;
481+
482+ Assert . That ( result , Is . False ) ;
483+ Assert . That ( _wordRepo . IsInFrontier ( ProjId , frontierWordToDelete . Id ) . Result , Is . False ) ;
484+ }
485+
486+ [ Test ]
487+ public void TestRevertMergeReplaceFrontierOverlappingIdsThrows ( )
488+ {
489+ var word = _wordRepo . Create ( new Word { ProjectId = ProjId } ) . Result ;
490+
491+ var ex = Assert . Throws < AggregateException > (
492+ ( ) => _wordService . RevertMergeReplaceFrontier ( ProjId , UserId , [ word . Id ] , [ word . Id ] ) . Wait ( ) ) ;
493+ Assert . That ( ex ? . InnerException , Is . InstanceOf < ArgumentException > ( ) ) ;
494+ }
495+
496+ [ Test ]
497+ public void TestRevertMergeReplaceFrontierNoOpReturnsTrueAndLeavesStateUnchanged ( )
498+ {
499+ _wordRepo . Create ( new Word { ProjectId = ProjId } ) . Wait ( ) ;
500+ _wordRepo . Add ( new Word { ProjectId = ProjId } ) . Wait ( ) ;
501+
502+ var wordsBefore = _wordRepo . GetAllWords ( ProjId ) . Result . Select ( w => w . Id ) . OrderBy ( id => id ) . ToList ( ) ;
503+ var frontierBefore = _wordRepo . GetAllFrontier ( ProjId ) . Result . Select ( w => w . Id ) . OrderBy ( id => id ) . ToList ( ) ;
504+
505+ var result = _wordService . RevertMergeReplaceFrontier ( ProjId , UserId , [ ] , [ ] ) . Result ;
506+
507+ var wordsAfter = _wordRepo . GetAllWords ( ProjId ) . Result . Select ( w => w . Id ) . OrderBy ( id => id ) . ToList ( ) ;
508+ var frontierAfter = _wordRepo . GetAllFrontier ( ProjId ) . Result . Select ( w => w . Id ) . OrderBy ( id => id ) . ToList ( ) ;
509+
510+ Assert . That ( result , Is . True ) ;
511+ Assert . That ( wordsAfter , Is . EqualTo ( wordsBefore ) ) ;
512+ Assert . That ( frontierAfter , Is . EqualTo ( frontierBefore ) ) ;
513+ }
294514 }
295515}
0 commit comments