@@ -119,6 +119,64 @@ describe(LibraryService.name, () => {
119
119
} ) ;
120
120
} ) ;
121
121
122
+ describe ( 'onConfigUpdateEvent' , ( ) => {
123
+ beforeEach ( async ( ) => {
124
+ systemMock . get . mockResolvedValue ( defaults ) ;
125
+ databaseMock . tryLock . mockResolvedValue ( true ) ;
126
+ await sut . onBootstrap ( ) ;
127
+ } ) ;
128
+
129
+ it ( 'should do nothing if oldConfig is not provided' , async ( ) => {
130
+ await sut . onConfigUpdate ( { newConfig : systemConfigStub . libraryScan as SystemConfig } ) ;
131
+ expect ( jobMock . updateCronJob ) . not . toHaveBeenCalled ( ) ;
132
+ } ) ;
133
+
134
+ it ( 'should do nothing if instance does not have the watch lock' , async ( ) => {
135
+ databaseMock . tryLock . mockResolvedValue ( false ) ;
136
+ await sut . onBootstrap ( ) ;
137
+ await sut . onConfigUpdate ( { newConfig : systemConfigStub . libraryScan as SystemConfig , oldConfig : defaults } ) ;
138
+ expect ( jobMock . updateCronJob ) . not . toHaveBeenCalled ( ) ;
139
+ } ) ;
140
+
141
+ it ( 'should update cron job and enable watching' , async ( ) => {
142
+ libraryMock . getAll . mockResolvedValue ( [ ] ) ;
143
+ await sut . onConfigUpdate ( {
144
+ newConfig : {
145
+ library : { ...systemConfigStub . libraryScan . library , ...systemConfigStub . libraryWatchEnabled . library } ,
146
+ } as SystemConfig ,
147
+ oldConfig : defaults ,
148
+ } ) ;
149
+
150
+ expect ( jobMock . updateCronJob ) . toHaveBeenCalledWith (
151
+ 'libraryScan' ,
152
+ systemConfigStub . libraryScan . library . scan . cronExpression ,
153
+ systemConfigStub . libraryScan . library . scan . enabled ,
154
+ ) ;
155
+ } ) ;
156
+
157
+ it ( 'should update cron job and disable watching' , async ( ) => {
158
+ libraryMock . getAll . mockResolvedValue ( [ ] ) ;
159
+ await sut . onConfigUpdate ( {
160
+ newConfig : {
161
+ library : { ...systemConfigStub . libraryScan . library , ...systemConfigStub . libraryWatchEnabled . library } ,
162
+ } as SystemConfig ,
163
+ oldConfig : defaults ,
164
+ } ) ;
165
+ await sut . onConfigUpdate ( {
166
+ newConfig : {
167
+ library : { ...systemConfigStub . libraryScan . library , ...systemConfigStub . libraryWatchDisabled . library } ,
168
+ } as SystemConfig ,
169
+ oldConfig : defaults ,
170
+ } ) ;
171
+
172
+ expect ( jobMock . updateCronJob ) . toHaveBeenCalledWith (
173
+ 'libraryScan' ,
174
+ systemConfigStub . libraryScan . library . scan . cronExpression ,
175
+ systemConfigStub . libraryScan . library . scan . enabled ,
176
+ ) ;
177
+ } ) ;
178
+ } ) ;
179
+
122
180
describe ( 'onConfigValidateEvent' , ( ) => {
123
181
it ( 'should allow a valid cron expression' , ( ) => {
124
182
expect ( ( ) =>
@@ -139,7 +197,7 @@ describe(LibraryService.name, () => {
139
197
} ) ;
140
198
} ) ;
141
199
142
- describe ( 'handleQueueAssetRefresh ' , ( ) => {
200
+ describe ( 'handleQueueSyncFiles ' , ( ) => {
143
201
it ( 'should queue refresh of a new asset' , async ( ) => {
144
202
libraryMock . get . mockResolvedValue ( libraryStub . externalLibrary1 ) ;
145
203
storageMock . walk . mockImplementation ( mockWalk ) ;
@@ -559,8 +617,8 @@ describe(LibraryService.name, () => {
559
617
expect ( jobMock . queueAll ) . not . toHaveBeenCalled ( ) ;
560
618
} ) ;
561
619
562
- it ( 'should throw BadRequestException when asset does not exist ' , async ( ) => {
563
- storageMock . stat . mockRejectedValue ( new Error ( "ENOENT, no such file or directory '/data/user1/photo.jpg'" ) ) ;
620
+ it ( 'should fail when the file could not be read ' , async ( ) => {
621
+ storageMock . stat . mockRejectedValue ( new Error ( 'Could not read file' ) ) ;
564
622
565
623
const mockLibraryJob : ILibraryFileJob = {
566
624
id : libraryStub . externalLibrary1 . id ,
@@ -572,6 +630,27 @@ describe(LibraryService.name, () => {
572
630
assetMock . create . mockResolvedValue ( assetStub . image ) ;
573
631
574
632
await expect ( sut . handleSyncFile ( mockLibraryJob ) ) . resolves . toBe ( JobStatus . FAILED ) ;
633
+ expect ( libraryMock . get ) . not . toHaveBeenCalled ( ) ;
634
+ expect ( assetMock . create ) . not . toHaveBeenCalled ( ) ;
635
+ } ) ;
636
+
637
+ it ( 'should skip if the file could not be found' , async ( ) => {
638
+ const error = new Error ( 'File not found' ) as any ;
639
+ error . code = 'ENOENT' ;
640
+ storageMock . stat . mockRejectedValue ( error ) ;
641
+
642
+ const mockLibraryJob : ILibraryFileJob = {
643
+ id : libraryStub . externalLibrary1 . id ,
644
+ ownerId : userStub . admin . id ,
645
+ assetPath : '/data/user1/photo.jpg' ,
646
+ } ;
647
+
648
+ assetMock . getByLibraryIdAndOriginalPath . mockResolvedValue ( null ) ;
649
+ assetMock . create . mockResolvedValue ( assetStub . image ) ;
650
+
651
+ await expect ( sut . handleSyncFile ( mockLibraryJob ) ) . resolves . toBe ( JobStatus . SKIPPED ) ;
652
+ expect ( libraryMock . get ) . not . toHaveBeenCalled ( ) ;
653
+ expect ( assetMock . create ) . not . toHaveBeenCalled ( ) ;
575
654
} ) ;
576
655
} ) ;
577
656
@@ -654,6 +733,10 @@ describe(LibraryService.name, () => {
654
733
655
734
expect ( libraryMock . getStatistics ) . toHaveBeenCalledWith ( libraryStub . externalLibrary1 . id ) ;
656
735
} ) ;
736
+
737
+ it ( 'should throw an error if the library could not be found' , async ( ) => {
738
+ await expect ( sut . getStatistics ( 'foo' ) ) . rejects . toBeInstanceOf ( BadRequestException ) ;
739
+ } ) ;
657
740
} ) ;
658
741
659
742
describe ( 'create' , ( ) => {
@@ -783,6 +866,13 @@ describe(LibraryService.name, () => {
783
866
} ) ;
784
867
} ) ;
785
868
869
+ describe ( 'getAll' , ( ) => {
870
+ it ( 'should get all libraries' , async ( ) => {
871
+ libraryMock . getAll . mockResolvedValue ( [ libraryStub . externalLibrary1 ] ) ;
872
+ await expect ( sut . getAll ( ) ) . resolves . toEqual ( [ expect . objectContaining ( { id : libraryStub . externalLibrary1 . id } ) ] ) ;
873
+ } ) ;
874
+ } ) ;
875
+
786
876
describe ( 'handleQueueCleanup' , ( ) => {
787
877
it ( 'should queue cleanup jobs' , async ( ) => {
788
878
libraryMock . getAllDeleted . mockResolvedValue ( [ libraryStub . externalLibrary1 , libraryStub . externalLibrary2 ] ) ;
@@ -803,15 +893,38 @@ describe(LibraryService.name, () => {
803
893
await sut . onBootstrap ( ) ;
804
894
} ) ;
805
895
896
+ it ( 'should throw an error if an import path is invalid' , async ( ) => {
897
+ libraryMock . update . mockResolvedValue ( libraryStub . externalLibrary1 ) ;
898
+ libraryMock . get . mockResolvedValue ( libraryStub . externalLibrary1 ) ;
899
+
900
+ await expect ( sut . update ( 'library-id' , { importPaths : [ 'foo/bar' ] } ) ) . rejects . toBeInstanceOf ( BadRequestException ) ;
901
+ expect ( libraryMock . update ) . not . toHaveBeenCalled ( ) ;
902
+ } ) ;
903
+
806
904
it ( 'should update library' , async ( ) => {
807
905
libraryMock . update . mockResolvedValue ( libraryStub . externalLibrary1 ) ;
808
906
libraryMock . get . mockResolvedValue ( libraryStub . externalLibrary1 ) ;
809
- await expect ( sut . update ( 'library-id' , { } ) ) . resolves . toEqual ( mapLibrary ( libraryStub . externalLibrary1 ) ) ;
907
+ storageMock . stat . mockResolvedValue ( { isDirectory : ( ) => true } as Stats ) ;
908
+ storageMock . checkFileExists . mockResolvedValue ( true ) ;
909
+
910
+ await expect ( sut . update ( 'library-id' , { importPaths : [ 'foo/bar' ] } ) ) . resolves . toEqual (
911
+ mapLibrary ( libraryStub . externalLibrary1 ) ,
912
+ ) ;
810
913
expect ( libraryMock . update ) . toHaveBeenCalledWith ( expect . objectContaining ( { id : 'library-id' } ) ) ;
811
914
} ) ;
812
915
} ) ;
813
916
917
+ describe ( 'onShutdown' , ( ) => {
918
+ it ( 'should do nothing if instance does not have the watch lock' , async ( ) => {
919
+ await sut . onShutdown ( ) ;
920
+ } ) ;
921
+ } ) ;
922
+
814
923
describe ( 'watchAll' , ( ) => {
924
+ it ( 'should return false if instance does not have the watch lock' , async ( ) => {
925
+ await expect ( sut . watchAll ( ) ) . resolves . toBe ( false ) ;
926
+ } ) ;
927
+
815
928
describe ( 'watching disabled' , ( ) => {
816
929
beforeEach ( async ( ) => {
817
930
systemMock . get . mockResolvedValue ( systemConfigStub . libraryWatchDisabled ) ;
@@ -872,6 +985,7 @@ describe(LibraryService.name, () => {
872
985
it ( 'should handle a new file event' , async ( ) => {
873
986
libraryMock . get . mockResolvedValue ( libraryStub . externalLibraryWithImportPaths1 ) ;
874
987
libraryMock . getAll . mockResolvedValue ( [ libraryStub . externalLibraryWithImportPaths1 ] ) ;
988
+ assetMock . getByLibraryIdAndOriginalPath . mockResolvedValue ( assetStub . image ) ;
875
989
storageMock . watch . mockImplementation ( makeMockWatcher ( { items : [ { event : 'add' , value : '/foo/photo.jpg' } ] } ) ) ;
876
990
877
991
await sut . watchAll ( ) ;
@@ -886,11 +1000,15 @@ describe(LibraryService.name, () => {
886
1000
} ,
887
1001
} ,
888
1002
] ) ;
1003
+ expect ( jobMock . queueAll ) . toHaveBeenCalledWith ( [
1004
+ { name : JobName . LIBRARY_SYNC_ASSET , data : expect . objectContaining ( { id : assetStub . image . id } ) } ,
1005
+ ] ) ;
889
1006
} ) ;
890
1007
891
1008
it ( 'should handle a file change event' , async ( ) => {
892
1009
libraryMock . get . mockResolvedValue ( libraryStub . externalLibraryWithImportPaths1 ) ;
893
1010
libraryMock . getAll . mockResolvedValue ( [ libraryStub . externalLibraryWithImportPaths1 ] ) ;
1011
+ assetMock . getByLibraryIdAndOriginalPath . mockResolvedValue ( assetStub . image ) ;
894
1012
storageMock . watch . mockImplementation (
895
1013
makeMockWatcher ( { items : [ { event : 'change' , value : '/foo/photo.jpg' } ] } ) ,
896
1014
) ;
@@ -907,6 +1025,24 @@ describe(LibraryService.name, () => {
907
1025
} ,
908
1026
} ,
909
1027
] ) ;
1028
+ expect ( jobMock . queueAll ) . toHaveBeenCalledWith ( [
1029
+ { name : JobName . LIBRARY_SYNC_ASSET , data : expect . objectContaining ( { id : assetStub . image . id } ) } ,
1030
+ ] ) ;
1031
+ } ) ;
1032
+
1033
+ it ( 'should handle a file unlink event' , async ( ) => {
1034
+ libraryMock . get . mockResolvedValue ( libraryStub . externalLibraryWithImportPaths1 ) ;
1035
+ libraryMock . getAll . mockResolvedValue ( [ libraryStub . externalLibraryWithImportPaths1 ] ) ;
1036
+ assetMock . getByLibraryIdAndOriginalPath . mockResolvedValue ( assetStub . image ) ;
1037
+ storageMock . watch . mockImplementation (
1038
+ makeMockWatcher ( { items : [ { event : 'unlink' , value : '/foo/photo.jpg' } ] } ) ,
1039
+ ) ;
1040
+
1041
+ await sut . watchAll ( ) ;
1042
+
1043
+ expect ( jobMock . queueAll ) . toHaveBeenCalledWith ( [
1044
+ { name : JobName . LIBRARY_SYNC_ASSET , data : expect . objectContaining ( { id : assetStub . image . id } ) } ,
1045
+ ] ) ;
910
1046
} ) ;
911
1047
912
1048
it ( 'should handle an error event' , async ( ) => {
@@ -986,15 +1122,14 @@ describe(LibraryService.name, () => {
986
1122
it ( 'should delete an empty library' , async ( ) => {
987
1123
libraryMock . get . mockResolvedValue ( libraryStub . externalLibrary1 ) ;
988
1124
assetMock . getAll . mockResolvedValue ( { items : [ ] , hasNextPage : false } ) ;
989
- libraryMock . delete . mockImplementation ( async ( ) => { } ) ;
990
1125
991
1126
await expect ( sut . handleDeleteLibrary ( { id : libraryStub . externalLibrary1 . id } ) ) . resolves . toBe ( JobStatus . SUCCESS ) ;
1127
+ expect ( libraryMock . delete ) . toHaveBeenCalled ( ) ;
992
1128
} ) ;
993
1129
994
- it ( 'should delete a library with assets ' , async ( ) => {
1130
+ it ( 'should delete all assets in a library ' , async ( ) => {
995
1131
libraryMock . get . mockResolvedValue ( libraryStub . externalLibrary1 ) ;
996
1132
assetMock . getAll . mockResolvedValue ( { items : [ assetStub . image1 ] , hasNextPage : false } ) ;
997
- libraryMock . delete . mockImplementation ( async ( ) => { } ) ;
998
1133
999
1134
assetMock . getById . mockResolvedValue ( assetStub . image1 ) ;
1000
1135
@@ -1076,6 +1211,10 @@ describe(LibraryService.name, () => {
1076
1211
} ) ;
1077
1212
1078
1213
describe ( 'validate' , ( ) => {
1214
+ it ( 'should not require import paths' , async ( ) => {
1215
+ await expect ( sut . validate ( 'library-id' , { } ) ) . resolves . toEqual ( { importPaths : [ ] } ) ;
1216
+ } ) ;
1217
+
1079
1218
it ( 'should validate directory' , async ( ) => {
1080
1219
storageMock . stat . mockResolvedValue ( {
1081
1220
isDirectory : ( ) => true ,
0 commit comments