@ -1,6 +1,5 @@
import { Stats } from 'node:fs' ;
import { defaults , SystemConfig } from 'src/config' ;
import { APP_MEDIA_LOCATION } from 'src/constants' ;
import { AssetPathType , JobStatus } from 'src/enum' ;
import { StorageTemplateService } from 'src/services/storage-template.service' ;
import { albumStub } from 'test/fixtures/album.stub' ;
@ -111,10 +110,8 @@ describe(StorageTemplateService.name, () => {
it ( 'should migrate single moving picture' , async ( ) = > {
mocks . user . get . mockResolvedValue ( userStub . user1 ) ;
const newMotionPicturePath =
APP_MEDIA_LOCATION + ` /library/ ${ motionAsset . ownerId } /2022/2022-06-19/ ${ motionAsset . originalFileName } ` ;
const newStillPicturePath =
APP_MEDIA_LOCATION + ` /library/ ${ stillAsset . ownerId } /2022/2022-06-19/ ${ stillAsset . originalFileName } ` ;
const newMotionPicturePath = ` /data/library/ ${ motionAsset . ownerId } /2022/2022-06-19/ ${ motionAsset . originalFileName } ` ;
const newStillPicturePath = ` /data/library/ ${ stillAsset . ownerId } /2022/2022-06-19/ ${ stillAsset . originalFileName } ` ;
mocks . assetJob . getForStorageTemplateJob . mockResolvedValueOnce ( stillAsset ) ;
mocks . assetJob . getForStorageTemplateJob . mockResolvedValueOnce ( motionAsset ) ;
@ -160,7 +157,7 @@ describe(StorageTemplateService.name, () => {
expect ( mocks . move . create ) . toHaveBeenCalledWith ( {
entityId : asset.id ,
newPath : expect.stringContaining (
` upload /library/${ user . id } / ${ asset . fileCreatedAt . getFullYear ( ) } / ${ album . albumName } / ${ asset . originalFileName } ` ,
` /data /library/${ user . id } / ${ asset . fileCreatedAt . getFullYear ( ) } / ${ album . albumName } / ${ asset . originalFileName } ` ,
) ,
oldPath : asset.originalPath ,
pathType : AssetPathType.Original ,
@ -183,7 +180,7 @@ describe(StorageTemplateService.name, () => {
expect ( mocks . move . create ) . toHaveBeenCalledWith ( {
entityId : asset.id ,
newPath : expect.stringContaining (
` upload /library/${ user . id } / ${ asset . fileCreatedAt . getFullYear ( ) } /other/ ${ month } / ${ asset . originalFileName } ` ,
` /data /library/${ user . id } / ${ asset . fileCreatedAt . getFullYear ( ) } /other/ ${ month } / ${ asset . originalFileName } ` ,
) ,
oldPath : asset.originalPath ,
pathType : AssetPathType.Original ,
@ -219,7 +216,7 @@ describe(StorageTemplateService.name, () => {
expect ( mocks . move . create ) . toHaveBeenCalledWith ( {
entityId : asset.id ,
newPath : expect.stringContaining (
` upload /library/${ user . id } / ${ asset . fileCreatedAt . getFullYear ( ) } / ${ month } - ${ album . albumName } / ${ asset . originalFileName } ` ,
` /data /library/${ user . id } / ${ asset . fileCreatedAt . getFullYear ( ) } / ${ month } - ${ album . albumName } / ${ asset . originalFileName } ` ,
) ,
oldPath : asset.originalPath ,
pathType : AssetPathType.Original ,
@ -243,9 +240,7 @@ describe(StorageTemplateService.name, () => {
const month = ( asset . fileCreatedAt . getMonth ( ) + 1 ) . toString ( ) . padStart ( 2 , '0' ) ;
expect ( mocks . move . create ) . toHaveBeenCalledWith ( {
entityId : asset.id ,
newPath :
APP_MEDIA_LOCATION +
` /library/ ${ user . id } / ${ asset . fileCreatedAt . getFullYear ( ) } / ${ month } / ${ asset . originalFileName } ` ,
newPath : ` /data/library/ ${ user . id } / ${ asset . fileCreatedAt . getFullYear ( ) } / ${ month } / ${ asset . originalFileName } ` ,
oldPath : asset.originalPath ,
pathType : AssetPathType.Original ,
} ) ;
@ -255,9 +250,8 @@ describe(StorageTemplateService.name, () => {
mocks . user . get . mockResolvedValue ( userStub . user1 ) ;
const asset = assetStub . storageAsset ( ) ;
const previousFailedNewPath =
APP_MEDIA_LOCATION + ` /library/ ${ userStub . user1 . id } /2023/Feb/ ${ asset . originalFileName } ` ;
const newPath = APP_MEDIA_LOCATION + ` /library/ ${ userStub . user1 . id } /2022/2022-06-19/ ${ asset . originalFileName } ` ;
const previousFailedNewPath = ` /data/library/ ${ userStub . user1 . id } /2023/Feb/ ${ asset . originalFileName } ` ;
const newPath = ` /data/library/ ${ userStub . user1 . id } /2022/2022-06-19/ ${ asset . originalFileName } ` ;
mocks . storage . checkFileExists . mockImplementation ( ( path ) = > Promise . resolve ( path === asset . originalPath ) ) ;
mocks . move . getByEntity . mockResolvedValue ( {
@ -296,9 +290,8 @@ describe(StorageTemplateService.name, () => {
mocks . user . get . mockResolvedValue ( userStub . user1 ) ;
const asset = assetStub . storageAsset ( { fileSizeInByte : 5000 } ) ;
const previousFailedNewPath =
APP_MEDIA_LOCATION + ` /library/ ${ asset . ownerId } /2022/June/ ${ asset . originalFileName } ` ;
const newPath = APP_MEDIA_LOCATION + ` /library/ ${ asset . ownerId } /2022/2022-06-19/ ${ asset . originalFileName } ` ;
const previousFailedNewPath = ` /data/library/ ${ asset . ownerId } /2022/June/ ${ asset . originalFileName } ` ;
const newPath = ` /data/library/ ${ asset . ownerId } /2022/2022-06-19/ ${ asset . originalFileName } ` ;
mocks . storage . checkFileExists . mockImplementation ( ( path ) = > Promise . resolve ( path === previousFailedNewPath ) ) ;
mocks . storage . stat . mockResolvedValue ( { size : 5000 } as Stats ) ;
@ -332,8 +325,7 @@ describe(StorageTemplateService.name, () => {
it ( 'should fail move if copying and hash of asset and the new file do not match' , async ( ) = > {
mocks . user . get . mockResolvedValue ( userStub . user1 ) ;
const newPath =
APP_MEDIA_LOCATION + ` /library/ ${ userStub . user1 . id } /2022/2022-06-19/ ${ testAsset . originalFileName } ` ;
const newPath = ` /data/library/ ${ userStub . user1 . id } /2022/2022-06-19/ ${ testAsset . originalFileName } ` ;
mocks . storage . rename . mockRejectedValue ( { code : 'EXDEV' } ) ;
mocks . storage . stat . mockResolvedValue ( { size : 5000 } as Stats ) ;
@ -375,8 +367,8 @@ describe(StorageTemplateService.name, () => {
'should fail to migrate previously failed move from previous new path when old path no longer exists if $reason validation fails' ,
async ( { failedPathChecksum , failedPathSize } ) = > {
mocks . user . get . mockResolvedValue ( userStub . user1 ) ;
const previousFailedNewPath = ` upload /library/${ userStub . user1 . id } /2023/Feb/ ${ testAsset . originalFileName } ` ;
const newPath = ` upload /library/${ userStub . user1 . id } /2023/2023-02-23/ ${ testAsset . originalFileName } ` ;
const previousFailedNewPath = ` /data /library/${ userStub . user1 . id } /2023/Feb/ ${ testAsset . originalFileName } ` ;
const newPath = ` /data /library/${ userStub . user1 . id } /2023/2023-02-23/ ${ testAsset . originalFileName } ` ;
mocks . storage . checkFileExists . mockImplementation ( ( path ) = > Promise . resolve ( previousFailedNewPath === path ) ) ;
mocks . storage . stat . mockResolvedValue ( { size : failedPathSize } as Stats ) ;
@ -423,7 +415,7 @@ describe(StorageTemplateService.name, () => {
it ( 'should handle an asset with a duplicate destination' , async ( ) = > {
const asset = assetStub . storageAsset ( ) ;
const oldPath = asset . originalPath ;
const newPath = APP_MEDIA_LOCATION + ` /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ;
const newPath = ` /data /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ;
const newPath2 = newPath . replace ( '.jpg' , '+1.jpg' ) ;
mocks . assetJob . streamForStorageTemplateJob . mockReturnValue ( makeStream ( [ asset ] ) ) ;
@ -448,7 +440,7 @@ describe(StorageTemplateService.name, () => {
} ) ;
it ( 'should skip when an asset already matches the template' , async ( ) = > {
const asset = assetStub . storageAsset ( { originalPath : ' upload /library/user-id/2023/2023-02-23/asset-id.jpg' } ) ;
const asset = assetStub . storageAsset ( { originalPath : ' /data /library/user-id/2023/2023-02-23/asset-id.jpg' } ) ;
mocks . assetJob . streamForStorageTemplateJob . mockReturnValue ( makeStream ( [ asset ] ) ) ;
mocks . user . getList . mockResolvedValue ( [ userStub . user1 ] ) ;
@ -463,7 +455,7 @@ describe(StorageTemplateService.name, () => {
} ) ;
it ( 'should skip when an asset is probably a duplicate' , async ( ) = > {
const asset = assetStub . storageAsset ( { originalPath : ' upload /library/user-id/2023/2023-02-23/asset-id+1.jpg' } ) ;
const asset = assetStub . storageAsset ( { originalPath : ' /data /library/user-id/2023/2023-02-23/asset-id+1.jpg' } ) ;
mocks . assetJob . streamForStorageTemplateJob . mockReturnValue ( makeStream ( [ asset ] ) ) ;
mocks . user . getList . mockResolvedValue ( [ userStub . user1 ] ) ;
@ -480,7 +472,7 @@ describe(StorageTemplateService.name, () => {
it ( 'should move an asset' , async ( ) = > {
const asset = assetStub . storageAsset ( ) ;
const oldPath = asset . originalPath ;
const newPath = APP_MEDIA_LOCATION + ` /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ;
const newPath = ` /data /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ;
mocks . assetJob . streamForStorageTemplateJob . mockReturnValue ( makeStream ( [ asset ] ) ) ;
mocks . user . getList . mockResolvedValue ( [ userStub . user1 ] ) ;
mocks . move . create . mockResolvedValue ( {
@ -508,7 +500,7 @@ describe(StorageTemplateService.name, () => {
entityId : asset.id ,
pathType : AssetPathType.Original ,
oldPath : asset.originalPath ,
newPath : ` upload /library/${ user . storageLabel } /2023/2023-02-23/ ${ asset . originalFileName } ` ,
newPath : ` /data /library/${ user . storageLabel } /2023/2023-02-23/ ${ asset . originalFileName } ` ,
} ) ;
await sut . handleMigration ( ) ;
@ -516,12 +508,12 @@ describe(StorageTemplateService.name, () => {
expect ( mocks . assetJob . streamForStorageTemplateJob ) . toHaveBeenCalled ( ) ;
expect ( mocks . storage . rename ) . toHaveBeenCalledWith (
'/original/path.jpg' ,
expect . stringContaining ( ` upload /library/${ user . storageLabel } /2022/2022-06-19/ ${ asset . originalFileName } ` ) ,
expect . stringContaining ( ` /data /library/${ user . storageLabel } /2022/2022-06-19/ ${ asset . originalFileName } ` ) ,
) ;
expect ( mocks . asset . update ) . toHaveBeenCalledWith ( {
id : asset.id ,
originalPath : expect.stringContaining (
` upload /library/${ user . storageLabel } /2022/2022-06-19/ ${ asset . originalFileName } ` ,
` /data /library/${ user . storageLabel } /2022/2022-06-19/ ${ asset . originalFileName } ` ,
) ,
} ) ;
} ) ;
@ -529,7 +521,7 @@ describe(StorageTemplateService.name, () => {
it ( 'should copy the file if rename fails due to EXDEV (rename across filesystems)' , async ( ) = > {
const asset = assetStub . storageAsset ( { originalPath : '/path/to/original.jpg' , fileSizeInByte : 5000 } ) ;
const oldPath = asset . originalPath ;
const newPath = APP_MEDIA_LOCATION + ` /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ;
const newPath = ` /data /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ;
mocks . assetJob . streamForStorageTemplateJob . mockReturnValue ( makeStream ( [ asset ] ) ) ;
mocks . storage . rename . mockRejectedValue ( { code : 'EXDEV' } ) ;
mocks . user . getList . mockResolvedValue ( [ userStub . user1 ] ) ;
@ -577,7 +569,7 @@ describe(StorageTemplateService.name, () => {
entityId : asset.id ,
pathType : AssetPathType.Original ,
oldPath : asset.originalPath ,
newPath : ` upload /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ,
newPath : ` /data /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ,
} ) ;
mocks . storage . stat . mockResolvedValue ( {
size : 100 ,
@ -588,14 +580,14 @@ describe(StorageTemplateService.name, () => {
expect ( mocks . assetJob . streamForStorageTemplateJob ) . toHaveBeenCalled ( ) ;
expect ( mocks . storage . rename ) . toHaveBeenCalledWith (
'/original/path.jpg' ,
expect . stringContaining ( ` upload /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ) ,
expect . stringContaining ( ` /data /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ) ,
) ;
expect ( mocks . storage . copyFile ) . toHaveBeenCalledWith (
'/original/path.jpg' ,
expect . stringContaining ( ` upload /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ) ,
expect . stringContaining ( ` /data /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ) ,
) ;
expect ( mocks . storage . stat ) . toHaveBeenCalledWith (
expect . stringContaining ( ` upload /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ) ,
expect . stringContaining ( ` /data /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ) ,
) ;
expect ( mocks . asset . update ) . not . toHaveBeenCalled ( ) ;
} ) ;
@ -619,7 +611,7 @@ describe(StorageTemplateService.name, () => {
expect ( mocks . assetJob . streamForStorageTemplateJob ) . toHaveBeenCalled ( ) ;
expect ( mocks . storage . rename ) . toHaveBeenCalledWith (
'/original/path.jpg' ,
expect . stringContaining ( ` upload /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ) ,
expect . stringContaining ( ` /data /library/user-id/2022/2022-06-19/${ asset . originalFileName } ` ) ,
) ;
expect ( mocks . asset . update ) . not . toHaveBeenCalled ( ) ;
} ) ;
@ -630,7 +622,7 @@ describe(StorageTemplateService.name, () => {
const user = factory . userAdmin ( { storageLabel : 'label-1' } ) ;
const asset = assetStub . storageAsset ( {
ownerId : user.id ,
originalPath : ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.heic ` ,
originalPath : ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.heic ` ,
originalFileName : 'IMG_7065.HEIC' ,
} ) ;
mocks . assetJob . streamForStorageTemplateJob . mockReturnValue ( makeStream ( [ asset ] ) ) ;
@ -639,16 +631,16 @@ describe(StorageTemplateService.name, () => {
id : '123' ,
entityId : asset.id ,
pathType : AssetPathType.Original ,
oldPath : ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.heic ` ,
newPath : ` upload /library/${ user . id } /2023/2023-02-23/IMG_7065.heic ` ,
oldPath : ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.heic ` ,
newPath : ` /data /library/${ user . id } /2023/2023-02-23/IMG_7065.heic ` ,
} ) ;
await sut . handleMigration ( ) ;
expect ( mocks . assetJob . streamForStorageTemplateJob ) . toHaveBeenCalled ( ) ;
expect ( mocks . storage . rename ) . toHaveBeenCalledWith (
expect . stringContaining ( ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.heic ` ) ,
expect . stringContaining ( ` upload /library/${ user . storageLabel } /2022/2022-06-19/IMG_7065.heic ` ) ,
expect . stringContaining ( ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.heic ` ) ,
expect . stringContaining ( ` /data /library/${ user . storageLabel } /2022/2022-06-19/IMG_7065.heic ` ) ,
) ;
} ) ;
@ -656,7 +648,7 @@ describe(StorageTemplateService.name, () => {
const user = factory . userAdmin ( ) ;
const asset = assetStub . storageAsset ( {
ownerId : user.id ,
originalPath : ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.HEIC ` ,
originalPath : ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.HEIC ` ,
originalFileName : 'IMG_7065.HEIC' ,
} ) ;
mocks . assetJob . streamForStorageTemplateJob . mockReturnValue ( makeStream ( [ asset ] ) ) ;
@ -665,16 +657,16 @@ describe(StorageTemplateService.name, () => {
id : '123' ,
entityId : asset.id ,
pathType : AssetPathType.Original ,
oldPath : ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.HEIC ` ,
newPath : ` upload /library/${ user . id } /2023/2023-02-23/IMG_7065.heic ` ,
oldPath : ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.HEIC ` ,
newPath : ` /data /library/${ user . id } /2023/2023-02-23/IMG_7065.heic ` ,
} ) ;
await sut . handleMigration ( ) ;
expect ( mocks . assetJob . streamForStorageTemplateJob ) . toHaveBeenCalled ( ) ;
expect ( mocks . storage . rename ) . toHaveBeenCalledWith (
expect . stringContaining ( ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.HEIC ` ) ,
expect . stringContaining ( ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.heic ` ) ,
expect . stringContaining ( ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.HEIC ` ) ,
expect . stringContaining ( ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.heic ` ) ,
) ;
} ) ;
@ -682,7 +674,7 @@ describe(StorageTemplateService.name, () => {
const user = factory . userAdmin ( ) ;
const asset = assetStub . storageAsset ( {
ownerId : user.id ,
originalPath : ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.JPEG ` ,
originalPath : ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.JPEG ` ,
originalFileName : 'IMG_7065.JPEG' ,
} ) ;
mocks . assetJob . streamForStorageTemplateJob . mockReturnValue ( makeStream ( [ asset ] ) ) ;
@ -691,16 +683,16 @@ describe(StorageTemplateService.name, () => {
id : '123' ,
entityId : asset.id ,
pathType : AssetPathType.Original ,
oldPath : ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.JPEG ` ,
newPath : ` upload /library/${ user . id } /2023/2023-02-23/IMG_7065.jpg ` ,
oldPath : ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.JPEG ` ,
newPath : ` /data /library/${ user . id } /2023/2023-02-23/IMG_7065.jpg ` ,
} ) ;
await sut . handleMigration ( ) ;
expect ( mocks . assetJob . streamForStorageTemplateJob ) . toHaveBeenCalled ( ) ;
expect ( mocks . storage . rename ) . toHaveBeenCalledWith (
expect . stringContaining ( ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.JPEG ` ) ,
expect . stringContaining ( ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.jpg ` ) ,
expect . stringContaining ( ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.JPEG ` ) ,
expect . stringContaining ( ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.jpg ` ) ,
) ;
} ) ;
@ -708,7 +700,7 @@ describe(StorageTemplateService.name, () => {
const user = factory . userAdmin ( ) ;
const asset = assetStub . storageAsset ( {
ownerId : user.id ,
originalPath : ' upload /library/user-id/2022/2022-06-19/IMG_7065.JPG',
originalPath : ' /data /library/user-id/2022/2022-06-19/IMG_7065.JPG',
originalFileName : 'IMG_7065.JPG' ,
} ) ;
mocks . assetJob . streamForStorageTemplateJob . mockReturnValue ( makeStream ( [ asset ] ) ) ;
@ -717,16 +709,16 @@ describe(StorageTemplateService.name, () => {
id : '123' ,
entityId : asset.id ,
pathType : AssetPathType.Original ,
oldPath : ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.JPG ` ,
newPath : ` upload /library/${ user . id } /2023/2023-02-23/IMG_7065.jpg ` ,
oldPath : ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.JPG ` ,
newPath : ` /data /library/${ user . id } /2023/2023-02-23/IMG_7065.jpg ` ,
} ) ;
await sut . handleMigration ( ) ;
expect ( mocks . assetJob . streamForStorageTemplateJob ) . toHaveBeenCalled ( ) ;
expect ( mocks . storage . rename ) . toHaveBeenCalledWith (
expect . stringContaining ( ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.JPG ` ) ,
expect . stringContaining ( ` upload /library/${ user . id } /2022/2022-06-19/IMG_7065.jpg ` ) ,
expect . stringContaining ( ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.JPG ` ) ,
expect . stringContaining ( ` /data /library/${ user . id } /2022/2022-06-19/IMG_7065.jpg ` ) ,
) ;
} ) ;
} ) ;