import { setupDatabaseAndSynchronizer, switchClient, createFolderTree, supportDir, msleep, resourceService } from '../testing/test-utils'; import Folder from '../models/Folder'; import { allNotesFolders } from '../testing/test-utils-synchronizer'; import Note from '../models/Note'; import shim from '../shim'; import Resource from '../models/Resource'; import { FolderEntity, NoteEntity, ResourceEntity } from '../services/database/types'; import ResourceService from '../services/ResourceService'; const testImagePath = `${supportDir}/photo.jpg`; describe('models/Folder.sharing', () => { beforeEach(async () => { await setupDatabaseAndSynchronizer(1); await switchClient(1); }); it('should apply the share ID to all children', (async () => { const folder = await createFolderTree('', [ { title: 'folder 1', children: [ { title: 'note 1', }, { title: 'note 2', }, { title: 'folder 2', children: [ { title: 'note 3', }, ], }, ], }, ]); await Folder.save({ id: folder.id, share_id: 'abcd1234' }); await Folder.updateAllShareIds(resourceService()); const allItems = await allNotesFolders(); for (const item of allItems) { expect(item.share_id).toBe('abcd1234'); } })); it('should apply the share ID to all sub-folders', (async () => { let folder1 = await createFolderTree('', [ { title: 'folder 1', children: [ { title: 'note 1', }, { title: 'note 2', }, { title: 'folder 2', children: [ { title: 'note 3', }, ], }, { title: 'folder 3', children: [ { title: 'folder 4', children: [], }, ], }, ], }, { title: 'folder 5', children: [], }, ]); await Folder.save({ id: folder1.id, share_id: 'abcd1234' }); await Folder.updateAllShareIds(resourceService()); folder1 = await Folder.loadByTitle('folder 1'); const folder2 = await Folder.loadByTitle('folder 2'); const folder3 = await Folder.loadByTitle('folder 3'); const folder4 = await Folder.loadByTitle('folder 4'); const folder5 = await Folder.loadByTitle('folder 5'); expect(folder1.share_id).toBe('abcd1234'); expect(folder2.share_id).toBe('abcd1234'); expect(folder3.share_id).toBe('abcd1234'); expect(folder4.share_id).toBe('abcd1234'); expect(folder5.share_id).toBe(''); })); it('should update the share ID when a folder is moved in or out of shared folder', (async () => { let folder1 = await createFolderTree('', [ { title: 'folder 1', children: [ { title: 'folder 2', children: [], }, ], }, { title: 'folder 3', children: [], }, ]); await Folder.save({ id: folder1.id, share_id: 'abcd1234' }); await Folder.updateAllShareIds(resourceService()); folder1 = await Folder.loadByTitle('folder 1'); let folder2 = await Folder.loadByTitle('folder 2'); const folder3 = await Folder.loadByTitle('folder 3'); expect(folder1.share_id).toBe('abcd1234'); expect(folder2.share_id).toBe('abcd1234'); // Move the folder outside the shared folder await Folder.save({ id: folder2.id, parent_id: folder3.id }); await Folder.updateAllShareIds(resourceService()); folder2 = await Folder.loadByTitle('folder 2'); expect(folder2.share_id).toBe(''); // Move the folder inside the shared folder { await Folder.save({ id: folder2.id, parent_id: folder1.id }); await Folder.updateAllShareIds(resourceService()); folder2 = await Folder.loadByTitle('folder 2'); expect(folder2.share_id).toBe('abcd1234'); } })); it('should apply the share ID to all notes', (async () => { const folder1 = await createFolderTree('', [ { title: 'folder 1', children: [ { title: 'note 1', }, { title: 'note 2', }, { title: 'folder 2', children: [ { title: 'note 3', }, ], }, ], }, { title: 'folder 5', children: [ { title: 'note 4', }, ], }, ]); await Folder.save({ id: folder1.id, share_id: 'abcd1234' }); await Folder.updateAllShareIds(resourceService()); const note1: NoteEntity = await Note.loadByTitle('note 1'); const note2: NoteEntity = await Note.loadByTitle('note 2'); const note3: NoteEntity = await Note.loadByTitle('note 3'); const note4: NoteEntity = await Note.loadByTitle('note 4'); expect(note1.share_id).toBe('abcd1234'); expect(note2.share_id).toBe('abcd1234'); expect(note3.share_id).toBe('abcd1234'); expect(note4.share_id).toBe(''); })); it('should remove the share ID when a note is moved in or out of shared folder', (async () => { const folder1 = await createFolderTree('', [ { title: 'folder 1', children: [ { title: 'note 1', }, ], }, { title: 'folder 2', children: [], }, ]); await Folder.save({ id: folder1.id, share_id: 'abcd1234' }); await Folder.updateAllShareIds(resourceService()); const note1: NoteEntity = await Note.loadByTitle('note 1'); const folder2: FolderEntity = await Folder.loadByTitle('folder 2'); expect(note1.share_id).toBe('abcd1234'); // Move the note outside of the shared folder await Note.save({ id: note1.id, parent_id: folder2.id }); await Folder.updateAllShareIds(resourceService()); { const note1: NoteEntity = await Note.loadByTitle('note 1'); expect(note1.share_id).toBe(''); } // Move the note back inside the shared folder await Note.save({ id: note1.id, parent_id: folder1.id }); await Folder.updateAllShareIds(resourceService()); { const note1: NoteEntity = await Note.loadByTitle('note 1'); expect(note1.share_id).toBe('abcd1234'); } })); it('should not remove the share ID of non-modified notes', (async () => { const folder1 = await createFolderTree('', [ { title: 'folder 1', children: [ { title: 'note 1', }, { title: 'note 2', }, ], }, { title: 'folder 2', children: [], }, ]); await Folder.save({ id: folder1.id, share_id: 'abcd1234' }); await Folder.updateAllShareIds(resourceService()); let note1: NoteEntity = await Note.loadByTitle('note 1'); let note2: NoteEntity = await Note.loadByTitle('note 2'); const folder2: FolderEntity = await Folder.loadByTitle('folder 2'); expect(note1.share_id).toBe('abcd1234'); expect(note2.share_id).toBe('abcd1234'); await Note.save({ id: note1.id, parent_id: folder2.id }); await Folder.updateAllShareIds(resourceService()); note1 = await Note.loadByTitle('note 1'); note2 = await Note.loadByTitle('note 2'); expect(note1.share_id).toBe(''); expect(note2.share_id).toBe('abcd1234'); })); it('should apply the note share ID to its resources', async () => { const resourceService = new ResourceService(); const folder = await createFolderTree('', [ { title: 'folder 1', children: [ { title: 'note 1', }, { title: 'note 2', }, ], }, { title: 'folder 2', children: [], }, ]); await Folder.save({ id: folder.id, share_id: 'abcd1234' }); await Folder.updateAllShareIds(resourceService); const folder2: FolderEntity = await Folder.loadByTitle('folder 2'); const note1: NoteEntity = await Note.loadByTitle('note 1'); await shim.attachFileToNote(note1, testImagePath); // We need to index the resources to populate the note_resources table await resourceService.indexNoteResources(); const resourceId: string = (await Resource.all())[0].id; { const resource: ResourceEntity = await Resource.load(resourceId); expect(resource.share_id).toBe(''); } const previousBlobUpdatedTime = (await Resource.load(resourceId)).blob_updated_time; await msleep(1); await Folder.updateAllShareIds(resourceService); { const resource: ResourceEntity = await Resource.load(resourceId); expect(resource.share_id).toBe(note1.share_id); expect(resource.blob_updated_time).toBeGreaterThan(previousBlobUpdatedTime); } await Note.save({ id: note1.id, parent_id: folder2.id }); await resourceService.indexNoteResources(); await Folder.updateAllShareIds(resourceService); { const resource: ResourceEntity = await Resource.load(resourceId); expect(resource.share_id).toBe(''); } }); it('should automatically duplicate resources when they are shared', async () => { const resourceService = new ResourceService(); const folder1 = await createFolderTree('', [ { title: 'folder 1', // SHARE 1 children: [ { title: 'note 1', }, { title: 'note 2', }, ], }, { title: 'folder 2', // SHARE 2 children: [ { title: 'note 3', }, ], }, { title: 'folder 3', // (not shared) children: [ { title: 'note 4', }, ], }, ]); const folder2: FolderEntity = await Folder.loadByTitle('folder 2'); // await Folder.loadByTitle('folder 3'); let note1: NoteEntity = await Note.loadByTitle('note 1'); let note2: NoteEntity = await Note.loadByTitle('note 2'); let note3: NoteEntity = await Note.loadByTitle('note 3'); let note4: NoteEntity = await Note.loadByTitle('note 4'); await Folder.save({ id: folder1.id, share_id: 'share1' }); await Folder.save({ id: folder2.id, share_id: 'share2' }); note1 = await shim.attachFileToNote(note1, testImagePath); note2 = await shim.attachFileToNote(note2, testImagePath); note3 = await Note.save({ id: note3.id, body: note1.body }); note4 = await Note.save({ id: note4.id, body: note1.body }); const userUpdatedTimes: Record = { [note1.id]: note1.user_updated_time, [note2.id]: note2.user_updated_time, [note3.id]: note3.user_updated_time, [note4.id]: note4.user_updated_time, }; await msleep(1); // We need to index the resources to populate the note_resources table await resourceService.indexNoteResources(); await Folder.updateAllShareIds(resourceService); // BEFORE: // // - Note 1 has resource 1 (share1) // - Note 2 has resource 2 (share1) // - Note 3 has resource 1 (share2) // - Note 4 has resource 1 (not shared) // AFTER: // // - Note 1 has resource 1 (share1) // - Note 2 has resource 2 (share1) // - Note 3 has resource 3 (share2) // - Note 4 has resource 4 (not shared) const resources = await Resource.all(); expect(resources.length).toBe(4); note1 = await Note.load(note1.id); note2 = await Note.load(note2.id); note3 = await Note.load(note3.id); note4 = await Note.load(note4.id); expect(note1.body).not.toBe(note2.body); expect(note1.body).not.toBe(note3.body); expect(note1.body).not.toBe(note4.body); expect(note2.body).not.toBe(note3.body); expect(note2.body).not.toBe(note4.body); expect(note3.body).not.toBe(note4.body); expect(note1.user_updated_time).toBe(userUpdatedTimes[note1.id]); expect(note2.user_updated_time).toBe(userUpdatedTimes[note2.id]); expect(note3.user_updated_time).toBe(userUpdatedTimes[note3.id]); expect(note4.user_updated_time).toBe(userUpdatedTimes[note4.id]); }); it('should unshare items that are no longer part of an existing share', async () => { await createFolderTree('', [ { title: 'folder 1', share_id: '1', children: [ { title: 'note 1', }, ], }, { title: 'folder 2', share_id: '2', children: [ { title: 'note 2', }, ], }, ]); const resourceService = new ResourceService(); const folder1: FolderEntity = await Folder.loadByTitle('folder 1'); const folder2: FolderEntity = await Folder.loadByTitle('folder 2'); let note1: NoteEntity = await Note.loadByTitle('note 1'); let note2: NoteEntity = await Note.loadByTitle('note 2'); note1 = await shim.attachFileToNote(note1, testImagePath); note2 = await shim.attachFileToNote(note2, testImagePath); const resourceId1 = (await Note.linkedResourceIds(note1.body))[0]; const resourceId2 = (await Note.linkedResourceIds(note2.body))[0]; await resourceService.indexNoteResources(); await Folder.updateAllShareIds(resourceService); await Folder.updateNoLongerSharedItems(['1']); // Since `updateNoLongerSharedItems` sets the parent_id too, // double-check that it's not actually modified. expect((await Note.loadByTitle('note 1')).parent_id).toBe(folder1.id); expect((await Note.loadByTitle('note 2')).parent_id).toBe(folder2.id); expect((await Folder.loadByTitle('folder 1')).parent_id).toBe(folder1.parent_id); expect((await Folder.loadByTitle('folder 2')).parent_id).toBe(folder2.parent_id); // At this point, all items associated with share 2 should have their // share_id cleared, because the share no longer exists. We also // double-check that share 1 hasn't been cleared. expect((await Note.loadByTitle('note 1')).share_id).toBe('1'); expect((await Note.loadByTitle('note 2')).share_id).toBe(''); expect((await Folder.loadByTitle('folder 1')).share_id).toBe('1'); expect((await Folder.loadByTitle('folder 2')).share_id).toBe(''); expect((await Resource.load(resourceId1)).share_id).toBe('1'); expect((await Resource.load(resourceId2)).share_id).toBe(''); // If we pass an empty array, it means there are no active share // anymore, so all share_id should be cleared. await Folder.updateNoLongerSharedItems([]); expect((await Note.loadByTitle('note 1')).share_id).toBe(''); expect((await Folder.loadByTitle('folder 1')).share_id).toBe(''); expect((await Resource.load(resourceId1)).share_id).toBe(''); { // If we run it again, it should not update the notes since the share_id // has already been cleared. const resource1 = await Resource.load(resourceId1); const resource2 = await Resource.load(resourceId2); const note1 = await Note.loadByTitle('note 1'); const note2 = await Note.loadByTitle('note 2'); const folder1 = await Folder.loadByTitle('folder 1'); const folder2 = await Folder.loadByTitle('folder 2'); await msleep(1); await Folder.updateNoLongerSharedItems(['1']); expect((await Resource.load(resourceId1)).updated_time).toBe(resource1.updated_time); expect((await Resource.load(resourceId2)).updated_time).toBe(resource2.updated_time); expect((await Note.loadByTitle('note 1')).updated_time).toBe(note1.updated_time); expect((await Note.loadByTitle('note 2')).updated_time).toBe(note2.updated_time); expect((await Folder.loadByTitle('folder 1')).updated_time).toBe(folder1.updated_time); expect((await Folder.loadByTitle('folder 2')).updated_time).toBe(folder2.updated_time); } }); });