import { writeFile, remove, mkdirp } from 'fs-extra'; import Folder from '../../models/Folder'; import Note from '../../models/Note'; import { createTempDir, setupDatabaseAndSynchronizer, supportDir, switchClient } from '../../testing/test-utils'; import { FolderEntity, NoteEntity, ResourceEntity } from '../database/types'; import InteropService from './InteropService'; import { ImportOptions } from './types'; import { copyFile } from 'fs/promises'; import Resource from '../../models/Resource'; const extractId = (rawContent: string): string => { const lines = rawContent.split('\n'); for (const line of lines) { if (line.startsWith('id: ')) return line.substr(4); } throw new Error(`Could not extract ID from: ${rawContent}`); }; const makeFilePath = (baseDir: string, itemContent: string): string => { return `${baseDir}/${extractId(itemContent)}.md`; }; const rawFolder1 = `import test id: 15fa3f4abe89429b8836cdc5859fe74b created_time: 2022-08-29T14:42:47.684Z updated_time: 2022-08-29T14:42:47.684Z user_created_time: 2022-08-29T14:42:47.684Z user_updated_time: 2022-08-29T14:42:47.684Z encryption_cipher_text: encryption_applied: 0 parent_id: is_shared: 0 share_id: master_key_id: icon: type_: 2`; const rawFolder2 = `sub-notebook id: 6c114fa9cc4d421db908a6293418c1b2 created_time: 2022-08-29T14:42:56.113Z updated_time: 2022-08-29T14:43:02.774Z user_created_time: 2022-08-29T14:42:56.113Z user_updated_time: 2022-08-29T14:43:02.774Z encryption_cipher_text: encryption_applied: 0 parent_id: 15fa3f4abe89429b8836cdc5859fe74b is_shared: 0 share_id: master_key_id: icon: type_: 2`; const rawNote1 = `Note 1 ![photo.jpg](:/b3ab7288b56d4dbf884e73bea1248dd1) id: 7e5e0c7202414cd38e2db12e2e92ac91 parent_id: 15fa3f4abe89429b8836cdc5859fe74b created_time: 2022-08-29T14:43:06.961Z updated_time: 2022-08-29T14:43:10.596Z is_conflict: 0 latitude: 53.80075540 longitude: -1.54907740 altitude: 0.0000 author: source_url: is_todo: 0 todo_due: 0 todo_completed: 0 source: joplindev-desktop source_application: net.cozic.joplindev-desktop application_data: order: 0 user_created_time: 2022-08-29T14:43:06.961Z user_updated_time: 2022-08-29T14:43:10.596Z encryption_cipher_text: encryption_applied: 0 markup_language: 1 is_shared: 0 share_id: conflict_original_id: master_key_id: type_: 1`; const rawNote2 = `Note 2 id: 49faf4793cc048b698a592f9a76567af parent_id: 6c114fa9cc4d421db908a6293418c1b2 created_time: 2022-08-29T14:43:13.117Z updated_time: 2022-08-29T14:43:15.023Z is_conflict: 0 latitude: 53.80075540 longitude: -1.54907740 altitude: 0.0000 author: source_url: is_todo: 0 todo_due: 0 todo_completed: 0 source: joplindev-desktop source_application: net.cozic.joplindev-desktop application_data: order: 0 user_created_time: 2022-08-29T14:43:13.117Z user_updated_time: 2022-08-29T14:43:15.023Z encryption_cipher_text: encryption_applied: 0 markup_language: 1 is_shared: 0 share_id: conflict_original_id: master_key_id: type_: 1`; const rawResource = `Resource 1 id: b3ab7288b56d4dbf884e73bea1248dd1 mime: image/jpeg filename: created_time: 2023-12-14T16:54:47.661Z updated_time: 2023-12-14T16:54:49.956Z user_created_time: 2023-12-14T16:54:47.661Z user_updated_time: 2023-12-14T16:54:49.956Z file_extension: jpg encryption_cipher_text: encryption_applied: 0 encryption_blob_encrypted: 0 size: 2720 is_shared: 0 share_id: master_key_id: user_data: blob_updated_time: 1702572887661 type_: 4`; let tempDir: string; const createFiles = async () => { const resourceDir = `${tempDir}/resources`; await mkdirp(resourceDir); await writeFile(makeFilePath(tempDir, rawFolder1), rawFolder1); await writeFile(makeFilePath(tempDir, rawFolder2), rawFolder2); await writeFile(makeFilePath(tempDir, rawNote1), rawNote1); await writeFile(makeFilePath(tempDir, rawNote2), rawNote2); await writeFile(makeFilePath(tempDir, rawResource), rawResource); await copyFile(`${supportDir}/photo.jpg`, `${resourceDir}/${extractId(rawResource)}.jpg`); }; describe('InteropService_Importer_Raw', () => { beforeEach(async () => { await setupDatabaseAndSynchronizer(1); await switchClient(1); tempDir = await createTempDir(); }); afterEach(async () => { await remove(tempDir); }); it('should import raw files', async () => { await createFiles(); const importOptions: ImportOptions = { path: tempDir, format: 'raw', destinationFolderId: '', }; await InteropService.instance().import(importOptions); const folder1: FolderEntity = await Folder.loadByTitle('import test'); const folder2: FolderEntity = await Folder.loadByTitle('sub-notebook'); const note1: NoteEntity = await Note.loadByTitle('Note 1'); const note2: NoteEntity = await Note.loadByTitle('Note 2'); const resource: ResourceEntity = await Resource.loadByTitle('Resource 1'); // Check that all items have been created expect(folder1).toBeTruthy(); expect(folder2).toBeTruthy(); expect(note1).toBeTruthy(); expect(note2).toBeTruthy(); expect(resource).toBeTruthy(); // Check that all IDs have been replaced - we don't keep the original // IDs when importing data. expect(folder1.id).not.toBe(extractId(rawFolder1)); expect(folder2.id).not.toBe(extractId(rawFolder2)); expect(note1.id).not.toBe(extractId(rawNote1)); expect(note2.id).not.toBe(extractId(rawNote2)); expect(resource.id).not.toBe(extractId(rawResource)); // Check that the notes are linked to the correct folder IDs expect(folder1.parent_id).toBe(''); expect(folder2.parent_id).toBe(folder1.id); expect(note1.parent_id).toBe(folder1.id); expect(note2.parent_id).toBe(folder2.id); // Check that the resource is still linked to the note and with the // correct ID. expect(note1.body).toBe(`![photo.jpg](:/${resource.id})`); }); it('should handle duplicate names', async () => { await createFiles(); const importOptions: ImportOptions = { path: tempDir, format: 'raw', destinationFolderId: '', }; // Import twice to create duplicate items await InteropService.instance().import(importOptions); await InteropService.instance().import(importOptions); // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied const tree: any = await Folder.allAsTree(null, { includeNotes: true }); expect(tree[0].title).toBe('import test'); expect(tree[0].notes[0].title).toBe('Note 1'); expect(tree[0].children[0].title).toBe('sub-notebook'); expect(tree[0].children[0].notes[0].title).toBe('Note 2'); // The first notebook should have a (1) because it's at the same level // as the other "import test" notebook. Its content however should not // have any (x) because they are at different levels. expect(tree[1].title).toBe('import test (1)'); expect(tree[1].notes[0].title).toBe('Note 1'); expect(tree[1].children[0].title).toBe('sub-notebook'); expect(tree[1].children[0].notes[0].title).toBe('Note 2'); }); });