2019-07-30 09:35:42 +02:00
/* eslint-disable no-unused-vars */
2018-03-15 20:08:46 +02:00
2020-11-05 18:58:23 +02:00
const time = require ( '@joplinapp/lib/time' ) . default ;
const { asyncTest , resourceService , decryptionWorker , encryptionService , loadEncryptionMasterKey , allSyncTargetItemsEncrypted , fileContentEqual , setupDatabase , setupDatabaseAndSynchronizer , db , synchronizer , fileApi , sleep , clearDatabase , switchClient , syncTargetId , objectsEqual , checkThrowAsync } = require ( './test-utils.js' ) ;
const InteropService = require ( '@joplinapp/lib/services/interop/InteropService' ) . default ;
const Folder = require ( '@joplinapp/lib/models/Folder.js' ) ;
const Note = require ( '@joplinapp/lib/models/Note.js' ) ;
const Tag = require ( '@joplinapp/lib/models/Tag.js' ) ;
const NoteTag = require ( '@joplinapp/lib/models/NoteTag.js' ) ;
const Resource = require ( '@joplinapp/lib/models/Resource.js' ) ;
const ItemChange = require ( '@joplinapp/lib/models/ItemChange.js' ) ;
const NoteResource = require ( '@joplinapp/lib/models/NoteResource.js' ) ;
const ResourceService = require ( '@joplinapp/lib/services/ResourceService.js' ) ;
2018-03-15 20:08:46 +02:00
const fs = require ( 'fs-extra' ) ;
2020-11-05 18:58:23 +02:00
const ArrayUtils = require ( '@joplinapp/lib/ArrayUtils' ) ;
const ObjectUtils = require ( '@joplinapp/lib/ObjectUtils' ) ;
const shim = require ( '@joplinapp/lib/shim' ) . default ;
const SearchEngine = require ( '@joplinapp/lib/services/searchengine/SearchEngine' ) ;
2018-03-15 20:08:46 +02:00
process . on ( 'unhandledRejection' , ( reason , p ) => {
console . log ( 'Unhandled Rejection at: Promise' , p , 'reason:' , reason ) ;
} ) ;
function exportDir ( ) {
2019-09-19 23:51:18 +02:00
return ` ${ _ _dirname } /export ` ;
2018-03-15 20:08:46 +02:00
}
function fieldsEqual ( model1 , model2 , fieldNames ) {
for ( let i = 0 ; i < fieldNames . length ; i ++ ) {
const f = fieldNames [ i ] ;
2019-09-19 23:51:18 +02:00
expect ( model1 [ f ] ) . toBe ( model2 [ f ] , ` For key ${ f } ` ) ;
2018-03-15 20:08:46 +02:00
}
}
describe ( 'services_ResourceService' , function ( ) {
beforeEach ( async ( done ) => {
await setupDatabaseAndSynchronizer ( 1 ) ;
2019-04-21 14:49:40 +02:00
await setupDatabaseAndSynchronizer ( 2 ) ;
2018-03-15 20:08:46 +02:00
await switchClient ( 1 ) ;
done ( ) ;
} ) ;
it ( 'should delete orphaned resources' , asyncTest ( async ( ) => {
const service = new ResourceService ( ) ;
2020-03-14 01:46:14 +02:00
const folder1 = await Folder . save ( { title : 'folder1' } ) ;
2018-03-15 20:08:46 +02:00
let note1 = await Note . save ( { title : 'ma note' , parent _id : folder1 . id } ) ;
2019-09-19 23:51:18 +02:00
note1 = await shim . attachFileToNote ( note1 , ` ${ _ _dirname } /../tests/support/photo.jpg ` ) ;
2020-03-14 01:46:14 +02:00
const resource1 = ( await Resource . all ( ) ) [ 0 ] ;
2018-03-15 20:08:46 +02:00
const resourcePath = Resource . fullPath ( resource1 ) ;
await service . indexNoteResources ( ) ;
await service . deleteOrphanResources ( 0 ) ;
expect ( ! ! ( await Resource . load ( resource1 . id ) ) ) . toBe ( true ) ;
await Note . delete ( note1 . id ) ;
await service . deleteOrphanResources ( 0 ) ;
expect ( ! ! ( await Resource . load ( resource1 . id ) ) ) . toBe ( true ) ;
await service . indexNoteResources ( ) ;
await service . deleteOrphanResources ( 1000 * 60 ) ;
expect ( ! ! ( await Resource . load ( resource1 . id ) ) ) . toBe ( true ) ;
await service . deleteOrphanResources ( 0 ) ;
expect ( ! ! ( await Resource . load ( resource1 . id ) ) ) . toBe ( false ) ;
expect ( await shim . fsDriver ( ) . exists ( resourcePath ) ) . toBe ( false ) ;
2018-03-16 19:39:44 +02:00
expect ( ! ( await NoteResource . all ( ) ) . length ) . toBe ( true ) ;
} ) ) ;
it ( 'should not delete resource if still associated with at least one note' , asyncTest ( async ( ) => {
const service = new ResourceService ( ) ;
2020-03-14 01:46:14 +02:00
const folder1 = await Folder . save ( { title : 'folder1' } ) ;
2018-03-16 19:39:44 +02:00
let note1 = await Note . save ( { title : 'ma note' , parent _id : folder1 . id } ) ;
2020-03-14 01:46:14 +02:00
const note2 = await Note . save ( { title : 'ma deuxième note' , parent _id : folder1 . id } ) ;
2019-09-19 23:51:18 +02:00
note1 = await shim . attachFileToNote ( note1 , ` ${ _ _dirname } /../tests/support/photo.jpg ` ) ;
2020-03-14 01:46:14 +02:00
const resource1 = ( await Resource . all ( ) ) [ 0 ] ;
2018-03-16 19:39:44 +02:00
await service . indexNoteResources ( ) ;
await Note . delete ( note1 . id ) ;
2019-07-30 09:35:42 +02:00
2018-03-16 19:39:44 +02:00
await service . indexNoteResources ( ) ;
2019-07-30 09:35:42 +02:00
2018-03-16 19:39:44 +02:00
await Note . save ( { id : note2 . id , body : Resource . markdownTag ( resource1 ) } ) ;
await service . indexNoteResources ( ) ;
await service . deleteOrphanResources ( 0 ) ;
expect ( ! ! ( await Resource . load ( resource1 . id ) ) ) . toBe ( true ) ;
2018-03-15 20:08:46 +02:00
} ) ) ;
2018-06-17 17:59:06 +02:00
it ( 'should not delete a resource that has never been associated with any note, because it probably means the resource came via sync, and associated note has not arrived yet' , asyncTest ( async ( ) => {
const service = new ResourceService ( ) ;
2019-09-19 23:51:18 +02:00
const resource = await shim . createResourceFromPath ( ` ${ _ _dirname } /../tests/support/photo.jpg ` ) ;
2018-06-17 17:59:06 +02:00
await service . indexNoteResources ( ) ;
await service . deleteOrphanResources ( 0 ) ;
expect ( ( await Resource . all ( ) ) . length ) . toBe ( 1 ) ;
} ) ) ;
2018-09-30 20:24:02 +02:00
it ( 'should not delete resource if it is used in an IMG tag' , asyncTest ( async ( ) => {
const service = new ResourceService ( ) ;
2020-03-14 01:46:14 +02:00
const folder1 = await Folder . save ( { title : 'folder1' } ) ;
2018-09-30 20:24:02 +02:00
let note1 = await Note . save ( { title : 'ma note' , parent _id : folder1 . id } ) ;
2019-09-19 23:51:18 +02:00
note1 = await shim . attachFileToNote ( note1 , ` ${ _ _dirname } /../tests/support/photo.jpg ` ) ;
2020-03-14 01:46:14 +02:00
const resource1 = ( await Resource . all ( ) ) [ 0 ] ;
2018-09-30 20:24:02 +02:00
await service . indexNoteResources ( ) ;
2019-09-19 23:51:18 +02:00
await Note . save ( { id : note1 . id , body : ` This is HTML: <img src=":/ ${ resource1 . id } "/> ` } ) ;
2019-07-30 09:35:42 +02:00
2018-09-30 20:24:02 +02:00
await service . indexNoteResources ( ) ;
2019-07-30 09:35:42 +02:00
2018-09-30 20:24:02 +02:00
await service . deleteOrphanResources ( 0 ) ;
expect ( ! ! ( await Resource . load ( resource1 . id ) ) ) . toBe ( true ) ;
} ) ) ;
2018-12-10 02:39:31 +02:00
it ( 'should not process twice the same change' , asyncTest ( async ( ) => {
const service = new ResourceService ( ) ;
2020-03-14 01:46:14 +02:00
const folder1 = await Folder . save ( { title : 'folder1' } ) ;
2018-12-10 02:39:31 +02:00
let note1 = await Note . save ( { title : 'ma note' , parent _id : folder1 . id } ) ;
2019-09-19 23:51:18 +02:00
note1 = await shim . attachFileToNote ( note1 , ` ${ _ _dirname } /../tests/support/photo.jpg ` ) ;
2020-03-14 01:46:14 +02:00
const resource1 = ( await Resource . all ( ) ) [ 0 ] ;
2018-12-10 02:39:31 +02:00
await service . indexNoteResources ( ) ;
const before = ( await NoteResource . all ( ) ) [ 0 ] ;
await time . sleep ( 0.1 ) ;
await service . indexNoteResources ( ) ;
const after = ( await NoteResource . all ( ) ) [ 0 ] ;
expect ( before . last _seen _time ) . toBe ( after . last _seen _time ) ;
} ) ) ;
2019-04-21 14:49:40 +02:00
it ( 'should not delete resources that are associated with an encrypted note' , asyncTest ( async ( ) => {
// https://github.com/laurent22/joplin/issues/1433
//
// Client 1 and client 2 have E2EE setup.
//
// - Client 1 creates note N1 and add resource R1 to it
// - Client 1 syncs
// - Client 2 syncs and get N1
// - Client 2 add resource R2 to N1
// - Client 2 syncs
// - Client 1 syncs
// - Client 1 runs resource indexer - but because N1 hasn't been decrypted yet, it found that R1 is no longer associated with any note
// - Client 1 decrypts notes, but too late
2019-07-30 09:35:42 +02:00
//
2019-04-21 14:49:40 +02:00
// Eventually R1 is deleted because service thinks that it was at some point associated with a note, but no longer.
const masterKey = await loadEncryptionMasterKey ( ) ;
await encryptionService ( ) . enableEncryption ( masterKey , '123456' ) ;
await encryptionService ( ) . loadMasterKeysFromSettings ( ) ;
2020-03-14 01:46:14 +02:00
const folder1 = await Folder . save ( { title : 'folder1' } ) ;
const note1 = await Note . save ( { title : 'ma note' , parent _id : folder1 . id } ) ;
2019-09-19 23:51:18 +02:00
await shim . attachFileToNote ( note1 , ` ${ _ _dirname } /../tests/support/photo.jpg ` ) ; // R1
2019-04-21 14:49:40 +02:00
await resourceService ( ) . indexNoteResources ( ) ;
await synchronizer ( ) . start ( ) ;
expect ( await allSyncTargetItemsEncrypted ( ) ) . toBe ( true ) ;
await switchClient ( 2 ) ;
await synchronizer ( ) . start ( ) ;
await encryptionService ( ) . enableEncryption ( masterKey , '123456' ) ;
await encryptionService ( ) . loadMasterKeysFromSettings ( ) ;
await decryptionWorker ( ) . start ( ) ;
{
const n1 = await Note . load ( note1 . id ) ;
2019-09-19 23:51:18 +02:00
await shim . attachFileToNote ( n1 , ` ${ _ _dirname } /../tests/support/photo.jpg ` ) ; // R2
2019-04-21 14:49:40 +02:00
}
await synchronizer ( ) . start ( ) ;
await switchClient ( 1 ) ;
await synchronizer ( ) . start ( ) ;
await resourceService ( ) . indexNoteResources ( ) ;
await resourceService ( ) . deleteOrphanResources ( 0 ) ; // Previously, R1 would be deleted here because it's not indexed
expect ( ( await Resource . all ( ) ) . length ) . toBe ( 2 ) ;
} ) ) ;
it ( 'should double-check if the resource is still linked before deleting it' , asyncTest ( async ( ) => {
SearchEngine . instance ( ) . setDb ( db ( ) ) ; // /!\ Note that we use the global search engine here, which we shouldn't but will work for now
2020-03-14 01:46:14 +02:00
const folder1 = await Folder . save ( { title : 'folder1' } ) ;
2019-04-21 14:49:40 +02:00
let note1 = await Note . save ( { title : 'ma note' , parent _id : folder1 . id } ) ;
2019-09-19 23:51:18 +02:00
note1 = await shim . attachFileToNote ( note1 , ` ${ _ _dirname } /../tests/support/photo.jpg ` ) ;
2019-04-21 14:49:40 +02:00
await resourceService ( ) . indexNoteResources ( ) ;
const bodyWithResource = note1 . body ;
await Note . save ( { id : note1 . id , body : '' } ) ;
await resourceService ( ) . indexNoteResources ( ) ;
await Note . save ( { id : note1 . id , body : bodyWithResource } ) ;
await SearchEngine . instance ( ) . syncTables ( ) ;
await resourceService ( ) . deleteOrphanResources ( 0 ) ;
expect ( ( await Resource . all ( ) ) . length ) . toBe ( 1 ) ; // It should not have deleted the resource
const nr = ( await NoteResource . all ( ) ) [ 0 ] ;
expect ( ! ! nr . is _associated ) . toBe ( true ) ; // And it should have fixed the situation by re-indexing the note content
} ) ) ;
2019-07-30 09:35:42 +02:00
} ) ;