"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("../../models/utils/types");
const Api_1 = require("../../services/rest/Api");
const notes_1 = require("./routes/notes");
const shim_1 = require("../../shim");
const test_utils_1 = require("../../testing/test-utils");
const Folder_1 = require("../../models/Folder");
const Resource_1 = require("../../models/Resource");
const Note_1 = require("../../models/Note");
const Tag_1 = require("../../models/Tag");
const NoteTag_1 = require("../../models/NoteTag");
const ResourceService_1 = require("../../services/ResourceService");
const SearchEngine_1 = require("../search/SearchEngine");
const { MarkupToHtml } = require('@joplin/renderer');
const path_1 = require("@joplin/utils/path");
const path_2 = require("path");
const html_1 = require("@joplin/utils/html");
const createFolderForPagination = async (num, time) => {
    await Folder_1.default.save({
        title: `folder${num}`,
        updated_time: time,
        created_time: time,
    }, { autoTimestamp: false });
};
const createNoteForPagination = async (numOrTitle, time) => {
    const title = typeof numOrTitle === 'string' ? numOrTitle : `note${numOrTitle}`;
    const body = typeof numOrTitle === 'string' ? `Note body ${numOrTitle}` : `noteBody${numOrTitle}`;
    await Note_1.default.save({
        title: title,
        body: body,
        updated_time: time,
        created_time: time,
    }, { autoTimestamp: false });
};
let api = null;
describe('services/rest/Api', () => {
    beforeEach(async () => {
        api = new Api_1.default();
        await (0, test_utils_1.setupDatabaseAndSynchronizer)(1);
        await (0, test_utils_1.switchClient)(1);
    });
    it('should ping', (async () => {
        const response = await api.route(Api_1.RequestMethod.GET, 'ping');
        expect(response).toBe('JoplinClipperServer');
    }));
    it('should handle Not Found errors', (async () => {
        const hasThrown = await (0, test_utils_1.checkThrowAsync)(async () => await api.route(Api_1.RequestMethod.GET, 'pong'));
        expect(hasThrown).toBe(true);
    }));
    it('should get folders', (async () => {
        await Folder_1.default.save({ title: 'mon carnet' });
        const response = await api.route(Api_1.RequestMethod.GET, 'folders');
        expect(response.items.length).toBe(1);
    }));
    it('should update folders', (async () => {
        const f1 = await Folder_1.default.save({ title: 'mon carnet' });
        await api.route(Api_1.RequestMethod.PUT, `folders/${f1.id}`, null, JSON.stringify({
            title: 'modifié',
        }));
        const f1b = await Folder_1.default.load(f1.id);
        expect(f1b.title).toBe('modifié');
    }));
    it('should delete folders', (async () => {
        const f1 = await Folder_1.default.save({ title: 'mon carnet' });
        await api.route(Api_1.RequestMethod.DELETE, `folders/${f1.id}`, { permanent: '1' });
        const f1b = await Folder_1.default.load(f1.id);
        expect(!f1b).toBe(true);
    }));
    it('should create folders', (async () => {
        const response = await api.route(Api_1.RequestMethod.POST, 'folders', null, JSON.stringify({
            title: 'from api',
        }));
        expect(!!response.id).toBe(true);
        const f = await Folder_1.default.all();
        expect(f.length).toBe(1);
        expect(f[0].title).toBe('from api');
    }));
    it('should get one folder', (async () => {
        const f1 = await Folder_1.default.save({ title: 'mon carnet' });
        const response = await api.route(Api_1.RequestMethod.GET, `folders/${f1.id}`);
        expect(response.id).toBe(f1.id);
        const hasThrown = await (0, test_utils_1.checkThrowAsync)(async () => await api.route(Api_1.RequestMethod.GET, 'folders/doesntexist'));
        expect(hasThrown).toBe(true);
    }));
    it('should get the folder notes', (async () => {
        const f1 = await Folder_1.default.save({ title: 'mon carnet' });
        const response2 = await api.route(Api_1.RequestMethod.GET, `folders/${f1.id}/notes`);
        expect(response2.items.length).toBe(0);
        await Note_1.default.save({ title: 'un', parent_id: f1.id });
        await Note_1.default.save({ title: 'deux', parent_id: f1.id });
        const response = await api.route(Api_1.RequestMethod.GET, `folders/${f1.id}/notes`);
        expect(response.items.length).toBe(2);
    }));
    it('should return folders as a tree', async () => {
        const folder1 = await Folder_1.default.save({ title: 'Folder 1' });
        await Folder_1.default.save({ title: 'Folder 2', parent_id: folder1.id });
        await Folder_1.default.save({ title: 'Folder 3', parent_id: folder1.id });
        const response = await api.route(Api_1.RequestMethod.GET, 'folders', { as_tree: 1 });
        expect(response).toMatchObject([{
                title: 'Folder 1',
                id: folder1.id,
                children: [
                    { title: 'Folder 2' },
                    { title: 'Folder 3' },
                ],
            }]);
    });
    it('should fail on invalid paths', (async () => {
        const hasThrown = await (0, test_utils_1.checkThrowAsync)(async () => await api.route(Api_1.RequestMethod.GET, 'schtroumpf'));
        expect(hasThrown).toBe(true);
    }));
    it('should get notes', (async () => {
        let response = null;
        const f1 = await Folder_1.default.save({ title: 'mon carnet' });
        const f2 = await Folder_1.default.save({ title: 'mon deuxième carnet' });
        const n1 = await Note_1.default.save({ title: 'un', parent_id: f1.id });
        await Note_1.default.save({ title: 'deux', parent_id: f1.id });
        const n3 = await Note_1.default.save({ title: 'trois', parent_id: f2.id });
        response = await api.route(Api_1.RequestMethod.GET, 'notes');
        expect(response.items.length).toBe(3);
        response = await api.route(Api_1.RequestMethod.GET, `notes/${n1.id}`);
        expect(response.id).toBe(n1.id);
        response = await api.route(Api_1.RequestMethod.GET, `notes/${n3.id}`, { fields: 'id,title' });
        expect(Object.getOwnPropertyNames(response).length).toBe(3);
        expect(response.id).toBe(n3.id);
        expect(response.title).toBe('trois');
    }));
    it('should create notes', (async () => {
        let response = null;
        const f = await Folder_1.default.save({ title: 'mon carnet' });
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            title: 'testing',
            parent_id: f.id,
        }));
        expect(response.title).toBe('testing');
        expect(!!response.id).toBe(true);
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            title: 'testing',
            parent_id: f.id,
        }));
        expect(response.title).toBe('testing');
        expect(!!response.id).toBe(true);
    }));
    it('should allow setting note properties', (async () => {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        let response = null;
        const f = await Folder_1.default.save({ title: 'mon carnet' });
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            title: 'testing',
            parent_id: f.id,
            latitude: '48.732071',
            longitude: '-3.458700',
            altitude: '21',
            source: 'testing',
        }));
        const noteId = response.id;
        {
            const note = await Note_1.default.load(noteId);
            expect(note.latitude).toBe('48.73207100');
            expect(note.longitude).toBe('-3.45870000');
            expect(note.altitude).toBe('21.0000');
            expect(note.source).toBe('testing');
        }
        await api.route(Api_1.RequestMethod.PUT, `notes/${noteId}`, null, JSON.stringify({
            latitude: '49',
            longitude: '-3',
            altitude: '22',
            source: 'testing 2',
        }));
        {
            const note = await Note_1.default.load(noteId);
            expect(note.latitude).toBe('49.00000000');
            expect(note.longitude).toBe('-3.00000000');
            expect(note.altitude).toBe('22.0000');
            expect(note.source).toBe('testing 2');
        }
    }));
    it('should preserve user timestamps when creating notes', (async () => {
        let response = null;
        const f = await Folder_1.default.save({ title: 'mon carnet' });
        const updatedTime = Date.now() - 1000;
        const createdTime = Date.now() - 10000;
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            parent_id: f.id,
            user_updated_time: updatedTime,
            user_created_time: createdTime,
        }));
        expect(response.user_updated_time).toBe(updatedTime);
        expect(response.user_created_time).toBe(createdTime);
        const timeBefore = Date.now();
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            parent_id: f.id,
        }));
        const newNote = await Note_1.default.load(response.id);
        expect(newNote.user_updated_time).toBeGreaterThanOrEqual(timeBefore);
        expect(newNote.user_created_time).toBeGreaterThanOrEqual(timeBefore);
    }));
    it('should preserve user timestamps when updating notes', (async () => {
        const folder = await Folder_1.default.save({ title: 'mon carnet' });
        const updatedTime = Date.now() - 1000;
        const createdTime = Date.now() - 10000;
        const response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            parent_id: folder.id,
        }));
        const noteId = response.id;
        {
            // Check that if user timestamps are supplied, they are preserved by the API
            await api.route(Api_1.RequestMethod.PUT, `notes/${noteId}`, null, JSON.stringify({
                user_updated_time: updatedTime,
                user_created_time: createdTime,
                title: 'mod',
            }));
            const modNote = await Note_1.default.load(noteId);
            expect(modNote.title).toBe('mod');
            expect(modNote.user_updated_time).toBe(updatedTime);
            expect(modNote.user_created_time).toBe(createdTime);
        }
        {
            // Check if no user timestamps are supplied they are automatically updated.
            const beforeTime = Date.now();
            await api.route(Api_1.RequestMethod.PUT, `notes/${noteId}`, null, JSON.stringify({
                title: 'mod2',
            }));
            const modNote = await Note_1.default.load(noteId);
            expect(modNote.title).toBe('mod2');
            expect(modNote.user_updated_time).toBeGreaterThanOrEqual(beforeTime);
            expect(modNote.user_created_time).toBeGreaterThanOrEqual(createdTime);
        }
    }));
    it('should create notes with supplied ID', (async () => {
        let response = null;
        const f = await Folder_1.default.save({ title: 'mon carnet' });
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            id: '12345678123456781234567812345678',
            title: 'testing',
            parent_id: f.id,
        }));
        expect(response.id).toBe('12345678123456781234567812345678');
    }));
    it('should create todos', (async () => {
        let response = null;
        const f = await Folder_1.default.save({ title: 'stuff to do' });
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            title: 'testing',
            parent_id: f.id,
            is_todo: 1,
        }));
        expect(response.is_todo).toBe(1);
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            title: 'testing 2',
            parent_id: f.id,
            is_todo: 0,
        }));
        expect(response.is_todo).toBe(0);
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            title: 'testing 3',
            parent_id: f.id,
        }));
        expect(response.is_todo).toBeUndefined();
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            title: 'testing 4',
            parent_id: f.id,
            is_todo: '1',
            todo_due: '2',
            todo_completed: '3',
        }));
        expect(response.todo_due).toBe(2);
        expect(response.todo_completed).toBe(3);
    }));
    it('should create folders with supplied ID', (async () => {
        const response = await api.route(Api_1.RequestMethod.POST, 'folders', null, JSON.stringify({
            id: '12345678123456781234567812345678',
            title: 'from api',
        }));
        expect(response.id).toBe('12345678123456781234567812345678');
    }));
    it('should create notes with images', (async () => {
        let response = null;
        const f = await Folder_1.default.save({ title: 'mon carnet' });
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            title: 'testing image',
            parent_id: f.id,
            image_data_url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANZJREFUeNoAyAA3/wFwtO3K6gUB/vz2+Prw9fj/+/r+/wBZKAAExOgF4/MC9ff+MRH6Ui4E+/0Bqc/zutj6AgT+/Pz7+vv7++nu82c4DlMqCvLs8goA/gL8/fz09fb59vXa6vzZ6vjT5fbn6voD/fwC8vX4UiT9Zi//APHyAP8ACgUBAPv5APz7BPj2+DIaC2o3E+3o6ywaC5fT6gD6/QD9/QEVf9kD+/dcLQgJA/7v8vqfwOf18wA1IAIEVycAyt//v9XvAPv7APz8LhoIAPz9Ri4OAgwARgx4W/6fVeEAAAAASUVORK5CYII=',
        }));
        const resources = await Resource_1.default.all();
        expect(resources.length).toBe(1);
        const resource = resources[0];
        expect(response.body.indexOf(resource.id) >= 0).toBe(true);
    }));
    it('should not compress images uploaded through resource API', (async () => {
        const originalImagePath = `${test_utils_1.supportDir}/photo-large.png`;
        await api.route(Api_1.RequestMethod.POST, 'resources', null, JSON.stringify({
            title: 'testing resource',
        }), [
            {
                path: originalImagePath,
            },
        ]);
        const resources = await Resource_1.default.all();
        expect(resources.length).toBe(1);
        const uploadedImagePath = Resource_1.default.fullPath(resources[0]);
        const originalImageSize = (await shim_1.default.fsDriver().stat(originalImagePath)).size;
        const uploadedImageSize = (await shim_1.default.fsDriver().stat(uploadedImagePath)).size;
        expect(originalImageSize).toEqual(uploadedImageSize);
    }));
    it('should update a resource', (async () => {
        await api.route(Api_1.RequestMethod.POST, 'resources', null, JSON.stringify({
            title: 'resource',
        }), [
            {
                path: `${test_utils_1.supportDir}/photo.jpg`,
            },
        ]);
        const resourceV1 = (await Resource_1.default.all())[0];
        await (0, test_utils_1.msleep)(1);
        await api.route(Api_1.RequestMethod.PUT, `resources/${resourceV1.id}`, null, JSON.stringify({
            title: 'resource mod',
        }), [
            {
                path: `${test_utils_1.supportDir}/photo-large.png`,
            },
        ]);
        const resourceV2 = (await Resource_1.default.all())[0];
        expect(resourceV2.title).toBe('resource mod');
        expect(resourceV2.mime).toBe('image/png');
        expect(resourceV2.file_extension).toBe('png');
        expect(resourceV2.updated_time).toBeGreaterThan(resourceV1.updated_time);
        expect(resourceV2.created_time).toBe(resourceV1.created_time);
        expect(resourceV2.size).toBeGreaterThan(resourceV1.size);
        expect(resourceV2.size).toBe((await shim_1.default.fsDriver().stat(Resource_1.default.fullPath(resourceV2))).size);
    }));
    it('should allow updating a resource file only', (async () => {
        await api.route(Api_1.RequestMethod.POST, 'resources', null, JSON.stringify({
            title: 'resource',
        }), [{ path: `${test_utils_1.supportDir}/photo.jpg` }]);
        const resourceV1 = (await Resource_1.default.all())[0];
        await (0, test_utils_1.msleep)(1);
        await api.route(Api_1.RequestMethod.PUT, `resources/${resourceV1.id}`, null, null, [
            {
                path: `${test_utils_1.supportDir}/photo-large.png`,
            },
        ]);
        const resourceV2 = (await Resource_1.default.all())[0];
        // It should have updated the file content, but not the metadata
        expect(resourceV2.title).toBe(resourceV1.title);
        expect(resourceV2.size).toBeGreaterThan(resourceV1.size);
    }));
    it('should update resource properties', (async () => {
        await api.route(Api_1.RequestMethod.POST, 'resources', null, JSON.stringify({
            title: 'resource',
        }), [{ path: `${test_utils_1.supportDir}/photo.jpg` }]);
        const resourceV1 = (await Resource_1.default.all())[0];
        await (0, test_utils_1.msleep)(1);
        await api.route(Api_1.RequestMethod.PUT, `resources/${resourceV1.id}`, null, JSON.stringify({
            title: 'my new title',
        }));
        const resourceV2 = (await Resource_1.default.all())[0];
        expect(resourceV2.title).toBe('my new title');
        expect(resourceV2.mime).toBe(resourceV1.mime);
    }));
    it('should delete resources', (async () => {
        const f = await Folder_1.default.save({ title: 'mon carnet' });
        await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            title: 'testing image',
            parent_id: f.id,
            image_data_url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANZJREFUeNoAyAA3/wFwtO3K6gUB/vz2+Prw9fj/+/r+/wBZKAAExOgF4/MC9ff+MRH6Ui4E+/0Bqc/zutj6AgT+/Pz7+vv7++nu82c4DlMqCvLs8goA/gL8/fz09fb59vXa6vzZ6vjT5fbn6voD/fwC8vX4UiT9Zi//APHyAP8ACgUBAPv5APz7BPj2+DIaC2o3E+3o6ywaC5fT6gD6/QD9/QEVf9kD+/dcLQgJA/7v8vqfwOf18wA1IAIEVycAyt//v9XvAPv7APz8LhoIAPz9Ri4OAgwARgx4W/6fVeEAAAAASUVORK5CYII=',
        }));
        const resource = (await Resource_1.default.all())[0];
        const filePath = Resource_1.default.fullPath(resource);
        expect(await shim_1.default.fsDriver().exists(filePath)).toBe(true);
        await api.route(Api_1.RequestMethod.DELETE, `resources/${resource.id}`);
        expect(await shim_1.default.fsDriver().exists(filePath)).toBe(false);
        expect(!(await Resource_1.default.load(resource.id))).toBe(true);
    }));
    it('should create notes from HTML', (async () => {
        let response = null;
        const f = await Folder_1.default.save({ title: 'mon carnet' });
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            title: 'testing HTML',
            parent_id: f.id,
            body_html: '<b>Bold text</b>',
        }));
        expect(response.body).toBe('**Bold text**');
    }));
    it('should extract media urls from body', (() => {
        const tests = [
            {
                language: MarkupToHtml.MARKUP_LANGUAGE_HTML,
                body: '<div> <img src="https://example.com/img.png" /> <embed src="https://example.com/sample.pdf"/> <object data="https://example.com/file.PDF"></object> </div>',
                result: ['https://example.com/img.png', 'https://example.com/sample.pdf', 'https://example.com/file.PDF'],
            },
            {
                language: MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN,
                body: 'test text \n ![img 1](https://example.com/img1.png) [embedded_pdf](https://example.com/sample1.pdf) [embedded_pdf](https://example.com/file.PDF)',
                result: ['https://example.com/img1.png', 'https://example.com/sample1.pdf', 'https://example.com/file.PDF'],
            },
            {
                language: MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN,
                body: '> <a id="attachment68076"></a>[![Enable or Disable Sync Your Settings in Windows 10-disabled_sync_your_settings.png](https://www.tenforums.com/attachments/tutorials/68076d1485964056t-enable-disable-sync-your-settings-windows-10-a-disabled_sync_your_settings.png?s=0bbd1c630a9a924f05134d51b4768d2b "Enable or Disable Sync Your Settings in Windows 10-disabled_sync_your_settings.png")](https://www.tenforums.com/attachments/tutorials/68076d1457326453-enable-disable-sync-your-settings-windows-10-a-disabled_sync_your_settings.png?s=0bbd1c630a9a924f05134d51b4768d2b)',
                result: ['https://www.tenforums.com/attachments/tutorials/68076d1485964056t-enable-disable-sync-your-settings-windows-10-a-disabled_sync_your_settings.png?s=0bbd1c630a9a924f05134d51b4768d2b'],
            },
            {
                language: MarkupToHtml.MARKUP_LANGUAGE_HTML,
                body: '<div> <embed src="https://example.com/sample"/> <embed /> <object data="https://example.com/file.pdfff"></object> <a href="https://test.com/file.pdf">Link</a> </div>',
                result: [],
            },
        ];
        // eslint-disable-next-line github/array-foreach -- Old code before rule was applied
        tests.forEach((test) => {
            const urls = (0, notes_1.extractMediaUrls)(test.language, test.body);
            expect(urls).toEqual(test.result);
        });
    }));
    it('should create notes with pdf embeds', (async () => {
        let response = null;
        const f = await Folder_1.default.save({ title: 'pdf test1' });
        const url = (0, path_1.toFileProtocolPath)((0, path_2.join)(test_utils_1.supportDir, 'welcome.pdf'));
        response = await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({
            title: 'testing PDF embeds',
            parent_id: f.id,
            body_html: `<div> <embed src="${(0, html_1.htmlentities)(url)}" type="application/pdf" /> </div>`,
        }));
        const resources = await Resource_1.default.all();
        expect(resources.length).toBe(1);
        const resource = resources[0];
        expect(response.body.indexOf(resource.id) >= 0).toBe(true);
    }));
    it('should handle tokens', (async () => {
        api = new Api_1.default('mytoken');
        let hasThrown = await (0, test_utils_1.checkThrowAsync)(async () => await api.route(Api_1.RequestMethod.GET, 'notes'));
        expect(hasThrown).toBe(true);
        const response = await api.route(Api_1.RequestMethod.GET, 'notes', { token: 'mytoken' });
        expect(response.items.length).toBe(0);
        hasThrown = await (0, test_utils_1.checkThrowAsync)(async () => await api.route(Api_1.RequestMethod.POST, 'notes', null, JSON.stringify({ title: 'testing' })));
        expect(hasThrown).toBe(true);
    }));
    it('should add tags to notes', (async () => {
        const tag = await Tag_1.default.save({ title: 'mon étiquette' });
        const note = await Note_1.default.save({ title: 'ma note' });
        await api.route(Api_1.RequestMethod.POST, `tags/${tag.id}/notes`, null, JSON.stringify({
            id: note.id,
        }));
        const noteIds = await Tag_1.default.noteIds(tag.id);
        expect(noteIds[0]).toBe(note.id);
    }));
    it('should remove tags from notes', (async () => {
        const tag = await Tag_1.default.save({ title: 'mon étiquette' });
        const note = await Note_1.default.save({ title: 'ma note' });
        await Tag_1.default.addNote(tag.id, note.id);
        await api.route(Api_1.RequestMethod.DELETE, `tags/${tag.id}/notes/${note.id}`);
        const noteIds = await Tag_1.default.noteIds(tag.id);
        expect(noteIds.length).toBe(0);
    }));
    it('should list all tag notes', (async () => {
        const tag = await Tag_1.default.save({ title: 'mon étiquette' });
        const tag2 = await Tag_1.default.save({ title: 'mon étiquette 2' });
        const note1 = await Note_1.default.save({ title: 'ma note un' });
        const note2 = await Note_1.default.save({ title: 'ma note deux' });
        await Tag_1.default.addNote(tag.id, note1.id);
        await Tag_1.default.addNote(tag.id, note2.id);
        const response = await api.route(Api_1.RequestMethod.GET, `tags/${tag.id}/notes`);
        expect(response.items.length).toBe(2);
        expect('id' in response.items[0]).toBe(true);
        expect('title' in response.items[0]).toBe(true);
        const response2 = await api.route(Api_1.RequestMethod.GET, `notes/${note1.id}/tags`);
        expect(response2.items.length).toBe(1);
        await Tag_1.default.addNote(tag2.id, note1.id);
        const response3 = await api.route(Api_1.RequestMethod.GET, `notes/${note1.id}/tags`, { fields: 'id' });
        expect(response3.items.length).toBe(2);
        // Also check that it only returns the required fields
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        response3.items.sort((a, b) => {
            return a.id < b.id ? -1 : +1;
        });
        const sortedTagIds = [tag.id, tag2.id];
        sortedTagIds.sort();
        expect(JSON.stringify(response3.items)).toBe(`[{"id":"${sortedTagIds[0]}"},{"id":"${sortedTagIds[1]}"}]`);
    }));
    it('should update tags when updating notes', (async () => {
        const tag1 = await Tag_1.default.save({ title: 'mon étiquette 1' });
        const tag2 = await Tag_1.default.save({ title: 'mon étiquette 2' });
        const tag3 = await Tag_1.default.save({ title: 'mon étiquette 3' });
        const note = await Note_1.default.save({
            title: 'ma note un',
        });
        await Tag_1.default.addNote(tag1.id, note.id);
        await Tag_1.default.addNote(tag2.id, note.id);
        const response = await api.route(Api_1.RequestMethod.PUT, `notes/${note.id}`, null, JSON.stringify({
            tags: `${tag1.title},${tag3.title}`,
        }));
        const tagIds = await NoteTag_1.default.tagIdsByNoteId(note.id);
        expect(response.tags === `${tag1.title},${tag3.title}`).toBe(true);
        expect(tagIds.length === 2).toBe(true);
        expect(tagIds.includes(tag1.id)).toBe(true);
        expect(tagIds.includes(tag3.id)).toBe(true);
    }));
    it('should create and update tags when updating notes', (async () => {
        const tag1 = await Tag_1.default.save({ title: 'mon étiquette 1' });
        const tag2 = await Tag_1.default.save({ title: 'mon étiquette 2' });
        const newTagTitle = 'mon étiquette 3';
        const note = await Note_1.default.save({
            title: 'ma note un',
        });
        await Tag_1.default.addNote(tag1.id, note.id);
        await Tag_1.default.addNote(tag2.id, note.id);
        const response = await api.route(Api_1.RequestMethod.PUT, `notes/${note.id}`, null, JSON.stringify({
            tags: `${tag1.title},${newTagTitle}`,
        }));
        const newTag = await Tag_1.default.loadByTitle(newTagTitle);
        const tagIds = await NoteTag_1.default.tagIdsByNoteId(note.id);
        expect(response.tags === `${tag1.title},${newTag.title}`).toBe(true);
        expect(tagIds.length === 2).toBe(true);
        expect(tagIds.includes(tag1.id)).toBe(true);
        expect(tagIds.includes(newTag.id)).toBe(true);
    }));
    it('should not update tags if tags is not mentioned when updating', (async () => {
        const tag1 = await Tag_1.default.save({ title: 'mon étiquette 1' });
        const tag2 = await Tag_1.default.save({ title: 'mon étiquette 2' });
        const note = await Note_1.default.save({
            title: 'ma note un',
        });
        await Tag_1.default.addNote(tag1.id, note.id);
        await Tag_1.default.addNote(tag2.id, note.id);
        const response = await api.route(Api_1.RequestMethod.PUT, `notes/${note.id}`, null, JSON.stringify({
            title: 'Some other title',
        }));
        const tagIds = await NoteTag_1.default.tagIdsByNoteId(note.id);
        expect(response.tags === undefined).toBe(true);
        expect(tagIds.length === 2).toBe(true);
        expect(tagIds.includes(tag1.id)).toBe(true);
        expect(tagIds.includes(tag2.id)).toBe(true);
    }));
    it('should remove tags from note if tags is set to empty string when updating', (async () => {
        const tag1 = await Tag_1.default.save({ title: 'mon étiquette 1' });
        const tag2 = await Tag_1.default.save({ title: 'mon étiquette 2' });
        const note = await Note_1.default.save({
            title: 'ma note un',
        });
        await Tag_1.default.addNote(tag1.id, note.id);
        await Tag_1.default.addNote(tag2.id, note.id);
        const response = await api.route(Api_1.RequestMethod.PUT, `notes/${note.id}`, null, JSON.stringify({
            tags: '',
        }));
        const tagIds = await NoteTag_1.default.tagIdsByNoteId(note.id);
        expect(response.tags === '').toBe(true);
        expect(tagIds.length === 0).toBe(true);
    }));
    it('should paginate results', (async () => {
        await createFolderForPagination(1, 1001);
        await createFolderForPagination(2, 1002);
        await createFolderForPagination(3, 1003);
        await createFolderForPagination(4, 1004);
        {
            const baseQuery = {
                fields: ['id', 'title', 'updated_time'],
                limit: 2,
                order_dir: types_1.PaginationOrderDir.ASC,
                order_by: 'updated_time',
            };
            const r1 = await api.route(Api_1.RequestMethod.GET, 'folders', Object.assign({}, baseQuery));
            expect(r1.has_more).toBe(true);
            expect(r1.items.length).toBe(2);
            expect(r1.items[0].title).toBe('folder1');
            expect(r1.items[1].title).toBe('folder2');
            const r2 = await api.route(Api_1.RequestMethod.GET, 'folders', Object.assign(Object.assign({}, baseQuery), { page: 2 }));
            // The API currently doesn't check if there's effectively a
            // page of data after the current one. If the number of
            // returned items === limit, it sets `has_more` and the next
            // result set will be empty
            expect(r1.has_more).toBe(true);
            expect(r2.items.length).toBe(2);
            expect(r2.items[0].title).toBe('folder3');
            expect(r2.items[1].title).toBe('folder4');
            const r3 = await api.route(Api_1.RequestMethod.GET, 'folders', Object.assign(Object.assign({}, baseQuery), { page: 3 }));
            expect(r3.items.length).toBe(0);
            expect(r3.has_more).toBe(false);
        }
        {
            const baseQuery = {
                fields: ['id', 'title', 'updated_time'],
                limit: 3,
                order_dir: types_1.PaginationOrderDir.ASC,
                order_by: 'updated_time',
            };
            const r1 = await api.route(Api_1.RequestMethod.GET, 'folders', Object.assign({}, baseQuery));
            expect(r1.items.length).toBe(3);
            expect(r1.items[0].title).toBe('folder1');
            expect(r1.items[1].title).toBe('folder2');
            expect(r1.items[2].title).toBe('folder3');
            const r2 = await api.route(Api_1.RequestMethod.GET, 'folders', Object.assign(Object.assign({}, baseQuery), { page: 2 }));
            expect(r2.items.length).toBe(1);
            expect(r2.items[0].title).toBe('folder4');
            expect(r2.has_more).toBe(false);
        }
    }));
    it('should paginate results and handle duplicate field values', (async () => {
        // If, for example, ordering by updated_time, and two of the rows
        // have the same updated_time, it should make sure that the sort
        // order is stable and all results are correctly returned.
        await createFolderForPagination(1, 1001);
        await createFolderForPagination(2, 1002);
        await createFolderForPagination(3, 1002);
        await createFolderForPagination(4, 1003);
        const baseQuery = {
            fields: ['id', 'title', 'updated_time'],
            limit: 2,
            order_dir: types_1.PaginationOrderDir.ASC,
            order_by: 'updated_time',
        };
        const r1 = await api.route(Api_1.RequestMethod.GET, 'folders', Object.assign({}, baseQuery));
        expect(r1.items.length).toBe(2);
        expect(r1.items[0].title).toBe('folder1');
        expect(['folder2', 'folder3'].includes(r1.items[1].title)).toBe(true);
        const r2 = await api.route(Api_1.RequestMethod.GET, 'folders', Object.assign(Object.assign({}, baseQuery), { page: 2 }));
        expect(r2.items.length).toBe(2);
        expect(r2.items[0].title).toBe(r1.items[1].title === 'folder2' ? 'folder3' : 'folder2');
        expect(r2.items[1].title).toBe('folder4');
    }));
    it('should paginate results and return the requested fields only', (async () => {
        await createNoteForPagination(1, 1001);
        await createNoteForPagination(2, 1002);
        await createNoteForPagination(3, 1003);
        const baseQuery = {
            fields: ['id', 'title', 'body'],
            limit: 2,
            order_dir: types_1.PaginationOrderDir.ASC,
            order_by: 'updated_time',
        };
        const r1 = await api.route(Api_1.RequestMethod.GET, 'notes', Object.assign({}, baseQuery));
        expect(Object.keys(r1.items[0]).sort().join(',')).toBe('body,id,title');
        expect(r1.items.length).toBe(2);
        expect(r1.items[0].title).toBe('note1');
        expect(r1.items[0].body).toBe('noteBody1');
        expect(r1.items[1].title).toBe('note2');
        expect(r1.items[1].body).toBe('noteBody2');
        const r2 = await api.route(Api_1.RequestMethod.GET, 'notes', Object.assign(Object.assign({}, baseQuery), { fields: ['id'], page: 2 }));
        expect(Object.keys(r2.items[0]).sort().join(',')).toBe('id');
        expect(r2.items.length).toBe(1);
        expect(!!r2.items[0].id).toBe(true);
    }));
    it('should paginate folder notes', (async () => {
        const folder = await Folder_1.default.save({});
        const note1 = await Note_1.default.save({ parent_id: folder.id });
        await (0, test_utils_1.msleep)(1);
        const note2 = await Note_1.default.save({ parent_id: folder.id });
        await (0, test_utils_1.msleep)(1);
        const note3 = await Note_1.default.save({ parent_id: folder.id });
        const r1 = await api.route(Api_1.RequestMethod.GET, `folders/${folder.id}/notes`, {
            limit: 2,
        });
        expect(r1.items.length).toBe(2);
        expect(r1.items[0].id).toBe(note1.id);
        expect(r1.items[1].id).toBe(note2.id);
        const r2 = await api.route(Api_1.RequestMethod.GET, `folders/${folder.id}/notes`, {
            limit: 2,
            page: 2,
        });
        expect(r2.items.length).toBe(1);
        expect(r2.items[0].id).toBe(note3.id);
    }));
    it('should sort search paginated results', (async () => {
        SearchEngine_1.default.instance().setDb((0, test_utils_1.db)());
        await createNoteForPagination('note c', 1000);
        await createNoteForPagination('note e', 1001);
        await createNoteForPagination('note b', 1002);
        await createNoteForPagination('note a', 1003);
        await createNoteForPagination('note d', 1004);
        await SearchEngine_1.default.instance().syncTables();
        {
            const baseQuery = {
                query: 'note',
                fields: ['id', 'title', 'updated_time'],
                limit: 3,
                order_dir: types_1.PaginationOrderDir.ASC,
                order_by: 'updated_time',
            };
            const r1 = await api.route(Api_1.RequestMethod.GET, 'search', baseQuery);
            expect(r1.items[0].updated_time).toBe(1000);
            expect(r1.items[1].updated_time).toBe(1001);
            expect(r1.items[2].updated_time).toBe(1002);
            const r2 = await api.route(Api_1.RequestMethod.GET, 'search', Object.assign(Object.assign({}, baseQuery), { page: 2 }));
            expect(r2.items[0].updated_time).toBe(1003);
            expect(r2.items[1].updated_time).toBe(1004);
        }
        {
            const baseQuery = {
                query: 'note',
                fields: ['id', 'title', 'updated_time'],
                limit: 2,
                order_dir: types_1.PaginationOrderDir.DESC,
                order_by: 'title',
            };
            const r1 = await api.route(Api_1.RequestMethod.GET, 'search', baseQuery);
            expect(r1.items[0].title).toBe('note e');
            expect(r1.items[1].title).toBe('note d');
            const r2 = await api.route(Api_1.RequestMethod.GET, 'search', Object.assign(Object.assign({}, baseQuery), { page: 2 }));
            expect(r2.items[0].title).toBe('note c');
            expect(r2.items[1].title).toBe('note b');
            const r3 = await api.route(Api_1.RequestMethod.GET, 'search', Object.assign(Object.assign({}, baseQuery), { page: 3 }));
            expect(r3.items[0].title).toBe('note a');
        }
    }));
    it('should return default fields', (async () => {
        const folder = await Folder_1.default.save({ title: 'folder' });
        const note1 = await Note_1.default.save({ title: 'note1', parent_id: folder.id });
        await Note_1.default.save({ title: 'note2', parent_id: folder.id });
        const tag = await Tag_1.default.save({ title: 'tag' });
        await Tag_1.default.addNote(tag.id, note1.id);
        {
            const r = await api.route(Api_1.RequestMethod.GET, `folders/${folder.id}`);
            expect('id' in r).toBe(true);
            expect('title' in r).toBe(true);
            expect('parent_id' in r).toBe(true);
        }
        {
            const r = await api.route(Api_1.RequestMethod.GET, `folders/${folder.id}/notes`);
            expect('id' in r.items[0]).toBe(true);
            expect('title' in r.items[0]).toBe(true);
            expect('parent_id' in r.items[0]).toBe(true);
        }
        {
            const r = await api.route(Api_1.RequestMethod.GET, 'notes');
            expect('id' in r.items[0]).toBe(true);
            expect('title' in r.items[0]).toBe(true);
            expect('parent_id' in r.items[0]).toBe(true);
        }
        {
            const r = await api.route(Api_1.RequestMethod.GET, `notes/${note1.id}/tags`);
            expect('id' in r.items[0]).toBe(true);
            expect('title' in r.items[0]).toBe(true);
        }
        {
            const r = await api.route(Api_1.RequestMethod.GET, `tags/${tag.id}`);
            expect('id' in r).toBe(true);
            expect('title' in r).toBe(true);
        }
    }));
    it('should return the notes associated with a resource', (async () => {
        const note = await Note_1.default.save({});
        await shim_1.default.attachFileToNote(note, `${test_utils_1.supportDir}/photo.jpg`);
        const resource = (await Resource_1.default.all())[0];
        const resourceService = new ResourceService_1.default();
        await resourceService.indexNoteResources();
        const r = await api.route(Api_1.RequestMethod.GET, `resources/${resource.id}/notes`);
        expect(r.items.length).toBe(1);
        expect(r.items[0].id).toBe(note.id);
    }));
    it('should return the resources associated with a note', (async () => {
        const note = await Note_1.default.save({});
        await shim_1.default.attachFileToNote(note, `${test_utils_1.supportDir}/photo.jpg`);
        const resource = (await Resource_1.default.all())[0];
        const r = await api.route(Api_1.RequestMethod.GET, `notes/${note.id}/resources`);
        expect(r.items.length).toBe(1);
        expect(r.items[0].id).toBe(resource.id);
    }));
    it('should return search results', (async () => {
        SearchEngine_1.default.instance().setDb((0, test_utils_1.db)());
        for (let i = 0; i < 10; i++) {
            await Note_1.default.save({ title: 'a' });
        }
        await SearchEngine_1.default.instance().syncTables();
        // Mostly testing pagination below
        const r1 = await api.route(Api_1.RequestMethod.GET, 'search', { query: 'a', limit: 4 });
        expect(r1.items.length).toBe(4);
        expect(r1.has_more).toBe(true);
        const r2 = await api.route(Api_1.RequestMethod.GET, 'search', { query: 'a', limit: 4, page: 2 });
        expect(r2.items.length).toBe(4);
        expect(r2.has_more).toBe(true);
        const r3 = await api.route(Api_1.RequestMethod.GET, 'search', { query: 'a', limit: 4, page: 3 });
        expect(r3.items.length).toBe(2);
        expect(!!r3.has_more).toBe(false);
        await SearchEngine_1.default.instance().destroy();
    }));
    it('should not fail when both deleted and conflict notes are included', (async () => {
        const folder = await Folder_1.default.save({});
        const note1 = await Note_1.default.save({ parent_id: folder.id });
        await (0, test_utils_1.msleep)(1);
        const note2 = await Note_1.default.save({ parent_id: folder.id, deleted_time: 1 });
        await (0, test_utils_1.msleep)(1);
        const note3 = await Note_1.default.save({ parent_id: folder.id, is_conflict: 1 });
        const r1 = await api.route(Api_1.RequestMethod.GET, 'notes', {
            limit: 3,
            include_conflicts: '1',
            include_deleted: '1',
        });
        expect(r1.items.map((item) => item.id)).toEqual([
            note1.id,
            note2.id,
            note3.id,
        ]);
    }));
});
//# sourceMappingURL=Api.test.js.map