From 8655bdb5ddb92128ea00ac6dad417d7d961eb7ff Mon Sep 17 00:00:00 2001 From: Mo Date: Sun, 6 Aug 2023 15:23:31 -0500 Subject: [PATCH] tests: fix memory leaks (#2389) --- packages/sncrypto-web/test/crypto.test.js | 3 +- packages/sncrypto-web/test/memory.test.js | 3 +- packages/sncrypto-web/test/utils.test.js | 3 +- packages/snjs/mocha/000.test.js | 16 +- packages/snjs/mocha/001.test.js | 23 +- packages/snjs/mocha/002.test.js | 22 +- packages/snjs/mocha/003.test.js | 41 +- packages/snjs/mocha/004.test.js | 23 +- packages/snjs/mocha/actions.test.js | 248 +-- packages/snjs/mocha/app-group.test.js | 27 +- packages/snjs/mocha/application.test.js | 9 +- packages/snjs/mocha/auth-fringe-cases.test.js | 17 +- packages/snjs/mocha/auth.test.js | 60 +- packages/snjs/mocha/backups.test.js | 123 +- packages/snjs/mocha/collections.test.js | 7 +- packages/snjs/mocha/device_auth.test.js | 3 +- packages/snjs/mocha/features.test.js | 5 +- packages/snjs/mocha/files.test.js | 13 +- packages/snjs/mocha/history.test.js | 240 ++- packages/snjs/mocha/item.test.js | 80 +- packages/snjs/mocha/item_manager.test.js | 15 +- packages/snjs/mocha/key_params.test.js | 8 +- .../snjs/mocha/key_recovery_service.test.js | 13 +- packages/snjs/mocha/keys.test.js | 391 ++-- packages/snjs/mocha/lib/factory.js | 7 +- packages/snjs/mocha/memory.test.js | 10 +- packages/snjs/mocha/mfa_service.test.js | 66 +- .../snjs/mocha/migrations/migration.test.js | 1 + .../mocha/migrations/tags-to-folders.test.js | 49 +- .../snjs/mocha/model_tests/appmodels.test.js | 223 ++- .../snjs/mocha/model_tests/importing.test.js | 1664 ++++++++--------- packages/snjs/mocha/model_tests/items.test.js | 66 +- .../snjs/mocha/model_tests/mapping.test.js | 87 +- .../model_tests/notes_smart_tags.test.js | 31 +- .../snjs/mocha/model_tests/notes_tags.test.js | 421 ++--- .../model_tests/notes_tags_folders.test.js | 73 +- .../mocha/model_tests/performance.test.js | 11 +- packages/snjs/mocha/mutator.test.js | 93 +- packages/snjs/mocha/mutator_service.test.js | 22 +- .../snjs/mocha/note_display_criteria.test.js | 444 +++-- packages/snjs/mocha/payload.test.js | 59 +- .../snjs/mocha/payload_encryption.test.js | 30 +- packages/snjs/mocha/payload_manager.test.js | 51 +- packages/snjs/mocha/preferences.test.js | 87 +- packages/snjs/mocha/protection.test.js | 4 +- packages/snjs/mocha/protocol.test.js | 53 +- packages/snjs/mocha/session.test.js | 342 ++-- packages/snjs/mocha/settings.test.js | 10 +- packages/snjs/mocha/singletons.test.js | 235 +-- packages/snjs/mocha/storage.test.js | 240 +-- packages/snjs/mocha/subscriptions.test.js | 10 +- .../snjs/mocha/sync_tests/conflicting.test.js | 606 +++--- .../snjs/mocha/sync_tests/integrity.test.js | 73 +- .../snjs/mocha/sync_tests/notes_tags.test.js | 116 +- .../snjs/mocha/sync_tests/offline.test.js | 97 +- packages/snjs/mocha/sync_tests/online.test.js | 637 +++---- packages/snjs/mocha/upgrading.test.js | 206 +- packages/snjs/mocha/utils.test.js | 3 +- .../mocha/vaults/asymmetric-messages.test.js | 11 +- packages/snjs/mocha/vaults/conflicts.test.js | 16 +- packages/snjs/mocha/vaults/contacts.test.js | 11 +- packages/snjs/mocha/vaults/crypto.test.js | 11 +- packages/snjs/mocha/vaults/deletion.test.js | 16 +- packages/snjs/mocha/vaults/files.test.js | 11 +- packages/snjs/mocha/vaults/importing.test.js | 11 +- packages/snjs/mocha/vaults/invites.test.js | 11 +- packages/snjs/mocha/vaults/items.test.js | 11 +- .../snjs/mocha/vaults/key-management.test.js | 11 +- .../snjs/mocha/vaults/key-rotation.test.js | 11 +- .../snjs/mocha/vaults/key-sharing.test.js | 11 +- .../snjs/mocha/vaults/keypair-change.test.js | 11 +- .../snjs/mocha/vaults/permissions.test.js | 13 +- packages/snjs/mocha/vaults/pkc.test.js | 21 +- .../snjs/mocha/vaults/shared_vaults.test.js | 2 + packages/snjs/mocha/vaults/signatures.test.js | 12 +- packages/snjs/mocha/vaults/vaults.test.js | 23 +- 76 files changed, 3904 insertions(+), 3840 deletions(-) diff --git a/packages/sncrypto-web/test/crypto.test.js b/packages/sncrypto-web/test/crypto.test.js index ced5eb3f9..09ab13a3c 100644 --- a/packages/sncrypto-web/test/crypto.test.js +++ b/packages/sncrypto-web/test/crypto.test.js @@ -1,5 +1,4 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ + import './vendor/chai-as-promised-built.js' import './vendor/buffer@5.6.0.js' diff --git a/packages/sncrypto-web/test/memory.test.js b/packages/sncrypto-web/test/memory.test.js index 8a7909d0c..cd3a2a33f 100644 --- a/packages/sncrypto-web/test/memory.test.js +++ b/packages/sncrypto-web/test/memory.test.js @@ -1,5 +1,4 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ + import './vendor/chai-as-promised-built.js' chai.use(chaiAsPromised) diff --git a/packages/sncrypto-web/test/utils.test.js b/packages/sncrypto-web/test/utils.test.js index c591050d1..55a1649a4 100644 --- a/packages/sncrypto-web/test/utils.test.js +++ b/packages/sncrypto-web/test/utils.test.js @@ -1,5 +1,4 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ + import './vendor/chai-as-promised-built.js' chai.use(chaiAsPromised) diff --git a/packages/snjs/mocha/000.test.js b/packages/snjs/mocha/000.test.js index f612e88ae..705010e87 100644 --- a/packages/snjs/mocha/000.test.js +++ b/packages/snjs/mocha/000.test.js @@ -1,19 +1,17 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ -import * as Factory from './lib/factory.js' chai.use(chaiAsPromised) const expect = chai.expect describe('000 legacy protocol operations', () => { - const application = Factory.createApplicationWithRealCrypto() - const protocol004 = new SNProtocolOperator004(new SNWebCrypto()) + let protocol004 - before(async () => { - await Factory.initializeApplication(application) + beforeEach(async () => { + localStorage.clear() + + protocol004 = new SNProtocolOperator004(new SNWebCrypto()) }) - after(async () => { - await Factory.safeDeinit(application) + afterEach(async () => { + localStorage.clear() }) it('cannot decode 000 item', function () { diff --git a/packages/snjs/mocha/001.test.js b/packages/snjs/mocha/001.test.js index 2d8214eb0..b3f41fbbf 100644 --- a/packages/snjs/mocha/001.test.js +++ b/packages/snjs/mocha/001.test.js @@ -1,27 +1,32 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('001 protocol operations', () => { - const application = Factory.createApplicationWithRealCrypto() - const protocol001 = new SNProtocolOperator001(new SNWebCrypto()) + let application + let protocol001 - const _identifier = 'hello@test.com' - const _password = 'password' + let _identifier = 'hello@test.com' + let _password = 'password' let _keyParams, _key - // runs once before all tests in this block - before(async () => { + beforeEach(async () => { localStorage.clear() + + application = Factory.createApplicationWithRealCrypto() + protocol001 = new SNProtocolOperator001(new SNWebCrypto()) + await Factory.initializeApplication(application) + _key = await protocol001.createRootKey(_identifier, _password, KeyParamsOrigination.Registration) _keyParams = _key.keyParams }) - after(async () => { + afterEach(async () => { await Factory.safeDeinit(application) + localStorage.clear() + application = undefined }) it('generates random key', async () => { diff --git a/packages/snjs/mocha/002.test.js b/packages/snjs/mocha/002.test.js index 21d9779fd..ff47fd240 100644 --- a/packages/snjs/mocha/002.test.js +++ b/packages/snjs/mocha/002.test.js @@ -1,26 +1,30 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('002 protocol operations', () => { - const _identifier = 'hello@test.com' - const _password = 'password' + let _identifier = 'hello@test.com' + let _password = 'password' let _keyParams, _key - const application = Factory.createApplicationWithRealCrypto() - const protocol002 = new SNProtocolOperator002(new SNWebCrypto()) + let application + let protocol002 - // runs once before all tests in this block - before(async () => { + beforeEach(async () => { localStorage.clear() + + application = Factory.createApplicationWithRealCrypto() + protocol002 = new SNProtocolOperator002(new SNWebCrypto()) + await Factory.initializeApplication(application) _key = await protocol002.createRootKey(_identifier, _password, KeyParamsOrigination.Registration) _keyParams = _key.keyParams }) - after(async () => { + afterEach(async () => { await Factory.safeDeinit(application) + localStorage.clear() + application = undefined }) it('generates random key', async () => { diff --git a/packages/snjs/mocha/003.test.js b/packages/snjs/mocha/003.test.js index 25f5cb850..167ee5eea 100644 --- a/packages/snjs/mocha/003.test.js +++ b/packages/snjs/mocha/003.test.js @@ -1,34 +1,31 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('003 protocol operations', () => { - before(async () => { - localStorage.clear() - }) - - after(async () => { - localStorage.clear() - }) - - const _identifier = 'hello@test.com' - const _password = 'password' + let _identifier = 'hello@test.com' + let _password = 'password' let _keyParams, _key - const sharedApplication = Factory.createApplicationWithRealCrypto() - const protocol003 = new SNProtocolOperator003(new SNWebCrypto()) + let application + let protocol003 - // runs once before all tests in this block - before(async () => { - await Factory.initializeApplication(sharedApplication) + beforeEach(async () => { + localStorage.clear() + + application = Factory.createApplicationWithRealCrypto() + protocol003 = new SNProtocolOperator003(new SNWebCrypto()) + + await Factory.initializeApplication(application) _key = await protocol003.createRootKey(_identifier, _password, KeyParamsOrigination.Registration) _keyParams = _key.keyParams }) - after(async () => { - await Factory.safeDeinit(sharedApplication) + afterEach(async () => { + await Factory.safeDeinit(application) + localStorage.clear() + application = undefined }) it('generates random key', async () => { @@ -39,7 +36,7 @@ describe('003 protocol operations', () => { it('cost minimum should throw', () => { expect(() => { - sharedApplication.encryption.costMinimumForVersion('003') + application.encryption.costMinimumForVersion('003') }).to.throw('Cost minimums only apply to versions <= 002') }) @@ -59,7 +56,7 @@ describe('003 protocol operations', () => { it('computes proper keys for sign in', async () => { const identifier = 'foo@bar.com' const password = 'very_secure' - const keyParams = sharedApplication.encryption.createKeyParams({ + const keyParams = application.encryption.createKeyParams({ pw_nonce: 'baaec0131d677cf993381367eb082fe377cefe70118c1699cb9b38f0bc850e7b', identifier: identifier, version: '003', @@ -73,7 +70,7 @@ describe('003 protocol operations', () => { it('can decrypt item generated with web version 3.3.6', async () => { const identifier = 'demo@standardnotes.org' const password = 'password' - const keyParams = sharedApplication.encryption.createKeyParams({ + const keyParams = application.encryption.createKeyParams({ pw_nonce: '31107837b44d86179140b7c602a55d694243e2e9ced0c4c914ac21ad90215055', identifier: identifier, version: '003', diff --git a/packages/snjs/mocha/004.test.js b/packages/snjs/mocha/004.test.js index 839fc765b..503fe953a 100644 --- a/packages/snjs/mocha/004.test.js +++ b/packages/snjs/mocha/004.test.js @@ -1,26 +1,33 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('004 protocol operations', function () { - const _identifier = 'hello@test.com' - const _password = 'password' + let _identifier = 'hello@test.com' + let _password = 'password' let rootKeyParams let rootKey - const application = Factory.createApplicationWithRealCrypto() - const protocol004 = new SNProtocolOperator004(new SNWebCrypto()) + let application + let protocol004 + + beforeEach(async function () { + localStorage.clear() + + application = Factory.createApplicationWithRealCrypto() + protocol004 = new SNProtocolOperator004(new SNWebCrypto()) - before(async function () { await Factory.initializeApplication(application) + rootKey = await protocol004.createRootKey(_identifier, _password, KeyParamsOrigination.Registration) rootKeyParams = rootKey.keyParams }) - after(async function () { + afterEach(async function () { await Factory.safeDeinit(application) + application = undefined + localStorage.clear() }) it('cost minimum should throw', function () { diff --git a/packages/snjs/mocha/actions.test.js b/packages/snjs/mocha/actions.test.js index 574bacad3..1c410d7f2 100644 --- a/packages/snjs/mocha/actions.test.js +++ b/packages/snjs/mocha/actions.test.js @@ -1,41 +1,46 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' import * as Utils from './lib/Utils.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('actions service', () => { const errorProcessingActionMessage = 'An issue occurred while processing this action. Please try again.' - before(async function () { + let application + let itemManager + let actionsManager + let email + let password + let authParams + let fakeServer + let actionsExtension + let extensionItemUuid + + beforeEach(async function () { this.timeout(20000) localStorage.clear() - this.application = await Factory.createInitAppWithFakeCrypto() - this.itemManager = this.application.items - this.actionsManager = this.application.actions - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() + application = await Factory.createInitAppWithFakeCrypto() + itemManager = application.items + actionsManager = application.actions + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const rootKey = await this.application.encryption.createRootKey( - this.email, - this.password, - KeyParamsOrigination.Registration, - ) - this.authParams = rootKey.keyParams.content + const rootKey = await application.encryption.createRootKey(email, password, KeyParamsOrigination.Registration) + authParams = rootKey.keyParams.content - this.fakeServer = sinon.fakeServer.create() - this.fakeServer.respondImmediately = true + fakeServer = sinon.fakeServer.create() + fakeServer.respondImmediately = true - this.actionsExtension = { + actionsExtension = { identifier: 'org.standardnotes.testing', name: 'Test extension', content_type: 'Extension', @@ -81,9 +86,9 @@ describe('actions service', () => { ], } - this.fakeServer.respondWith('GET', /http:\/\/my-extension.sn.org\/get_actions\/(.*)/, (request, params) => { + fakeServer.respondWith('GET', /http:\/\/my-extension.sn.org\/get_actions\/(.*)/, (request, params) => { const urlParams = new URLSearchParams(params) - const extension = Copy(this.actionsExtension) + const extension = Copy(actionsExtension) if (urlParams.has('item_uuid')) { extension.actions.push({ @@ -117,31 +122,31 @@ describe('actions service', () => { }) const encryptedPayload = CreateEncryptedServerSyncPushPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, }), ) - this.fakeServer.respondWith('GET', /http:\/\/my-extension.sn.org\/action_[1,2]\/(.*)/, (request) => { + fakeServer.respondWith('GET', /http:\/\/my-extension.sn.org\/action_[1,2]\/(.*)/, (request) => { request.respond( 200, { 'Content-Type': 'application/json' }, JSON.stringify({ item: encryptedPayload, - auth_params: this.authParams, + auth_params: authParams, }), ) }) - this.fakeServer.respondWith('GET', 'http://my-extension.sn.org/action_3/', [ + fakeServer.respondWith('GET', 'http://my-extension.sn.org/action_3/', [ 200, { 'Content-Type': 'text/html; charset=utf-8' }, '

Action #3

', ]) - this.fakeServer.respondWith('POST', /http:\/\/my-extension.sn.org\/action_[4,6]\/(.*)/, (request) => { + fakeServer.respondWith('POST', /http:\/\/my-extension.sn.org\/action_[4,6]\/(.*)/, (request) => { const requestBody = JSON.parse(request.requestBody) const response = { @@ -152,7 +157,7 @@ describe('actions service', () => { request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(response)) }) - this.fakeServer.respondWith('GET', 'http://my-extension.sn.org/action_5/', (request) => { + fakeServer.respondWith('GET', 'http://my-extension.sn.org/action_5/', (request) => { const encryptedPayloadClone = Copy(encryptedPayload) encryptedPayloadClone.items_key_id = undefined @@ -163,53 +168,56 @@ describe('actions service', () => { const payload = { item: encryptedPayloadClone, - auth_params: this.authParams, + auth_params: authParams, } request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(payload)) }) // Extension item - const extensionItem = await this.application.mutator.createItem( - ContentType.TYPES.ActionsExtension, - this.actionsExtension, - ) - this.extensionItemUuid = extensionItem.uuid + const extensionItem = await application.mutator.createItem(ContentType.TYPES.ActionsExtension, actionsExtension) + extensionItemUuid = extensionItem.uuid }) - after(async function () { - this.fakeServer.restore() - await Factory.safeDeinit(this.application) - this.application = null + afterEach(async function () { + fakeServer.restore() + + await Factory.safeDeinit(application) + + application = undefined + itemManager = undefined + actionsManager = undefined + fakeServer = undefined + localStorage.clear() }) it('should get extension items', async function () { - await this.application.mutator.createItem(ContentType.TYPES.Note, { + await application.mutator.createItem(ContentType.TYPES.Note, { title: 'A simple note', text: 'Standard Notes rocks! lml.', }) - const extensions = this.actionsManager.getExtensions() + const extensions = actionsManager.getExtensions() expect(extensions.length).to.eq(1) }) it('should get extensions in context of item', async function () { - const noteItem = await this.application.mutator.createItem(ContentType.TYPES.Note, { + const noteItem = await application.mutator.createItem(ContentType.TYPES.Note, { title: 'Another note', text: 'Whiskey In The Jar', }) - const noteItemExtensions = this.actionsManager.extensionsInContextOfItem(noteItem) + const noteItemExtensions = actionsManager.extensionsInContextOfItem(noteItem) expect(noteItemExtensions.length).to.eq(1) expect(noteItemExtensions[0].supported_types).to.include(noteItem.content_type) }) it('should get actions based on item context', async function () { - const tagItem = await this.application.mutator.createItem(ContentType.TYPES.Tag, { + const tagItem = await application.mutator.createItem(ContentType.TYPES.Tag, { title: 'Music', }) - const extensionItem = await this.itemManager.findItem(this.extensionItemUuid) + const extensionItem = await itemManager.findItem(extensionItemUuid) const tagActions = extensionItem.actionsWithContextForItem(tagItem) expect(tagActions.length).to.eq(1) @@ -217,15 +225,15 @@ describe('actions service', () => { }) it('should load extension in context of item', async function () { - const noteItem = await this.application.mutator.createItem(ContentType.TYPES.Note, { + const noteItem = await application.mutator.createItem(ContentType.TYPES.Note, { title: 'Yet another note', text: 'And all things will end ♫', }) - const extensionItem = await this.itemManager.findItem(this.extensionItemUuid) + const extensionItem = await itemManager.findItem(extensionItemUuid) expect(extensionItem.actions.length).to.be.eq(5) - const extensionWithItem = await this.actionsManager.loadExtensionInContextOfItem(extensionItem, noteItem) + const extensionWithItem = await actionsManager.loadExtensionInContextOfItem(extensionItem, noteItem) expect(extensionWithItem.actions.length).to.be.eq(7) expect(extensionWithItem.actions.map((action) => action.label)).to.include.members([ /** @@ -237,7 +245,7 @@ describe('actions service', () => { ]) // Actions that are relevant for an item should not be stored. - const updatedExtensionItem = await this.itemManager.findItem(this.extensionItemUuid) + const updatedExtensionItem = await itemManager.findItem(extensionItemUuid) const expectedActions = extensionItem.actions.map((action) => { const { id, ...rest } = action return rest @@ -248,18 +256,22 @@ describe('actions service', () => { describe('render action', async function () { const sandbox = sinon.createSandbox() - before(async function () { - this.noteItem = await this.application.mutator.createItem(ContentType.TYPES.Note, { + let noteItem + let renderAction + let alertServiceAlert + let windowAlert + let httpServiceGetAbsolute + + beforeEach(async function () { + noteItem = await application.mutator.createItem(ContentType.TYPES.Note, { title: 'Hey', text: 'Welcome To Paradise', }) - const extensionItem = await this.itemManager.findItem(this.extensionItemUuid) - this.renderAction = extensionItem.actions.filter((action) => action.verb === 'render')[0] - }) + const extensionItem = await itemManager.findItem(extensionItemUuid) - beforeEach(async function () { - this.alertServiceAlert = sandbox.spy(this.actionsManager.alertService, 'alert') - this.windowAlert = sandbox.stub(window, 'alert').callsFake((message) => message) + renderAction = extensionItem.actions.filter((action) => action.verb === 'render')[0] + alertServiceAlert = sandbox.spy(actionsManager.alertService, 'alert') + windowAlert = sandbox.stub(window, 'alert').callsFake((message) => message) }) afterEach(async function () { @@ -267,35 +279,35 @@ describe('actions service', () => { }) it('should show an alert if the request fails', async function () { - this.httpServiceGetAbsolute = sandbox - .stub(this.actionsManager.httpService, 'getAbsolute') + httpServiceGetAbsolute = sandbox + .stub(actionsManager.httpService, 'getAbsolute') .callsFake((url) => Promise.reject(new Error('Dummy error.'))) - const actionResponse = await this.actionsManager.runAction(this.renderAction, this.noteItem) + const actionResponse = await actionsManager.runAction(renderAction, noteItem) - sinon.assert.calledOnceWithExactly(this.httpServiceGetAbsolute, this.renderAction.url) - sinon.assert.calledOnceWithExactly(this.alertServiceAlert, errorProcessingActionMessage) + sinon.assert.calledOnceWithExactly(httpServiceGetAbsolute, renderAction.url) + sinon.assert.calledOnceWithExactly(alertServiceAlert, errorProcessingActionMessage) expect(actionResponse.error.message).to.eq(errorProcessingActionMessage) }) it('should return a response if payload is valid', async function () { - const actionResponse = await this.actionsManager.runAction(this.renderAction, this.noteItem) + const actionResponse = await actionsManager.runAction(renderAction, noteItem) expect(actionResponse).to.have.property('item') expect(actionResponse.item.payload.content.title).to.eq('Testing') }) it('should return undefined if payload is invalid', async function () { - sandbox.stub(this.actionsManager, 'payloadByDecryptingResponse').returns(null) + sandbox.stub(actionsManager, 'payloadByDecryptingResponse').returns(null) - const actionResponse = await this.actionsManager.runAction(this.renderAction, this.noteItem) + const actionResponse = await actionsManager.runAction(renderAction, noteItem) expect(actionResponse).to.be.undefined }) it('should return decrypted payload if password is valid', async function () { - const extensionItem = await this.itemManager.findItem(this.extensionItemUuid) - this.renderAction = extensionItem.actions.filter((action) => action.verb === 'render')[0] - const actionResponse = await this.actionsManager.runAction(this.renderAction, this.noteItem) + const extensionItem = await itemManager.findItem(extensionItemUuid) + renderAction = extensionItem.actions.filter((action) => action.verb === 'render')[0] + const actionResponse = await actionsManager.runAction(renderAction, noteItem) expect(actionResponse.item).to.be.ok expect(actionResponse.item.title).to.be.equal('Testing') @@ -305,24 +317,24 @@ describe('actions service', () => { describe('show action', async function () { const sandbox = sinon.createSandbox() - before(async function () { - const extensionItem = await this.itemManager.findItem(this.extensionItemUuid) - this.showAction = extensionItem.actions[2] - }) + let showAction + let deviceInterfaceOpenUrl beforeEach(async function () { - this.actionsManager.device.openUrl = (url) => url - this.deviceInterfaceOpenUrl = sandbox.spy(this.actionsManager.device, 'openUrl') + const extensionItem = await itemManager.findItem(extensionItemUuid) + showAction = extensionItem.actions[2] + actionsManager.device.openUrl = (url) => url + deviceInterfaceOpenUrl = sandbox.spy(actionsManager.device, 'openUrl') }) - this.afterEach(async function () { + afterEach(async function () { sandbox.restore() }) it('should open the action url', async function () { - const response = await this.actionsManager.runAction(this.showAction) + const response = await actionsManager.runAction(showAction) - sandbox.assert.calledOnceWithExactly(this.deviceInterfaceOpenUrl, this.showAction.url) + sandbox.assert.calledOnceWithExactly(deviceInterfaceOpenUrl, showAction.url) expect(response).to.eql({}) }) }) @@ -330,28 +342,32 @@ describe('actions service', () => { describe('post action', async function () { const sandbox = sinon.createSandbox() - before(async function () { - this.noteItem = await this.application.mutator.createItem(ContentType.TYPES.Note, { + let noteItem + let extensionItem + let decryptedPostAction + let encryptedPostAction + let alertServiceAlert + let httpServicePostAbsolute + + beforeEach(async function () { + noteItem = await application.mutator.createItem(ContentType.TYPES.Note, { title: 'Excuse Me', text: 'Time To Be King 8)', }) - this.extensionItem = await this.itemManager.findItem(this.extensionItemUuid) - this.extensionItem = await this.actionsManager.loadExtensionInContextOfItem(this.extensionItem, this.noteItem) + extensionItem = await itemManager.findItem(extensionItemUuid) + extensionItem = await actionsManager.loadExtensionInContextOfItem(extensionItem, noteItem) - this.decryptedPostAction = this.extensionItem.actions.filter( + decryptedPostAction = extensionItem.actions.filter( (action) => action.access_type === 'decrypted' && action.verb === 'post', )[0] - this.encryptedPostAction = this.extensionItem.actions.filter( + encryptedPostAction = extensionItem.actions.filter( (action) => action.access_type === 'encrypted' && action.verb === 'post', )[0] - }) - - beforeEach(async function () { - this.alertServiceAlert = sandbox.spy(this.actionsManager.alertService, 'alert') - this.windowAlert = sandbox.stub(window, 'alert').callsFake((message) => message) - this.httpServicePostAbsolute = sandbox.stub(this.actionsManager.httpService, 'postAbsolute') - this.httpServicePostAbsolute.callsFake((url, params) => Promise.resolve(params)) + alertServiceAlert = sandbox.spy(actionsManager.alertService, 'alert') + sandbox.stub(window, 'alert').callsFake((message) => message) + httpServicePostAbsolute = sandbox.stub(actionsManager.httpService, 'postAbsolute') + httpServicePostAbsolute.callsFake((url, params) => Promise.resolve(params)) }) afterEach(async function () { @@ -359,52 +375,50 @@ describe('actions service', () => { }) it('should include generic encrypted payload within request body', async function () { - const response = await this.actionsManager.runAction(this.encryptedPostAction, this.noteItem) + const response = await actionsManager.runAction(encryptedPostAction, noteItem) expect(response.items[0].enc_item_key).to.satisfy((string) => { - return string.startsWith(this.application.encryption.getLatestVersion()) + return string.startsWith(application.encryption.getLatestVersion()) }) - expect(response.items[0].uuid).to.eq(this.noteItem.uuid) + expect(response.items[0].uuid).to.eq(noteItem.uuid) expect(response.items[0].auth_hash).to.not.be.ok expect(response.items[0].content_type).to.be.ok expect(response.items[0].created_at).to.be.ok expect(response.items[0].content).to.satisfy((string) => { - return string.startsWith(this.application.encryption.getLatestVersion()) + return string.startsWith(application.encryption.getLatestVersion()) }) }) it('should include generic decrypted payload within request body', async function () { - const response = await this.actionsManager.runAction(this.decryptedPostAction, this.noteItem) + const response = await actionsManager.runAction(decryptedPostAction, noteItem) - expect(response.items[0].uuid).to.eq(this.noteItem.uuid) + expect(response.items[0].uuid).to.eq(noteItem.uuid) expect(response.items[0].enc_item_key).to.not.be.ok expect(response.items[0].auth_hash).to.not.be.ok expect(response.items[0].content_type).to.be.ok expect(response.items[0].created_at).to.be.ok - expect(response.items[0].content.title).to.eq(this.noteItem.title) - expect(response.items[0].content.text).to.eq(this.noteItem.text) + expect(response.items[0].content.title).to.eq(noteItem.title) + expect(response.items[0].content.text).to.eq(noteItem.text) }) it('should post to the action url', async function () { - this.httpServicePostAbsolute.restore() - const response = await this.actionsManager.runAction(this.decryptedPostAction, this.noteItem) + httpServicePostAbsolute.restore() + const response = await actionsManager.runAction(decryptedPostAction, noteItem) expect(response).to.be.ok - expect(response.uuid).to.eq(this.noteItem.uuid) + expect(response.uuid).to.eq(noteItem.uuid) expect(response.result).to.eq('Action POSTed successfully.') }) it('should alert if an error occurred while processing the action', async function () { - this.httpServicePostAbsolute.restore() + httpServicePostAbsolute.restore() const dummyError = new Error('Dummy error.') - sandbox - .stub(this.actionsManager.httpService, 'postAbsolute') - .callsFake((url, params) => Promise.reject(dummyError)) + sandbox.stub(actionsManager.httpService, 'postAbsolute').callsFake((url, params) => Promise.reject(dummyError)) - const response = await this.actionsManager.runAction(this.decryptedPostAction, this.noteItem) + const response = await actionsManager.runAction(decryptedPostAction, noteItem) - sinon.assert.calledOnceWithExactly(this.alertServiceAlert, errorProcessingActionMessage) + sinon.assert.calledOnceWithExactly(alertServiceAlert, errorProcessingActionMessage) expect(response).to.be.eq(dummyError) }) }) @@ -412,15 +426,17 @@ describe('actions service', () => { describe('nested action', async function () { const sandbox = sinon.createSandbox() - before(async function () { - const extensionItem = await this.itemManager.findItem(this.extensionItemUuid) - this.nestedAction = extensionItem.actions.filter((action) => action.verb === 'nested')[0] - }) + let nestedAction + let actionsManagerRunAction + let httpServiceRunHttp + let actionResponse beforeEach(async function () { - this.actionsManagerRunAction = sandbox.spy(this.actionsManager, 'runAction') - this.httpServiceRunHttp = sandbox.spy(this.actionsManager.httpService, 'runHttp') - this.actionResponse = await this.actionsManager.runAction(this.nestedAction) + const extensionItem = await itemManager.findItem(extensionItemUuid) + nestedAction = extensionItem.actions.filter((action) => action.verb === 'nested')[0] + actionsManagerRunAction = sandbox.spy(actionsManager, 'runAction') + httpServiceRunHttp = sandbox.spy(actionsManager.httpService, 'runHttp') + actionResponse = await actionsManager.runAction(nestedAction) }) afterEach(async function () { @@ -428,15 +444,15 @@ describe('actions service', () => { }) it('should return undefined', async function () { - expect(this.actionResponse).to.be.undefined + expect(actionResponse).to.be.undefined }) it('should call runAction once', async function () { - sandbox.assert.calledOnce(this.actionsManagerRunAction) + sandbox.assert.calledOnce(actionsManagerRunAction) }) it('should not make any http requests', async function () { - sandbox.assert.notCalled(this.httpServiceRunHttp) + sandbox.assert.notCalled(httpServiceRunHttp) }) }) }) diff --git a/packages/snjs/mocha/app-group.test.js b/packages/snjs/mocha/app-group.test.js index 4cb7fba6d..49d9789ee 100644 --- a/packages/snjs/mocha/app-group.test.js +++ b/packages/snjs/mocha/app-group.test.js @@ -1,23 +1,32 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import WebDeviceInterface from './lib/web_device_interface.js' import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('application group', function () { - const globalDevice = new WebDeviceInterface(setTimeout.bind(window), setInterval.bind(window)) + let device + let group beforeEach(async function () { localStorage.clear() + + device = new WebDeviceInterface(setTimeout.bind(window), setInterval.bind(window)) + group = new SNApplicationGroup(device) }) afterEach(async function () { + if (group.primaryApplication) { + await Factory.safeDeinit(group.primaryApplication) + } + device.deinit() + localStorage.clear() + + group = undefined }) it('initializing a group should result with primary application', async function () { - const group = new SNApplicationGroup(globalDevice) await group.initialize({ applicationCreator: (descriptor, deviceInterface) => { return Factory.createApplicationWithFakeCrypto(descriptor.identifier, deviceInterface) @@ -25,12 +34,9 @@ describe('application group', function () { }) expect(group.primaryApplication).to.be.ok expect(group.primaryApplication.identifier).to.be.ok - - await Factory.safeDeinit(group.primaryApplication) }) it('initializing a group should result with proper descriptor setup', async function () { - const group = new SNApplicationGroup(globalDevice) await group.initialize({ applicationCreator: (descriptor, deviceInterface) => { return Factory.createApplicationWithFakeCrypto(descriptor.identifier, deviceInterface) @@ -38,12 +44,9 @@ describe('application group', function () { }) const identifier = group.primaryApplication.identifier expect(group.descriptorRecord[identifier].identifier).to.equal(identifier) - - await Factory.safeDeinit(group.primaryApplication) }) it('should persist descriptor record after changes', async function () { - const group = new SNApplicationGroup(globalDevice) await group.initialize({ applicationCreator: (descriptor, device) => { return Factory.createInitAppWithFakeCryptoWithOptions({ @@ -59,14 +62,13 @@ describe('application group', function () { expect(descriptorRecord[identifier].primary).to.equal(true) await group.unloadCurrentAndCreateNewDescriptor() - const descriptorRecord2 = await globalDevice.getJsonParsedRawStorageValue(RawStorageKey.DescriptorRecord) + const descriptorRecord2 = await device.getJsonParsedRawStorageValue(RawStorageKey.DescriptorRecord) expect(Object.keys(descriptorRecord2).length).to.equal(2) expect(descriptorRecord2[identifier].primary).to.equal(false) }) it('adding new application should incrememnt total descriptor count', async function () { - const group = new SNApplicationGroup(globalDevice) await group.initialize({ applicationCreator: (descriptor, device) => { return Factory.createInitAppWithFakeCryptoWithOptions({ @@ -82,7 +84,6 @@ describe('application group', function () { }) it('should be notified when application changes', async function () { - const group = new SNApplicationGroup(globalDevice) let notifyCount = 0 const expectedCount = 2 // eslint-disable-next-line no-async-promise-executor diff --git a/packages/snjs/mocha/application.test.js b/packages/snjs/mocha/application.test.js index 013c48242..54f4196c1 100644 --- a/packages/snjs/mocha/application.test.js +++ b/packages/snjs/mocha/application.test.js @@ -1,7 +1,6 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import { BaseItemCounts } from './lib/BaseItemCounts.js' import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect @@ -133,6 +132,12 @@ describe('application instances', () => { deinit = sinon.spy(testSNApp, 'deinit') }) + afterEach(async () => { + await Factory.safeDeinit(testSNApp) + localStorage.clear() + sinon.restore() + }) + it('shows confirmation dialog when there are unsaved changes', async () => { await testSNApp.mutator.setItemDirty(testNote1) await testSNApp.user.signOut() diff --git a/packages/snjs/mocha/auth-fringe-cases.test.js b/packages/snjs/mocha/auth-fringe-cases.test.js index aa8039ce2..f78ba7c74 100644 --- a/packages/snjs/mocha/auth-fringe-cases.test.js +++ b/packages/snjs/mocha/auth-fringe-cases.test.js @@ -1,14 +1,16 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import { BaseItemCounts } from './lib/BaseItemCounts.js' import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('auth fringe cases', () => { - const createContext = async () => { + let context + + beforeEach(async function () { + localStorage.clear() const application = await Factory.createInitAppWithFakeCrypto() - return { + context = { expectedItemCount: BaseItemCounts.DefaultItems, application: application, email: UuidGenerator.GenerateUuid(), @@ -17,10 +19,6 @@ describe('auth fringe cases', () => { await Factory.safeDeinit(application) }, } - } - - beforeEach(async function () { - localStorage.clear() }) afterEach(async function () { @@ -40,7 +38,6 @@ describe('auth fringe cases', () => { describe('localStorage improperly cleared with 1 item', function () { it('item should be errored', async function () { - const context = await createContext() await context.application.register(context.email, context.password) const note = await Factory.createSyncedNote(context.application) clearApplicationLocalStorageOfNonItems() @@ -55,7 +52,6 @@ describe('auth fringe cases', () => { }) it('signing in again should decrypt item', async function () { - const context = await createContext() await context.application.register(context.email, context.password) const note = await Factory.createSyncedNote(context.application) clearApplicationLocalStorageOfNonItems() @@ -76,7 +72,6 @@ describe('auth fringe cases', () => { describe('having offline item matching remote item uuid', function () { it('offline item should not overwrite recently updated server item and conflict should be created', async function () { - const context = await createContext() await context.application.register(context.email, context.password) const staleText = 'stale text' diff --git a/packages/snjs/mocha/auth.test.js b/packages/snjs/mocha/auth.test.js index 45715bfae..dc3f7e141 100644 --- a/packages/snjs/mocha/auth.test.js +++ b/packages/snjs/mocha/auth.test.js @@ -13,11 +13,7 @@ describe('basic auth', function () { } let context - - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) + let expectedItemCount beforeEach(async function () { localStorage.clear() @@ -26,12 +22,23 @@ describe('basic auth', function () { await context.launch() - this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount + expectedItemCount = BaseItemCounts.DefaultItemsWithAccount + }) + + afterEach(async function () { + await context.deinit() + + localStorage.clear() + + context = undefined + + sinon.restore() }) it('successfully register new account', async function () { const response = await context.register() expect(response).to.be.ok + expect(await context.application.encryption.getRootKey()).to.be.ok }) @@ -58,7 +65,7 @@ describe('basic auth', function () { expect(await context.application.encryption.getRootKey()).to.be.ok - context.application = await Factory.signOutApplicationAndReturnNew(context.application) + await context.signout() expect(await context.application.encryption.getRootKey()).to.not.be.ok expect(context.application.encryption.rootKeyManager.getKeyMode()).to.equal(KeyMode.RootKeyNone) @@ -233,7 +240,7 @@ describe('basic auth', function () { await specContext.launch() await specContext.register() - await specContext.signout() + await specContext.deinit() specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), uppercase, password) @@ -255,10 +262,11 @@ describe('basic auth', function () { * with an uppercase email */ const password = UuidGenerator.GenerateUuid() + let specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), nospace, password) await specContext.launch() await specContext.register() - await specContext.signout() + await specContext.deinit() specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), withspace, password) await specContext.launch() @@ -272,7 +280,8 @@ describe('basic auth', function () { it('fails login with wrong password', async function () { await context.register() - context.application = await Factory.signOutApplicationAndReturnNew(context.application) + await context.signout() + const response = await context.application.signIn( context.email, 'wrongpassword', @@ -299,7 +308,8 @@ describe('basic auth', function () { expect(response.error).to.be.ok /** Ensure we can still log in */ - context.application = await Factory.signOutAndBackIn(context.application, context.email, context.password) + await context.signout() + await context.signIn() }).timeout(20000) it('registering for new account and completing first after download sync should not put us out of sync', async function () { @@ -339,23 +349,23 @@ describe('basic auth', function () { const noteCount = 5 await Factory.createManyMappedNotes(context.application, noteCount) - this.expectedItemCount += noteCount + expectedItemCount += noteCount await context.sync() - expect(context.application.items.items.length).to.equal(this.expectedItemCount) + expect(context.application.items.items.length).to.equal(expectedItemCount) const newPassword = 'newpassword' const response = await context.application.changePassword(context.password, newPassword) expect(response.error).to.not.be.ok - this.expectedItemCount += ['new items key'].length - expect(context.application.items.items.length).to.equal(this.expectedItemCount) + expectedItemCount += ['new items key'].length + expect(context.application.items.items.length).to.equal(expectedItemCount) expect(context.application.payloads.invalidPayloads.length).to.equal(0) await context.application.sync.markAllItemsAsNeedingSyncAndPersist() await context.sync(syncOptions) - expect(context.application.items.items.length).to.equal(this.expectedItemCount) + expect(context.application.items.items.length).to.equal(expectedItemCount) }).timeout(40000) it('should sign into account after changing password', async function () { @@ -365,7 +375,7 @@ describe('basic auth', function () { const response = await context.application.changePassword(context.password, newPassword) expect(response.error).to.not.be.ok - this.expectedItemCount += ['new items key'].length + expectedItemCount += ['new items key'].length await context.signout() @@ -382,7 +392,7 @@ describe('basic auth', function () { expect(signinResponse.data.error).to.not.be.ok expect(await context.application.encryption.getRootKey()).to.be.ok - expect(context.application.items.items.length).to.equal(this.expectedItemCount) + expect(context.application.items.items.length).to.equal(expectedItemCount) expect(context.application.payloads.invalidPayloads.length).to.equal(0) }) @@ -393,7 +403,7 @@ describe('basic auth', function () { const noteCount = 3 await Factory.createManyMappedNotes(context.application, noteCount) - this.expectedItemCount += noteCount + expectedItemCount += noteCount await context.sync() @@ -401,9 +411,9 @@ describe('basic auth', function () { const response = await context.application.changePassword(context.password, newPassword) expect(response.error).to.not.be.ok - this.expectedItemCount += ['new items key'].length + expectedItemCount += ['new items key'].length - expect(context.application.items.items.length).to.equal(this.expectedItemCount) + expect(context.application.items.items.length).to.equal(expectedItemCount) }) it('changes password many times', async function () { @@ -411,7 +421,7 @@ describe('basic auth', function () { const noteCount = 10 await Factory.createManyMappedNotes(context.application, noteCount) - this.expectedItemCount += noteCount + expectedItemCount += noteCount await context.application.sync.sync(syncOptions) const numTimesToChangePw = 3 @@ -422,12 +432,12 @@ describe('basic auth', function () { await context.application.changePassword(currentPassword, newPassword) /** New items key */ - this.expectedItemCount++ + expectedItemCount++ currentPassword = newPassword newPassword = Factory.randomString() - expect(context.application.items.items.length).to.equal(this.expectedItemCount) + expect(context.application.items.items.length).to.equal(expectedItemCount) expect(context.application.payloads.invalidPayloads.length).to.equal(0) await context.application.sync.markAllItemsAsNeedingSyncAndPersist() @@ -460,7 +470,7 @@ describe('basic auth', function () { email: context.email, password: context.password, }) - context.application = await Factory.signOutApplicationAndReturnNew(context.application) + await context.signout() const performSignIn = sinon.spy(context.application.sessions, 'performSignIn') await context.application.signIn(context.email, 'wrong password', undefined, undefined, undefined, true) expect(performSignIn.callCount).to.equal(1) diff --git a/packages/snjs/mocha/backups.test.js b/packages/snjs/mocha/backups.test.js index ab9a5db87..709c83bee 100644 --- a/packages/snjs/mocha/backups.test.js +++ b/packages/snjs/mocha/backups.test.js @@ -1,41 +1,38 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import { BaseItemCounts } from './lib/BaseItemCounts.js' import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('backups', function () { - before(function () { - localStorage.clear() - }) - - after(function () { - localStorage.clear() - }) + let application + let email + let password beforeEach(async function () { - this.application = await Factory.createInitAppWithFakeCrypto() - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() + localStorage.clear() + application = await Factory.createInitAppWithFakeCrypto() + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() }) afterEach(async function () { - await Factory.safeDeinit(this.application) - this.application = null + await Factory.safeDeinit(application) + application = null + localStorage.clear() }) it('backup file should have a version number', async function () { - let data = await this.application.createDecryptedBackupFile() - expect(data.version).to.equal(this.application.encryption.getLatestVersion()) - await this.application.addPasscode('passcode') - data = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() - expect(data.version).to.equal(this.application.encryption.getLatestVersion()) + let data = await application.createDecryptedBackupFile() + expect(data.version).to.equal(application.encryption.getLatestVersion()) + await application.addPasscode('passcode') + data = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + expect(data.version).to.equal(application.encryption.getLatestVersion()) }) it('no passcode + no account backup file should have correct number of items', async function () { - await Promise.all([Factory.createSyncedNote(this.application), Factory.createSyncedNote(this.application)]) - const data = await this.application.createDecryptedBackupFile() + await Promise.all([Factory.createSyncedNote(application), Factory.createSyncedNote(application)]) + const data = await application.createDecryptedBackupFile() const offsetForNewItems = 2 const offsetForNoItemsKey = -1 expect(data.items.length).to.equal(BaseItemCounts.DefaultItems + offsetForNewItems + offsetForNoItemsKey) @@ -43,68 +40,68 @@ describe('backups', function () { it('passcode + no account backup file should have correct number of items', async function () { const passcode = 'passcode' - await this.application.addPasscode(passcode) - await Promise.all([Factory.createSyncedNote(this.application), Factory.createSyncedNote(this.application)]) + await application.addPasscode(passcode) + await Promise.all([Factory.createSyncedNote(application), Factory.createSyncedNote(application)]) // Encrypted backup without authorization - const encryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() + const encryptedData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItems + 2) // Encrypted backup with authorization - Factory.handlePasswordChallenges(this.application, passcode) - const authorizedEncryptedData = await this.application.createEncryptedBackupFile() + Factory.handlePasswordChallenges(application, passcode) + const authorizedEncryptedData = await application.createEncryptedBackupFile() expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItems + 2) }) it('no passcode + account backup file should have correct number of items', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - await Promise.all([Factory.createSyncedNote(this.application), Factory.createSyncedNote(this.application)]) + await Promise.all([Factory.createSyncedNote(application), Factory.createSyncedNote(application)]) // Encrypted backup without authorization - const encryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() + const encryptedData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2) - Factory.handlePasswordChallenges(this.application, this.password) + Factory.handlePasswordChallenges(application, password) // Decrypted backup - const decryptedData = await this.application.createDecryptedBackupFile() + const decryptedData = await application.createDecryptedBackupFile() expect(decryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccountWithoutItemsKey + 2) // Encrypted backup with authorization - const authorizedEncryptedData = await this.application.createEncryptedBackupFile() + const authorizedEncryptedData = await application.createEncryptedBackupFile() expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2) }) it('passcode + account backup file should have correct number of items', async function () { const passcode = 'passcode' - await this.application.register(this.email, this.password) - Factory.handlePasswordChallenges(this.application, this.password) - await this.application.addPasscode(passcode) - await Promise.all([Factory.createSyncedNote(this.application), Factory.createSyncedNote(this.application)]) + await application.register(email, password) + Factory.handlePasswordChallenges(application, password) + await application.addPasscode(passcode) + await Promise.all([Factory.createSyncedNote(application), Factory.createSyncedNote(application)]) // Encrypted backup without authorization - const encryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() + const encryptedData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2) - Factory.handlePasswordChallenges(this.application, passcode) + Factory.handlePasswordChallenges(application, passcode) // Decrypted backup - const decryptedData = await this.application.createDecryptedBackupFile() + const decryptedData = await application.createDecryptedBackupFile() expect(decryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccountWithoutItemsKey + 2) // Encrypted backup with authorization - const authorizedEncryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() + const authorizedEncryptedData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2) }).timeout(10000) it('backup file item should have correct fields', async function () { - await Factory.createSyncedNote(this.application) - let backupData = await this.application.createDecryptedBackupFile() + await Factory.createSyncedNote(application) + let backupData = await application.createDecryptedBackupFile() let rawItem = backupData.items.find((i) => i.content_type === ContentType.TYPES.Note) expect(rawItem.fields).to.not.be.ok @@ -118,12 +115,12 @@ describe('backups', function () { expect(rawItem.updated_at).to.be.ok await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - backupData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() + backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() rawItem = backupData.items.find((i) => i.content_type === ContentType.TYPES.Note) expect(rawItem.fields).to.not.be.ok @@ -138,11 +135,11 @@ describe('backups', function () { }) it('downloading backup if item is error decrypting should succeed', async function () { - await Factory.createSyncedNote(this.application) + await Factory.createSyncedNote(application) - const note = await Factory.createSyncedNote(this.application) + const note = await Factory.createSyncedNote(application) - const encrypted = await this.application.encryption.encryptSplitSingle({ + const encrypted = await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note.payload], }, @@ -152,19 +149,19 @@ describe('backups', function () { errorDecrypting: true, }) - await this.application.payloads.emitPayload(errored) + await application.payloads.emitPayload(errored) - const erroredItem = this.application.items.findAnyItem(errored.uuid) + const erroredItem = application.items.findAnyItem(errored.uuid) expect(erroredItem.errorDecrypting).to.equal(true) - const backupData = await this.application.createDecryptedBackupFile() + const backupData = await application.createDecryptedBackupFile() expect(backupData.items.length).to.equal(BaseItemCounts.DefaultItemsNoAccounNoItemsKey + 2) }) it('decrypted backup file should not have keyParams', async function () { - const backup = await this.application.createDecryptedBackupFile() + const backup = await application.createDecryptedBackupFile() expect(backup).to.not.haveOwnProperty('keyParams') }) @@ -187,31 +184,31 @@ describe('backups', function () { }) it('encrypted backup file should have keyParams', async function () { - await this.application.addPasscode('passcode') - const backup = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() + await application.addPasscode('passcode') + const backup = await application.createEncryptedBackupFileForAutomatedDesktopBackups() expect(backup).to.haveOwnProperty('keyParams') }) it('decrypted backup file should not have itemsKeys', async function () { - const backup = await this.application.createDecryptedBackupFile() + const backup = await application.createDecryptedBackupFile() expect(backup.items.some((item) => item.content_type === ContentType.TYPES.ItemsKey)).to.be.false }) it('encrypted backup file should have itemsKeys', async function () { - await this.application.addPasscode('passcode') - const backup = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() + await application.addPasscode('passcode') + const backup = await application.createEncryptedBackupFileForAutomatedDesktopBackups() expect(backup.items.some((item) => item.content_type === ContentType.TYPES.ItemsKey)).to.be.true }) it('backup file with no account and no passcode should be decrypted', async function () { - const note = await Factory.createSyncedNote(this.application) - const backup = await this.application.createDecryptedBackupFile() + const note = await Factory.createSyncedNote(application) + const backup = await application.createDecryptedBackupFile() expect(backup).to.not.haveOwnProperty('keyParams') expect(backup.items.some((item) => item.content_type === ContentType.TYPES.ItemsKey)).to.be.false expect(backup.items.find((item) => item.content_type === ContentType.TYPES.Note).uuid).to.equal(note.uuid) let error try { - await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() + await application.createEncryptedBackupFileForAutomatedDesktopBackups() } catch (e) { error = e } diff --git a/packages/snjs/mocha/collections.test.js b/packages/snjs/mocha/collections.test.js index 72f057cdb..3a4eb00d8 100644 --- a/packages/snjs/mocha/collections.test.js +++ b/packages/snjs/mocha/collections.test.js @@ -1,16 +1,15 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' import { createRelatedNoteTagPairPayload } from './lib/Items.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('payload collections', () => { - before(async () => { + beforeEach(async () => { localStorage.clear() }) - after(async () => { + afterEach(async () => { localStorage.clear() }) diff --git a/packages/snjs/mocha/device_auth.test.js b/packages/snjs/mocha/device_auth.test.js index 982ddb90b..230669562 100644 --- a/packages/snjs/mocha/device_auth.test.js +++ b/packages/snjs/mocha/device_auth.test.js @@ -1,6 +1,5 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect diff --git a/packages/snjs/mocha/features.test.js b/packages/snjs/mocha/features.test.js index 334315cb0..a9d95ce4a 100644 --- a/packages/snjs/mocha/features.test.js +++ b/packages/snjs/mocha/features.test.js @@ -1,6 +1,6 @@ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' chai.use(chaiAsPromised) + const expect = chai.expect describe('features', () => { @@ -9,6 +9,7 @@ describe('features', () => { let password beforeEach(async function () { + localStorage.clear() application = await Factory.createInitAppWithFakeCrypto() sinon.spy(application.mutator, 'createItem') @@ -28,6 +29,8 @@ describe('features', () => { afterEach(async function () { Factory.safeDeinit(application) sinon.restore() + localStorage.clear() + application = undefined }) describe('new user roles received on api response meta', () => { diff --git a/packages/snjs/mocha/files.test.js b/packages/snjs/mocha/files.test.js index 8b02d9b37..7a266170b 100644 --- a/packages/snjs/mocha/files.test.js +++ b/packages/snjs/mocha/files.test.js @@ -17,6 +17,14 @@ describe('files', function () { localStorage.clear() }) + afterEach(async function () { + await Factory.safeDeinit(application) + localStorage.clear() + + application = undefined + context = undefined + }) + const setup = async ({ fakeCrypto, subscription = true }) => { if (fakeCrypto) { context = await Factory.createAppContextWithFakeCrypto() @@ -41,11 +49,6 @@ describe('files', function () { } } - afterEach(async function () { - await Factory.safeDeinit(application) - localStorage.clear() - }) - it('should create valet token from server', async function () { await setup({ fakeCrypto: true, subscription: true }) diff --git a/packages/snjs/mocha/history.test.js b/packages/snjs/mocha/history.test.js index 421a7b54b..7aa52fd4e 100644 --- a/packages/snjs/mocha/history.test.js +++ b/packages/snjs/mocha/history.test.js @@ -1,13 +1,14 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' import { createNoteParams } from './lib/Items.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('history manager', () => { const largeCharacterChange = 25 + let application, history, email, password + const syncOptions = { checkIntegrity: true, awaitAll: true, @@ -23,15 +24,15 @@ describe('history manager', () => { describe('session', function () { beforeEach(async function () { - this.application = await Factory.createInitAppWithFakeCrypto() - this.history = this.application.dependencies.get(TYPES.HistoryManager) - this.payloadManager = this.application.payloads + application = await Factory.createInitAppWithFakeCrypto() + history = application.dependencies.get(TYPES.HistoryManager) + /** Automatically optimize after every revision by setting this to 0 */ - this.history.itemRevisionThreshold = 0 + history.itemRevisionThreshold = 0 }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) }) async function setTextAndSync(application, item, text) { @@ -53,15 +54,15 @@ describe('history manager', () => { } it('create basic history entries 1', async function () { - const item = await Factory.createSyncedNote(this.application) - expect(this.history.sessionHistoryForItem(item).length).to.equal(0) + const item = await Factory.createSyncedNote(application) + expect(history.sessionHistoryForItem(item).length).to.equal(0) /** Sync with same contents, should not create new entry */ - await Factory.markDirtyAndSyncItem(this.application, item) - expect(this.history.sessionHistoryForItem(item).length).to.equal(0) + await Factory.markDirtyAndSyncItem(application, item) + expect(history.sessionHistoryForItem(item).length).to.equal(0) /** Sync with different contents, should create new entry */ - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item, (mutator) => { mutator.title = Math.random() @@ -70,12 +71,12 @@ describe('history manager', () => { undefined, syncOptions, ) - expect(this.history.sessionHistoryForItem(item).length).to.equal(1) + expect(history.sessionHistoryForItem(item).length).to.equal(1) }) it('first change should create revision with previous value', async function () { - const identifier = this.application.identifier - const item = await Factory.createSyncedNote(this.application) + const identifier = application.identifier + const item = await Factory.createSyncedNote(application) /** Simulate loading new application session */ const context = await Factory.createAppContext({ identifier }) @@ -119,53 +120,41 @@ describe('history manager', () => { }) it('should optimize basic entries', async function () { - let item = await Factory.createSyncedNote(this.application) + let item = await Factory.createSyncedNote(application) /** * Add 1 character. This typically would be discarded as an entry, but it * won't here because it's the first change, which we want to keep. */ - await setTextAndSync(this.application, item, item.content.text + '1') - expect(this.history.sessionHistoryForItem(item).length).to.equal(1) + await setTextAndSync(application, item, item.content.text + '1') + expect(history.sessionHistoryForItem(item).length).to.equal(1) /** * Changing it by one character should keep this entry, * since it's now the last (and will keep the first) */ - item = await setTextAndSync(this.application, item, item.content.text + '2') - expect(this.history.sessionHistoryForItem(item).length).to.equal(2) + item = await setTextAndSync(application, item, item.content.text + '2') + expect(history.sessionHistoryForItem(item).length).to.equal(2) /** * Change it over the largeCharacterChange threshold. It should keep this * revision, but now remove the previous revision, since it's no longer * the last, and is a small change. */ - item = await setTextAndSync( - this.application, - item, - item.content.text + Factory.randomString(largeCharacterChange + 1), - ) - expect(this.history.sessionHistoryForItem(item).length).to.equal(2) + item = await setTextAndSync(application, item, item.content.text + Factory.randomString(largeCharacterChange + 1)) + expect(history.sessionHistoryForItem(item).length).to.equal(2) - item = await setTextAndSync( - this.application, - item, - item.content.text + Factory.randomString(largeCharacterChange + 1), - ) - expect(this.history.sessionHistoryForItem(item).length).to.equal(2) + item = await setTextAndSync(application, item, item.content.text + Factory.randomString(largeCharacterChange + 1)) + expect(history.sessionHistoryForItem(item).length).to.equal(2) /** Delete over threshold text. */ - item = await setTextAndSync( - this.application, - item, - deleteCharsFromString(item.content.text, largeCharacterChange + 1), - ) - expect(this.history.sessionHistoryForItem(item).length).to.equal(3) + item = await setTextAndSync(application, item, deleteCharsFromString(item.content.text, largeCharacterChange + 1)) + expect(history.sessionHistoryForItem(item).length).to.equal(3) /** * Delete just 1 character. It should now retain the previous revision, as well as the * one previous to that. */ - item = await setTextAndSync(this.application, item, deleteCharsFromString(item.content.text, 1)) - expect(this.history.sessionHistoryForItem(item).length).to.equal(4) - item = await setTextAndSync(this.application, item, deleteCharsFromString(item.content.text, 1)) - expect(this.history.sessionHistoryForItem(item).length).to.equal(5) + item = await setTextAndSync(application, item, deleteCharsFromString(item.content.text, 1)) + expect(history.sessionHistoryForItem(item).length).to.equal(4) + item = await setTextAndSync(application, item, deleteCharsFromString(item.content.text, 1)) + expect(history.sessionHistoryForItem(item).length).to.equal(5) }) it('should keep the entry right before a large deletion, regardless of its delta', async function () { @@ -174,27 +163,19 @@ describe('history manager', () => { text: Factory.randomString(100), }), ) - let item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) - await this.application.mutator.setItemDirty(item) - await this.application.sync.sync(syncOptions) + let item = await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) + await application.mutator.setItemDirty(item) + await application.sync.sync(syncOptions) /** It should keep the first and last by default */ - item = await setTextAndSync(this.application, item, item.content.text) - item = await setTextAndSync(this.application, item, item.content.text + Factory.randomString(1)) - expect(this.history.sessionHistoryForItem(item).length).to.equal(2) - item = await setTextAndSync( - this.application, - item, - deleteCharsFromString(item.content.text, largeCharacterChange + 1), - ) - expect(this.history.sessionHistoryForItem(item).length).to.equal(2) - item = await setTextAndSync(this.application, item, item.content.text + Factory.randomString(1)) - expect(this.history.sessionHistoryForItem(item).length).to.equal(3) - item = await setTextAndSync( - this.application, - item, - item.content.text + Factory.randomString(largeCharacterChange + 1), - ) - expect(this.history.sessionHistoryForItem(item).length).to.equal(4) + item = await setTextAndSync(application, item, item.content.text) + item = await setTextAndSync(application, item, item.content.text + Factory.randomString(1)) + expect(history.sessionHistoryForItem(item).length).to.equal(2) + item = await setTextAndSync(application, item, deleteCharsFromString(item.content.text, largeCharacterChange + 1)) + expect(history.sessionHistoryForItem(item).length).to.equal(2) + item = await setTextAndSync(application, item, item.content.text + Factory.randomString(1)) + expect(history.sessionHistoryForItem(item).length).to.equal(3) + item = await setTextAndSync(application, item, item.content.text + Factory.randomString(largeCharacterChange + 1)) + expect(history.sessionHistoryForItem(item).length).to.equal(4) }) it('entries should be ordered from newest to oldest', async function () { @@ -204,32 +185,23 @@ describe('history manager', () => { }), ) - let item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) + let item = await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) - await this.application.mutator.setItemDirty(item) - await this.application.sync.sync(syncOptions) + await application.mutator.setItemDirty(item) + await application.sync.sync(syncOptions) - item = await setTextAndSync(this.application, item, item.content.text + Factory.randomString(1)) + item = await setTextAndSync(application, item, item.content.text + Factory.randomString(1)) - item = await setTextAndSync( - this.application, - item, - deleteCharsFromString(item.content.text, largeCharacterChange + 1), - ) + item = await setTextAndSync(application, item, deleteCharsFromString(item.content.text, largeCharacterChange + 1)) - item = await setTextAndSync(this.application, item, item.content.text + Factory.randomString(1)) + item = await setTextAndSync(application, item, item.content.text + Factory.randomString(1)) - item = await setTextAndSync( - this.application, - item, - item.content.text + Factory.randomString(largeCharacterChange + 1), - ) + item = await setTextAndSync(application, item, item.content.text + Factory.randomString(largeCharacterChange + 1)) /** First entry should be the latest revision. */ - const latestRevision = this.history.sessionHistoryForItem(item)[0] + const latestRevision = history.sessionHistoryForItem(item)[0] /** Last entry should be the initial revision. */ - const initialRevision = - this.history.sessionHistoryForItem(item)[this.history.sessionHistoryForItem(item).length - 1] + const initialRevision = history.sessionHistoryForItem(item)[history.sessionHistoryForItem(item).length - 1] expect(latestRevision).to.not.equal(initialRevision) @@ -243,9 +215,9 @@ describe('history manager', () => { it('unsynced entries should use payload created_at for preview titles', async function () { const payload = Factory.createNotePayload() - await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) - const item = this.application.items.findItem(payload.uuid) - await this.application.changeAndSaveItem.execute( + await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) + const item = application.items.findItem(payload.uuid) + await application.changeAndSaveItem.execute( item, (mutator) => { mutator.title = Math.random() @@ -254,7 +226,7 @@ describe('history manager', () => { undefined, syncOptions, ) - const historyItem = this.history.sessionHistoryForItem(item)[0] + const historyItem = history.sessionHistoryForItem(item)[0] expect(historyItem.previewTitle()).to.equal(historyItem.payload.created_at.toLocaleString()) }) }) @@ -263,52 +235,54 @@ describe('history manager', () => { this.timeout(Factory.TwentySecondTimeout) beforeEach(async function () { - this.application = await Factory.createInitAppWithFakeCrypto() - this.history = this.application.dependencies.get(TYPES.HistoryManager) - this.payloadManager = this.application.payloads - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() + localStorage.clear() + + application = await Factory.createInitAppWithFakeCrypto() + history = application.dependencies.get(TYPES.HistoryManager) + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) + localStorage.clear() }) it('response from server should be failed if not signed in', async function () { - await this.application.user.signOut() - this.application = await Factory.createInitAppWithFakeCrypto() - this.history = this.application.dependencies.get(TYPES.HistoryManager) - this.payloadManager = this.application.payloads - const item = await Factory.createSyncedNote(this.application) - await this.application.sync.sync(syncOptions) - const itemHistoryOrError = await this.application.listRevisions.execute({ itemUuid: item.uuid }) + await application.user.signOut() + application = await Factory.createInitAppWithFakeCrypto() + history = application.dependencies.get(TYPES.HistoryManager) + + const item = await Factory.createSyncedNote(application) + await application.sync.sync(syncOptions) + const itemHistoryOrError = await application.listRevisions.execute({ itemUuid: item.uuid }) expect(itemHistoryOrError.isFailed()).to.equal(true) }) it('create basic history entries 2', async function () { - const item = await Factory.createSyncedNote(this.application) + const item = await Factory.createSyncedNote(application) await Factory.sleep(Factory.ServerRevisionCreationDelay) - let itemHistoryOrError = await this.application.listRevisions.execute({ itemUuid: item.uuid }) + let itemHistoryOrError = await application.listRevisions.execute({ itemUuid: item.uuid }) let itemHistory = itemHistoryOrError.getValue() /** Server history should save initial revision */ expect(itemHistory.length).to.equal(1) /** Sync within 5 seconds (ENV VAR dependend on self-hosted setup), should not create a new entry */ - await Factory.markDirtyAndSyncItem(this.application, item) - itemHistoryOrError = await this.application.listRevisions.execute({ itemUuid: item.uuid }) + await Factory.markDirtyAndSyncItem(application, item) + itemHistoryOrError = await application.listRevisions.execute({ itemUuid: item.uuid }) itemHistory = itemHistoryOrError.getValue() expect(itemHistory.length).to.equal(1) /** Sync with different contents, should not create a new entry */ - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item, (mutator) => { mutator.title = Math.random() @@ -318,18 +292,18 @@ describe('history manager', () => { syncOptions, ) await Factory.sleep(Factory.ServerRevisionCreationDelay) - itemHistoryOrError = await this.application.listRevisions.execute({ itemUuid: item.uuid }) + itemHistoryOrError = await application.listRevisions.execute({ itemUuid: item.uuid }) itemHistory = itemHistoryOrError.getValue() expect(itemHistory.length).to.equal(1) }) it('returns revisions from server', async function () { - let item = await Factory.createSyncedNote(this.application) + let item = await Factory.createSyncedNote(application) await Factory.sleep(Factory.ServerRevisionFrequency) /** Sync with different contents, should create new entry */ const newTitleAfterFirstChange = `The title should be: ${Math.random()}` - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item, (mutator) => { mutator.title = newTitleAfterFirstChange @@ -340,12 +314,12 @@ describe('history manager', () => { ) await Factory.sleep(Factory.ServerRevisionCreationDelay) - const itemHistoryOrError = await this.application.listRevisions.execute({ itemUuid: item.uuid }) + const itemHistoryOrError = await application.listRevisions.execute({ itemUuid: item.uuid }) const itemHistory = itemHistoryOrError.getValue() expect(itemHistory.length).to.equal(2) const oldestEntry = lastElement(itemHistory) - let revisionFromServerOrError = await this.application.getRevision.execute({ + let revisionFromServerOrError = await application.getRevision.execute({ itemUuid: item.uuid, revisionUuid: oldestEntry.uuid, }) @@ -357,22 +331,22 @@ describe('history manager', () => { expect(payloadFromServer.uuid).to.eq(item.payload.uuid) expect(payloadFromServer.content).to.eql(item.payload.content) - item = this.application.items.findItem(item.uuid) + item = application.items.findItem(item.uuid) expect(payloadFromServer.content).to.not.eql(item.payload.content) }) it('duplicate revisions should not have the originals uuid', async function () { - const note = await Factory.createSyncedNote(this.application) - await Factory.markDirtyAndSyncItem(this.application, note) - const dupe = await this.application.mutator.duplicateItem(note, true) - await Factory.markDirtyAndSyncItem(this.application, dupe) + const note = await Factory.createSyncedNote(application) + await Factory.markDirtyAndSyncItem(application, note) + const dupe = await application.mutator.duplicateItem(note, true) + await Factory.markDirtyAndSyncItem(application, dupe) await Factory.sleep(Factory.ServerRevisionCreationDelay) - const dupeHistoryOrError = await this.application.listRevisions.execute({ itemUuid: dupe.uuid }) + const dupeHistoryOrError = await application.listRevisions.execute({ itemUuid: dupe.uuid }) const dupeHistory = dupeHistoryOrError.getValue() - const dupeRevisionOrError = await this.application.getRevision.execute({ + const dupeRevisionOrError = await application.getRevision.execute({ itemUuid: dupe.uuid, revisionUuid: dupeHistory[0].uuid, }) @@ -381,54 +355,54 @@ describe('history manager', () => { }) it('revisions count matches original for duplicated items', async function () { - const note = await Factory.createSyncedNote(this.application) + const note = await Factory.createSyncedNote(application) await Factory.sleep(Factory.ServerRevisionFrequency) - await Factory.markDirtyAndSyncItem(this.application, note) + await Factory.markDirtyAndSyncItem(application, note) await Factory.sleep(Factory.ServerRevisionFrequency) - await Factory.markDirtyAndSyncItem(this.application, note) + await Factory.markDirtyAndSyncItem(application, note) await Factory.sleep(Factory.ServerRevisionFrequency) - await Factory.markDirtyAndSyncItem(this.application, note) + await Factory.markDirtyAndSyncItem(application, note) - const dupe = await this.application.mutator.duplicateItem(note, true) - await Factory.markDirtyAndSyncItem(this.application, dupe) + const dupe = await application.mutator.duplicateItem(note, true) + await Factory.markDirtyAndSyncItem(application, dupe) await Factory.sleep(Factory.ServerRevisionCreationDelay) const expectedRevisions = 4 - const noteHistoryOrError = await this.application.listRevisions.execute({ itemUuid: note.uuid }) + const noteHistoryOrError = await application.listRevisions.execute({ itemUuid: note.uuid }) const noteHistory = noteHistoryOrError.getValue() - const dupeHistoryOrError = await this.application.listRevisions.execute({ itemUuid: dupe.uuid }) + const dupeHistoryOrError = await application.listRevisions.execute({ itemUuid: dupe.uuid }) const dupeHistory = dupeHistoryOrError.getValue() expect(noteHistory.length).to.equal(expectedRevisions) expect(dupeHistory.length).to.equal(expectedRevisions + 1) - }).timeout(Factory.ThirtySecondTimeout) + }).timeout(Factory.SixtySecondTimeout) it('can decrypt revisions for duplicate_of items', async function () { - const note = await Factory.createSyncedNote(this.application) + const note = await Factory.createSyncedNote(application) await Factory.sleep(Factory.ServerRevisionFrequency) const changedText = `${Math.random()}` - await this.application.changeAndSaveItem.execute(note, (mutator) => { + await application.changeAndSaveItem.execute(note, (mutator) => { mutator.title = changedText }) - await Factory.markDirtyAndSyncItem(this.application, note) + await Factory.markDirtyAndSyncItem(application, note) - const dupe = await this.application.mutator.duplicateItem(note, true) - await Factory.markDirtyAndSyncItem(this.application, dupe) + const dupe = await application.mutator.duplicateItem(note, true) + await Factory.markDirtyAndSyncItem(application, dupe) await Factory.sleep(Factory.ServerRevisionCreationDelay) - const itemHistoryOrError = await this.application.listRevisions.execute({ itemUuid: dupe.uuid }) + const itemHistoryOrError = await application.listRevisions.execute({ itemUuid: dupe.uuid }) const itemHistory = itemHistoryOrError.getValue() expect(itemHistory.length).to.be.above(1) const newestRevision = itemHistory[0] - const fetchedOrError = await this.application.getRevision.execute({ + const fetchedOrError = await application.getRevision.execute({ itemUuid: dupe.uuid, revisionUuid: newestRevision.uuid, }) diff --git a/packages/snjs/mocha/item.test.js b/packages/snjs/mocha/item.test.js index bbbd58640..4eaed8e1a 100644 --- a/packages/snjs/mocha/item.test.js +++ b/packages/snjs/mocha/item.test.js @@ -1,45 +1,43 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect -describe('item', () => { - beforeEach(async function () { - this.createBarePayload = () => { - return new DecryptedPayload({ - uuid: '123', - content_type: ContentType.TYPES.Note, - content: { - title: 'hello', - }, - }) - } +const createBarePayload = () => { + return new DecryptedPayload({ + uuid: '123', + content_type: ContentType.TYPES.Note, + content: { + title: 'hello', + }, + }) +} - this.createNote = () => { - return new DecryptedItem(this.createBarePayload()) - } +const createNote = () => { + return new DecryptedItem(createBarePayload()) +} - this.createTag = (notes = []) => { - const references = notes.map((note) => { - return { - uuid: note.uuid, - content_type: note.content_type, - } - }) - return new SNTag( - new DecryptedPayload({ - uuid: Factory.generateUuidish(), - content_type: ContentType.TYPES.Tag, - content: { - title: 'thoughts', - references: references, - }, - }), - ) +const createTag = (notes = []) => { + const references = notes.map((note) => { + return { + uuid: note.uuid, + content_type: note.content_type, } }) + return new SNTag( + new DecryptedPayload({ + uuid: Factory.generateUuidish(), + content_type: ContentType.TYPES.Tag, + content: { + title: 'thoughts', + references: references, + }, + }), + ) +} + +describe('item', () => { it('constructing without uuid should throw', function () { let error @@ -53,40 +51,40 @@ describe('item', () => { }) it('healthy constructor', function () { - const item = this.createNote() + const item = createNote() expect(item).to.be.ok expect(item.payload).to.be.ok }) it('user modified date should be ok', function () { - const item = this.createNote() + const item = createNote() expect(item.userModifiedDate).to.be.ok }) it('has relationship with item true', function () { - const note = this.createNote() - const tag = this.createTag() + const note = createNote() + const tag = createTag() expect(tag.isReferencingItem(note)).to.equal(false) }) it('has relationship with item true', function () { - const note = this.createNote() - const tag = this.createTag([note]) + const note = createNote() + const tag = createTag([note]) expect(tag.isReferencingItem(note)).to.equal(true) }) it('getDomainData for random domain should return undefined', function () { - const note = this.createNote() + const note = createNote() expect(note.getDomainData('random')).to.not.be.ok }) it('getDomainData for app domain should return object', function () { - const note = this.createNote() + const note = createNote() expect(note.getDomainData(DecryptedItem.DefaultAppDomain())).to.be.ok }) diff --git a/packages/snjs/mocha/item_manager.test.js b/packages/snjs/mocha/item_manager.test.js index 78aa4811b..bf8fa3d40 100644 --- a/packages/snjs/mocha/item_manager.test.js +++ b/packages/snjs/mocha/item_manager.test.js @@ -1,5 +1,3 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' import { BaseItemCounts } from './lib/BaseItemCounts.js' @@ -10,11 +8,6 @@ describe('item manager', function () { let context let application - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -24,6 +17,14 @@ describe('item manager', function () { await context.launch() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + + context = undefined + application = undefined + }) + const createNote = async () => { return application.mutator.createItem(ContentType.TYPES.Note, { title: 'hello', diff --git a/packages/snjs/mocha/key_params.test.js b/packages/snjs/mocha/key_params.test.js index 65d739998..3e57e11b6 100644 --- a/packages/snjs/mocha/key_params.test.js +++ b/packages/snjs/mocha/key_params.test.js @@ -1,17 +1,17 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ + import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('key params', function () { this.timeout(Factory.TenSecondTimeout) - before(async function () { + beforeEach(async function () { localStorage.clear() }) - after(async function () { + afterEach(async function () { localStorage.clear() }) diff --git a/packages/snjs/mocha/key_recovery_service.test.js b/packages/snjs/mocha/key_recovery_service.test.js index 9b94eec5b..ccf4cfec6 100644 --- a/packages/snjs/mocha/key_recovery_service.test.js +++ b/packages/snjs/mocha/key_recovery_service.test.js @@ -1,23 +1,18 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('key recovery service', function () { this.timeout(Factory.TwentySecondTimeout) - const syncOptions = { - checkIntegrity: true, - awaitAll: true, - } - beforeEach(function () { localStorage.clear() }) afterEach(function () { localStorage.clear() + sinon.restore() }) it('when encountering an undecryptable items key, should recover through recovery wizard', async function () { @@ -433,9 +428,7 @@ describe('key recovery service', function () { KeyParamsOrigination.Registration, ProtocolVersion.V003, ) - const randomItemsKey = await context.operators - .operatorForVersion(ProtocolVersion.V003) - .createItemsKey() + const randomItemsKey = await context.operators.operatorForVersion(ProtocolVersion.V003).createItemsKey() const encrypted = await application.encryption.encryptSplitSingle({ usesRootKey: { diff --git a/packages/snjs/mocha/keys.test.js b/packages/snjs/mocha/keys.test.js index 024b745be..9e7e87677 100644 --- a/packages/snjs/mocha/keys.test.js +++ b/packages/snjs/mocha/keys.test.js @@ -1,35 +1,40 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' import * as Utils from './lib/Utils.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('keys', function () { this.timeout(Factory.TwentySecondTimeout) + let context + let application + let email + let password + beforeEach(async function () { localStorage.clear() - this.context = await Factory.createAppContext() - await this.context.launch() + context = await Factory.createAppContext() + await context.launch() - this.application = this.context.application - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() + application = context.application + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() }) afterEach(async function () { - if (!this.application.dealloced) { - await Factory.safeDeinit(this.application) + if (!application.dealloced) { + await Factory.safeDeinit(application) } - this.application = undefined + application = undefined + context = undefined localStorage.clear() }) it('should not have root key by default', async function () { - expect(await this.application.encryption.getRootKey()).to.not.be.ok + expect(await application.encryption.getRootKey()).to.not.be.ok }) it('validates content types requiring root encryption', function () { @@ -43,7 +48,7 @@ describe('keys', function () { /** Items key available by default */ const payload = Factory.createNotePayload() const processedPayload = CreateEncryptedLocalStorageContextPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -53,45 +58,41 @@ describe('keys', function () { }) it('has root key and one items key after registering user', async function () { - await Factory.registerUserToApplication({ application: this.application }) - expect(this.application.encryption.getRootKey()).to.be.ok - expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) + await Factory.registerUserToApplication({ application: application }) + expect(application.encryption.getRootKey()).to.be.ok + expect(application.items.getDisplayableItemsKeys().length).to.equal(1) }) it('changing root key with passcode should re-wrap root key', async function () { const email = 'foo' const password = 'bar' - const key = await this.application.encryption.createRootKey(email, password, KeyParamsOrigination.Registration) - await this.application.encryption.setRootKey(key) - Factory.handlePasswordChallenges(this.application, password) - await this.application.addPasscode(password) + const key = await application.encryption.createRootKey(email, password, KeyParamsOrigination.Registration) + await application.encryption.setRootKey(key) + Factory.handlePasswordChallenges(application, password) + await application.addPasscode(password) /** We should be able to decrypt wrapped root key with passcode */ - const wrappingKeyParams = await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams() - const wrappingKey = await this.application.encryption.computeRootKey(password, wrappingKeyParams) - await this.application.encryption.unwrapRootKey(wrappingKey).catch((error) => { + const wrappingKeyParams = await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams() + const wrappingKey = await application.encryption.computeRootKey(password, wrappingKeyParams) + await application.encryption.unwrapRootKey(wrappingKey).catch((error) => { expect(error).to.not.be.ok }) const newPassword = 'bar' - const newKey = await this.application.encryption.createRootKey( - email, - newPassword, - KeyParamsOrigination.Registration, - ) - await this.application.encryption.setRootKey(newKey, wrappingKey) - await this.application.encryption.unwrapRootKey(wrappingKey).catch((error) => { + const newKey = await application.encryption.createRootKey(email, newPassword, KeyParamsOrigination.Registration) + await application.encryption.setRootKey(newKey, wrappingKey) + await application.encryption.unwrapRootKey(wrappingKey).catch((error) => { expect(error).to.not.be.ok }) }) it('items key should be encrypted with root key', async function () { - await Factory.registerUserToApplication({ application: this.application }) - const itemsKey = await this.application.encryption.getSureDefaultItemsKey() - const rootKey = await this.application.encryption.getRootKey() + await Factory.registerUserToApplication({ application: application }) + const itemsKey = await application.encryption.getSureDefaultItemsKey() + const rootKey = await application.encryption.getRootKey() /** Encrypt items key */ - const encryptedPayload = await this.application.encryption.encryptSplitSingle({ + const encryptedPayload = await application.encryption.encryptSplitSingle({ usesRootKey: { items: [itemsKey.payloadRepresentation()], key: rootKey, @@ -102,7 +103,7 @@ describe('keys', function () { expect(encryptedPayload.items_key_id).to.not.be.ok /** Attempt to decrypt with root key. Should succeed. */ - const decryptedPayload = await this.application.encryption.decryptSplitSingle({ + const decryptedPayload = await application.encryption.decryptSplitSingle({ usesRootKey: { items: [encryptedPayload], key: rootKey, @@ -114,7 +115,7 @@ describe('keys', function () { }) it('should create random items key if no account and no passcode', async function () { - const itemsKeys = this.application.items.getDisplayableItemsKeys() + const itemsKeys = application.items.getDisplayableItemsKeys() expect(itemsKeys.length).to.equal(1) const notePayload = Factory.createNotePayload() @@ -122,55 +123,55 @@ describe('keys', function () { dirty: true, dirtyIndex: getIncrementedDirtyIndex(), }) - await this.application.payloads.emitPayload(dirtied, PayloadEmitSource.LocalChanged) - await this.application.sync.sync() + await application.payloads.emitPayload(dirtied, PayloadEmitSource.LocalChanged) + await application.sync.sync() - const rawPayloads = await this.application.storage.getAllRawPayloads() + const rawPayloads = await application.storage.getAllRawPayloads() const rawNotePayload = rawPayloads.find((r) => r.content_type === ContentType.TYPES.Note) expect(typeof rawNotePayload.content).to.equal('string') }) it('should keep offline created items key upon registration', async function () { - expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) - const originalItemsKey = this.application.items.getDisplayableItemsKeys()[0] - await this.application.register(this.email, this.password) + expect(application.items.getDisplayableItemsKeys().length).to.equal(1) + const originalItemsKey = application.items.getDisplayableItemsKeys()[0] + await application.register(email, password) - expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) - const newestItemsKey = this.application.items.getDisplayableItemsKeys()[0] + expect(application.items.getDisplayableItemsKeys().length).to.equal(1) + const newestItemsKey = application.items.getDisplayableItemsKeys()[0] expect(newestItemsKey.uuid).to.equal(originalItemsKey.uuid) }) it('should use items key for encryption of note', async function () { const notePayload = Factory.createNotePayload() - const keyToUse = await this.application.encryption.itemsEncryption.keyToUseForItemEncryption(notePayload) + const keyToUse = await application.encryption.itemsEncryption.keyToUseForItemEncryption(notePayload) expect(keyToUse.content_type).to.equal(ContentType.TYPES.ItemsKey) }) it('encrypting an item should associate an items key to it', async function () { const note = Factory.createNotePayload() - const encryptedPayload = await this.application.encryption.encryptSplitSingle({ + const encryptedPayload = await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note], }, }) - const itemsKey = this.application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) + const itemsKey = application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) expect(itemsKey).to.be.ok }) it('decrypt encrypted item with associated key', async function () { const note = Factory.createNotePayload() const title = note.content.title - const encryptedPayload = await this.application.encryption.encryptSplitSingle({ + const encryptedPayload = await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note], }, }) - const itemsKey = this.application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) + const itemsKey = application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) expect(itemsKey).to.be.ok - const decryptedPayload = await this.application.encryption.decryptSplitSingle({ + const decryptedPayload = await application.encryption.decryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [encryptedPayload], }, @@ -182,30 +183,30 @@ describe('keys', function () { it('decrypts items waiting for keys', async function () { const notePayload = Factory.createNotePayload() const title = notePayload.content.title - const encryptedPayload = await this.application.encryption.encryptSplitSingle({ + const encryptedPayload = await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [notePayload], }, }) - const itemsKey = this.application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) + const itemsKey = application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) - await this.application.items.removeItemFromMemory(itemsKey) + await application.items.removeItemFromMemory(itemsKey) - const erroredPayload = await this.application.encryption.decryptSplitSingle({ + const erroredPayload = await application.encryption.decryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [encryptedPayload], }, }) - await this.application.mutator.emitItemsFromPayloads([erroredPayload], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([erroredPayload], PayloadEmitSource.LocalChanged) - const note = this.application.items.findAnyItem(notePayload.uuid) + const note = application.items.findAnyItem(notePayload.uuid) expect(note.errorDecrypting).to.equal(true) expect(note.waitingForKey).to.equal(true) const keyPayload = new DecryptedPayload(itemsKey.payload.ejected()) - await this.application.mutator.emitItemsFromPayloads([keyPayload], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([keyPayload], PayloadEmitSource.LocalChanged) /** * Sleeping is required to trigger asyncronous encryptionService.decryptItemsWaitingForKeys, @@ -213,7 +214,7 @@ describe('keys', function () { */ await Factory.sleep(0.2) - const updatedNote = this.application.items.findItem(note.uuid) + const updatedNote = application.items.findItem(note.uuid) expect(updatedNote.errorDecrypting).to.not.be.ok expect(updatedNote.waitingForKey).to.not.be.ok @@ -221,9 +222,9 @@ describe('keys', function () { }) it('attempting to emit errored items key for which there exists a non errored master copy should ignore it', async function () { - await Factory.registerUserToApplication({ application: this.application }) + await Factory.registerUserToApplication({ application: application }) - const itemsKey = await this.application.encryption.getSureDefaultItemsKey() + const itemsKey = await application.encryption.getSureDefaultItemsKey() expect(itemsKey.errorDecrypting).to.not.be.ok @@ -239,44 +240,44 @@ describe('keys', function () { }, }) - await this.application.sync.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response) + await application.sync.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response) - const refreshedKey = this.application.payloads.findOne(itemsKey.uuid) + const refreshedKey = application.payloads.findOne(itemsKey.uuid) expect(refreshedKey.errorDecrypting).to.not.be.ok expect(refreshedKey.content.itemsKey).to.be.ok }) it('generating export params with logged in account should produce encrypted payload', async function () { - await Factory.registerUserToApplication({ application: this.application }) + await Factory.registerUserToApplication({ application: application }) const payload = Factory.createNotePayload() - const encryptedPayload = await this.application.encryption.encryptSplitSingle({ + const encryptedPayload = await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, }) expect(typeof encryptedPayload.content).to.equal('string') - expect(encryptedPayload.content.substring(0, 3)).to.equal(this.application.encryption.getLatestVersion()) + expect(encryptedPayload.content.substring(0, 3)).to.equal(application.encryption.getLatestVersion()) }) it('When setting passcode, should encrypt items keys', async function () { - await this.application.addPasscode('foo') - const itemsKey = this.application.items.getDisplayableItemsKeys()[0] - const rawPayloads = await this.application.storage.getAllRawPayloads() + await application.addPasscode('foo') + const itemsKey = application.items.getDisplayableItemsKeys()[0] + const rawPayloads = await application.storage.getAllRawPayloads() const itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === itemsKey.uuid) const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload) expect(itemsKeyPayload.enc_item_key).to.be.ok }) it('items key encrypted payload should contain root key params', async function () { - await this.application.addPasscode('foo') - const itemsKey = this.application.items.getDisplayableItemsKeys()[0] - const rawPayloads = await this.application.storage.getAllRawPayloads() + await application.addPasscode('foo') + const itemsKey = application.items.getDisplayableItemsKeys()[0] + const rawPayloads = await application.storage.getAllRawPayloads() const itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === itemsKey.uuid) const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload) - const authenticatedData = this.context.encryption.getEmbeddedPayloadAuthenticatedData(itemsKeyPayload) - const rootKeyParams = await this.application.encryption.getRootKeyParams() + const authenticatedData = context.encryption.getEmbeddedPayloadAuthenticatedData(itemsKeyPayload) + const rootKeyParams = await application.encryption.getRootKeyParams() expect(authenticatedData.kp).to.be.ok expect(authenticatedData.kp).to.eql(rootKeyParams.getPortableValue()) @@ -285,9 +286,9 @@ describe('keys', function () { it('correctly validates local passcode', async function () { const passcode = 'foo' - await this.application.addPasscode('foo') - expect((await this.application.encryption.validatePasscode('wrong')).valid).to.equal(false) - expect((await this.application.encryption.validatePasscode(passcode)).valid).to.equal(true) + await application.addPasscode('foo') + expect((await application.encryption.validatePasscode('wrong')).valid).to.equal(false) + expect((await application.encryption.validatePasscode(passcode)).valid).to.equal(true) }) it('signing into 003 account should delete latest offline items key and create 003 items key', async function () { @@ -296,63 +297,63 @@ describe('keys', function () { * Upon signing into an 003 account, the application should delete any neverSynced items keys, * and create a new default items key that is the default for a given protocol version. */ - const defaultItemsKey = await this.application.encryption.getSureDefaultItemsKey() - const latestVersion = this.application.encryption.getLatestVersion() + const defaultItemsKey = await application.encryption.getSureDefaultItemsKey() + const latestVersion = application.encryption.getLatestVersion() expect(defaultItemsKey.keyVersion).to.equal(latestVersion) /** Register with 003 version */ await Factory.registerOldUser({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, version: ProtocolVersion.V003, }) - const itemsKeys = this.application.items.getDisplayableItemsKeys() + const itemsKeys = application.items.getDisplayableItemsKeys() expect(itemsKeys.length).to.equal(1) const newestItemsKey = itemsKeys[0] expect(newestItemsKey.keyVersion).to.equal(ProtocolVersion.V003) - const rootKey = await this.application.encryption.getRootKey() + const rootKey = await application.encryption.getRootKey() expect(newestItemsKey.itemsKey).to.equal(rootKey.masterKey) expect(newestItemsKey.dataAuthenticationKey).to.equal(rootKey.dataAuthenticationKey) }) it('reencrypts existing notes when logging into an 003 account', async function () { - await Factory.createManyMappedNotes(this.application, 10) + await Factory.createManyMappedNotes(application, 10) await Factory.registerOldUser({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, version: ProtocolVersion.V003, }) - expect(this.application.payloads.invalidPayloads.length).to.equal(0) - expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) - expect(this.application.items.getDisplayableItemsKeys()[0].dirty).to.equal(false) + expect(application.payloads.invalidPayloads.length).to.equal(0) + expect(application.items.getDisplayableItemsKeys().length).to.equal(1) + expect(application.items.getDisplayableItemsKeys()[0].dirty).to.equal(false) /** Sign out and back in */ - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) + application = await Factory.signOutApplicationAndReturnNew(application) + await application.signIn(email, password, undefined, undefined, undefined, true) - expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) - expect(this.application.items.getDisplayableNotes().length).to.equal(10) - expect(this.application.payloads.invalidPayloads.length).to.equal(0) + expect(application.items.getDisplayableItemsKeys().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(10) + expect(application.payloads.invalidPayloads.length).to.equal(0) }) it('When root key changes, all items keys must be re-encrypted', async function () { const passcode = 'foo' - await this.application.addPasscode(passcode) - await Factory.createSyncedNote(this.application) - const itemsKeys = this.application.items.getDisplayableItemsKeys() + await application.addPasscode(passcode) + await Factory.createSyncedNote(application) + const itemsKeys = application.items.getDisplayableItemsKeys() expect(itemsKeys.length).to.equal(1) const originalItemsKey = itemsKeys[0] - const originalRootKey = await this.application.encryption.getRootKey() + const originalRootKey = await application.encryption.getRootKey() /** Expect that we can decrypt raw payload with current root key */ - const rawPayloads = await this.application.storage.getAllRawPayloads() + const rawPayloads = await application.storage.getAllRawPayloads() const itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === originalItemsKey.uuid) const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload) - const decrypted = await this.application.encryption.decryptSplitSingle({ + const decrypted = await application.encryption.decryptSplitSingle({ usesRootKey: { items: [itemsKeyPayload], key: originalRootKey, @@ -363,10 +364,10 @@ describe('keys', function () { expect(decrypted.content).to.eql(originalItemsKey.content) /** Change passcode */ - Factory.handlePasswordChallenges(this.application, passcode) - await this.application.changePasscode('bar') + Factory.handlePasswordChallenges(application, passcode) + await application.changePasscode('bar') - const newRootKey = await this.application.encryption.getRootKey() + const newRootKey = await application.encryption.getRootKey() expect(newRootKey).to.not.equal(originalRootKey) expect(newRootKey.masterKey).to.not.equal(originalRootKey.masterKey) @@ -374,12 +375,12 @@ describe('keys', function () { * Expect that originalRootKey can no longer decrypt originalItemsKey * as items key has been re-encrypted with new root key */ - const rawPayloads2 = await this.application.storage.getAllRawPayloads() + const rawPayloads2 = await application.storage.getAllRawPayloads() const itemsKeyRawPayload2 = rawPayloads2.find((p) => p.uuid === originalItemsKey.uuid) expect(itemsKeyRawPayload2.content).to.not.equal(itemsKeyRawPayload.content) const itemsKeyPayload2 = new EncryptedPayload(itemsKeyRawPayload2) - const decrypted2 = await this.application.encryption.decryptSplitSingle({ + const decrypted2 = await application.encryption.decryptSplitSingle({ usesRootKey: { items: [itemsKeyPayload2], key: originalRootKey, @@ -388,7 +389,7 @@ describe('keys', function () { expect(decrypted2.errorDecrypting).to.equal(true) /** Should be able to decrypt with new root key */ - const decrypted3 = await this.application.encryption.decryptSplitSingle({ + const decrypted3 = await application.encryption.decryptSplitSingle({ usesRootKey: { items: [itemsKeyPayload2], key: newRootKey, @@ -399,23 +400,23 @@ describe('keys', function () { it('changing account password should create new items key and encrypt items with that key', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const itemsKeys = this.application.items.getDisplayableItemsKeys() + const itemsKeys = application.items.getDisplayableItemsKeys() expect(itemsKeys.length).to.equal(1) - const defaultItemsKey = await this.application.encryption.getSureDefaultItemsKey() + const defaultItemsKey = await application.encryption.getSureDefaultItemsKey() - const result = await this.application.changePassword(this.password, 'foobarfoo') + const result = await application.changePassword(password, 'foobarfoo') expect(result.error).to.not.be.ok - expect(this.application.items.getDisplayableItemsKeys().length).to.equal(2) - const newDefaultItemsKey = await this.application.encryption.getSureDefaultItemsKey() + expect(application.items.getDisplayableItemsKeys().length).to.equal(2) + const newDefaultItemsKey = await application.encryption.getSureDefaultItemsKey() expect(newDefaultItemsKey.uuid).to.not.equal(defaultItemsKey.uuid) - const note = await Factory.createSyncedNote(this.application) - const payload = await this.application.encryption.encryptSplitSingle({ + const note = await Factory.createSyncedNote(application) + const payload = await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note.payload], }, @@ -480,29 +481,29 @@ describe('keys', function () { }) it('loading the keychain root key should also load its key params', async function () { - await Factory.registerUserToApplication({ application: this.application }) - const rootKey = await this.application.encryption.rootKeyManager.getRootKeyFromKeychain() + await Factory.registerUserToApplication({ application: application }) + const rootKey = await application.encryption.rootKeyManager.getRootKeyFromKeychain() expect(rootKey.keyParams).to.be.ok }) it('key params should be persisted separately and not as part of root key', async function () { - await Factory.registerUserToApplication({ application: this.application }) - const rawKey = await this.application.device.getNamespacedKeychainValue(this.application.identifier) + await Factory.registerUserToApplication({ application: application }) + const rawKey = await application.device.getNamespacedKeychainValue(application.identifier) expect(rawKey.keyParams).to.not.be.ok - const rawKeyParams = await this.application.storage.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped) + const rawKeyParams = await application.storage.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped) expect(rawKeyParams).to.be.ok }) it('persisted key params should exactly equal in memory rootKey.keyParams', async function () { - await Factory.registerUserToApplication({ application: this.application }) - const rootKey = await this.application.encryption.getRootKey() - const rawKeyParams = await this.application.storage.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped) + await Factory.registerUserToApplication({ application: application }) + const rootKey = await application.encryption.getRootKey() + const rawKeyParams = await application.storage.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped) expect(rootKey.keyParams.content).to.eql(rawKeyParams) }) it('key params should have expected values', async function () { - await Factory.registerUserToApplication({ application: this.application }) - const keyParamsObject = await this.application.encryption.getRootKeyParams() + await Factory.registerUserToApplication({ application: application }) + const keyParamsObject = await application.encryption.getRootKeyParams() const keyParams = keyParamsObject.content expect(keyParams.identifier).to.be.ok expect(keyParams.pw_nonce).to.be.ok @@ -515,20 +516,18 @@ describe('keys', function () { }) it('key params obtained when signing in should have created and origination', async function () { - const email = this.email - const password = this.password await Factory.registerUserToApplication({ - application: this.application, + application: application, email, password, }) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + application = await Factory.signOutApplicationAndReturnNew(application) await Factory.loginToApplication({ - application: this.application, + application: application, email, password, }) - const keyParamsObject = await this.application.encryption.getRootKeyParams() + const keyParamsObject = await application.encryption.getRootKeyParams() const keyParams = keyParamsObject.content expect(keyParams.created).to.be.ok @@ -541,12 +540,12 @@ describe('keys', function () { /** Register with 003 version */ await Factory.registerOldUser({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, version: ProtocolVersion.V003, }) - const keyParamsObject = await this.application.encryption.getRootKeyParams() + const keyParamsObject = await application.encryption.getRootKeyParams() const keyParams = keyParamsObject.content expect(keyParams.created).to.be.ok @@ -556,17 +555,17 @@ describe('keys', function () { it('encryption name should be dependent on key params version', async function () { /** Register with 003 account */ await Factory.registerOldUser({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, version: ProtocolVersion.V003, }) - expect(await this.application.encryption.getEncryptionDisplayName()).to.equal('AES-256') + expect(await application.encryption.getEncryptionDisplayName()).to.equal('AES-256') - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + application = await Factory.signOutApplicationAndReturnNew(application) /** Register with 004 account */ - await this.application.register(this.email + 'new', this.password) - expect(await this.application.encryption.getEncryptionDisplayName()).to.equal('XChaCha20-Poly1305') + await application.register(email + 'new', password) + expect(await application.encryption.getEncryptionDisplayName()).to.equal('XChaCha20-Poly1305') }) it('when launching app with no keychain but data, should present account recovery challenge', async function () { @@ -575,21 +574,21 @@ describe('keys', function () { * when setting up a new device from restore, the keychain is deleted, but the data persists. * We want to make sure we're prompting the user to re-authenticate their account. */ - const id = this.application.identifier + const id = application.identifier await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) /** Simulate empty keychain */ - await this.application.device.clearRawKeychainValue() + await application.device.clearRawKeychainValue() const recreatedApp = await Factory.createApplicationWithFakeCrypto(id) let totalChallenges = 0 const expectedChallenges = 1 const receiveChallenge = (challenge) => { totalChallenges++ - recreatedApp.submitValuesForChallenge(challenge, [CreateChallengeValue(challenge.prompts[0], this.password)]) + recreatedApp.submitValuesForChallenge(challenge, [CreateChallengeValue(challenge.prompts[0], password)]) } await recreatedApp.prepareForLaunch({ receiveChallenge }) await recreatedApp.launch(true) @@ -610,13 +609,13 @@ describe('keys', function () { */ it.skip('should add new items key', async function () { this.timeout(Factory.TwentySecondTimeout * 3) - let oldClient = this.application + let oldClient = application /** Register an 003 account */ await Factory.registerOldUser({ application: oldClient, - email: this.email, - password: this.password, + email: email, + password: password, version: ProtocolVersion.V003, }) @@ -626,8 +625,8 @@ describe('keys', function () { receiveChallenge: (challenge) => { /** Reauth session challenge */ newClient.submitValuesForChallenge(challenge, [ - CreateChallengeValue(challenge.prompts[0], this.email), - CreateChallengeValue(challenge.prompts[1], this.password), + CreateChallengeValue(challenge.prompts[0], email), + CreateChallengeValue(challenge.prompts[1], password), ]) }, }) @@ -636,11 +635,11 @@ describe('keys', function () { /** Change password through session manager directly instead of application, * as not to create any items key (to simulate 003 client behavior) */ const currentRootKey = await oldClient.encryption.computeRootKey( - this.password, + password, await oldClient.encryption.getRootKeyParams(), ) - const operator = this.context.operators.operatorForVersion(ProtocolVersion.V003) - const newRootKey = await operator.createRootKey(this.email, this.password) + const operator = context.operators.operatorForVersion(ProtocolVersion.V003) + const newRootKey = await operator.createRootKey(email, password) Object.defineProperty(oldClient.legacyApi, 'apiVersion', { get: function () { return '20190520' @@ -650,7 +649,7 @@ describe('keys', function () { /** * Sign in as late as possible on new client to prevent session timeouts */ - await newClient.signIn(this.email, this.password) + await newClient.signIn(email, password) await oldClient.sessions.changeCredentials({ currentServerPassword: currentRootKey.serverPassword, @@ -669,28 +668,28 @@ describe('keys', function () { }) it('should add new items key from migration if pw change already happened', async function () { - this.context.anticipateConsoleError('Shared vault network errors due to not accepting JWT-based token') - this.context.anticipateConsoleError( + context.anticipateConsoleError('Shared vault network errors due to not accepting JWT-based token') + context.anticipateConsoleError( 'Cannot find items key to use for encryption', 'No items keys being created in this test', ) /** Register an 003 account */ await Factory.registerOldUser({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, version: ProtocolVersion.V003, }) /** Change password through session manager directly instead of application, * as not to create any items key (to simulate 003 client behavior) */ - const currentRootKey = await this.application.encryption.computeRootKey( - this.password, - await this.application.encryption.getRootKeyParams(), + const currentRootKey = await application.encryption.computeRootKey( + password, + await application.encryption.getRootKeyParams(), ) - const operator = this.context.operators.operatorForVersion(ProtocolVersion.V003) - const newRootKeyTemplate = await operator.createRootKey(this.email, this.password) + const operator = context.operators.operatorForVersion(ProtocolVersion.V003) + const newRootKeyTemplate = await operator.createRootKey(email, password) const newRootKey = CreateNewRootKey({ ...newRootKeyTemplate.content, ...{ @@ -699,28 +698,28 @@ describe('keys', function () { }, }) - Object.defineProperty(this.application.legacyApi, 'apiVersion', { + Object.defineProperty(application.legacyApi, 'apiVersion', { get: function () { return '20190520' }, }) /** Renew session to prevent timeouts */ - this.application = await Factory.signOutAndBackIn(this.application, this.email, this.password) + application = await Factory.signOutAndBackIn(application, email, password) - await this.application.sessions.changeCredentials({ + await application.sessions.changeCredentials({ currentServerPassword: currentRootKey.serverPassword, newRootKey, }) - await this.application.dependencies.get(TYPES.ReencryptTypeAItems).execute() + await application.dependencies.get(TYPES.ReencryptTypeAItems).execute() /** Note: this may result in a deadlock if features_service syncs and results in an error */ - await this.application.sync.sync({ awaitAll: true }) + await application.sync.sync({ awaitAll: true }) /** Relaunch application and expect new items key to be created */ - const identifier = this.application.identifier + const identifier = application.identifier /** Set to pre 2.0.15 version so migration runs */ - await this.application.device.setRawStorageValue(`${identifier}-snjs_version`, '2.0.14') - await Factory.safeDeinit(this.application) + await application.device.setRawStorageValue(`${identifier}-snjs_version`, '2.0.14') + await Factory.safeDeinit(application) const refreshedApp = await Factory.createApplicationWithFakeCrypto(identifier) await Factory.initializeApplication(refreshedApp) @@ -740,16 +739,16 @@ describe('keys', function () { * The corrective action was to do a final check in encryptionService.handleDownloadFirstSyncCompletion * to ensure there exists an items key corresponding to the user's account version. */ - const promise = this.context.awaitNextSucessfulSync() - await this.context.sync() + const promise = context.awaitNextSucessfulSync() + await context.sync() await promise - await this.application.items.removeAllItemsFromMemory() - expect(this.application.encryption.getSureDefaultItemsKey()).to.not.be.ok + await application.items.removeAllItemsFromMemory() + expect(application.encryption.getSureDefaultItemsKey()).to.not.be.ok const protocol003 = new SNProtocolOperator003(new SNWebCrypto()) const key = await protocol003.createItemsKey() - await this.application.mutator.emitItemFromPayload( + await application.mutator.emitItemFromPayload( key.payload.copy({ content: { ...key.payload.content, @@ -761,30 +760,30 @@ describe('keys', function () { }), ) - const defaultKey = this.application.encryption.getSureDefaultItemsKey() + const defaultKey = application.encryption.getSureDefaultItemsKey() expect(defaultKey.keyVersion).to.equal(ProtocolVersion.V003) expect(defaultKey.uuid).to.equal(key.uuid) - await Factory.registerUserToApplication({ application: this.application }) + await Factory.registerUserToApplication({ application: application }) const notePayload = Factory.createNotePayload() - expect(await this.application.encryption.itemsEncryption.keyToUseForItemEncryption(notePayload)).to.be.ok + expect(await application.encryption.itemsEncryption.keyToUseForItemEncryption(notePayload)).to.be.ok }) it('having unsynced items keys should resync them upon download first sync completion', async function () { - await Factory.registerUserToApplication({ application: this.application }) - const itemsKey = this.application.items.getDisplayableItemsKeys()[0] - await this.application.mutator.emitItemFromPayload( + await Factory.registerUserToApplication({ application: application }) + const itemsKey = application.items.getDisplayableItemsKeys()[0] + await application.mutator.emitItemFromPayload( itemsKey.payload.copy({ dirty: false, updated_at: new Date(0), deleted: false, }), ) - await this.application.sync.sync({ + await application.sync.sync({ mode: SyncMode.DownloadFirst, }) - const updatedKey = this.application.items.findItem(itemsKey.uuid) + const updatedKey = application.items.findItem(itemsKey.uuid) expect(updatedKey.neverSynced).to.equal(false) }) @@ -794,18 +793,18 @@ describe('keys', function () { otherClient.items.itemsKeyDisplayController.setDisplayOptions({ sortBy: 'dsc' }) /** On client A, create account and note */ await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - await Factory.createSyncedNote(this.application) - const itemsKey = this.application.items.getItems(ContentType.TYPES.ItemsKey)[0] + await Factory.createSyncedNote(application) + const itemsKey = application.items.getItems(ContentType.TYPES.ItemsKey)[0] /** Create another client and sign into account */ await Factory.loginToApplication({ application: otherClient, - email: this.email, - password: this.password, + email: email, + password: password, }) const defaultKeys = otherClient.encryption.itemsEncryption.getItemsKeys().filter((key) => { return key.isDefault diff --git a/packages/snjs/mocha/lib/factory.js b/packages/snjs/mocha/lib/factory.js index afbcdd98e..9cb682d2d 100644 --- a/packages/snjs/mocha/lib/factory.js +++ b/packages/snjs/mocha/lib/factory.js @@ -1,5 +1,4 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ + import FakeWebCrypto from './fake_web_crypto.js' import { AppContext } from './AppContext.js' import { VaultsContext } from './VaultsContext.js' @@ -63,6 +62,10 @@ export async function createVaultsContextWithRealCrypto(identifier) { return createVaultsContext({ identifier, crypto: new SNWebCrypto() }) } +export async function createVaultsContextWithFakeCrypto(identifier) { + return createVaultsContext({ identifier, crypto: new FakeWebCrypto() }) +} + export async function createVaultsContext({ identifier, crypto, email, password, host } = {}) { const context = new VaultsContext({ identifier, crypto, email, password, host }) await context.initialize() diff --git a/packages/snjs/mocha/memory.test.js b/packages/snjs/mocha/memory.test.js index 806b9c10c..e336d243e 100644 --- a/packages/snjs/mocha/memory.test.js +++ b/packages/snjs/mocha/memory.test.js @@ -1,6 +1,5 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect @@ -9,6 +8,7 @@ const expect = chai.expect * Then check browser Memory tool to make sure there are no leaks. */ describe('memory', function () { + let application before(async function () { localStorage.clear() }) @@ -18,12 +18,12 @@ describe('memory', function () { }) beforeEach(async function () { - this.application = await Factory.createInitAppWithFakeCrypto() + application = await Factory.createInitAppWithFakeCrypto() }) afterEach(async function () { - await Factory.safeDeinit(this.application) - this.application = null + await Factory.safeDeinit(application) + application = undefined }) it('passes', async function () { diff --git a/packages/snjs/mocha/mfa_service.test.js b/packages/snjs/mocha/mfa_service.test.js index cb585202a..6cb62b967 100644 --- a/packages/snjs/mocha/mfa_service.test.js +++ b/packages/snjs/mocha/mfa_service.test.js @@ -1,68 +1,76 @@ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect -const createApp = async () => Factory.createInitAppWithFakeCrypto(Environment.Web, Platform.MacWeb) - const accountPassword = 'password' -const registerApp = async (snApp) => { +const registerApp = async (application) => { const email = UuidGenerator.GenerateUuid() const password = accountPassword const ephemeral = false const mergeLocal = true - await snApp.register(email, password, ephemeral, mergeLocal) - return snApp + await application.register(email, password, ephemeral, mergeLocal) + return application } describe('mfa service', () => { + let application + + beforeEach(async () => { + localStorage.clear() + application = await Factory.createInitAppWithFakeCrypto(Environment.Web, Platform.MacWeb) + }) + + afterEach(async () => { + Factory.safeDeinit(application) + localStorage.clear() + application = undefined + sinon.restore() + }) + it('generates 160 bit base32-encoded mfa secret', async () => { const RFC4648 = /[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]/g - const snApp = await createApp() - const secret = await snApp.generateMfaSecret() + const secret = await application.generateMfaSecret() expect(secret).to.have.lengthOf(32) expect(secret.replace(RFC4648, '')).to.have.lengthOf(0) - - Factory.safeDeinit(snApp) }) it('activates mfa, checks if enabled, deactivates mfa', async () => { - const snApp = await createApp().then(registerApp) - Factory.handlePasswordChallenges(snApp, accountPassword) + await registerApp(application) - expect(await snApp.isMfaActivated()).to.equal(false) + Factory.handlePasswordChallenges(application, accountPassword) - const secret = await snApp.generateMfaSecret() - const token = await snApp.getOtpToken(secret) + expect(await application.isMfaActivated()).to.equal(false) - await snApp.enableMfa(secret, token) + const secret = await application.generateMfaSecret() + const token = await application.getOtpToken(secret) - expect(await snApp.isMfaActivated()).to.equal(true) + await application.enableMfa(secret, token) - await snApp.disableMfa() + expect(await application.isMfaActivated()).to.equal(true) - expect(await snApp.isMfaActivated()).to.equal(false) + await application.disableMfa() - Factory.safeDeinit(snApp) + expect(await application.isMfaActivated()).to.equal(false) }).timeout(Factory.TenSecondTimeout) it('prompts for account password when disabling mfa', async () => { - const snApp = await createApp().then(registerApp) - Factory.handlePasswordChallenges(snApp, accountPassword) - const secret = await snApp.generateMfaSecret() - const token = await snApp.getOtpToken(secret) + await registerApp(application) - sinon.spy(snApp.challenges, 'sendChallenge') - await snApp.enableMfa(secret, token) - await snApp.disableMfa() + Factory.handlePasswordChallenges(application, accountPassword) + const secret = await application.generateMfaSecret() + const token = await application.getOtpToken(secret) - const spyCall = snApp.challenges.sendChallenge.getCall(0) + sinon.spy(application.challenges, 'sendChallenge') + await application.enableMfa(secret, token) + await application.disableMfa() + + const spyCall = application.challenges.sendChallenge.getCall(0) const challenge = spyCall.firstArg expect(challenge.prompts).to.have.lengthOf(2) expect(challenge.prompts[0].validation).to.equal(ChallengeValidation.AccountPassword) - - Factory.safeDeinit(snApp) }).timeout(Factory.TenSecondTimeout) }) diff --git a/packages/snjs/mocha/migrations/migration.test.js b/packages/snjs/mocha/migrations/migration.test.js index 85f363042..60ce05a03 100644 --- a/packages/snjs/mocha/migrations/migration.test.js +++ b/packages/snjs/mocha/migrations/migration.test.js @@ -1,4 +1,5 @@ import * as Factory from '../lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect diff --git a/packages/snjs/mocha/migrations/tags-to-folders.test.js b/packages/snjs/mocha/migrations/tags-to-folders.test.js index 989a2be42..ec182908c 100644 --- a/packages/snjs/mocha/migrations/tags-to-folders.test.js +++ b/packages/snjs/mocha/migrations/tags-to-folders.test.js @@ -1,6 +1,5 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from '../lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect @@ -11,25 +10,29 @@ const setupRandomUuid = () => { } describe('web native folders migration', () => { + let application + beforeEach(async function () { - this.application = await Factory.createInitAppWithFakeCrypto() + localStorage.clear() + application = await Factory.createInitAppWithFakeCrypto() setupRandomUuid() }) afterEach(async function () { - await Factory.safeDeinit(this.application) - // TODO: cleanup uuid behind us or we'll mess other tests. + await Factory.safeDeinit(application) + application = undefined + localStorage.clear() }) it('migration with flat tag folders', async function () { const titles = ['a', 'b', 'c'] - await makeTags(this.application, titles) + await makeTags(application, titles) // Run the migration - await this.application.mutator.migrateTagsToFolders() + await application.mutator.migrateTagsToFolders() // Check new tags - const result = extractTagHierarchy(this.application) + const result = extractTagHierarchy(application) expect(result).to.deep.equal({ a: { _uuid: 'a' }, @@ -40,13 +43,13 @@ describe('web native folders migration', () => { it('migration with simple tag folders', async function () { const titles = ['a.b.c', 'b', 'a.b'] - await makeTags(this.application, titles) + await makeTags(application, titles) // Run the migration - await this.application.mutator.migrateTagsToFolders() + await application.mutator.migrateTagsToFolders() // Check new tags - const result = extractTagHierarchy(this.application) + const result = extractTagHierarchy(application) expect(result).to.deep.equal({ a: { @@ -62,13 +65,13 @@ describe('web native folders migration', () => { it('migration with more complex cases', async function () { const titles = ['a.b.c', 'b', 'a.b'] - await makeTags(this.application, titles) + await makeTags(application, titles) // Run the migration - await this.application.mutator.migrateTagsToFolders() + await application.mutator.migrateTagsToFolders() // Check new tags - const result = extractTagHierarchy(this.application) + const result = extractTagHierarchy(application) expect(result).to.deep.equal({ a: { @@ -84,13 +87,13 @@ describe('web native folders migration', () => { it('should produce a valid hierarchy cases with missing intermediate tags or unordered', async function () { const titles = ['y.2', 'w.3', 'y'] - await makeTags(this.application, titles) + await makeTags(application, titles) // Run the migration - await this.application.mutator.migrateTagsToFolders() + await application.mutator.migrateTagsToFolders() // Check new tags - const result = extractTagHierarchy(this.application) + const result = extractTagHierarchy(application) expect(result).to.deep.equal({ w: { @@ -105,13 +108,13 @@ describe('web native folders migration', () => { it('skip prefixed names', async function () { const titles = ['.something', '.something...something', 'something.a.b.c'] - await makeTags(this.application, titles) + await makeTags(application, titles) // Run the migration - await this.application.mutator.migrateTagsToFolders() + await application.mutator.migrateTagsToFolders() // Check new tags - const result = extractTagHierarchy(this.application) + const result = extractTagHierarchy(application) expect(result).to.deep.equal({ '.something': { _uuid: '.something' }, @@ -132,13 +135,13 @@ describe('web native folders migration', () => { 'a', 'something..another.thing..anyway', ] - await makeTags(this.application, titles) + await makeTags(application, titles) // Run the migration - await this.application.mutator.migrateTagsToFolders() + await application.mutator.migrateTagsToFolders() // Check new tags - const result = extractTagHierarchy(this.application) + const result = extractTagHierarchy(application) expect(result).to.deep.equal({ 'something.': { _uuid: 'something.' }, diff --git a/packages/snjs/mocha/model_tests/appmodels.test.js b/packages/snjs/mocha/model_tests/appmodels.test.js index 5e2acd8c5..9ea4d8395 100644 --- a/packages/snjs/mocha/model_tests/appmodels.test.js +++ b/packages/snjs/mocha/model_tests/appmodels.test.js @@ -1,37 +1,26 @@ -/* eslint-disable camelcase */ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import { BaseItemCounts } from '../lib/BaseItemCounts.js' import * as Factory from '../lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('app models', () => { - const sharedApplication = Factory.createApplicationWithFakeCrypto() - - before(async function () { - localStorage.clear() - await Factory.initializeApplication(sharedApplication) - }) - - after(async function () { - localStorage.clear() - await Factory.safeDeinit(sharedApplication) - }) + let application + let expectedItemCount + let context beforeEach(async function () { - this.expectedItemCount = BaseItemCounts.DefaultItems - this.context = await Factory.createAppContext() - this.application = this.context.application - await this.context.launch() + localStorage.clear() + expectedItemCount = BaseItemCounts.DefaultItems + context = await Factory.createAppContext() + application = context.application + await context.launch() }) afterEach(async function () { - await Factory.safeDeinit(this.application) - }) - - it('payloads should be defined', () => { - expect(sharedApplication.payloads).to.be.ok + await context.deinit() + localStorage.clear() + sinon.restore() }) it('item should be defined', () => { @@ -72,17 +61,17 @@ describe('app models', () => { }, }) - await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) - await this.application.mutator.emitItemsFromPayloads([params2], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([params2], PayloadEmitSource.LocalChanged) - const item1 = this.application.items.findItem(params1.uuid) - const item2 = this.application.items.findItem(params2.uuid) + const item1 = application.items.findItem(params1.uuid) + const item2 = application.items.findItem(params2.uuid) expect(item1.content.references.length).to.equal(1) expect(item2.content.references.length).to.equal(0) - expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0) - expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1) + expect(application.items.itemsReferencingItem(item1).length).to.equal(0) + expect(application.items.itemsReferencingItem(item2).length).to.equal(1) }) it('mapping an item twice shouldnt cause problems', async function () { @@ -95,45 +84,45 @@ describe('app models', () => { }, }) - let items = await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) + let items = await application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) let item = items[0] expect(item).to.be.ok - items = await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) + items = await application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) item = items[0] expect(item.content.foo).to.equal('bar') - expect(this.application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(1) }) it('mapping item twice should preserve references', async function () { - const item1 = await Factory.createMappedNote(this.application) - const item2 = await Factory.createMappedNote(this.application) + const item1 = await Factory.createMappedNote(application) + const item2 = await Factory.createMappedNote(application) - await this.application.mutator.changeItem(item1, (mutator) => { + await application.mutator.changeItem(item1, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item2) }) - await this.application.mutator.changeItem(item2, (mutator) => { + await application.mutator.changeItem(item2, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item1) }) - const refreshedItem = this.application.items.findItem(item1.uuid) + const refreshedItem = application.items.findItem(item1.uuid) expect(refreshedItem.content.references.length).to.equal(1) }) it('fixes relationship integrity', async function () { - var item1 = await Factory.createMappedNote(this.application) - var item2 = await Factory.createMappedNote(this.application) + var item1 = await Factory.createMappedNote(application) + var item2 = await Factory.createMappedNote(application) - await this.application.mutator.changeItem(item1, (mutator) => { + await application.mutator.changeItem(item1, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item2) }) - await this.application.mutator.changeItem(item2, (mutator) => { + await application.mutator.changeItem(item2, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item1) }) - const refreshedItem1 = this.application.items.findItem(item1.uuid) - const refreshedItem2 = this.application.items.findItem(item2.uuid) + const refreshedItem1 = application.items.findItem(item1.uuid) + const refreshedItem2 = application.items.findItem(item2.uuid) expect(refreshedItem1.content.references.length).to.equal(1) expect(refreshedItem2.content.references.length).to.equal(1) @@ -145,54 +134,54 @@ describe('app models', () => { references: [], }, }) - await this.application.mutator.emitItemsFromPayloads([damagedPayload], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([damagedPayload], PayloadEmitSource.LocalChanged) - const refreshedItem1_2 = this.application.items.findItem(item1.uuid) - const refreshedItem2_2 = this.application.items.findItem(item2.uuid) + const refreshedItem1_2 = application.items.findItem(item1.uuid) + const refreshedItem2_2 = application.items.findItem(item2.uuid) expect(refreshedItem1_2.content.references.length).to.equal(0) expect(refreshedItem2_2.content.references.length).to.equal(1) }) it('creating and removing relationships between two items should have valid references', async function () { - var item1 = await Factory.createMappedNote(this.application) - var item2 = await Factory.createMappedNote(this.application) - await this.application.mutator.changeItem(item1, (mutator) => { + var item1 = await Factory.createMappedNote(application) + var item2 = await Factory.createMappedNote(application) + await application.mutator.changeItem(item1, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item2) }) - await this.application.mutator.changeItem(item2, (mutator) => { + await application.mutator.changeItem(item2, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item1) }) - const refreshedItem1 = this.application.items.findItem(item1.uuid) - const refreshedItem2 = this.application.items.findItem(item2.uuid) + const refreshedItem1 = application.items.findItem(item1.uuid) + const refreshedItem2 = application.items.findItem(item2.uuid) expect(refreshedItem1.content.references.length).to.equal(1) expect(refreshedItem2.content.references.length).to.equal(1) - expect(this.application.items.itemsReferencingItem(item1)).to.include(refreshedItem2) - expect(this.application.items.itemsReferencingItem(item2)).to.include(refreshedItem1) + expect(application.items.itemsReferencingItem(item1)).to.include(refreshedItem2) + expect(application.items.itemsReferencingItem(item2)).to.include(refreshedItem1) - await this.application.mutator.changeItem(item1, (mutator) => { + await application.mutator.changeItem(item1, (mutator) => { mutator.removeItemAsRelationship(item2) }) - await this.application.mutator.changeItem(item2, (mutator) => { + await application.mutator.changeItem(item2, (mutator) => { mutator.removeItemAsRelationship(item1) }) - const refreshedItem1_2 = this.application.items.findItem(item1.uuid) - const refreshedItem2_2 = this.application.items.findItem(item2.uuid) + const refreshedItem1_2 = application.items.findItem(item1.uuid) + const refreshedItem2_2 = application.items.findItem(item2.uuid) expect(refreshedItem1_2.content.references.length).to.equal(0) expect(refreshedItem2_2.content.references.length).to.equal(0) - expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0) - expect(this.application.items.itemsReferencingItem(item2).length).to.equal(0) + expect(application.items.itemsReferencingItem(item1).length).to.equal(0) + expect(application.items.itemsReferencingItem(item2).length).to.equal(0) }) it('properly duplicates item with no relationships', async function () { - const item = await Factory.createMappedNote(this.application) - const duplicate = await this.application.mutator.duplicateItem(item) + const item = await Factory.createMappedNote(application) + const duplicate = await application.mutator.duplicateItem(item) expect(duplicate.uuid).to.not.equal(item.uuid) expect(item.isItemContentEqualWith(duplicate)).to.equal(true) expect(item.created_at.toISOString()).to.equal(duplicate.created_at.toISOString()) @@ -200,36 +189,36 @@ describe('app models', () => { }) it('properly duplicates item with relationships', async function () { - const item1 = await Factory.createMappedNote(this.application) - const item2 = await Factory.createMappedNote(this.application) + const item1 = await Factory.createMappedNote(application) + const item2 = await Factory.createMappedNote(application) - const refreshedItem1 = await this.application.mutator.changeItem(item1, (mutator) => { + const refreshedItem1 = await application.mutator.changeItem(item1, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item2) }) expect(refreshedItem1.content.references.length).to.equal(1) - const duplicate = await this.application.mutator.duplicateItem(item1) + const duplicate = await application.mutator.duplicateItem(item1) expect(duplicate.uuid).to.not.equal(item1.uuid) expect(duplicate.content.references.length).to.equal(1) - expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0) - expect(this.application.items.itemsReferencingItem(item2).length).to.equal(2) + expect(application.items.itemsReferencingItem(item1).length).to.equal(0) + expect(application.items.itemsReferencingItem(item2).length).to.equal(2) - const refreshedItem1_2 = this.application.items.findItem(item1.uuid) + const refreshedItem1_2 = application.items.findItem(item1.uuid) expect(refreshedItem1_2.isItemContentEqualWith(duplicate)).to.equal(true) expect(refreshedItem1_2.created_at.toISOString()).to.equal(duplicate.created_at.toISOString()) expect(refreshedItem1_2.content_type).to.equal(duplicate.content_type) }) it('removing references should update cross-refs', async function () { - const item1 = await Factory.createMappedNote(this.application) - const item2 = await Factory.createMappedNote(this.application) - const refreshedItem1 = await this.application.mutator.changeItem(item1, (mutator) => { + const item1 = await Factory.createMappedNote(application) + const item2 = await Factory.createMappedNote(application) + const refreshedItem1 = await application.mutator.changeItem(item1, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item2) }) - const refreshedItem1_2 = await this.application.mutator.emitItemFromPayload( + const refreshedItem1_2 = await application.mutator.emitItemFromPayload( refreshedItem1.payloadRepresentation({ deleted: true, content: { @@ -240,49 +229,49 @@ describe('app models', () => { PayloadEmitSource.LocalChanged, ) - expect(this.application.items.itemsReferencingItem(item2).length).to.equal(0) - expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0) + expect(application.items.itemsReferencingItem(item2).length).to.equal(0) + expect(application.items.itemsReferencingItem(item1).length).to.equal(0) expect(refreshedItem1_2.content.references.length).to.equal(0) }) it('properly handles single item uuid alternation', async function () { - const item1 = await Factory.createMappedNote(this.application) - const item2 = await Factory.createMappedNote(this.application) + const item1 = await Factory.createMappedNote(application) + const item2 = await Factory.createMappedNote(application) - const refreshedItem1 = await this.application.mutator.changeItem(item1, (mutator) => { + const refreshedItem1 = await application.mutator.changeItem(item1, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item2) }) expect(refreshedItem1.content.references.length).to.equal(1) - expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1) + expect(application.items.itemsReferencingItem(item2).length).to.equal(1) - const alternatedItem = await Factory.alternateUuidForItem(this.application, item1.uuid) - const refreshedItem1_2 = this.application.items.findItem(item1.uuid) + const alternatedItem = await Factory.alternateUuidForItem(application, item1.uuid) + const refreshedItem1_2 = application.items.findItem(item1.uuid) expect(refreshedItem1_2).to.not.be.ok - expect(this.application.items.getDisplayableNotes().length).to.equal(2) + expect(application.items.getDisplayableNotes().length).to.equal(2) expect(alternatedItem.content.references.length).to.equal(1) - expect(this.application.items.itemsReferencingItem(alternatedItem.uuid).length).to.equal(0) + expect(application.items.itemsReferencingItem(alternatedItem.uuid).length).to.equal(0) - expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1) + expect(application.items.itemsReferencingItem(item2).length).to.equal(1) expect(alternatedItem.isReferencingItem(item2)).to.equal(true) expect(alternatedItem.dirty).to.equal(true) }) it('alterating uuid of item should fill its duplicateOf value', async function () { - const item1 = await Factory.createMappedNote(this.application) - const alternatedItem = await Factory.alternateUuidForItem(this.application, item1.uuid) + const item1 = await Factory.createMappedNote(application) + const alternatedItem = await Factory.alternateUuidForItem(application, item1.uuid) expect(alternatedItem.duplicateOf).to.equal(item1.uuid) }) it('alterating itemskey uuid should update errored items encrypted with that key', async function () { - const item1 = await Factory.createMappedNote(this.application) - const itemsKey = this.application.items.getDisplayableItemsKeys()[0] + const item1 = await Factory.createMappedNote(application) + const itemsKey = application.items.getDisplayableItemsKeys()[0] /** Encrypt item1 and emit as errored so it persists with items_key_id */ - const encrypted = await this.application.encryption.encryptSplitSingle({ + const encrypted = await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [item1.payload], }, @@ -292,46 +281,46 @@ describe('app models', () => { waitingForKey: true, }) - await this.application.payloads.emitPayload(errored) + await application.payloads.emitPayload(errored) - expect(this.application.payloads.findOne(item1.uuid).errorDecrypting).to.equal(true) - expect(this.application.payloads.findOne(item1.uuid).items_key_id).to.equal(itemsKey.uuid) + expect(application.payloads.findOne(item1.uuid).errorDecrypting).to.equal(true) + expect(application.payloads.findOne(item1.uuid).items_key_id).to.equal(itemsKey.uuid) - sinon.stub(this.application.encryption.itemsEncryption, 'decryptErroredItemPayloads').callsFake(() => { + sinon.stub(application.encryption.itemsEncryption, 'decryptErroredItemPayloads').callsFake(() => { // prevent auto decryption }) - const alternatedKey = await Factory.alternateUuidForItem(this.application, itemsKey.uuid) - const updatedPayload = this.application.payloads.findOne(item1.uuid) + const alternatedKey = await Factory.alternateUuidForItem(application, itemsKey.uuid) + const updatedPayload = application.payloads.findOne(item1.uuid) expect(updatedPayload.items_key_id).to.equal(alternatedKey.uuid) }) it('properly handles mutli item uuid alternation', async function () { - const item1 = await Factory.createMappedNote(this.application) - const item2 = await Factory.createMappedNote(this.application) - this.expectedItemCount += 2 + const item1 = await Factory.createMappedNote(application) + const item2 = await Factory.createMappedNote(application) + expectedItemCount += 2 - await this.application.mutator.changeItem(item1, (mutator) => { + await application.mutator.changeItem(item1, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item2) }) - expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1) + expect(application.items.itemsReferencingItem(item2).length).to.equal(1) - const alternatedItem1 = await Factory.alternateUuidForItem(this.application, item1.uuid) - const alternatedItem2 = await Factory.alternateUuidForItem(this.application, item2.uuid) + const alternatedItem1 = await Factory.alternateUuidForItem(application, item1.uuid) + const alternatedItem2 = await Factory.alternateUuidForItem(application, item2.uuid) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) expect(item1.uuid).to.not.equal(alternatedItem1.uuid) expect(item2.uuid).to.not.equal(alternatedItem2.uuid) - const refreshedAltItem1 = this.application.items.findItem(alternatedItem1.uuid) + const refreshedAltItem1 = application.items.findItem(alternatedItem1.uuid) expect(refreshedAltItem1.content.references.length).to.equal(1) expect(refreshedAltItem1.content.references[0].uuid).to.equal(alternatedItem2.uuid) expect(alternatedItem2.content.references.length).to.equal(0) - expect(this.application.items.itemsReferencingItem(alternatedItem2).length).to.equal(1) + expect(application.items.itemsReferencingItem(alternatedItem2).length).to.equal(1) expect(refreshedAltItem1.isReferencingItem(alternatedItem2)).to.equal(true) expect(alternatedItem2.isReferencingItem(refreshedAltItem1)).to.equal(false) @@ -339,39 +328,39 @@ describe('app models', () => { }) it('maintains referencing relationships when duplicating', async function () { - const tag = await Factory.createMappedTag(this.application) - const note = await Factory.createMappedNote(this.application) - const refreshedTag = await this.application.mutator.changeItem(tag, (mutator) => { + const tag = await Factory.createMappedTag(application) + const note = await Factory.createMappedNote(application) + const refreshedTag = await application.mutator.changeItem(tag, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(note) }) expect(refreshedTag.content.references.length).to.equal(1) - const noteCopy = await this.application.mutator.duplicateItem(note) + const noteCopy = await application.mutator.duplicateItem(note) expect(note.uuid).to.not.equal(noteCopy.uuid) - expect(this.application.items.getDisplayableNotes().length).to.equal(2) - expect(this.application.items.getDisplayableTags().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(2) + expect(application.items.getDisplayableTags().length).to.equal(1) expect(note.content.references.length).to.equal(0) expect(noteCopy.content.references.length).to.equal(0) - const refreshedTag_2 = this.application.items.findItem(tag.uuid) + const refreshedTag_2 = application.items.findItem(tag.uuid) expect(refreshedTag_2.content.references.length).to.equal(2) }) it('maintains editor reference when duplicating note', async function () { - const component = await this.application.mutator.createItem( + const component = await application.mutator.createItem( ContentType.TYPES.Component, { area: ComponentArea.Editor, package_info: { identifier: 'foo-editor' } }, true, ) - const note = await Factory.insertItemWithOverride(this.application, ContentType.TYPES.Note, { + const note = await Factory.insertItemWithOverride(application, ContentType.TYPES.Note, { editorIdentifier: 'foo-editor', }) - expect(this.application.componentManager.editorForNote(note).uniqueIdentifier.value).to.equal(component.uuid) + expect(application.componentManager.editorForNote(note).uniqueIdentifier.value).to.equal(component.uuid) - const duplicate = await this.application.mutator.duplicateItem(note, true) - expect(this.application.componentManager.editorForNote(duplicate).uniqueIdentifier.value).to.equal(component.uuid) + const duplicate = await application.mutator.duplicateItem(note, true) + expect(application.componentManager.editorForNote(duplicate).uniqueIdentifier.value).to.equal(component.uuid) }) }) diff --git a/packages/snjs/mocha/model_tests/importing.test.js b/packages/snjs/mocha/model_tests/importing.test.js index 9a9a0a923..f94585a9d 100644 --- a/packages/snjs/mocha/model_tests/importing.test.js +++ b/packages/snjs/mocha/model_tests/importing.test.js @@ -1,8 +1,7 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import { BaseItemCounts } from '../lib/BaseItemCounts.js' import * as Factory from '../lib/factory.js' import { createRelatedNoteTagPairPayload } from '../lib/Items.js' + chai.use(chaiAsPromised) const expect = chai.expect @@ -15,869 +14,862 @@ describe('importing', function () { let password let context - beforeEach(function () { - localStorage.clear() - }) - - const setup = async ({ fakeCrypto }) => { - expectedItemCount = BaseItemCounts.DefaultItems - - if (fakeCrypto) { - context = await Factory.createAppContext() - } else { - context = await Factory.createAppContextWithRealCrypto() - } - - await context.launch() - application = context.application - - email = UuidGenerator.GenerateUuid() - password = UuidGenerator.GenerateUuid() - Factory.handlePasswordChallenges(application, password) - } - afterEach(async function () { - await Factory.safeDeinit(application) - localStorage.clear() - }) - - it('should not import backups made from unsupported versions', async function () { - await setup({ fakeCrypto: true }) - const result = await application.importData({ - version: '-1', - items: [], - }) - expect(result.error).to.exist - }) - - it('should not import backups made from 004 into 003 account', async function () { - await setup({ fakeCrypto: true }) - await Factory.registerOldUser({ - application, - email, - password, - version: ProtocolVersion.V003, - }) - const result = await application.importData({ - version: ProtocolVersion.V004, - items: [], - }) - expect(result.error).to.exist - }) - - it('importing existing data should keep relationships valid', async function () { - await setup({ fakeCrypto: true }) - const pair = createRelatedNoteTagPairPayload() - const notePayload = pair[0] - const tagPayload = pair[1] - - await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - expectedItemCount += 2 - const note = application.items.getItems([ContentType.TYPES.Note])[0] - const tag = application.items.getItems([ContentType.TYPES.Tag])[0] - - expect(tag.content.references.length).to.equal(1) - expect(tag.noteCount).to.equal(1) - - expect(note.content.references.length).to.equal(0) - expect(application.items.itemsReferencingItem(note).length).to.equal(1) - - await application.importData( - { - items: [notePayload, tagPayload], - }, - true, - ) - - expect(application.items.items.length).to.equal(expectedItemCount) - - expect(tag.content.references.length).to.equal(1) - expect(tag.noteCount).to.equal(1) - - expect(note.content.references.length).to.equal(0) - expect(application.items.itemsReferencingItem(note).length).to.equal(1) - }) - - it('importing same note many times should create only one duplicate', async function () { - /** - * Used strategy here will be KEEP_LEFT_DUPLICATE_RIGHT - * which means that new right items will be created with different - */ - await setup({ fakeCrypto: true }) - const notePayload = Factory.createNotePayload() - await application.mutator.emitItemFromPayload(notePayload, PayloadEmitSource.LocalChanged) - expectedItemCount++ - const mutatedNote = new DecryptedPayload({ - ...notePayload, - content: { - ...notePayload.content, - title: `${Math.random()}`, - }, - }) - await application.importData( - { - items: [mutatedNote, mutatedNote, mutatedNote], - }, - true, - ) - expectedItemCount++ - expect(application.items.getDisplayableNotes().length).to.equal(2) - const imported = application.items.getDisplayableNotes().find((n) => n.uuid !== notePayload.uuid) - expect(imported.content.title).to.equal(mutatedNote.content.title) - }) - - it('importing a tag with lesser references should not create duplicate', async function () { - await setup({ fakeCrypto: true }) - const pair = createRelatedNoteTagPairPayload() - const tagPayload = pair[1] - await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) - const mutatedTag = new DecryptedPayload({ - ...tagPayload, - content: { - ...tagPayload.content, - references: [], - }, - }) - await application.importData( - { - items: [mutatedTag], - }, - true, - ) - expect(application.items.getDisplayableTags().length).to.equal(1) - expect(application.items.findItem(tagPayload.uuid).content.references.length).to.equal(1) - }) - - it('importing data with differing content should create duplicates', async function () { - await setup({ fakeCrypto: true }) - const pair = createRelatedNoteTagPairPayload() - const notePayload = pair[0] - const tagPayload = pair[1] - await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) - expectedItemCount += 2 - const note = application.items.getDisplayableNotes()[0] - const tag = application.items.getDisplayableTags()[0] - const mutatedNote = new DecryptedPayload({ - ...notePayload, - content: { - ...notePayload.content, - title: `${Math.random()}`, - }, - }) - const mutatedTag = new DecryptedPayload({ - ...tagPayload, - content: { - ...tagPayload.content, - title: `${Math.random()}`, - }, - }) - await application.importData( - { - items: [mutatedNote, mutatedTag], - }, - true, - ) - expectedItemCount += 2 - expect(application.items.items.length).to.equal(expectedItemCount) - - const newNote = application.items.getDisplayableNotes().find((n) => n.uuid !== notePayload.uuid) - const newTag = application.items.getDisplayableTags().find((t) => t.uuid !== tagPayload.uuid) - - expect(newNote.uuid).to.not.equal(note.uuid) - expect(newTag.uuid).to.not.equal(tag.uuid) - - const refreshedTag = application.items.findItem(tag.uuid) - expect(refreshedTag.content.references.length).to.equal(2) - expect(refreshedTag.noteCount).to.equal(2) - - const refreshedNote = application.items.findItem(note.uuid) - expect(refreshedNote.content.references.length).to.equal(0) - expect(application.items.itemsReferencingItem(refreshedNote).length).to.equal(2) - - expect(newTag.content.references.length).to.equal(1) - expect(newTag.noteCount).to.equal(1) - - expect(newNote.content.references.length).to.equal(0) - expect(application.items.itemsReferencingItem(newNote).length).to.equal(1) - }) - - it('when importing items, imported values should not be used to determine if changed', async function () { - /** - * If you have a note and a tag, and the tag has 1 reference to the note, - * and you import the same two items, except modify the note value so that - * a duplicate is created, we expect only the note to be duplicated, and the - * tag not to. However, if only the note changes, and you duplicate the note, - * which causes the tag's references content to change, then when the incoming - * tag is being processed, it will also think it has changed, since our local - * value now doesn't match what's coming in. The solution is to get all values - * ahead of time before any changes are made. - */ - await setup({ fakeCrypto: true }) - const note = await Factory.createMappedNote(application) - const tag = await Factory.createMappedTag(application) - expectedItemCount += 2 - - await application.mutator.changeItem(tag, (mutator) => { - mutator.e2ePendingRefactor_addItemAsRelationship(note) - }) - - const externalNote = Object.assign( - {}, - { - uuid: note.uuid, - content: note.getContentCopy(), - content_type: note.content_type, - }, - ) - externalNote.content.text = `${Math.random()}` - - const externalTag = Object.assign( - {}, - { - uuid: tag.uuid, - content: tag.getContentCopy(), - content_type: tag.content_type, - }, - ) - - await application.importData( - { - items: [externalNote, externalTag], - }, - true, - ) - expectedItemCount += 1 - - /** We expect now that the total item count is 3, not 4. */ - expect(application.items.items.length).to.equal(expectedItemCount) - - const refreshedTag = application.items.findItem(tag.uuid) - /** References from both items have merged. */ - expect(refreshedTag.content.references.length).to.equal(2) - }) - - it('should import decrypted data and keep items that were previously deleted', async function () { - await setup({ fakeCrypto: true }) - - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, - }) - - Factory.handlePasswordChallenges(application, password) - - const [note, tag] = await Promise.all([Factory.createMappedNote(application), Factory.createMappedTag(application)]) - - await application.sync.sync({ awaitAll: true }) - - await application.mutator.deleteItem(note) - await application.sync.sync() - expect(application.items.findItem(note.uuid)).to.not.exist - - await application.mutator.deleteItem(tag) - await application.sync.sync() - expect(application.items.findItem(tag.uuid)).to.not.exist - - await application.importData( - { - items: [note, tag], - }, - true, - ) - - expect(application.items.getDisplayableNotes().length).to.equal(1) - expect(application.items.findItem(note.uuid).deleted).to.not.be.ok - - expect(application.items.getDisplayableTags().length).to.equal(1) - expect(application.items.findItem(tag.uuid).deleted).to.not.be.ok - }) - - it('should duplicate notes by alternating UUIDs when dealing with conflicts during importing', async function () { - await setup({ fakeCrypto: true }) - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, - }) - const note = await Factory.createSyncedNote(application) - - /** Sign into another account and import the same item. It should get a different UUID. */ - application = await Factory.signOutApplicationAndReturnNew(application) - email = UuidGenerator.GenerateUuid() - Factory.handlePasswordChallenges(application, password) - - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, - }) - - await application.importData( - { - items: [note.payload], - }, - true, - ) - - expect(application.items.getDisplayableNotes().length).to.equal(1) - expect(application.items.getDisplayableNotes()[0].uuid).to.not.equal(note.uuid) - }) - - it('should maintain consistency between storage and PayloadManager after an import with conflicts', async function () { - await setup({ fakeCrypto: true }) - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, - }) - const note = await Factory.createSyncedNote(application) - - /** Sign into another account and import the same items. They should get a different UUID. */ - application = await Factory.signOutApplicationAndReturnNew(application) - email = UuidGenerator.GenerateUuid() - Factory.handlePasswordChallenges(application, password) - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, - }) - - await application.importData( - { - items: [note], - }, - true, - ) - - const storedPayloads = await application.storage.getAllRawPayloads() - expect(application.items.items.length).to.equal(storedPayloads.length) - const notes = storedPayloads.filter((p) => p.content_type === ContentType.TYPES.Note) - const itemsKeys = storedPayloads.filter((p) => p.content_type === ContentType.TYPES.ItemsKey) - expect(notes.length).to.equal(1) - expect(itemsKeys.length).to.equal(1) - }) - - it('should import encrypted data and keep items that were previously deleted', async function () { - await setup({ fakeCrypto: true }) - - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, - }) - - const [note, tag] = await Promise.all([Factory.createMappedNote(application), Factory.createMappedTag(application)]) - - const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() - - await application.sync.sync({ awaitAll: true }) - - await application.mutator.deleteItem(note) - await application.sync.sync() - expect(application.items.findItem(note.uuid)).to.not.exist - - await application.mutator.deleteItem(tag) - await application.sync.sync() - expect(application.items.findItem(tag.uuid)).to.not.exist - - await application.importData(backupData, true) - - expect(application.items.getDisplayableNotes().length).to.equal(1) - expect(application.items.findItem(note.uuid).deleted).to.not.be.ok - - expect(application.items.getDisplayableTags().length).to.equal(1) - expect(application.items.findItem(tag.uuid).deleted).to.not.be.ok - }) - - it('should import decrypted data and all items payload source should be FileImport', async function () { - await setup({ fakeCrypto: true }) - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, - }) - - const [note, tag] = await Promise.all([Factory.createMappedNote(application), Factory.createMappedTag(application)]) - - const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() - - await Factory.safeDeinit(application) - application = await Factory.createInitAppWithFakeCrypto() - Factory.handlePasswordChallenges(application, password) - - await application.importData(backupData, true) - - const importedNote = application.items.findItem(note.uuid) - const importedTag = application.items.findItem(tag.uuid) - - expect(importedNote.payload.source).to.be.equal(PayloadSource.FileImport) - expect(importedTag.payload.source).to.be.equal(PayloadSource.FileImport) - }) - - it('should import encrypted data and all items payload source should be FileImport', async function () { - await setup({ fakeCrypto: true }) - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, - }) - - const [note, tag] = await Promise.all([Factory.createMappedNote(application), Factory.createMappedTag(application)]) - - const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() - - await Factory.safeDeinit(application) - application = await Factory.createInitAppWithFakeCrypto() - Factory.handlePasswordChallenges(application, password) - - await application.importData(backupData, true) - - const importedNote = application.items.findItem(note.uuid) - const importedTag = application.items.findItem(tag.uuid) - expect(importedNote.payload.source).to.be.equal(PayloadSource.FileImport) - expect(importedTag.payload.source).to.be.equal(PayloadSource.FileImport) - }) - - it('should import data from 003 encrypted payload using client generated backup', async function () { - await setup({ fakeCrypto: true }) - const oldVersion = ProtocolVersion.V003 - await Factory.registerOldUser({ - application: application, - email: email, - password: password, - version: oldVersion, - }) - - const noteItem = await application.mutator.createItem(ContentType.TYPES.Note, { - title: 'Encrypted note', - text: 'On protocol version 003.', - }) - - const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() - - await Factory.safeDeinit(application) - application = await Factory.createInitAppWithFakeCrypto() - Factory.handlePasswordChallenges(application, password) - - const result = await application.importData(backupData, true) - expect(result).to.not.be.undefined - expect(result.affectedItems.length).to.be.eq(backupData.items.length) - expect(result.errorCount).to.be.eq(0) - - const decryptedNote = application.items.findItem(noteItem.uuid) - expect(decryptedNote.title).to.be.eq('Encrypted note') - expect(decryptedNote.text).to.be.eq('On protocol version 003.') - expect(application.items.getDisplayableNotes().length).to.equal(1) - }) - - it('should import data from 003 encrypted payload using server generated backup with 004 key params', async function () { - await setup({ fakeCrypto: false }) - const backupData = { - items: [ - { - uuid: 'eb1b7eed-e43d-48dd-b257-b7fc8ccba3da', - duplicate_of: null, - items_key_id: null, - content: - '003:618138e365a13f8aed17d4f52e3da47d4b5d6e02004a0f827118e8a981a57c35:eb1b7eed-e43d-48dd-b257-b7fc8ccba3da:9f38642b7a3f57546520a9e32aa7c0ad:qa9rUcaD904m1Knv63dnATEHwfHJjsbq9bWb06zGTsyQxzLaAYT7uRGp2KB2g1eo5Aqxc5FqhvuF0+dE1f4+uQOeiRFNX73V2pJJY0w5Qq7l7ZuhB08ZtOMY4Ctq7evBBSIVZ+PEIfFnACelNJhsB5Uhn3kS4ZBx6qtvQ6ciSQGfYAwc6wSKhjUm1umEINeb08LNgwbP6XAm8U/la1bdtdMO112XjUW7ixkWi3POWcM=:eyJpZGVudGlmaWVyIjoicGxheWdyb3VuZEBiaXRhci5pbyIsInB3X2Nvc3QiOjExMDAwMCwicHdfbm9uY2UiOiJhZmIwYjE3NGJlYjViMmJmZTIyNTk1NDlmMTgxNDI1NzlkMDE1ZmE3ZTBhMjE4YzVmNDIxNmU0Mzg2ZGI3OWFiIiwidmVyc2lvbiI6IjAwMyJ9', - content_type: 'Note', - enc_item_key: - '003:5a01e913c52899ba10c16dbe7e713dd9caf9b9554c82176ddfcf1424f5bfd94f:eb1b7eed-e43d-48dd-b257-b7fc8ccba3da:14721ff8dbdd36fb57ae4bf7414c5eab:odmq91dfaTZG/zeSUA09fD/PdB2OkiDxcQZ0FL06GPstxdvxnU17k1rtsWoA7HoNNnd5494BZ/b7YiKqUb76ddd8x3/+cTZgCa4tYxNINmb1T3wwUX0Ebxc8xynAhg6nTY/BGq+ba6jTyl8zw12dL3kBEGGglRCHnO0ZTeylwQW7asfONN8s0BwrvHdonRlx:eyJpZGVudGlmaWVyIjoicGxheWdyb3VuZEBiaXRhci5pbyIsInB3X2Nvc3QiOjExMDAwMCwicHdfbm9uY2UiOiJhZmIwYjE3NGJlYjViMmJmZTIyNTk1NDlmMTgxNDI1NzlkMDE1ZmE3ZTBhMjE4YzVmNDIxNmU0Mzg2ZGI3OWFiIiwidmVyc2lvbiI6IjAwMyJ9', - auth_hash: null, - created_at: '2019-05-12T02:29:21.789000Z', - updated_at: '2019-11-12T21:47:48.382708Z', - deleted: false, - }, - { - uuid: '10051be7-4ca2-4af3-aae9-021939df4fab', - duplicate_of: null, - items_key_id: null, - content: - '004:77a986823b8ffdd87164b6f541de6ed420b70ac67e055774:+8cjww1QbyXNX+PSKeCwmnysv0rAoEaKh409VWQJpDbEy/pPZCT6c0rKxLzvyMiSq6EwkOiduZMzokRgCKP7RuRqNPJceWsxNnpIUwa40KR1IP2tdreW4J8v9pFEzPMec1oq40u+c+UI/Y6ChOLV/4ozyWmpQCK3y8Ugm7B1/FzaeDs9Ie6Mvf98+XECoi0fWv9SO2TeBvq1G24LXd4zf0j8jd0sKZbLPXH0+gaUXtBH7A56lHvB0ED9NuiHI8xopTBd9ogKlz/b5+JB4zA2zQCQ3WMEE1qz6WeB2S4FMomgeO1e3trabdU0ICu0WMvDVii4qNlQo/inD41oHXKeV5QwnYoGjPrLJIaP0hiLKhDURTHygCdvWdp63OWI+aGxv0/HI+nfcRsqSE+aYECrWB/kp/c5yTrEqBEafuWZkw==:eyJrcCI6eyJpZGVudGlmaWVyIjoicGxheWdyb3VuZEBiaXRhci5pbyIsInB3X25vbmNlIjoiNjUxYWUxZWM5NTgwMzM5YTM1NjdlZTdmMGY4NjcyNDkyZGUyYzE2NmE1NTZjMTNkMTE5NzI4YTAzYzYwZjc5MyIsInZlcnNpb24iOiIwMDQiLCJvcmlnaW5hdGlvbiI6InByb3RvY29sLXVwZ3JhZGUiLCJjcmVhdGVkIjoiMTYxNDc4NDE5MjQ5NyJ9LCJ1IjoiMTAwNTFiZTctNGNhMi00YWYzLWFhZTktMDIxOTM5ZGY0ZmFiIiwidiI6IjAwNCJ9', - content_type: 'SN|ItemsKey', - enc_item_key: - '004:d25deb224251b4705a44d8ce125a62f6a2f0e0e856603e8f:FEv1pfU/VfY7XhJrTfpcdhaSBfmNySTQtHohFYDm8V84KlyF5YaXRKV7BfXsa77DKTjOCU/EHHsWwhBEEfsNnzNySHxTHNc26bpoz0V8h50=:eyJrcCI6eyJpZGVudGlmaWVyIjoicGxheWdyb3VuZEBiaXRhci5pbyIsInB3X25vbmNlIjoiNjUxYWUxZWM5NTgwMzM5YTM1NjdlZTdmMGY4NjcyNDkyZGUyYzE2NmE1NTZjMTNkMTE5NzI4YTAzYzYwZjc5MyIsInZlcnNpb24iOiIwMDQiLCJvcmlnaW5hdGlvbiI6InByb3RvY29sLXVwZ3JhZGUiLCJjcmVhdGVkIjoiMTYxNDc4NDE5MjQ5NyJ9LCJ1IjoiMTAwNTFiZTctNGNhMi00YWYzLWFhZTktMDIxOTM5ZGY0ZmFiIiwidiI6IjAwNCJ9', - auth_hash: null, - created_at: '2020-09-07T12:22:06.562000Z', - updated_at: '2021-03-03T15:09:55.741107Z', - deleted: false, - }, - ], - auth_params: { - identifier: 'playground@bitar.io', - pw_nonce: '651ae1ec9580339a3567ee7f0f8672492de2c166a556c13d119728a03c60f793', - version: '004', - }, + if (application) { + await Factory.safeDeinit(application) } - - const password = 'password' - - application = await Factory.createInitAppWithRealCrypto() - Factory.handlePasswordChallenges(application, password) - - const result = await application.importData(backupData, true) - expect(result).to.not.be.undefined - expect(result.affectedItems.length).to.be.eq(backupData.items.length) - expect(result.errorCount).to.be.eq(0) + localStorage.clear() + application = undefined + context = undefined }) - it('should import data from 004 encrypted payload', async function () { - await setup({ fakeCrypto: true }) - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, + describe('fake crypto', function () { + beforeEach(async function () { + localStorage.clear() + expectedItemCount = BaseItemCounts.DefaultItems + + context = await Factory.createAppContext() + + await context.launch() + application = context.application + + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() + Factory.handlePasswordChallenges(application, password) }) - const noteItem = await application.mutator.createItem(ContentType.TYPES.Note, { - title: 'Encrypted note', - text: 'On protocol version 004.', + it('should not import backups made from unsupported versions', async function () { + const result = await application.importData({ + version: '-1', + items: [], + }) + expect(result.error).to.exist }) - const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() - - await Factory.safeDeinit(application) - application = await Factory.createInitAppWithFakeCrypto() - Factory.handlePasswordChallenges(application, password) - - const result = await application.importData(backupData, true) - expect(result).to.not.be.undefined - expect(result.affectedItems.length).to.be.eq(backupData.items.length) - expect(result.errorCount).to.be.eq(0) - - const decryptedNote = application.items.findItem(noteItem.uuid) - expect(decryptedNote.title).to.be.eq('Encrypted note') - expect(decryptedNote.text).to.be.eq('On protocol version 004.') - expect(application.items.getDisplayableNotes().length).to.equal(1) - }) - - it('should return correct errorCount', async function () { - await setup({ fakeCrypto: true }) - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, + it('should not import backups made from 004 into 003 account', async function () { + await Factory.registerOldUser({ + application, + email, + password, + version: ProtocolVersion.V003, + }) + const result = await application.importData({ + version: ProtocolVersion.V004, + items: [], + }) + expect(result.error).to.exist }) - const noteItem = await application.mutator.createItem(ContentType.TYPES.Note, { - title: 'This is a valid, encrypted note', - text: 'On protocol version 004.', - }) + it('importing existing data should keep relationships valid', async function () { + const pair = createRelatedNoteTagPairPayload() + const notePayload = pair[0] + const tagPayload = pair[1] - const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) + expectedItemCount += 2 + const note = application.items.getItems([ContentType.TYPES.Note])[0] + const tag = application.items.getItems([ContentType.TYPES.Tag])[0] - await Factory.safeDeinit(application) - application = await Factory.createInitAppWithFakeCrypto() - Factory.handlePasswordChallenges(application, password) + expect(tag.content.references.length).to.equal(1) + expect(tag.noteCount).to.equal(1) - const madeUpPayload = JSON.parse(JSON.stringify(noteItem)) + expect(note.content.references.length).to.equal(0) + expect(application.items.itemsReferencingItem(note).length).to.equal(1) - madeUpPayload.items_key_id = undefined - madeUpPayload.content = '004:somenonsense' - madeUpPayload.enc_item_key = '003:anothernonsense' - madeUpPayload.version = '004' - madeUpPayload.uuid = 'fake-uuid' - - backupData.items = [...backupData.items, madeUpPayload] - - const result = await application.importData(backupData, true) - expect(result).to.not.be.undefined - expect(result.affectedItems.length).to.be.eq(backupData.items.length - 1) - expect(result.errorCount).to.be.eq(1) - }) - - it('should not import data from 003 encrypted payload if an invalid password is provided', async function () { - await setup({ fakeCrypto: true }) - - const oldVersion = ProtocolVersion.V003 - await Factory.registerOldUser({ - application: application, - email: email, - password: UuidGenerator.GenerateUuid(), - version: oldVersion, - }) - - await application.mutator.createItem(ContentType.TYPES.Note, { - title: 'Encrypted note', - text: 'On protocol version 003.', - }) - - const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() - - await Factory.safeDeinit(application) - application = await Factory.createInitAppWithFakeCrypto() - application.setLaunchCallback({ - receiveChallenge: (challenge) => { - const values = challenge.prompts.map((prompt) => - CreateChallengeValue( - prompt, - prompt.validation === ChallengeValidation.None ? 'incorrect password' : password, - ), - ) - application.submitValuesForChallenge(challenge, values) - }, - }) - - const result = await application.importData(backupData, true) - expect(result).to.not.be.undefined - - expect(result.affectedItems.length).to.be.eq(0) - expect(result.errorCount).to.be.eq(backupData.items.length) - expect(application.items.getDisplayableNotes().length).to.equal(0) - }) - - it('should not import data from 004 encrypted payload if an invalid password is provided', async function () { - await setup({ fakeCrypto: true }) - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, - }) - - await application.mutator.createItem(ContentType.TYPES.Note, { - title: 'This is a valid, encrypted note', - text: 'On protocol version 004.', - }) - - const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() - - await Factory.safeDeinit(application) - application = await Factory.createInitAppWithFakeCrypto() - application.setLaunchCallback({ - receiveChallenge: (challenge) => { - const values = challenge.prompts.map((prompt) => CreateChallengeValue(prompt, 'incorrect password')) - application.submitValuesForChallenge(challenge, values) - }, - }) - - const result = await application.importData(backupData, true) - expect(result).to.not.be.undefined - expect(result.affectedItems.length).to.be.eq(0) - expect(result.errorCount).to.be.eq(backupData.items.length) - expect(application.items.getDisplayableNotes().length).to.equal(0) - }) - - it('should not import encrypted data with no keyParams or auth_params', async function () { - await setup({ fakeCrypto: true }) - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, - }) - - await application.mutator.createItem(ContentType.TYPES.Note, { - title: 'Encrypted note', - text: 'On protocol version 004.', - }) - - const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() - delete backupData.keyParams - - await Factory.safeDeinit(application) - application = await Factory.createInitAppWithFakeCrypto() - - const result = await application.importData(backupData) - - expect(result.error).to.be.ok - }) - - it('should not import payloads if the corresponding ItemsKey is not present within the backup file', async function () { - await setup({ fakeCrypto: true }) - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, - }) - Factory.handlePasswordChallenges(application, password) - - await application.mutator.createItem(ContentType.TYPES.Note, { - title: 'Encrypted note', - text: 'On protocol version 004.', - }) - - const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() - backupData.items = backupData.items.filter((payload) => payload.content_type !== ContentType.TYPES.ItemsKey) - - await Factory.safeDeinit(application) - application = await Factory.createInitAppWithFakeCrypto() - Factory.handlePasswordChallenges(application, password) - - const result = await application.importData(backupData, true) - - expect(result).to.not.be.undefined - - expect(result.affectedItems.length).to.equal(BaseItemCounts.BackupFileRootKeyEncryptedItems) - - expect(result.errorCount).to.be.eq(backupData.items.length - BaseItemCounts.BackupFileRootKeyEncryptedItems) - expect(application.items.getDisplayableNotes().length).to.equal(0) - }) - - it('importing data with no items key should use the root key generated by the file password', async function () { - await setup({ fakeCrypto: false }) - /** - * In SNJS 2.0.12, this file import would fail with "incorrect password" on file. - * The reason was that we would use the default items key we had for the current account - * instead of using the password generated root key for the file. - * - * Note this test will not be able to properly sync as the credentials are invalid. - * This test is only meant to test successful local importing. - */ - const identifier = 'standardnotes' - const application = await Factory.createApplicationWithRealCrypto(identifier) - /** Create legacy migrations value so that base migration detects old app */ - await application.device.setRawStorageValue( - 'keychain', - JSON.stringify({ - [identifier]: { - version: '003', - masterKey: '30bae65687b45b20100be219df983bded23868baa44f4bbef1026403daee0a9d', - dataAuthenticationKey: 'c9b382ff1f7adb5c6cad620605ad139cd9f1e7700f507345ef1a1d46a6413712', + await application.importData( + { + items: [notePayload, tagPayload], }, - }), - ) - await application.device.setRawStorageValue( - 'descriptors', - JSON.stringify({ - [identifier]: { - identifier: 'standardnotes', - label: 'Main Application', - primary: true, - }, - }), - ) - await application.device.setRawStorageValue('standardnotes-snjs_version', '2.0.11') - await application.device.saveDatabaseEntry( - { - content: - '003:9f2c7527eb8b2a1f8bfb3ea6b885403b6886bce2640843ebd57a6c479cbf7597:58e3322b-269a-4be3-a658-b035dffcd70f:9140b23a0fa989e224e292049f133154:SESTNOgIGf2+ZqmJdFnGU4EMgQkhKOzpZNoSzx76SJaImsayzctAgbUmJ+UU2gSQAHADS3+Z5w11bXvZgIrStTsWriwvYkNyyKmUPadKHNSBwOk4WeBZpWsA9gtI5zgI04Q5pvb8hS+kNW2j1DjM4YWqd0JQxMOeOrMIrxr/6Awn5TzYE+9wCbXZdYHyvRQcp9ui/G02ZJ67IA86vNEdjTTBAAWipWqTqKH9VDZbSQ2W/IOKfIquB373SFDKZb1S1NmBFvcoG2G7w//fAl/+ehYiL6UdiNH5MhXCDAOTQRFNfOh57HFDWVnz1VIp8X+VAPy6d9zzQH+8aws1JxHq/7BOhXrFE8UCueV6kERt9njgQxKJzd9AH32ShSiUB9X/sPi0fUXbS178xAZMJrNx3w==:eyJwd19ub25jZSI6IjRjYjEwM2FhODljZmY0NTYzYTkxMWQzZjM5NjU4M2NlZmM2ODMzYzY2Zjg4MGZiZWUwNmJkYTk0YzMxZjg2OGIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzIyQGJpdGFyLmlvIiwidmVyc2lvbiI6IjAwMyIsIm9yaWdpbmF0aW9uIjoicmVnaXN0cmF0aW9uIn0=', - content_type: 'SN|ItemsKey', - created_at: new Date(), - enc_item_key: - '003:d7267919b07864ccc1da87a48db6c6192e2e892be29ce882e981c36f673b3847:58e3322b-269a-4be3-a658-b035dffcd70f:2384a22d8f8bf671ba6517c6e1d0be30:0qXjBDPLCcMlNTnuUDcFiJPIXU9OP6b4ttTVE58n2Jn7971xMhx6toLbAZWWLPk/ezX/19EYE9xmRngWsG4jJaZMxGZIz/melU08K7AHH3oahQpHwZvSM3iV2ufsN7liQywftdVH6NNzULnZnFX+FgEfpDquru++R4aWDLvsSegWYmde9zD62pPNUB9Kik6P:eyJwd19ub25jZSI6IjRjYjEwM2FhODljZmY0NTYzYTkxMWQzZjM5NjU4M2NlZmM2ODMzYzY2Zjg4MGZiZWUwNmJkYTk0YzMxZjg2OGIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzIyQGJpdGFyLmlvIiwidmVyc2lvbiI6IjAwMyIsIm9yaWdpbmF0aW9uIjoicmVnaXN0cmF0aW9uIn0=', - updated_at: new Date(), - uuid: '58e3322b-269a-4be3-a658-b035dffcd70f', - }, - identifier, - ) - /** - * Note that this storage contains "sync.standardnotes.org" as the API Host param. - */ - await application.device.setRawStorageValue( - 'standardnotes-storage', - JSON.stringify({ - wrapped: { - uuid: '15af096f-4e9d-4cde-8d67-f132218fa757', - content_type: 'SN|EncryptedStorage', - enc_item_key: - '003:2fb0c55859ddf0c16982b91d6202a6fb8174f711d820f8b785c558538cda5048:15af096f-4e9d-4cde-8d67-f132218fa757:09a4da52d5214e76642f0363246daa99:zt5fnmxYSZOqC+uA08oAKdtjfTdAoX1lPnbTe98CYQSlIvaePIpG5c9tAN5QzZbECkj4Lm9txwSA2O6Y4Y25rqO4lIerKjxxNqPwDze9mtPOGeoR48csUPiMIHiH78bLGZZs4VoBwYKAP+uEygXEFYRuscGnDOrFV7fnwGDL/nkhr6xpM159OTUKBgiBpVMS:eyJwd19ub25jZSI6IjRjYjEwM2FhODljZmY0NTYzYTkxMWQzZjM5NjU4M2NlZmM2ODMzYzY2Zjg4MGZiZWUwNmJkYTk0YzMxZjg2OGIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzIyQGJpdGFyLmlvIiwidmVyc2lvbiI6IjAwMyIsIm9yaWdpbmF0aW9uIjoicmVnaXN0cmF0aW9uIn0=', - content: - '003:70a02948696e09211cfd34cd312dbbf85751397189da06d7acc7c46dafa9aeeb:15af096f-4e9d-4cde-8d67-f132218fa757:b92fb4b030ac51f4d3eef0ada35f3d5f:r3gdrawyd069qOQQotD5EtabTwjs4IiLFWotK0Ygbt9oAT09xILx7v92z8YALJ6i6EKHOT7zyCytR5l2B9b1J7Tls00uVgfEKs3zX7n3F6ne+ju0++WsJuy0Gre5+Olov6lqQrY3I8hWQShxaG84huZaFTIPU5+LP0JAseWWDENqUQ+Vxr+w0wqNYO6TLtr/YAqk2yOY7DLQ0WhGzK+WH9JfvS8MCccJVeBD99ebM8lKVVfTaUfrk2AlbMv47TFSjTeCDblQuU68joE45HV8Y0g2CF4nkTvdr3wn0HhdDp07YuXditX9NGtBhI8oFkstwKEksblyX9dGpn7of4ctdvNOom3Vjw/m4x9mE0lCIbjxQVAiDyy+Hg0HDtVt1j205ycg1RS7cT7+Sn746Z06S8TixcVUUUQh+MGRIulIE5utOE81Lv/p+jb2vmv+TGHUV4kZJPluG7A9IEphMZrMWwiU56FdSlSDD82qd9iG+C3Pux+X/GYCMiWS2T/BoyI6a9OERSARuTUuom2bv59hqD1yUoj7VQXhqXmverSwLE1zDeF+dc0tMwuTNCNOTk08A6wRKTR9ZjuFlLcxHsg/VZyfIdCkElFh1FrliMbW2ZsgsPFaZAI+YN8pid1tTw+Ou1cOfyD85aki98DDvg/cTi8ahrrm8UvxRQwhIW17Cm1RnKxhIvaq5HRjEN76Y46ubkZv7/HjhNwJt9vPEr9wyOrMH6XSxCnSIFD1kbVHI33q444xyUWa/EQju8SoEGGU92HhpMWd1kIz37SJRJTC7u2ah2Xg60JGcUcCNtHG3IHMPVP+UKUjx5nKP6t/NVSa+xsjIvM/ZkSL37W0TMZykC1cKfzeUmlZhGQPCIqad3b4ognZ48LGCgwBP87rWn8Ln8Cqcz7X0Ze22HoouKBPAtWlYJ8fmvg2HiW6nX/L9DqoxK4OXt/LnC2BTEvtP4PUzBqx8WoqmVNNnYp+FgYptLcgxmgckle41w1eMr6NYGeaaC1Jk3i/e9Piw0w0XjV/lB+yn03gEMYPTT2yiXMQrfPmkUNYNN7/xfhY3bqqwfER7iXdr/80Lc+x9byywChXLvg8VCjHWGd+Sky3NHyMdxLY8IqefyyZWMeXtt1aNYH6QW9DeK5KvK3DI+MK3kWwMCySe51lkE9jzcqrxpYMZjb2Za9VDZNBgdwQYXfOlxFEje0so0LlMJmmxRfbMU06bYt0vszT2szAkOnVuyi6TBRiGLyjMxYI0csM0SHZWZUQK0z7ZoQAWR5D+adX29tOvrKc2kJA8Lrzgeqw/rJIh6zPg3kmsd2rFbo+Qfe3J6XrlZU+J+N96I98i0FU0quI6HwG1zFg6UOmfRjaCML8rSAPtMaNhlO7M2sgRmDCtsNcpU06Fua6F2fEHPiXs4+9:eyJwd19ub25jZSI6IjRjYjEwM2FhODljZmY0NTYzYTkxMWQzZjM5NjU4M2NlZmM2ODMzYzY2Zjg4MGZiZWUwNmJkYTk0YzMxZjg2OGIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzIyQGJpdGFyLmlvIiwidmVyc2lvbiI6IjAwMyIsIm9yaWdpbmF0aW9uIjoicmVnaXN0cmF0aW9uIn0=', - created_at: '2020-11-24T00:53:42.057Z', - updated_at: '1970-01-01T00:00:00.000Z', - }, - nonwrapped: { - ROOT_KEY_PARAMS: { - pw_nonce: '4cb103aa89cff4563a911d3f396583cefc6833c66f880fbee06bda94c31f868b', - pw_cost: 110000, - identifier: 'nov2322@bitar.io', - version: '003', - }, - }, - }), - ) - const password = 'password' + true, + ) - await application.prepareForLaunch({ - receiveChallenge: (challenge) => { - if (challenge.reason === ChallengeReason.Custom) { - return - } + expect(application.items.items.length).to.equal(expectedItemCount) - if ( - challenge.reason === ChallengeReason.DecryptEncryptedFile || - challenge.reason === ChallengeReason.ImportFile - ) { - application.submitValuesForChallenge( - challenge, - challenge.prompts.map((prompt) => - CreateChallengeValue( - prompt, - prompt.validation !== ChallengeValidation.ProtectionSessionDuration - ? password - : UnprotectedAccessSecondsDuration.OneMinute, - ), + expect(tag.content.references.length).to.equal(1) + expect(tag.noteCount).to.equal(1) + + expect(note.content.references.length).to.equal(0) + expect(application.items.itemsReferencingItem(note).length).to.equal(1) + }) + + it('importing same note many times should create only one duplicate', async function () { + /** + * Used strategy here will be KEEP_LEFT_DUPLICATE_RIGHT + * which means that new right items will be created with different + */ + + const notePayload = Factory.createNotePayload() + await application.mutator.emitItemFromPayload(notePayload, PayloadEmitSource.LocalChanged) + expectedItemCount++ + const mutatedNote = new DecryptedPayload({ + ...notePayload, + content: { + ...notePayload.content, + title: `${Math.random()}`, + }, + }) + await application.importData( + { + items: [mutatedNote, mutatedNote, mutatedNote], + }, + true, + ) + expectedItemCount++ + expect(application.items.getDisplayableNotes().length).to.equal(2) + const imported = application.items.getDisplayableNotes().find((n) => n.uuid !== notePayload.uuid) + expect(imported.content.title).to.equal(mutatedNote.content.title) + }) + + it('importing a tag with lesser references should not create duplicate', async function () { + const pair = createRelatedNoteTagPairPayload() + const tagPayload = pair[1] + await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) + const mutatedTag = new DecryptedPayload({ + ...tagPayload, + content: { + ...tagPayload.content, + references: [], + }, + }) + await application.importData( + { + items: [mutatedTag], + }, + true, + ) + expect(application.items.getDisplayableTags().length).to.equal(1) + expect(application.items.findItem(tagPayload.uuid).content.references.length).to.equal(1) + }) + + it('importing data with differing content should create duplicates', async function () { + const pair = createRelatedNoteTagPairPayload() + const notePayload = pair[0] + const tagPayload = pair[1] + await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) + expectedItemCount += 2 + const note = application.items.getDisplayableNotes()[0] + const tag = application.items.getDisplayableTags()[0] + const mutatedNote = new DecryptedPayload({ + ...notePayload, + content: { + ...notePayload.content, + title: `${Math.random()}`, + }, + }) + const mutatedTag = new DecryptedPayload({ + ...tagPayload, + content: { + ...tagPayload.content, + title: `${Math.random()}`, + }, + }) + await application.importData( + { + items: [mutatedNote, mutatedTag], + }, + true, + ) + expectedItemCount += 2 + expect(application.items.items.length).to.equal(expectedItemCount) + + const newNote = application.items.getDisplayableNotes().find((n) => n.uuid !== notePayload.uuid) + const newTag = application.items.getDisplayableTags().find((t) => t.uuid !== tagPayload.uuid) + + expect(newNote.uuid).to.not.equal(note.uuid) + expect(newTag.uuid).to.not.equal(tag.uuid) + + const refreshedTag = application.items.findItem(tag.uuid) + expect(refreshedTag.content.references.length).to.equal(2) + expect(refreshedTag.noteCount).to.equal(2) + + const refreshedNote = application.items.findItem(note.uuid) + expect(refreshedNote.content.references.length).to.equal(0) + expect(application.items.itemsReferencingItem(refreshedNote).length).to.equal(2) + + expect(newTag.content.references.length).to.equal(1) + expect(newTag.noteCount).to.equal(1) + + expect(newNote.content.references.length).to.equal(0) + expect(application.items.itemsReferencingItem(newNote).length).to.equal(1) + }) + + it('when importing items, imported values should not be used to determine if changed', async function () { + /** + * If you have a note and a tag, and the tag has 1 reference to the note, + * and you import the same two items, except modify the note value so that + * a duplicate is created, we expect only the note to be duplicated, and the + * tag not to. However, if only the note changes, and you duplicate the note, + * which causes the tag's references content to change, then when the incoming + * tag is being processed, it will also think it has changed, since our local + * value now doesn't match what's coming in. The solution is to get all values + * ahead of time before any changes are made. + */ + + const note = await Factory.createMappedNote(application) + const tag = await Factory.createMappedTag(application) + expectedItemCount += 2 + + await application.mutator.changeItem(tag, (mutator) => { + mutator.e2ePendingRefactor_addItemAsRelationship(note) + }) + + const externalNote = Object.assign( + {}, + { + uuid: note.uuid, + content: note.getContentCopy(), + content_type: note.content_type, + }, + ) + externalNote.content.text = `${Math.random()}` + + const externalTag = Object.assign( + {}, + { + uuid: tag.uuid, + content: tag.getContentCopy(), + content_type: tag.content_type, + }, + ) + + await application.importData( + { + items: [externalNote, externalTag], + }, + true, + ) + expectedItemCount += 1 + + /** We expect now that the total item count is 3, not 4. */ + expect(application.items.items.length).to.equal(expectedItemCount) + + const refreshedTag = application.items.findItem(tag.uuid) + /** References from both items have merged. */ + expect(refreshedTag.content.references.length).to.equal(2) + }) + + it('should import decrypted data and keep items that were previously deleted', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + + Factory.handlePasswordChallenges(application, password) + + const [note, tag] = await Promise.all([ + Factory.createMappedNote(application), + Factory.createMappedTag(application), + ]) + + await application.sync.sync({ awaitAll: true }) + + await application.mutator.deleteItem(note) + await application.sync.sync() + expect(application.items.findItem(note.uuid)).to.not.exist + + await application.mutator.deleteItem(tag) + await application.sync.sync() + expect(application.items.findItem(tag.uuid)).to.not.exist + + await application.importData( + { + items: [note, tag], + }, + true, + ) + + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.findItem(note.uuid).deleted).to.not.be.ok + + expect(application.items.getDisplayableTags().length).to.equal(1) + expect(application.items.findItem(tag.uuid).deleted).to.not.be.ok + }) + + it('should duplicate notes by alternating UUIDs when dealing with conflicts during importing', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + const note = await Factory.createSyncedNote(application) + + /** Sign into another account and import the same item. It should get a different UUID. */ + application = await Factory.signOutApplicationAndReturnNew(application) + email = UuidGenerator.GenerateUuid() + Factory.handlePasswordChallenges(application, password) + + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + + await application.importData( + { + items: [note.payload], + }, + true, + ) + + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableNotes()[0].uuid).to.not.equal(note.uuid) + }) + + it('should maintain consistency between storage and PayloadManager after an import with conflicts', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + const note = await Factory.createSyncedNote(application) + + /** Sign into another account and import the same items. They should get a different UUID. */ + application = await Factory.signOutApplicationAndReturnNew(application) + email = UuidGenerator.GenerateUuid() + Factory.handlePasswordChallenges(application, password) + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + + await application.importData( + { + items: [note], + }, + true, + ) + + const storedPayloads = await application.storage.getAllRawPayloads() + expect(application.items.items.length).to.equal(storedPayloads.length) + const notes = storedPayloads.filter((p) => p.content_type === ContentType.TYPES.Note) + const itemsKeys = storedPayloads.filter((p) => p.content_type === ContentType.TYPES.ItemsKey) + expect(notes.length).to.equal(1) + expect(itemsKeys.length).to.equal(1) + }) + + it('should import encrypted data and keep items that were previously deleted', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + + const [note, tag] = await Promise.all([ + Factory.createMappedNote(application), + Factory.createMappedTag(application), + ]) + + const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + + await application.sync.sync({ awaitAll: true }) + + await application.mutator.deleteItem(note) + await application.sync.sync() + expect(application.items.findItem(note.uuid)).to.not.exist + + await application.mutator.deleteItem(tag) + await application.sync.sync() + expect(application.items.findItem(tag.uuid)).to.not.exist + + await application.importData(backupData, true) + + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.findItem(note.uuid).deleted).to.not.be.ok + + expect(application.items.getDisplayableTags().length).to.equal(1) + expect(application.items.findItem(tag.uuid).deleted).to.not.be.ok + }) + + it('should import decrypted data and all items payload source should be FileImport', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + + const [note, tag] = await Promise.all([ + Factory.createMappedNote(application), + Factory.createMappedTag(application), + ]) + + const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + + await Factory.safeDeinit(application) + application = await Factory.createInitAppWithFakeCrypto() + Factory.handlePasswordChallenges(application, password) + + await application.importData(backupData, true) + + const importedNote = application.items.findItem(note.uuid) + const importedTag = application.items.findItem(tag.uuid) + + expect(importedNote.payload.source).to.be.equal(PayloadSource.FileImport) + expect(importedTag.payload.source).to.be.equal(PayloadSource.FileImport) + }) + + it('should import encrypted data and all items payload source should be FileImport', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + + const [note, tag] = await Promise.all([ + Factory.createMappedNote(application), + Factory.createMappedTag(application), + ]) + + const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + + await Factory.safeDeinit(application) + application = await Factory.createInitAppWithFakeCrypto() + Factory.handlePasswordChallenges(application, password) + + await application.importData(backupData, true) + + const importedNote = application.items.findItem(note.uuid) + const importedTag = application.items.findItem(tag.uuid) + expect(importedNote.payload.source).to.be.equal(PayloadSource.FileImport) + expect(importedTag.payload.source).to.be.equal(PayloadSource.FileImport) + }) + + it('should import data from 003 encrypted payload using client generated backup', async function () { + const oldVersion = ProtocolVersion.V003 + await Factory.registerOldUser({ + application: application, + email: email, + password: password, + version: oldVersion, + }) + + const noteItem = await application.mutator.createItem(ContentType.TYPES.Note, { + title: 'Encrypted note', + text: 'On protocol version 003.', + }) + + const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + + await Factory.safeDeinit(application) + application = await Factory.createInitAppWithFakeCrypto() + Factory.handlePasswordChallenges(application, password) + + const result = await application.importData(backupData, true) + expect(result).to.not.be.undefined + expect(result.affectedItems.length).to.be.eq(backupData.items.length) + expect(result.errorCount).to.be.eq(0) + + const decryptedNote = application.items.findItem(noteItem.uuid) + expect(decryptedNote.title).to.be.eq('Encrypted note') + expect(decryptedNote.text).to.be.eq('On protocol version 003.') + expect(application.items.getDisplayableNotes().length).to.equal(1) + }) + + it('should import data from 004 encrypted payload', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + + const noteItem = await application.mutator.createItem(ContentType.TYPES.Note, { + title: 'Encrypted note', + text: 'On protocol version 004.', + }) + + const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + + await Factory.safeDeinit(application) + application = await Factory.createInitAppWithFakeCrypto() + Factory.handlePasswordChallenges(application, password) + + const result = await application.importData(backupData, true) + expect(result).to.not.be.undefined + expect(result.affectedItems.length).to.be.eq(backupData.items.length) + expect(result.errorCount).to.be.eq(0) + + const decryptedNote = application.items.findItem(noteItem.uuid) + expect(decryptedNote.title).to.be.eq('Encrypted note') + expect(decryptedNote.text).to.be.eq('On protocol version 004.') + expect(application.items.getDisplayableNotes().length).to.equal(1) + }) + + it('should return correct errorCount', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + + const noteItem = await application.mutator.createItem(ContentType.TYPES.Note, { + title: 'This is a valid, encrypted note', + text: 'On protocol version 004.', + }) + + const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + + await Factory.safeDeinit(application) + application = await Factory.createInitAppWithFakeCrypto() + Factory.handlePasswordChallenges(application, password) + + const madeUpPayload = JSON.parse(JSON.stringify(noteItem)) + + madeUpPayload.items_key_id = undefined + madeUpPayload.content = '004:somenonsense' + madeUpPayload.enc_item_key = '003:anothernonsense' + madeUpPayload.version = '004' + madeUpPayload.uuid = 'fake-uuid' + + backupData.items = [...backupData.items, madeUpPayload] + + const result = await application.importData(backupData, true) + expect(result).to.not.be.undefined + expect(result.affectedItems.length).to.be.eq(backupData.items.length - 1) + expect(result.errorCount).to.be.eq(1) + }) + + it('should not import data from 003 encrypted payload if an invalid password is provided', async function () { + const oldVersion = ProtocolVersion.V003 + await Factory.registerOldUser({ + application: application, + email: email, + password: UuidGenerator.GenerateUuid(), + version: oldVersion, + }) + + await application.mutator.createItem(ContentType.TYPES.Note, { + title: 'Encrypted note', + text: 'On protocol version 003.', + }) + + const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + + await Factory.safeDeinit(application) + application = await Factory.createInitAppWithFakeCrypto() + application.setLaunchCallback({ + receiveChallenge: (challenge) => { + const values = challenge.prompts.map((prompt) => + CreateChallengeValue( + prompt, + prompt.validation === ChallengeValidation.None ? 'incorrect password' : password, ), ) - } - }, - }) - await application.launch(false) - await application.setHost.execute(Factory.getDefaultHost()) - - const backupFile = { - items: [ - { - uuid: '11204d02-5a8b-47c0-ab94-ae0727d656b5', - content_type: 'Note', - created_at: '2020-11-23T17:11:06.322Z', - enc_item_key: - '003:111edcff9ed3432b9e11c4a64bef9e810ed2b9147790963caf6886511c46bbc4:11204d02-5a8b-47c0-ab94-ae0727d656b5:62de2b95cca4d7948f70516d12f5cb3a:lhUF/EoQP2DC8CSVrXyLp1yXsiJUXxwmtkwXtLUJ5sm4E0+ZNzMCO9U9ho+q6i9V+777dSbfTqODz4ZSt6hj3gtYxi9ZlOM/VrTtmJ2YcxiMaRTVl5sVZPG+YTpQPMuugN5/0EfuT/SJ9IqVbjgYhKA5xt/lMgw4JSbiW8ZkVQ5tVDfgt0omhDRLlkh758ou:eyJwd19ub25jZSI6IjNlMzU3YzQxZmI1YWU2MTUyYmZmMzY2ZjBhOGE3ZjRmZDk2NDQxZDZhNWViYzY3MDA4OTk2ZWY2YzU1YTg3ZjIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzVAYml0YXIuaW8iLCJ2ZXJzaW9uIjoiMDAzIn0=', - content: - '003:d43c6d2dc9465796e01145843cf1b95031030c15cc79a73f14d941d15e28147a:11204d02-5a8b-47c0-ab94-ae0727d656b5:84a2b760019a62d7ad9c314bc7a5564a:G8Mm9fy9ybuo92VbV4NUERruJ1VA7garv1+fBg4KRDRjsRGoLvORhHldQHRfUQmSR6PkrG6ol/jOn1gjIH5gtgGczB5NgbKau7amYZHsQJPr1UleJVsLrjMJgiYGqbEDmXPtJSX2tLGFhAbYcVX4xrHKbkiuLQnu9bZp9zbR6txB1NtLoNFvwDZTMko7Q+28fM4TKBbQCCw3NufLHVUnfEwS7tLLFFPdEyyMXOerKP93u8X+7NG2eDmsUetPsPOq:eyJwd19ub25jZSI6IjNlMzU3YzQxZmI1YWU2MTUyYmZmMzY2ZjBhOGE3ZjRmZDk2NDQxZDZhNWViYzY3MDA4OTk2ZWY2YzU1YTg3ZjIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzVAYml0YXIuaW8iLCJ2ZXJzaW9uIjoiMDAzIn0=', - auth_hash: null, - updated_at: '2020-11-23T17:11:40.399Z', + application.submitValuesForChallenge(challenge, values) }, - ], - auth_params: { - pw_nonce: '3e357c41fb5ae6152bff366f0a8a7f4fd96441d6a5ebc67008996ef6c55a87f2', - pw_cost: 110000, - identifier: 'nov235@bitar.io', - version: '003', - }, - } + }) - const result = await application.importData(backupFile, false) - expect(result.errorCount).to.equal(0) - await Factory.safeDeinit(application) + const result = await application.importData(backupData, true) + expect(result).to.not.be.undefined + + expect(result.affectedItems.length).to.be.eq(0) + expect(result.errorCount).to.be.eq(backupData.items.length) + expect(application.items.getDisplayableNotes().length).to.equal(0) + }) + + it('should not import data from 004 encrypted payload if an invalid password is provided', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + + await application.mutator.createItem(ContentType.TYPES.Note, { + title: 'This is a valid, encrypted note', + text: 'On protocol version 004.', + }) + + const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + + await Factory.safeDeinit(application) + application = await Factory.createInitAppWithFakeCrypto() + application.setLaunchCallback({ + receiveChallenge: (challenge) => { + const values = challenge.prompts.map((prompt) => CreateChallengeValue(prompt, 'incorrect password')) + application.submitValuesForChallenge(challenge, values) + }, + }) + + const result = await application.importData(backupData, true) + expect(result).to.not.be.undefined + expect(result.affectedItems.length).to.be.eq(0) + expect(result.errorCount).to.be.eq(backupData.items.length) + expect(application.items.getDisplayableNotes().length).to.equal(0) + }) + + it('should not import encrypted data with no keyParams or auth_params', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + + await application.mutator.createItem(ContentType.TYPES.Note, { + title: 'Encrypted note', + text: 'On protocol version 004.', + }) + + const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + delete backupData.keyParams + + await Factory.safeDeinit(application) + application = await Factory.createInitAppWithFakeCrypto() + + const result = await application.importData(backupData) + + expect(result.error).to.be.ok + }) + + it('should not import payloads if the corresponding ItemsKey is not present within the backup file', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + Factory.handlePasswordChallenges(application, password) + + await application.mutator.createItem(ContentType.TYPES.Note, { + title: 'Encrypted note', + text: 'On protocol version 004.', + }) + + const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + backupData.items = backupData.items.filter((payload) => payload.content_type !== ContentType.TYPES.ItemsKey) + + await Factory.safeDeinit(application) + application = await Factory.createInitAppWithFakeCrypto() + Factory.handlePasswordChallenges(application, password) + + const result = await application.importData(backupData, true) + + expect(result).to.not.be.undefined + + expect(result.affectedItems.length).to.equal(BaseItemCounts.BackupFileRootKeyEncryptedItems) + + expect(result.errorCount).to.be.eq(backupData.items.length - BaseItemCounts.BackupFileRootKeyEncryptedItems) + expect(application.items.getDisplayableNotes().length).to.equal(0) + }) + + it('importing another accounts notes/tags should correctly keep relationships', async function () { + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + + Factory.handlePasswordChallenges(application, password) + + const pair = createRelatedNoteTagPairPayload() + await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) + + await application.sync.sync() + + const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + + await Factory.safeDeinit(application) + application = await Factory.createInitAppWithFakeCrypto() + Factory.handlePasswordChallenges(application, password) + + await Factory.registerUserToApplication({ + application: application, + email: `${Math.random()}`, + password: password, + }) + + await application.importData(backupData, true) + + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableTags().length).to.equal(1) + + const importedNote = application.items.getDisplayableNotes()[0] + const importedTag = application.items.getDisplayableTags()[0] + expect(application.items.referencesForItem(importedTag).length).to.equal(1) + expect(application.items.itemsReferencingItem(importedNote).length).to.equal(1) + + await Factory.safeDeinit(application) + }).timeout(Factory.TwentySecondTimeout) }) - it('importing another accounts notes/tags should correctly keep relationships', async function () { - await setup({ fakeCrypto: true }) + describe('real crypto', function () { + let identifier = 'standardnotes' - await Factory.registerUserToApplication({ - application: application, - email: email, - password: password, + it('should import data from 003 encrypted payload using server generated backup with 004 key params', async function () { + context = await Factory.createAppContextWithRealCrypto(identifier) + + await context.launch() + application = context.application + + const backupData = { + items: [ + { + uuid: 'eb1b7eed-e43d-48dd-b257-b7fc8ccba3da', + duplicate_of: null, + items_key_id: null, + content: + '003:618138e365a13f8aed17d4f52e3da47d4b5d6e02004a0f827118e8a981a57c35:eb1b7eed-e43d-48dd-b257-b7fc8ccba3da:9f38642b7a3f57546520a9e32aa7c0ad:qa9rUcaD904m1Knv63dnATEHwfHJjsbq9bWb06zGTsyQxzLaAYT7uRGp2KB2g1eo5Aqxc5FqhvuF0+dE1f4+uQOeiRFNX73V2pJJY0w5Qq7l7ZuhB08ZtOMY4Ctq7evBBSIVZ+PEIfFnACelNJhsB5Uhn3kS4ZBx6qtvQ6ciSQGfYAwc6wSKhjUm1umEINeb08LNgwbP6XAm8U/la1bdtdMO112XjUW7ixkWi3POWcM=:eyJpZGVudGlmaWVyIjoicGxheWdyb3VuZEBiaXRhci5pbyIsInB3X2Nvc3QiOjExMDAwMCwicHdfbm9uY2UiOiJhZmIwYjE3NGJlYjViMmJmZTIyNTk1NDlmMTgxNDI1NzlkMDE1ZmE3ZTBhMjE4YzVmNDIxNmU0Mzg2ZGI3OWFiIiwidmVyc2lvbiI6IjAwMyJ9', + content_type: 'Note', + enc_item_key: + '003:5a01e913c52899ba10c16dbe7e713dd9caf9b9554c82176ddfcf1424f5bfd94f:eb1b7eed-e43d-48dd-b257-b7fc8ccba3da:14721ff8dbdd36fb57ae4bf7414c5eab:odmq91dfaTZG/zeSUA09fD/PdB2OkiDxcQZ0FL06GPstxdvxnU17k1rtsWoA7HoNNnd5494BZ/b7YiKqUb76ddd8x3/+cTZgCa4tYxNINmb1T3wwUX0Ebxc8xynAhg6nTY/BGq+ba6jTyl8zw12dL3kBEGGglRCHnO0ZTeylwQW7asfONN8s0BwrvHdonRlx:eyJpZGVudGlmaWVyIjoicGxheWdyb3VuZEBiaXRhci5pbyIsInB3X2Nvc3QiOjExMDAwMCwicHdfbm9uY2UiOiJhZmIwYjE3NGJlYjViMmJmZTIyNTk1NDlmMTgxNDI1NzlkMDE1ZmE3ZTBhMjE4YzVmNDIxNmU0Mzg2ZGI3OWFiIiwidmVyc2lvbiI6IjAwMyJ9', + auth_hash: null, + created_at: '2019-05-12T02:29:21.789000Z', + updated_at: '2019-11-12T21:47:48.382708Z', + deleted: false, + }, + { + uuid: '10051be7-4ca2-4af3-aae9-021939df4fab', + duplicate_of: null, + items_key_id: null, + content: + '004:77a986823b8ffdd87164b6f541de6ed420b70ac67e055774:+8cjww1QbyXNX+PSKeCwmnysv0rAoEaKh409VWQJpDbEy/pPZCT6c0rKxLzvyMiSq6EwkOiduZMzokRgCKP7RuRqNPJceWsxNnpIUwa40KR1IP2tdreW4J8v9pFEzPMec1oq40u+c+UI/Y6ChOLV/4ozyWmpQCK3y8Ugm7B1/FzaeDs9Ie6Mvf98+XECoi0fWv9SO2TeBvq1G24LXd4zf0j8jd0sKZbLPXH0+gaUXtBH7A56lHvB0ED9NuiHI8xopTBd9ogKlz/b5+JB4zA2zQCQ3WMEE1qz6WeB2S4FMomgeO1e3trabdU0ICu0WMvDVii4qNlQo/inD41oHXKeV5QwnYoGjPrLJIaP0hiLKhDURTHygCdvWdp63OWI+aGxv0/HI+nfcRsqSE+aYECrWB/kp/c5yTrEqBEafuWZkw==:eyJrcCI6eyJpZGVudGlmaWVyIjoicGxheWdyb3VuZEBiaXRhci5pbyIsInB3X25vbmNlIjoiNjUxYWUxZWM5NTgwMzM5YTM1NjdlZTdmMGY4NjcyNDkyZGUyYzE2NmE1NTZjMTNkMTE5NzI4YTAzYzYwZjc5MyIsInZlcnNpb24iOiIwMDQiLCJvcmlnaW5hdGlvbiI6InByb3RvY29sLXVwZ3JhZGUiLCJjcmVhdGVkIjoiMTYxNDc4NDE5MjQ5NyJ9LCJ1IjoiMTAwNTFiZTctNGNhMi00YWYzLWFhZTktMDIxOTM5ZGY0ZmFiIiwidiI6IjAwNCJ9', + content_type: 'SN|ItemsKey', + enc_item_key: + '004:d25deb224251b4705a44d8ce125a62f6a2f0e0e856603e8f:FEv1pfU/VfY7XhJrTfpcdhaSBfmNySTQtHohFYDm8V84KlyF5YaXRKV7BfXsa77DKTjOCU/EHHsWwhBEEfsNnzNySHxTHNc26bpoz0V8h50=:eyJrcCI6eyJpZGVudGlmaWVyIjoicGxheWdyb3VuZEBiaXRhci5pbyIsInB3X25vbmNlIjoiNjUxYWUxZWM5NTgwMzM5YTM1NjdlZTdmMGY4NjcyNDkyZGUyYzE2NmE1NTZjMTNkMTE5NzI4YTAzYzYwZjc5MyIsInZlcnNpb24iOiIwMDQiLCJvcmlnaW5hdGlvbiI6InByb3RvY29sLXVwZ3JhZGUiLCJjcmVhdGVkIjoiMTYxNDc4NDE5MjQ5NyJ9LCJ1IjoiMTAwNTFiZTctNGNhMi00YWYzLWFhZTktMDIxOTM5ZGY0ZmFiIiwidiI6IjAwNCJ9', + auth_hash: null, + created_at: '2020-09-07T12:22:06.562000Z', + updated_at: '2021-03-03T15:09:55.741107Z', + deleted: false, + }, + ], + auth_params: { + identifier: 'playground@bitar.io', + pw_nonce: '651ae1ec9580339a3567ee7f0f8672492de2c166a556c13d119728a03c60f793', + version: '004', + }, + } + + Factory.handlePasswordChallenges(application, 'password') + + const result = await application.importData(backupData, true) + expect(result).to.not.be.undefined + expect(result.affectedItems.length).to.be.eq(backupData.items.length) + expect(result.errorCount).to.be.eq(0) }) - Factory.handlePasswordChallenges(application, password) + it('importing data with no items key should use the root key generated by the file password', async function () { + /** + * In SNJS 2.0.12, this file import would fail with "incorrect password" on file. + * The reason was that we would use the default items key we had for the current account + * instead of using the password generated root key for the file. + * + * Note this test will not be able to properly sync as the credentials are invalid. + * This test is only meant to test successful local importing. + */ + const application = await Factory.createApplicationWithRealCrypto(identifier) + /** Create legacy migrations value so that base migration detects old app */ + await application.device.setRawStorageValue( + 'keychain', + JSON.stringify({ + [identifier]: { + version: '003', + masterKey: '30bae65687b45b20100be219df983bded23868baa44f4bbef1026403daee0a9d', + dataAuthenticationKey: 'c9b382ff1f7adb5c6cad620605ad139cd9f1e7700f507345ef1a1d46a6413712', + }, + }), + ) + await application.device.setRawStorageValue( + 'descriptors', + JSON.stringify({ + [identifier]: { + identifier: 'standardnotes', + label: 'Main Application', + primary: true, + }, + }), + ) + await application.device.setRawStorageValue('standardnotes-snjs_version', '2.0.11') + await application.device.saveDatabaseEntry( + { + content: + '003:9f2c7527eb8b2a1f8bfb3ea6b885403b6886bce2640843ebd57a6c479cbf7597:58e3322b-269a-4be3-a658-b035dffcd70f:9140b23a0fa989e224e292049f133154:SESTNOgIGf2+ZqmJdFnGU4EMgQkhKOzpZNoSzx76SJaImsayzctAgbUmJ+UU2gSQAHADS3+Z5w11bXvZgIrStTsWriwvYkNyyKmUPadKHNSBwOk4WeBZpWsA9gtI5zgI04Q5pvb8hS+kNW2j1DjM4YWqd0JQxMOeOrMIrxr/6Awn5TzYE+9wCbXZdYHyvRQcp9ui/G02ZJ67IA86vNEdjTTBAAWipWqTqKH9VDZbSQ2W/IOKfIquB373SFDKZb1S1NmBFvcoG2G7w//fAl/+ehYiL6UdiNH5MhXCDAOTQRFNfOh57HFDWVnz1VIp8X+VAPy6d9zzQH+8aws1JxHq/7BOhXrFE8UCueV6kERt9njgQxKJzd9AH32ShSiUB9X/sPi0fUXbS178xAZMJrNx3w==:eyJwd19ub25jZSI6IjRjYjEwM2FhODljZmY0NTYzYTkxMWQzZjM5NjU4M2NlZmM2ODMzYzY2Zjg4MGZiZWUwNmJkYTk0YzMxZjg2OGIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzIyQGJpdGFyLmlvIiwidmVyc2lvbiI6IjAwMyIsIm9yaWdpbmF0aW9uIjoicmVnaXN0cmF0aW9uIn0=', + content_type: 'SN|ItemsKey', + created_at: new Date(), + enc_item_key: + '003:d7267919b07864ccc1da87a48db6c6192e2e892be29ce882e981c36f673b3847:58e3322b-269a-4be3-a658-b035dffcd70f:2384a22d8f8bf671ba6517c6e1d0be30:0qXjBDPLCcMlNTnuUDcFiJPIXU9OP6b4ttTVE58n2Jn7971xMhx6toLbAZWWLPk/ezX/19EYE9xmRngWsG4jJaZMxGZIz/melU08K7AHH3oahQpHwZvSM3iV2ufsN7liQywftdVH6NNzULnZnFX+FgEfpDquru++R4aWDLvsSegWYmde9zD62pPNUB9Kik6P:eyJwd19ub25jZSI6IjRjYjEwM2FhODljZmY0NTYzYTkxMWQzZjM5NjU4M2NlZmM2ODMzYzY2Zjg4MGZiZWUwNmJkYTk0YzMxZjg2OGIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzIyQGJpdGFyLmlvIiwidmVyc2lvbiI6IjAwMyIsIm9yaWdpbmF0aW9uIjoicmVnaXN0cmF0aW9uIn0=', + updated_at: new Date(), + uuid: '58e3322b-269a-4be3-a658-b035dffcd70f', + }, + identifier, + ) + /** + * Note that this storage contains "sync.standardnotes.org" as the API Host param. + */ + await application.device.setRawStorageValue( + 'standardnotes-storage', + JSON.stringify({ + wrapped: { + uuid: '15af096f-4e9d-4cde-8d67-f132218fa757', + content_type: 'SN|EncryptedStorage', + enc_item_key: + '003:2fb0c55859ddf0c16982b91d6202a6fb8174f711d820f8b785c558538cda5048:15af096f-4e9d-4cde-8d67-f132218fa757:09a4da52d5214e76642f0363246daa99:zt5fnmxYSZOqC+uA08oAKdtjfTdAoX1lPnbTe98CYQSlIvaePIpG5c9tAN5QzZbECkj4Lm9txwSA2O6Y4Y25rqO4lIerKjxxNqPwDze9mtPOGeoR48csUPiMIHiH78bLGZZs4VoBwYKAP+uEygXEFYRuscGnDOrFV7fnwGDL/nkhr6xpM159OTUKBgiBpVMS:eyJwd19ub25jZSI6IjRjYjEwM2FhODljZmY0NTYzYTkxMWQzZjM5NjU4M2NlZmM2ODMzYzY2Zjg4MGZiZWUwNmJkYTk0YzMxZjg2OGIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzIyQGJpdGFyLmlvIiwidmVyc2lvbiI6IjAwMyIsIm9yaWdpbmF0aW9uIjoicmVnaXN0cmF0aW9uIn0=', + content: + '003:70a02948696e09211cfd34cd312dbbf85751397189da06d7acc7c46dafa9aeeb:15af096f-4e9d-4cde-8d67-f132218fa757:b92fb4b030ac51f4d3eef0ada35f3d5f:r3gdrawyd069qOQQotD5EtabTwjs4IiLFWotK0Ygbt9oAT09xILx7v92z8YALJ6i6EKHOT7zyCytR5l2B9b1J7Tls00uVgfEKs3zX7n3F6ne+ju0++WsJuy0Gre5+Olov6lqQrY3I8hWQShxaG84huZaFTIPU5+LP0JAseWWDENqUQ+Vxr+w0wqNYO6TLtr/YAqk2yOY7DLQ0WhGzK+WH9JfvS8MCccJVeBD99ebM8lKVVfTaUfrk2AlbMv47TFSjTeCDblQuU68joE45HV8Y0g2CF4nkTvdr3wn0HhdDp07YuXditX9NGtBhI8oFkstwKEksblyX9dGpn7of4ctdvNOom3Vjw/m4x9mE0lCIbjxQVAiDyy+Hg0HDtVt1j205ycg1RS7cT7+Sn746Z06S8TixcVUUUQh+MGRIulIE5utOE81Lv/p+jb2vmv+TGHUV4kZJPluG7A9IEphMZrMWwiU56FdSlSDD82qd9iG+C3Pux+X/GYCMiWS2T/BoyI6a9OERSARuTUuom2bv59hqD1yUoj7VQXhqXmverSwLE1zDeF+dc0tMwuTNCNOTk08A6wRKTR9ZjuFlLcxHsg/VZyfIdCkElFh1FrliMbW2ZsgsPFaZAI+YN8pid1tTw+Ou1cOfyD85aki98DDvg/cTi8ahrrm8UvxRQwhIW17Cm1RnKxhIvaq5HRjEN76Y46ubkZv7/HjhNwJt9vPEr9wyOrMH6XSxCnSIFD1kbVHI33q444xyUWa/EQju8SoEGGU92HhpMWd1kIz37SJRJTC7u2ah2Xg60JGcUcCNtHG3IHMPVP+UKUjx5nKP6t/NVSa+xsjIvM/ZkSL37W0TMZykC1cKfzeUmlZhGQPCIqad3b4ognZ48LGCgwBP87rWn8Ln8Cqcz7X0Ze22HoouKBPAtWlYJ8fmvg2HiW6nX/L9DqoxK4OXt/LnC2BTEvtP4PUzBqx8WoqmVNNnYp+FgYptLcgxmgckle41w1eMr6NYGeaaC1Jk3i/e9Piw0w0XjV/lB+yn03gEMYPTT2yiXMQrfPmkUNYNN7/xfhY3bqqwfER7iXdr/80Lc+x9byywChXLvg8VCjHWGd+Sky3NHyMdxLY8IqefyyZWMeXtt1aNYH6QW9DeK5KvK3DI+MK3kWwMCySe51lkE9jzcqrxpYMZjb2Za9VDZNBgdwQYXfOlxFEje0so0LlMJmmxRfbMU06bYt0vszT2szAkOnVuyi6TBRiGLyjMxYI0csM0SHZWZUQK0z7ZoQAWR5D+adX29tOvrKc2kJA8Lrzgeqw/rJIh6zPg3kmsd2rFbo+Qfe3J6XrlZU+J+N96I98i0FU0quI6HwG1zFg6UOmfRjaCML8rSAPtMaNhlO7M2sgRmDCtsNcpU06Fua6F2fEHPiXs4+9:eyJwd19ub25jZSI6IjRjYjEwM2FhODljZmY0NTYzYTkxMWQzZjM5NjU4M2NlZmM2ODMzYzY2Zjg4MGZiZWUwNmJkYTk0YzMxZjg2OGIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzIyQGJpdGFyLmlvIiwidmVyc2lvbiI6IjAwMyIsIm9yaWdpbmF0aW9uIjoicmVnaXN0cmF0aW9uIn0=', + created_at: '2020-11-24T00:53:42.057Z', + updated_at: '1970-01-01T00:00:00.000Z', + }, + nonwrapped: { + ROOT_KEY_PARAMS: { + pw_nonce: '4cb103aa89cff4563a911d3f396583cefc6833c66f880fbee06bda94c31f868b', + pw_cost: 110000, + identifier: 'nov2322@bitar.io', + version: '003', + }, + }, + }), + ) + const password = 'password' - const pair = createRelatedNoteTagPairPayload() - await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) + await application.prepareForLaunch({ + receiveChallenge: (challenge) => { + if (challenge.reason === ChallengeReason.Custom) { + return + } - await application.sync.sync() + if ( + challenge.reason === ChallengeReason.DecryptEncryptedFile || + challenge.reason === ChallengeReason.ImportFile + ) { + application.submitValuesForChallenge( + challenge, + challenge.prompts.map((prompt) => + CreateChallengeValue( + prompt, + prompt.validation !== ChallengeValidation.ProtectionSessionDuration + ? password + : UnprotectedAccessSecondsDuration.OneMinute, + ), + ), + ) + } + }, + }) + await application.launch(false) + await application.setHost.execute(Factory.getDefaultHost()) - const backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups() + const backupFile = { + items: [ + { + uuid: '11204d02-5a8b-47c0-ab94-ae0727d656b5', + content_type: 'Note', + created_at: '2020-11-23T17:11:06.322Z', + enc_item_key: + '003:111edcff9ed3432b9e11c4a64bef9e810ed2b9147790963caf6886511c46bbc4:11204d02-5a8b-47c0-ab94-ae0727d656b5:62de2b95cca4d7948f70516d12f5cb3a:lhUF/EoQP2DC8CSVrXyLp1yXsiJUXxwmtkwXtLUJ5sm4E0+ZNzMCO9U9ho+q6i9V+777dSbfTqODz4ZSt6hj3gtYxi9ZlOM/VrTtmJ2YcxiMaRTVl5sVZPG+YTpQPMuugN5/0EfuT/SJ9IqVbjgYhKA5xt/lMgw4JSbiW8ZkVQ5tVDfgt0omhDRLlkh758ou:eyJwd19ub25jZSI6IjNlMzU3YzQxZmI1YWU2MTUyYmZmMzY2ZjBhOGE3ZjRmZDk2NDQxZDZhNWViYzY3MDA4OTk2ZWY2YzU1YTg3ZjIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzVAYml0YXIuaW8iLCJ2ZXJzaW9uIjoiMDAzIn0=', + content: + '003:d43c6d2dc9465796e01145843cf1b95031030c15cc79a73f14d941d15e28147a:11204d02-5a8b-47c0-ab94-ae0727d656b5:84a2b760019a62d7ad9c314bc7a5564a:G8Mm9fy9ybuo92VbV4NUERruJ1VA7garv1+fBg4KRDRjsRGoLvORhHldQHRfUQmSR6PkrG6ol/jOn1gjIH5gtgGczB5NgbKau7amYZHsQJPr1UleJVsLrjMJgiYGqbEDmXPtJSX2tLGFhAbYcVX4xrHKbkiuLQnu9bZp9zbR6txB1NtLoNFvwDZTMko7Q+28fM4TKBbQCCw3NufLHVUnfEwS7tLLFFPdEyyMXOerKP93u8X+7NG2eDmsUetPsPOq:eyJwd19ub25jZSI6IjNlMzU3YzQxZmI1YWU2MTUyYmZmMzY2ZjBhOGE3ZjRmZDk2NDQxZDZhNWViYzY3MDA4OTk2ZWY2YzU1YTg3ZjIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzVAYml0YXIuaW8iLCJ2ZXJzaW9uIjoiMDAzIn0=', + auth_hash: null, + updated_at: '2020-11-23T17:11:40.399Z', + }, + ], + auth_params: { + pw_nonce: '3e357c41fb5ae6152bff366f0a8a7f4fd96441d6a5ebc67008996ef6c55a87f2', + pw_cost: 110000, + identifier: 'nov235@bitar.io', + version: '003', + }, + } - await Factory.safeDeinit(application) - application = await Factory.createInitAppWithFakeCrypto() - Factory.handlePasswordChallenges(application, password) - - await Factory.registerUserToApplication({ - application: application, - email: `${Math.random()}`, - password: password, + const result = await application.importData(backupFile, false) + expect(result.errorCount).to.equal(0) + await Factory.safeDeinit(application) }) - - await application.importData(backupData, true) - - expect(application.items.getDisplayableNotes().length).to.equal(1) - expect(application.items.getDisplayableTags().length).to.equal(1) - - const importedNote = application.items.getDisplayableNotes()[0] - const importedTag = application.items.getDisplayableTags()[0] - expect(application.items.referencesForItem(importedTag).length).to.equal(1) - expect(application.items.itemsReferencingItem(importedNote).length).to.equal(1) - }).timeout(Factory.TwentySecondTimeout) + }) }) diff --git a/packages/snjs/mocha/model_tests/items.test.js b/packages/snjs/mocha/model_tests/items.test.js index a466887ed..ea30d8d27 100644 --- a/packages/snjs/mocha/model_tests/items.test.js +++ b/packages/snjs/mocha/model_tests/items.test.js @@ -1,7 +1,6 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import { BaseItemCounts } from '../lib/BaseItemCounts.js' import * as Factory from '../lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect @@ -11,47 +10,52 @@ describe('items', () => { awaitAll: true, } + let application + let expectedItemCount + beforeEach(async function () { - this.expectedItemCount = BaseItemCounts.DefaultItems - this.application = await Factory.createInitAppWithFakeCrypto() + localStorage.clear() + expectedItemCount = BaseItemCounts.DefaultItems + application = await Factory.createInitAppWithFakeCrypto() }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) + localStorage.clear() }) it('setting an item as dirty should update its client updated at', async function () { const params = Factory.createNotePayload() - await this.application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged) - const item = this.application.items.items[0] + await application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged) + const item = application.items.items[0] const prevDate = item.userModifiedDate.getTime() await Factory.sleep(0.1) - await this.application.mutator.setItemDirty(item, true) - const refreshedItem = this.application.items.findItem(item.uuid) + await application.mutator.setItemDirty(item, true) + const refreshedItem = application.items.findItem(item.uuid) const newDate = refreshedItem.userModifiedDate.getTime() expect(prevDate).to.not.equal(newDate) }) it('setting an item as dirty with option to skip client updated at', async function () { const params = Factory.createNotePayload() - await this.application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged) - const item = this.application.items.items[0] + await application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged) + const item = application.items.items[0] const prevDate = item.userModifiedDate.getTime() await Factory.sleep(0.1) - await this.application.mutator.setItemDirty(item) + await application.mutator.setItemDirty(item) const newDate = item.userModifiedDate.getTime() expect(prevDate).to.equal(newDate) }) it('properly pins, archives, and locks', async function () { const params = Factory.createNotePayload() - await this.application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged) - const item = this.application.items.items[0] + const item = application.items.items[0] expect(item.pinned).to.not.be.ok const refreshedItem = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item, (mutator) => { mutator.pinned = true @@ -71,16 +75,16 @@ describe('items', () => { it('properly compares item equality', async function () { const params1 = Factory.createNotePayload() const params2 = Factory.createNotePayload() - await this.application.mutator.emitItemsFromPayloads([params1, params2], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([params1, params2], PayloadEmitSource.LocalChanged) - let item1 = this.application.items.getDisplayableNotes()[0] - let item2 = this.application.items.getDisplayableNotes()[1] + let item1 = application.items.getDisplayableNotes()[0] + let item2 = application.items.getDisplayableNotes()[1] expect(item1.isItemContentEqualWith(item2)).to.equal(true) // items should ignore this field when checking for equality item1 = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item1, (mutator) => { mutator.userModifiedDate = new Date() @@ -91,7 +95,7 @@ describe('items', () => { ) ).getValue() item2 = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item2, (mutator) => { mutator.userModifiedDate = undefined @@ -105,7 +109,7 @@ describe('items', () => { expect(item1.isItemContentEqualWith(item2)).to.equal(true) item1 = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item1, (mutator) => { mutator.mutableContent.foo = 'bar' @@ -119,7 +123,7 @@ describe('items', () => { expect(item1.isItemContentEqualWith(item2)).to.equal(false) item2 = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item2, (mutator) => { mutator.mutableContent.foo = 'bar' @@ -134,7 +138,7 @@ describe('items', () => { expect(item2.isItemContentEqualWith(item1)).to.equal(true) item1 = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item1, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item2) @@ -145,7 +149,7 @@ describe('items', () => { ) ).getValue() item2 = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item2, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(item1) @@ -162,7 +166,7 @@ describe('items', () => { expect(item1.isItemContentEqualWith(item2)).to.equal(false) item1 = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item1, (mutator) => { mutator.removeItemAsRelationship(item2) @@ -173,7 +177,7 @@ describe('items', () => { ) ).getValue() item2 = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item2, (mutator) => { mutator.removeItemAsRelationship(item1) @@ -192,13 +196,13 @@ describe('items', () => { it('content equality should not have side effects', async function () { const params1 = Factory.createNotePayload() const params2 = Factory.createNotePayload() - await this.application.mutator.emitItemsFromPayloads([params1, params2], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([params1, params2], PayloadEmitSource.LocalChanged) - let item1 = this.application.items.getDisplayableNotes()[0] - const item2 = this.application.items.getDisplayableNotes()[1] + let item1 = application.items.getDisplayableNotes()[0] + const item2 = application.items.getDisplayableNotes()[1] item1 = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( item1, (mutator) => { mutator.mutableContent.foo = 'bar' @@ -223,7 +227,7 @@ describe('items', () => { // There was an issue where calling that function would modify values directly to omit keys // in contentKeysToIgnoreWhenCheckingEquality. - await this.application.mutator.setItemsDirty([item1, item2]) + await application.mutator.setItemsDirty([item1, item2]) expect(item1.userModifiedDate).to.be.ok expect(item2.userModifiedDate).to.be.ok diff --git a/packages/snjs/mocha/model_tests/mapping.test.js b/packages/snjs/mocha/model_tests/mapping.test.js index f1707bf50..3a69cbf70 100644 --- a/packages/snjs/mocha/model_tests/mapping.test.js +++ b/packages/snjs/mocha/model_tests/mapping.test.js @@ -1,28 +1,35 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import { BaseItemCounts } from '../lib/BaseItemCounts.js' import * as Factory from '../lib/factory.js' import { createNoteParams } from '../lib/Items.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('model manager mapping', () => { + let application + let expectedItemCount + let context + beforeEach(async function () { - this.expectedItemCount = BaseItemCounts.DefaultItems - this.context = await Factory.createAppContext() - await this.context.launch() - this.application = this.context.application + localStorage.clear() + expectedItemCount = BaseItemCounts.DefaultItems + context = await Factory.createAppContext() + await context.launch() + application = context.application }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) + application = undefined + context = undefined + localStorage.clear() }) it('mapping nonexistent item creates it', async function () { const payload = Factory.createNotePayload() - await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) - this.expectedItemCount++ - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + await application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) + expectedItemCount++ + expect(application.items.items.length).to.equal(expectedItemCount) }) it('mapping nonexistent deleted item doesnt create it', async function () { @@ -31,17 +38,17 @@ describe('model manager mapping', () => { dirty: false, deleted: true, }) - await this.application.payloads.emitPayload(payload, PayloadEmitSource.LocalChanged) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + await application.payloads.emitPayload(payload, PayloadEmitSource.LocalChanged) + expect(application.items.items.length).to.equal(expectedItemCount) }) it('mapping and deleting nonexistent item creates and deletes it', async function () { const payload = Factory.createNotePayload() - await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) - this.expectedItemCount++ + expectedItemCount++ - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) const changedParams = new DeletedPayload({ ...payload, @@ -49,32 +56,32 @@ describe('model manager mapping', () => { deleted: true, }) - this.expectedItemCount-- + expectedItemCount-- - await this.application.mutator.emitItemsFromPayloads([changedParams], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([changedParams], PayloadEmitSource.LocalChanged) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) }) it('mapping deleted but dirty item should not delete it', async function () { const payload = Factory.createNotePayload() - const [item] = await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) + const [item] = await application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) - this.expectedItemCount++ + expectedItemCount++ - await this.application.payloads.emitPayload(new DeleteItemMutator(item).getDeletedResult()) + await application.payloads.emitPayload(new DeleteItemMutator(item).getDeletedResult()) - const payload2 = new DeletedPayload(this.application.payloads.findOne(payload.uuid).ejected()) + const payload2 = new DeletedPayload(application.payloads.findOne(payload.uuid).ejected()) - await this.application.payloads.emitPayloads([payload2], PayloadEmitSource.LocalChanged) + await application.payloads.emitPayloads([payload2], PayloadEmitSource.LocalChanged) - expect(this.application.payloads.collection.all().length).to.equal(this.expectedItemCount) + expect(application.payloads.collection.all().length).to.equal(expectedItemCount) }) it('mapping existing item updates its properties', async function () { const payload = Factory.createNotePayload() - await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) const newTitle = 'updated title' const mutated = new DecryptedPayload({ @@ -84,45 +91,45 @@ describe('model manager mapping', () => { title: newTitle, }, }) - await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) - const item = this.application.items.getDisplayableNotes()[0] + await application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) + const item = application.items.getDisplayableNotes()[0] expect(item.content.title).to.equal(newTitle) }) it('setting an item dirty should retrieve it in dirty items', async function () { const payload = Factory.createNotePayload() - await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) - const note = this.application.items.getDisplayableNotes()[0] - await this.application.mutator.setItemDirty(note) - const dirtyItems = this.application.items.getDirtyItems() + await application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) + const note = application.items.getDisplayableNotes()[0] + await application.mutator.setItemDirty(note) + const dirtyItems = application.items.getDirtyItems() expect(Uuids(dirtyItems).includes(note.uuid)) }) it('set all items dirty', async function () { const count = 10 - this.expectedItemCount += count + expectedItemCount += count const payloads = [] for (let i = 0; i < count; i++) { payloads.push(Factory.createNotePayload()) } - await this.application.mutator.emitItemsFromPayloads(payloads, PayloadEmitSource.LocalChanged) - await this.application.sync.markAllItemsAsNeedingSyncAndPersist() + await application.mutator.emitItemsFromPayloads(payloads, PayloadEmitSource.LocalChanged) + await application.sync.markAllItemsAsNeedingSyncAndPersist() - const dirtyItems = this.application.items.getDirtyItems() - expect(dirtyItems.length).to.equal(this.expectedItemCount) + const dirtyItems = application.items.getDirtyItems() + expect(dirtyItems.length).to.equal(expectedItemCount) }) it('sync observers should be notified of changes', async function () { const payload = Factory.createNotePayload() - await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) - const item = this.application.items.items[0] + await application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) + const item = application.items.items[0] return new Promise((resolve) => { - this.application.items.addObserver(ContentType.TYPES.Any, ({ changed }) => { + application.items.addObserver(ContentType.TYPES.Any, ({ changed }) => { expect(changed[0].uuid === item.uuid) resolve() }) - this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) + application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) }) }) }) diff --git a/packages/snjs/mocha/model_tests/notes_smart_tags.test.js b/packages/snjs/mocha/model_tests/notes_smart_tags.test.js index b16700250..65ebf1410 100644 --- a/packages/snjs/mocha/model_tests/notes_smart_tags.test.js +++ b/packages/snjs/mocha/model_tests/notes_smart_tags.test.js @@ -1,5 +1,3 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from '../lib/factory.js' chai.use(chaiAsPromised) @@ -26,48 +24,53 @@ const titles = (items) => { } describe('notes and smart views', () => { + let application + beforeEach(async function () { - this.application = await Factory.createInitAppWithFakeCrypto() + localStorage.clear() + application = await Factory.createInitAppWithFakeCrypto() }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) + localStorage.clear() + application = undefined }) it('lets me create a smart view and use it', async function () { // ## The user creates 3 notes const [note_1, note_2, note_3] = await Promise.all([ - Factory.createMappedNote(this.application, 'long & pinned', generateLongString()), - Factory.createMappedNote(this.application, 'long & !pinned', generateLongString()), - Factory.createMappedNote(this.application, 'pinned', 'this is a pinned note'), + Factory.createMappedNote(application, 'long & pinned', generateLongString()), + Factory.createMappedNote(application, 'long & !pinned', generateLongString()), + Factory.createMappedNote(application, 'pinned', 'this is a pinned note'), ]) // The user pin 2 notes - await Promise.all([Factory.pinNote(this.application, note_1), Factory.pinNote(this.application, note_3)]) + await Promise.all([Factory.pinNote(application, note_1), Factory.pinNote(application, note_3)]) // ## The user creates smart views (long & pinned) const not_pinned = '!["Not Pinned", "pinned", "=", false]' const long = '!["Long", "text.length", ">", 500]' - const tag_not_pinned = await this.application.mutator.createTagOrSmartView(not_pinned) - const tag_long = await this.application.mutator.createTagOrSmartView(long) + const tag_not_pinned = await application.mutator.createTagOrSmartView(not_pinned) + const tag_long = await application.mutator.createTagOrSmartView(long) // ## The user can filter and see the pinned notes - const notes_not_pinned = getFilteredNotes(this.application, { + const notes_not_pinned = getFilteredNotes(application, { views: [tag_not_pinned], }) expect(titles(notes_not_pinned)).to.eql(['long & !pinned']) // ## The user can filter and see the long notes - const notes_long = getFilteredNotes(this.application, { views: [tag_long] }) + const notes_long = getFilteredNotes(application, { views: [tag_long] }) expect(titles(notes_long)).to.eql(['long & !pinned', 'long & pinned']) // ## The user creates a new long note - await Factory.createMappedNote(this.application, 'new long', generateLongString()) + await Factory.createMappedNote(application, 'new long', generateLongString()) // ## The user can filter and see the new long note - const notes_long2 = getFilteredNotes(this.application, { + const notes_long2 = getFilteredNotes(application, { views: [tag_long], }) expect(titles(notes_long2)).to.eql(['long & !pinned', 'long & pinned', 'new long']) diff --git a/packages/snjs/mocha/model_tests/notes_tags.test.js b/packages/snjs/mocha/model_tests/notes_tags.test.js index ad0d2fa06..aefae49ff 100644 --- a/packages/snjs/mocha/model_tests/notes_tags.test.js +++ b/packages/snjs/mocha/model_tests/notes_tags.test.js @@ -1,39 +1,44 @@ -/* eslint-disable no-undef */ import * as Factory from '../lib/factory.js' import * as Utils from '../lib/Utils.js' import { createRelatedNoteTagPairPayload } from '../lib/Items.js' -import { BaseItemCounts } from '../lib/BaseItemCounts.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('notes and tags', () => { + let application + let context + const syncOptions = { checkIntegrity: true, awaitAll: true, } beforeEach(async function () { - this.expectedItemCount = BaseItemCounts.DefaultItems - this.context = await Factory.createAppContext() - await this.context.launch() - this.application = this.context.application + localStorage.clear() + context = await Factory.createAppContext() + await context.launch() + application = context.application }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) + localStorage.clear() + application = undefined + context = undefined }) it('uses proper class for note', async function () { const payload = Factory.createNotePayload() - await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) - const note = this.application.items.getItems([ContentType.TYPES.Note])[0] + await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) + const note = application.items.getItems([ContentType.TYPES.Note])[0] expect(note.constructor === SNNote).to.equal(true) }) it('properly constructs syncing params', async function () { const title = 'Foo' const text = 'Bar' - const note = await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const note = await application.items.createTemplateItem(ContentType.TYPES.Note, { title, text, }) @@ -41,7 +46,7 @@ describe('notes and tags', () => { expect(note.content.title).to.equal(title) expect(note.content.text).to.equal(text) - const tag = await this.application.items.createTemplateItem(ContentType.TYPES.Tag, { + const tag = await application.items.createTemplateItem(ContentType.TYPES.Tag, { title, }) @@ -73,12 +78,12 @@ describe('notes and tags', () => { }, }) - await this.application.mutator.emitItemsFromPayloads([mutatedNote, mutatedTag], PayloadEmitSource.LocalChanged) - const note = this.application.items.getItems([ContentType.TYPES.Note])[0] - const tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] + await application.mutator.emitItemsFromPayloads([mutatedNote, mutatedTag], PayloadEmitSource.LocalChanged) + const note = application.items.getItems([ContentType.TYPES.Note])[0] + const tag = application.items.getItems([ContentType.TYPES.Tag])[0] expect(note.content.references.length).to.equal(1) - expect(this.application.items.itemsReferencingItem(tag).length).to.equal(1) + expect(application.items.itemsReferencingItem(tag).length).to.equal(1) }) it('creates relationship between note and tag', async function () { @@ -89,9 +94,9 @@ describe('notes and tags', () => { expect(notePayload.content.references.length).to.equal(0) expect(tagPayload.content.references.length).to.equal(1) - await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - let note = this.application.items.getDisplayableNotes()[0] - let tag = this.application.items.getDisplayableTags()[0] + await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) + let note = application.items.getDisplayableNotes()[0] + let tag = application.items.getDisplayableTags()[0] expect(note.dirty).to.not.be.ok expect(tag.dirty).to.not.be.ok @@ -102,26 +107,26 @@ describe('notes and tags', () => { expect(note.isReferencingItem(tag)).to.equal(false) expect(tag.isReferencingItem(note)).to.equal(true) - expect(this.application.items.itemsReferencingItem(note).length).to.equal(1) + expect(application.items.itemsReferencingItem(note).length).to.equal(1) expect(note.payload.references.length).to.equal(0) expect(tag.noteCount).to.equal(1) - await this.application.mutator.setItemToBeDeleted(note) + await application.mutator.setItemToBeDeleted(note) - tag = this.application.items.getDisplayableTags()[0] + tag = application.items.getDisplayableTags()[0] - const deletedNotePayload = this.application.payloads.findOne(note.uuid) + const deletedNotePayload = application.payloads.findOne(note.uuid) expect(deletedNotePayload.dirty).to.be.true expect(tag.dirty).to.be.true - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) expect(tag.content.references.length).to.equal(0) - expect(this.application.items.itemsReferencingItem(note).length).to.equal(0) + expect(application.items.itemsReferencingItem(note).length).to.equal(0) expect(tag.noteCount).to.equal(0) - tag = this.application.items.getDisplayableTags()[0] - expect(this.application.items.getDisplayableNotes().length).to.equal(0) + tag = application.items.getDisplayableTags()[0] + expect(application.items.getDisplayableNotes().length).to.equal(0) expect(tag.dirty).to.be.false }) @@ -130,14 +135,14 @@ describe('notes and tags', () => { const notePayload = pair[0] const tagPayload = pair[1] - await this.application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) - let note = this.application.items.getItems([ContentType.TYPES.Note])[0] - let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] + await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) + let note = application.items.getItems([ContentType.TYPES.Note])[0] + let tag = application.items.getItems([ContentType.TYPES.Tag])[0] expect(note.content.references.length).to.equal(0) expect(tag.content.references.length).to.equal(1) - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) const mutatedTag = new DecryptedPayload({ ...tagPayload, @@ -147,13 +152,13 @@ describe('notes and tags', () => { references: [], }, }) - await this.application.mutator.emitItemsFromPayloads([mutatedTag], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([mutatedTag], PayloadEmitSource.LocalChanged) - note = this.application.items.findItem(note.uuid) - tag = this.application.items.findItem(tag.uuid) + note = application.items.findItem(note.uuid) + tag = application.items.findItem(tag.uuid) expect(tag.content.references.length).to.equal(0) - expect(this.application.items.itemsReferencingItem(note).length).to.equal(0) + expect(application.items.itemsReferencingItem(note).length).to.equal(0) expect(tag.noteCount).to.equal(0) // expect to be false @@ -162,13 +167,13 @@ describe('notes and tags', () => { }) it('creating basic note should have text set', async function () { - const note = await Factory.createMappedNote(this.application) + const note = await Factory.createMappedNote(application) expect(note.title).to.be.ok expect(note.text).to.be.ok }) it('creating basic tag should have title', async function () { - const tag = await Factory.createMappedTag(this.application) + const tag = await Factory.createMappedTag(application) expect(tag.title).to.be.ok }) @@ -177,15 +182,15 @@ describe('notes and tags', () => { const notePayload = pair[0] const tagPayload = pair[1] - await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - const note = this.application.items.getItems([ContentType.TYPES.Note])[0] - let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] + await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) + const note = application.items.getItems([ContentType.TYPES.Note])[0] + let tag = application.items.getItems([ContentType.TYPES.Tag])[0] expect(note.content.references.length).to.equal(0) expect(tag.content.references.length).to.equal(1) tag = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( tag, (mutator) => { mutator.removeItemAsRelationship(note) @@ -196,21 +201,21 @@ describe('notes and tags', () => { ) ).getValue() - expect(this.application.items.itemsReferencingItem(note).length).to.equal(0) + expect(application.items.itemsReferencingItem(note).length).to.equal(0) expect(tag.noteCount).to.equal(0) }) it('properly handles tag duplication', async function () { const pair = createRelatedNoteTagPairPayload() - await this.application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) - let note = this.application.items.getDisplayableNotes()[0] - let tag = this.application.items.getDisplayableTags()[0] + await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) + let note = application.items.getDisplayableNotes()[0] + let tag = application.items.getDisplayableTags()[0] - const duplicateTag = await this.application.mutator.duplicateItem(tag, true) - await this.application.sync.sync(syncOptions) + const duplicateTag = await application.mutator.duplicateItem(tag, true) + await application.sync.sync(syncOptions) - note = this.application.items.findItem(note.uuid) - tag = this.application.items.findItem(tag.uuid) + note = application.items.findItem(note.uuid) + tag = application.items.findItem(tag.uuid) expect(tag.uuid).to.not.equal(duplicateTag.uuid) expect(tag.content.references.length).to.equal(1) @@ -218,7 +223,7 @@ describe('notes and tags', () => { expect(duplicateTag.content.references.length).to.equal(1) expect(duplicateTag.noteCount).to.equal(1) - const noteTags = this.application.items.itemsReferencingItem(note) + const noteTags = application.items.itemsReferencingItem(note) expect(noteTags.length).to.equal(2) const noteTag1 = noteTags[0] @@ -234,13 +239,13 @@ describe('notes and tags', () => { const pair = createRelatedNoteTagPairPayload() const notePayload = pair[0] const tagPayload = pair[1] - await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - const note = this.application.items.getItems([ContentType.TYPES.Note])[0] - const duplicateNote = await this.application.mutator.duplicateItem(note, true) + await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) + const note = application.items.getItems([ContentType.TYPES.Note])[0] + const duplicateNote = await application.mutator.duplicateItem(note, true) expect(note.uuid).to.not.equal(duplicateNote.uuid) - expect(this.application.items.itemsReferencingItem(duplicateNote).length).to.equal( - this.application.items.itemsReferencingItem(note).length, + expect(application.items.itemsReferencingItem(duplicateNote).length).to.equal( + application.items.itemsReferencingItem(note).length, ) }) @@ -248,27 +253,27 @@ describe('notes and tags', () => { const pair = createRelatedNoteTagPairPayload() const notePayload = pair[0] const tagPayload = pair[1] - await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - const note = this.application.items.getItems([ContentType.TYPES.Note])[0] - let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] + await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) + const note = application.items.getItems([ContentType.TYPES.Note])[0] + let tag = application.items.getItems([ContentType.TYPES.Tag])[0] expect(tag.content.references.length).to.equal(1) expect(tag.noteCount).to.equal(1) expect(note.content.references.length).to.equal(0) - expect(this.application.items.itemsReferencingItem(note).length).to.equal(1) + expect(application.items.itemsReferencingItem(note).length).to.equal(1) - await this.application.mutator.setItemToBeDeleted(tag) - tag = this.application.items.findItem(tag.uuid) + await application.mutator.setItemToBeDeleted(tag) + tag = application.items.findItem(tag.uuid) expect(tag).to.not.be.ok }) it('modifying item content should not modify payload content', async function () { const notePayload = Factory.createNotePayload() - await this.application.mutator.emitItemsFromPayloads([notePayload], PayloadEmitSource.LocalChanged) - let note = this.application.items.getItems([ContentType.TYPES.Note])[0] + await application.mutator.emitItemsFromPayloads([notePayload], PayloadEmitSource.LocalChanged) + let note = application.items.getItems([ContentType.TYPES.Note])[0] note = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( note, (mutator) => { mutator.mutableContent.title = Math.random() @@ -289,15 +294,15 @@ describe('notes and tags', () => { const notePayload = pair[0] const tagPayload = pair[1] - await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - let note = this.application.items.getItems([ContentType.TYPES.Note])[0] - let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] + await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) + let note = application.items.getItems([ContentType.TYPES.Note])[0] + let tag = application.items.getItems([ContentType.TYPES.Tag])[0] - await this.application.sync.sync(syncOptions) - await this.application.mutator.setItemToBeDeleted(tag) + await application.sync.sync(syncOptions) + await application.mutator.setItemToBeDeleted(tag) - note = this.application.items.findItem(note.uuid) - this.application.items.findItem(tag.uuid) + note = application.items.findItem(note.uuid) + application.items.findItem(tag.uuid) expect(note.dirty).to.not.be.ok }) @@ -305,26 +310,26 @@ describe('notes and tags', () => { it('should sort notes', async function () { await Promise.all( ['Y', 'Z', 'A', 'B'].map(async (title) => { - return this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { title }), + return application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title }), ) }), ) - this.application.items.setPrimaryItemDisplayOptions({ + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'dsc', }) - const titles = this.application.items.getDisplayableNotes().map((note) => note.title) + const titles = application.items.getDisplayableNotes().map((note) => note.title) /** setPrimaryItemDisplayOptions inverses sort for title */ expect(titles).to.deep.equal(['A', 'B', 'Y', 'Z']) }) it('setting a note dirty should collapse its properties into content', async function () { - let note = await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + let note = await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'Foo', }) - await this.application.mutator.insertItem(note) - note = this.application.items.findItem(note.uuid) + await application.mutator.insertItem(note) + note = application.items.findItem(note.uuid) expect(note.content.title).to.equal('Foo') }) @@ -332,49 +337,49 @@ describe('notes and tags', () => { it('should sort tags in ascending alphabetical order by default', async function () { const titles = ['1', 'A', 'b', '2'] const sortedTitles = titles.sort((a, b) => a.localeCompare(b)) - await Promise.all(titles.map((title) => this.application.mutator.findOrCreateTag(title))) - expect(this.application.items.tagDisplayController.items().map((t) => t.title)).to.deep.equal(sortedTitles) + await Promise.all(titles.map((title) => application.mutator.findOrCreateTag(title))) + expect(application.items.tagDisplayController.items().map((t) => t.title)).to.deep.equal(sortedTitles) }) it('should match a tag', async function () { - const taggedNote = await Factory.createMappedNote(this.application) - const tag = await this.application.mutator.findOrCreateTag('A') - await this.application.mutator.changeItem(tag, (mutator) => { + const taggedNote = await Factory.createMappedNote(application) + const tag = await application.mutator.findOrCreateTag('A') + await application.mutator.changeItem(tag, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote) }) - await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', }), ) - this.application.items.setPrimaryItemDisplayOptions({ + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'dsc', tags: [tag], }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes.length).to.equal(1) expect(displayedNotes[0].uuid).to.equal(taggedNote.uuid) }) it('should not show trashed notes when displaying a tag', async function () { - const taggedNote = await Factory.createMappedNote(this.application) - const trashedNote = await Factory.createMappedNote(this.application) - const tag = await this.application.mutator.findOrCreateTag('A') - await this.application.mutator.changeItem(tag, (mutator) => { + const taggedNote = await Factory.createMappedNote(application) + const trashedNote = await Factory.createMappedNote(application) + const tag = await application.mutator.findOrCreateTag('A') + await application.mutator.changeItem(tag, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote) mutator.e2ePendingRefactor_addItemAsRelationship(trashedNote) }) - await this.application.mutator.changeItem(trashedNote, (mutator) => { + await application.mutator.changeItem(trashedNote, (mutator) => { mutator.trashed = true }) - this.application.items.setPrimaryItemDisplayOptions({ + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'dsc', tags: [tag], includeTrashed: false, }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes.length).to.equal(1) expect(displayedNotes[0].uuid).to.equal(taggedNote.uuid) }) @@ -382,31 +387,31 @@ describe('notes and tags', () => { it('should sort notes when displaying tag', async function () { await Promise.all( ['Y', 'Z', 'A', 'B'].map(async (title) => { - return this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + return application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title, }), ) }), ) - const pinnedNote = this.application.items.getDisplayableNotes().find((note) => note.title === 'B') - await this.application.mutator.changeItem(pinnedNote, (mutator) => { + const pinnedNote = application.items.getDisplayableNotes().find((note) => note.title === 'B') + await application.mutator.changeItem(pinnedNote, (mutator) => { mutator.pinned = true }) - const tag = await this.application.mutator.findOrCreateTag('A') - await this.application.mutator.changeItem(tag, (mutator) => { - for (const note of this.application.items.getDisplayableNotes()) { + const tag = await application.mutator.findOrCreateTag('A') + await application.mutator.changeItem(tag, (mutator) => { + for (const note of application.items.getDisplayableNotes()) { mutator.e2ePendingRefactor_addItemAsRelationship(note) } }) - this.application.items.setPrimaryItemDisplayOptions({ + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'dsc', tags: [tag], }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes).to.have.length(4) /** setPrimaryItemDisplayOptions inverses sort for title */ expect(displayedNotes[0].title).to.equal('B') @@ -416,18 +421,18 @@ describe('notes and tags', () => { describe('Smart views', function () { it('"title", "startsWith", "Foo"', async function () { - const note = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const note = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'Foo 🎲', }), ) - await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'Not Foo 🎲', }), ) - const view = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { + const view = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.SmartView, { title: 'Foo Notes', predicate: { keypath: 'title', @@ -436,36 +441,36 @@ describe('notes and tags', () => { }, }), ) - const matches = this.application.items.notesMatchingSmartView(view) - this.application.items.setPrimaryItemDisplayOptions({ + const matches = application.items.notesMatchingSmartView(view) + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'asc', views: [view], }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes).to.deep.equal(matches) expect(matches.length).to.equal(1) expect(matches[0].uuid).to.equal(note.uuid) }) it('"pinned", "=", true', async function () { - const note = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const note = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', }), ) - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.pinned = true }) - await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'B', pinned: false, }), ) - const view = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { + const view = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.SmartView, { title: 'Pinned', predicate: { keypath: 'pinned', @@ -474,35 +479,35 @@ describe('notes and tags', () => { }, }), ) - const matches = this.application.items.notesMatchingSmartView(view) - this.application.items.setPrimaryItemDisplayOptions({ + const matches = application.items.notesMatchingSmartView(view) + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'asc', views: [view], }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes).to.deep.equal(matches) expect(matches.length).to.equal(1) expect(matches[0].uuid).to.equal(note.uuid) }) it('"pinned", "=", false', async function () { - const pinnedNote = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const pinnedNote = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', }), ) - await this.application.mutator.changeItem(pinnedNote, (mutator) => { + await application.mutator.changeItem(pinnedNote, (mutator) => { mutator.pinned = true }) - const unpinnedNote = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const unpinnedNote = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'B', }), ) - const view = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { + const view = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.SmartView, { title: 'Not pinned', predicate: { keypath: 'pinned', @@ -511,34 +516,34 @@ describe('notes and tags', () => { }, }), ) - const matches = this.application.items.notesMatchingSmartView(view) - this.application.items.setPrimaryItemDisplayOptions({ + const matches = application.items.notesMatchingSmartView(view) + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'asc', views: [view], }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes).to.deep.equal(matches) expect(matches.length).to.equal(1) expect(matches[0].uuid).to.equal(unpinnedNote.uuid) }) it('"text.length", ">", 500', async function () { - const longNote = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const longNote = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', text: Array(501).fill(0).join(''), }), ) - await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'B', text: 'b', }), ) - const view = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { + const view = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.SmartView, { title: 'Long', predicate: { keypath: 'text.length', @@ -547,13 +552,13 @@ describe('notes and tags', () => { }, }), ) - const matches = this.application.items.notesMatchingSmartView(view) - this.application.items.setPrimaryItemDisplayOptions({ + const matches = application.items.notesMatchingSmartView(view) + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'asc', views: [view], }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes).to.deep.equal(matches) expect(matches.length).to.equal(1) expect(matches[0].uuid).to.equal(longNote.uuid) @@ -561,22 +566,22 @@ describe('notes and tags', () => { it('"updated_at", ">", "1.days.ago"', async function () { await Factory.registerUserToApplication({ - application: this.application, + application: application, email: Utils.generateUuid(), password: Utils.generateUuid(), }) - const recentNote = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const recentNote = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', }), true, ) - await this.application.sync.sync() + await application.sync.sync() - const olderNote = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const olderNote = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'B', text: 'b', }), @@ -584,17 +589,17 @@ describe('notes and tags', () => { ) const threeDays = 3 * 24 * 60 * 60 * 1000 - await Factory.changePayloadUpdatedAt(this.application, olderNote.payload, new Date(Date.now() - threeDays)) + await Factory.changePayloadUpdatedAt(application, olderNote.payload, new Date(Date.now() - threeDays)) /** Create an unsynced note which shouldn't get an updated_at */ - await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'B', text: 'b', }), ) - const view = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { + const view = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.SmartView, { title: 'One day ago', predicate: { keypath: 'serverUpdatedAt', @@ -603,33 +608,33 @@ describe('notes and tags', () => { }, }), ) - const matches = this.application.items.notesMatchingSmartView(view) + const matches = application.items.notesMatchingSmartView(view) expect(matches.length).to.equal(1) expect(matches[0].uuid).to.equal(recentNote.uuid) - this.application.items.setPrimaryItemDisplayOptions({ + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'asc', views: [view], }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes).to.deep.equal(matches) }) it('"tags.length", "=", 0', async function () { - const untaggedNote = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const untaggedNote = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', }), ) - const taggedNote = await Factory.createMappedNote(this.application) - const tag = await this.application.mutator.findOrCreateTag('A') - await this.application.mutator.changeItem(tag, (mutator) => { + const taggedNote = await Factory.createMappedNote(application) + const tag = await application.mutator.findOrCreateTag('A') + await application.mutator.changeItem(tag, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote) }) - const view = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { + const view = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.SmartView, { title: 'Untagged', predicate: { keypath: 'tags.length', @@ -638,32 +643,32 @@ describe('notes and tags', () => { }, }), ) - const matches = this.application.items.notesMatchingSmartView(view) - this.application.items.setPrimaryItemDisplayOptions({ + const matches = application.items.notesMatchingSmartView(view) + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'asc', views: [view], }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes).to.deep.equal(matches) expect(matches.length).to.equal(1) expect(matches[0].uuid).to.equal(untaggedNote.uuid) }) it('"tags", "includes", ["title", "startsWith", "b"]', async function () { - const taggedNote = await Factory.createMappedNote(this.application) - const tag = await this.application.mutator.findOrCreateTag('B') - await this.application.mutator.changeItem(tag, (mutator) => { + const taggedNote = await Factory.createMappedNote(application) + const tag = await application.mutator.findOrCreateTag('B') + await application.mutator.changeItem(tag, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote) }) - await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', }), ) - const view = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { + const view = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.SmartView, { title: 'B-tags', predicate: { keypath: 'tags', @@ -672,45 +677,45 @@ describe('notes and tags', () => { }, }), ) - const matches = this.application.items.notesMatchingSmartView(view) - this.application.items.setPrimaryItemDisplayOptions({ + const matches = application.items.notesMatchingSmartView(view) + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'asc', views: [view], }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes).to.deep.equal(matches) expect(matches.length).to.equal(1) expect(matches[0].uuid).to.equal(taggedNote.uuid) }) it('"ignored", "and", [["pinned", "=", true], ["locked", "=", true]]', async function () { - const pinnedAndLockedNote = await Factory.createMappedNote(this.application) - await this.application.mutator.changeItem(pinnedAndLockedNote, (mutator) => { + const pinnedAndLockedNote = await Factory.createMappedNote(application) + await application.mutator.changeItem(pinnedAndLockedNote, (mutator) => { mutator.pinned = true mutator.locked = true }) - const pinnedNote = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const pinnedNote = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', }), ) - await this.application.mutator.changeItem(pinnedNote, (mutator) => { + await application.mutator.changeItem(pinnedNote, (mutator) => { mutator.pinned = true }) - const lockedNote = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const lockedNote = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', }), ) - await this.application.mutator.changeItem(lockedNote, (mutator) => { + await application.mutator.changeItem(lockedNote, (mutator) => { mutator.locked = true }) - const view = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { + const view = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.SmartView, { title: 'Pinned & Locked', predicate: { operator: 'and', @@ -721,51 +726,51 @@ describe('notes and tags', () => { }, }), ) - const matches = this.application.items.notesMatchingSmartView(view) - this.application.items.setPrimaryItemDisplayOptions({ + const matches = application.items.notesMatchingSmartView(view) + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'asc', views: [view], }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes).to.deep.equal(matches) expect(matches.length).to.equal(1) expect(matches[0].uuid).to.equal(pinnedAndLockedNote.uuid) }) it('"ignored", "or", [["content.protected", "=", true], ["pinned", "=", true]]', async function () { - const protectedNote = await Factory.createMappedNote(this.application) - await this.application.mutator.changeItem(protectedNote, (mutator) => { + const protectedNote = await Factory.createMappedNote(application) + await application.mutator.changeItem(protectedNote, (mutator) => { mutator.protected = true }) - const pinnedNote = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const pinnedNote = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', }), ) - await this.application.mutator.changeItem(pinnedNote, (mutator) => { + await application.mutator.changeItem(pinnedNote, (mutator) => { mutator.pinned = true }) - const pinnedAndProtectedNote = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + const pinnedAndProtectedNote = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', }), ) - await this.application.mutator.changeItem(pinnedAndProtectedNote, (mutator) => { + await application.mutator.changeItem(pinnedAndProtectedNote, (mutator) => { mutator.pinned = true mutator.protected = true }) - await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.Note, { + await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.Note, { title: 'A', }), ) - const view = await this.application.mutator.insertItem( - await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { + const view = await application.mutator.insertItem( + await application.items.createTemplateItem(ContentType.TYPES.SmartView, { title: 'Protected or Pinned', predicate: { operator: 'or', @@ -776,13 +781,13 @@ describe('notes and tags', () => { }, }), ) - const matches = this.application.items.notesMatchingSmartView(view) - this.application.items.setPrimaryItemDisplayOptions({ + const matches = application.items.notesMatchingSmartView(view) + application.items.setPrimaryItemDisplayOptions({ sortBy: 'created_at', sortDirection: 'asc', views: [view], }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes.length).to.equal(matches.length) expect(matches.length).to.equal(3) expect(matches.find((note) => note.uuid === protectedNote.uuid)).to.exist @@ -801,12 +806,12 @@ describe('notes and tags', () => { const notePayload3 = Factory.createNotePayload('Bar') const notePayload4 = Factory.createNotePayload('Testing') - await this.application.mutator.emitItemsFromPayloads( + await application.mutator.emitItemsFromPayloads( [notePayload1, notePayload2, notePayload3, notePayload4, tagPayload1], PayloadEmitSource.LocalChanged, ) - this.application.items.setPrimaryItemDisplayOptions({ + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'dsc', searchQuery: { @@ -814,7 +819,7 @@ describe('notes and tags', () => { }, }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes.length).to.equal(2) /** setPrimaryItemDisplayOptions inverses sort for title */ expect(displayedNotes[0].uuid).to.equal(notePayload1.uuid) @@ -831,12 +836,12 @@ describe('notes and tags', () => { const notePayload3 = Factory.createNotePayload('Testing FOO (Bar)') const notePayload4 = Factory.createNotePayload('This should not match') - await this.application.mutator.emitItemsFromPayloads( + await application.mutator.emitItemsFromPayloads( [notePayload1, notePayload2, notePayload3, notePayload4, tagPayload1], PayloadEmitSource.LocalChanged, ) - this.application.items.setPrimaryItemDisplayOptions({ + application.items.setPrimaryItemDisplayOptions({ sortBy: 'title', sortDirection: 'dsc', searchQuery: { @@ -844,7 +849,7 @@ describe('notes and tags', () => { }, }) - const displayedNotes = this.application.items.getDisplayableNotes() + const displayedNotes = application.items.getDisplayableNotes() expect(displayedNotes.length).to.equal(3) /** setPrimaryItemDisplayOptions inverses sort for title */ expect(displayedNotes[0].uuid).to.equal(notePayload1.uuid) diff --git a/packages/snjs/mocha/model_tests/notes_tags_folders.test.js b/packages/snjs/mocha/model_tests/notes_tags_folders.test.js index ffa239153..5bbbac98a 100644 --- a/packages/snjs/mocha/model_tests/notes_tags_folders.test.js +++ b/packages/snjs/mocha/model_tests/notes_tags_folders.test.js @@ -1,86 +1,91 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from '../lib/factory.js' chai.use(chaiAsPromised) const expect = chai.expect describe('tags as folders', () => { + let context + let application + beforeEach(async function () { - this.context = await Factory.createAppContext() - await this.context.launch() - this.application = this.context.application + localStorage.clear() + context = await Factory.createAppContext() + await context.launch() + application = context.application }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) + context = undefined + application = undefined + localStorage.clear() }) it('lets me create a tag, add relationships, move a note to a children, and query data all along', async function () { // ## The user creates four tags - let tagChildren = await Factory.createMappedTag(this.application, { + let tagChildren = await Factory.createMappedTag(application, { title: 'children', }) - let tagParent = await Factory.createMappedTag(this.application, { + let tagParent = await Factory.createMappedTag(application, { title: 'parent', }) - let tagGrandParent = await Factory.createMappedTag(this.application, { + let tagGrandParent = await Factory.createMappedTag(application, { title: 'grandparent', }) - let tagGrandParent2 = await Factory.createMappedTag(this.application, { + let tagGrandParent2 = await Factory.createMappedTag(application, { title: 'grandparent2', }) // ## Now the users moves the tag children into the parent - await this.application.mutator.setTagParent(tagParent, tagChildren) + await application.mutator.setTagParent(tagParent, tagChildren) - expect(this.application.items.getTagParent(tagChildren)).to.equal(tagParent) - expect(Uuids(this.application.items.getTagChildren(tagParent))).deep.to.equal(Uuids([tagChildren])) + expect(application.items.getTagParent(tagChildren)).to.equal(tagParent) + expect(Uuids(application.items.getTagChildren(tagParent))).deep.to.equal(Uuids([tagChildren])) // ## Now the user moves the tag parent into the grand parent - await this.application.mutator.setTagParent(tagGrandParent, tagParent) + await application.mutator.setTagParent(tagGrandParent, tagParent) - expect(this.application.items.getTagParent(tagParent)).to.equal(tagGrandParent) - expect(Uuids(this.application.items.getTagChildren(tagGrandParent))).deep.to.equal(Uuids([tagParent])) + expect(application.items.getTagParent(tagParent)).to.equal(tagGrandParent) + expect(Uuids(application.items.getTagChildren(tagGrandParent))).deep.to.equal(Uuids([tagParent])) // ## Now the user moves the tag parent into another grand parent - await this.application.mutator.setTagParent(tagGrandParent2, tagParent) + await application.mutator.setTagParent(tagGrandParent2, tagParent) - expect(this.application.items.getTagParent(tagParent)).to.equal(tagGrandParent2) - expect(this.application.items.getTagChildren(tagGrandParent)).deep.to.equal([]) - expect(Uuids(this.application.items.getTagChildren(tagGrandParent2))).deep.to.equal(Uuids([tagParent])) + expect(application.items.getTagParent(tagParent)).to.equal(tagGrandParent2) + expect(application.items.getTagChildren(tagGrandParent)).deep.to.equal([]) + expect(Uuids(application.items.getTagChildren(tagGrandParent2))).deep.to.equal(Uuids([tagParent])) // ## Now the user tries to move the tag into one of its children - await expect(this.application.mutator.setTagParent(tagChildren, tagParent)).to.eventually.be.rejected + await expect(application.mutator.setTagParent(tagChildren, tagParent)).to.eventually.be.rejected - expect(this.application.items.getTagParent(tagParent)).to.equal(tagGrandParent2) - expect(this.application.items.getTagChildren(tagGrandParent)).deep.to.equal([]) - expect(Uuids(this.application.items.getTagChildren(tagGrandParent2))).deep.to.equal(Uuids([tagParent])) + expect(application.items.getTagParent(tagParent)).to.equal(tagGrandParent2) + expect(application.items.getTagChildren(tagGrandParent)).deep.to.equal([]) + expect(Uuids(application.items.getTagChildren(tagGrandParent2))).deep.to.equal(Uuids([tagParent])) // ## Now the user move the tag outside any hierarchy - await this.application.mutator.unsetTagParent(tagParent) + await application.mutator.unsetTagParent(tagParent) - expect(this.application.items.getTagParent(tagParent)).to.equal(undefined) - expect(this.application.items.getTagChildren(tagGrandParent2)).deep.to.equals([]) + expect(application.items.getTagParent(tagParent)).to.equal(undefined) + expect(application.items.getTagChildren(tagGrandParent2)).deep.to.equals([]) }) it('lets me add a note to a tag hierarchy', async function () { // ## The user creates four tags hierarchy - const tags = await Factory.createTags(this.application, { + const tags = await Factory.createTags(application, { grandparent: { parent: { child: true } }, another: true, }) - const note1 = await Factory.createMappedNote(this.application, 'my first note') - const note2 = await Factory.createMappedNote(this.application, 'my second note') + const note1 = await Factory.createMappedNote(application, 'my first note') + const note2 = await Factory.createMappedNote(application, 'my second note') // ## The user add a note to the child tag - await this.application.mutator.addTagToNote(note1, tags.child, true) - await this.application.mutator.addTagToNote(note2, tags.another, true) + await application.mutator.addTagToNote(note1, tags.child, true) + await application.mutator.addTagToNote(note2, tags.another, true) // ## The note has been added to other tags - const note1Tags = await this.application.items.getSortedTagsForItem(note1) - const note2Tags = await this.application.items.getSortedTagsForItem(note2) + const note1Tags = await application.items.getSortedTagsForItem(note1) + const note2Tags = await application.items.getSortedTagsForItem(note2) expect(note1Tags.length).to.equal(3) expect(note2Tags.length).to.equal(1) diff --git a/packages/snjs/mocha/model_tests/performance.test.js b/packages/snjs/mocha/model_tests/performance.test.js index ab6e6391c..87b4ee847 100644 --- a/packages/snjs/mocha/model_tests/performance.test.js +++ b/packages/snjs/mocha/model_tests/performance.test.js @@ -1,10 +1,17 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from '../lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('mapping performance', () => { + beforeEach(function () { + localStorage.clear() + }) + + afterEach(async function () { + localStorage.clear() + }) + it('shouldnt take a long time', async () => { /* There was an issue with mapping where we were using arrays for everything instead of hashes (like items, missedReferences), diff --git a/packages/snjs/mocha/mutator.test.js b/packages/snjs/mocha/mutator.test.js index 59bae15c4..ebe8c5bd1 100644 --- a/packages/snjs/mocha/mutator.test.js +++ b/packages/snjs/mocha/mutator.test.js @@ -1,47 +1,44 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('item mutator', () => { - beforeEach(async function () { - this.createBarePayload = () => { - return new DecryptedPayload({ - uuid: '123', - content_type: ContentType.TYPES.Note, + const createBarePayload = () => { + return new DecryptedPayload({ + uuid: '123', + content_type: ContentType.TYPES.Note, + content: { + title: 'hello', + }, + }) + } + + const createNote = () => { + return new DecryptedItem(createBarePayload()) + } + + const createTag = (notes = []) => { + const references = notes.map((note) => { + return { + uuid: note.uuid, + content_type: note.content_type, + } + }) + return new SNTag( + new DecryptedPayload({ + uuid: Factory.generateUuidish(), + content_type: ContentType.TYPES.Tag, content: { - title: 'hello', + title: 'thoughts', + references: references, }, - }) - } - - this.createNote = () => { - return new DecryptedItem(this.createBarePayload()) - } - - this.createTag = (notes = []) => { - const references = notes.map((note) => { - return { - uuid: note.uuid, - content_type: note.content_type, - } - }) - return new SNTag( - new DecryptedPayload({ - uuid: Factory.generateUuidish(), - content_type: ContentType.TYPES.Tag, - content: { - title: 'thoughts', - references: references, - }, - }), - ) - } - }) + }), + ) + } it('mutate set domain data key', function () { - const item = this.createNote() + const item = createNote() const mutator = new DecryptedItemMutator(item) mutator.setDomainDataKey('somekey', 'somevalue', 'somedomain') const payload = mutator.getResult() @@ -50,7 +47,7 @@ describe('item mutator', () => { }) it('mutate set pinned', function () { - const item = this.createNote() + const item = createNote() const mutator = new DecryptedItemMutator(item) mutator.pinned = true const payload = mutator.getResult() @@ -59,7 +56,7 @@ describe('item mutator', () => { }) it('mutate set archived', function () { - const item = this.createNote() + const item = createNote() const mutator = new DecryptedItemMutator(item) mutator.archived = true const payload = mutator.getResult() @@ -68,7 +65,7 @@ describe('item mutator', () => { }) it('mutate set locked', function () { - const item = this.createNote() + const item = createNote() const mutator = new DecryptedItemMutator(item) mutator.locked = true const payload = mutator.getResult() @@ -77,7 +74,7 @@ describe('item mutator', () => { }) it('mutate set protected', function () { - const item = this.createNote() + const item = createNote() const mutator = new DecryptedItemMutator(item) mutator.protected = true const payload = mutator.getResult() @@ -86,7 +83,7 @@ describe('item mutator', () => { }) it('mutate set trashed', function () { - const item = this.createNote() + const item = createNote() const mutator = new DecryptedItemMutator(item) mutator.trashed = true const payload = mutator.getResult() @@ -95,7 +92,7 @@ describe('item mutator', () => { }) it('calling get result should set us dirty', function () { - const item = this.createNote() + const item = createNote() const mutator = new DecryptedItemMutator(item) const payload = mutator.getResult() @@ -103,7 +100,7 @@ describe('item mutator', () => { }) it('get result should always have userModifiedDate', function () { - const item = this.createNote() + const item = createNote() const mutator = new DecryptedItemMutator(item) const payload = mutator.getResult() const resultItem = CreateDecryptedItemFromPayload(payload) @@ -111,7 +108,7 @@ describe('item mutator', () => { }) it('mutate set deleted', function () { - const item = this.createNote() + const item = createNote() const mutator = new DeleteItemMutator(item) const payload = mutator.getDeletedResult() @@ -121,7 +118,7 @@ describe('item mutator', () => { }) it('mutate app data', function () { - const item = this.createNote() + const item = createNote() const mutator = new DecryptedItemMutator(item, MutationType.UpdateUserTimestamps) mutator.setAppDataItem('foo', 'bar') mutator.setAppDataItem('bar', 'foo') @@ -131,8 +128,8 @@ describe('item mutator', () => { }) it('mutate add item as relationship', function () { - const note = this.createNote() - const tag = this.createTag() + const note = createNote() + const tag = createTag() const mutator = new DecryptedItemMutator(tag) mutator.e2ePendingRefactor_addItemAsRelationship(note) const payload = mutator.getResult() @@ -142,8 +139,8 @@ describe('item mutator', () => { }) it('mutate remove item as relationship', function () { - const note = this.createNote() - const tag = this.createTag([note]) + const note = createNote() + const tag = createTag([note]) const mutator = new DecryptedItemMutator(tag) mutator.removeItemAsRelationship(note) const payload = mutator.getResult() diff --git a/packages/snjs/mocha/mutator_service.test.js b/packages/snjs/mocha/mutator_service.test.js index dc93ecab9..cff5d36e0 100644 --- a/packages/snjs/mocha/mutator_service.test.js +++ b/packages/snjs/mocha/mutator_service.test.js @@ -1,7 +1,6 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' import { BaseItemCounts } from './lib/BaseItemCounts.js' + chai.use(chaiAsPromised) const expect = chai.expect @@ -12,11 +11,6 @@ describe('mutator service', function () { let application let mutator - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -27,6 +21,12 @@ describe('mutator service', function () { await context.launch() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + context = undefined + }) + const createNote = async () => { return mutator.createItem(ContentType.TYPES.Note, { title: 'hello', @@ -154,8 +154,10 @@ describe('mutator service', function () { describe('duplicateItem', async function () { const sandbox = sinon.createSandbox() + let emitPayloads + beforeEach(async function () { - this.emitPayloads = sandbox.spy(application.payloads, 'emitPayloads') + emitPayloads = sandbox.spy(application.payloads, 'emitPayloads') }) afterEach(async function () { @@ -165,7 +167,7 @@ describe('mutator service', function () { it('should duplicate the item and set the duplicate_of property', async function () { const note = await createNote() await mutator.duplicateItem(note) - sinon.assert.calledTwice(this.emitPayloads) + sinon.assert.calledTwice(emitPayloads) const originalNote = application.items.getDisplayableNotes()[0] const duplicatedNote = application.items.getDisplayableNotes()[1] @@ -182,7 +184,7 @@ describe('mutator service', function () { it('should duplicate the item and set the duplicate_of and conflict_of properties', async function () { const note = await createNote() await mutator.duplicateItem(note, true) - sinon.assert.calledTwice(this.emitPayloads) + sinon.assert.calledTwice(emitPayloads) const originalNote = application.items.getDisplayableNotes()[0] const duplicatedNote = application.items.getDisplayableNotes()[1] diff --git a/packages/snjs/mocha/note_display_criteria.test.js b/packages/snjs/mocha/note_display_criteria.test.js index d8df0fae5..65b627d6c 100644 --- a/packages/snjs/mocha/note_display_criteria.test.js +++ b/packages/snjs/mocha/note_display_criteria.test.js @@ -1,30 +1,36 @@ -/* eslint-disable no-undef */ chai.use(chaiAsPromised) const expect = chai.expect describe('note display criteria', function () { + let payloadManager + let itemManager + let mutator + + let createNote + let createTag + beforeEach(async function () { const logger = new Logger('test') - this.payloadManager = new PayloadManager(logger) - this.itemManager = new ItemManager(this.payloadManager) - this.mutator = new MutatorService(this.itemManager, this.payloadManager) + payloadManager = new PayloadManager(logger) + itemManager = new ItemManager(payloadManager) + mutator = new MutatorService(itemManager, payloadManager) - this.createNote = async (title = 'hello', text = 'world') => { - return this.mutator.createItem(ContentType.TYPES.Note, { + createNote = async (title = 'hello', text = 'world') => { + return mutator.createItem(ContentType.TYPES.Note, { title: title, text: text, }) } - this.createTag = async (notes = [], title = 'thoughts') => { + createTag = async (notes = [], title = 'thoughts') => { const references = notes.map((note) => { return { uuid: note.uuid, content_type: note.content_type, } }) - return this.mutator.createItem(ContentType.TYPES.Tag, { + return mutator.createItem(ContentType.TYPES.Tag, { title: title, references: references, }) @@ -32,177 +38,147 @@ describe('note display criteria', function () { }) it('includePinned off', async function () { - await this.createNote() - const pendingPin = await this.createNote() - await this.mutator.changeItem(pendingPin, (mutator) => { + await createNote() + const pendingPin = await createNote() + await mutator.changeItem(pendingPin, (mutator) => { mutator.pinned = true }) const criteria = { includePinned: false, } expect( - notesAndFilesMatchingOptions( - criteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, - ).length, + notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection) + .length, ).to.equal(1) }) it('includePinned on', async function () { - await this.createNote() - const pendingPin = await this.createNote() - await this.mutator.changeItem(pendingPin, (mutator) => { + await createNote() + const pendingPin = await createNote() + await mutator.changeItem(pendingPin, (mutator) => { mutator.pinned = true }) const criteria = { includePinned: true } expect( - notesAndFilesMatchingOptions( - criteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, - ).length, + notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection) + .length, ).to.equal(2) }) it('includeTrashed off', async function () { - await this.createNote() - const pendingTrash = await this.createNote() - await this.mutator.changeItem(pendingTrash, (mutator) => { + await createNote() + const pendingTrash = await createNote() + await mutator.changeItem(pendingTrash, (mutator) => { mutator.trashed = true }) const criteria = { includeTrashed: false } expect( - notesAndFilesMatchingOptions( - criteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, - ).length, + notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection) + .length, ).to.equal(1) }) it('includeTrashed on', async function () { - await this.createNote() - const pendingTrash = await this.createNote() - await this.mutator.changeItem(pendingTrash, (mutator) => { + await createNote() + const pendingTrash = await createNote() + await mutator.changeItem(pendingTrash, (mutator) => { mutator.trashed = true }) const criteria = { includeTrashed: true } expect( - notesAndFilesMatchingOptions( - criteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, - ).length, + notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection) + .length, ).to.equal(2) }) it('includeArchived off', async function () { - await this.createNote() - const pendingArchive = await this.createNote() - await this.mutator.changeItem(pendingArchive, (mutator) => { + await createNote() + const pendingArchive = await createNote() + await mutator.changeItem(pendingArchive, (mutator) => { mutator.archived = true }) const criteria = { includeArchived: false } expect( - notesAndFilesMatchingOptions( - criteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, - ).length, + notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection) + .length, ).to.equal(1) }) it('includeArchived on', async function () { - await this.createNote() - const pendingArchive = await this.createNote() - await this.mutator.changeItem(pendingArchive, (mutator) => { + await createNote() + const pendingArchive = await createNote() + await mutator.changeItem(pendingArchive, (mutator) => { mutator.archived = true }) const criteria = { includeArchived: true, } expect( - notesAndFilesMatchingOptions( - criteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, - ).length, + notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection) + .length, ).to.equal(2) }) it('includeProtected off', async function () { - await this.createNote() - const pendingProtected = await this.createNote() - await this.mutator.changeItem(pendingProtected, (mutator) => { + await createNote() + const pendingProtected = await createNote() + await mutator.changeItem(pendingProtected, (mutator) => { mutator.protected = true }) const criteria = { includeProtected: false } expect( - notesAndFilesMatchingOptions( - criteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, - ).length, + notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection) + .length, ).to.equal(1) }) it('includeProtected on', async function () { - await this.createNote() - const pendingProtected = await this.createNote() - await this.mutator.changeItem(pendingProtected, (mutator) => { + await createNote() + const pendingProtected = await createNote() + await mutator.changeItem(pendingProtected, (mutator) => { mutator.protected = true }) const criteria = { includeProtected: true, } expect( - notesAndFilesMatchingOptions( - criteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, - ).length, + notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection) + .length, ).to.equal(2) }) it('protectedSearchEnabled false', async function () { - const normal = await this.createNote('hello', 'world') - await this.mutator.changeItem(normal, (mutator) => { + const normal = await createNote('hello', 'world') + await mutator.changeItem(normal, (mutator) => { mutator.protected = true }) const criteria = { searchQuery: { query: 'world', includeProtectedNoteText: false }, } expect( - notesAndFilesMatchingOptions( - criteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, - ).length, + notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection) + .length, ).to.equal(0) }) it('protectedSearchEnabled true', async function () { - const normal = await this.createNote() - await this.mutator.changeItem(normal, (mutator) => { + const normal = await createNote() + await mutator.changeItem(normal, (mutator) => { mutator.protected = true }) const criteria = { searchQuery: { query: 'world', includeProtectedNoteText: true }, } expect( - notesAndFilesMatchingOptions( - criteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, - ).length, + notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection) + .length, ).to.equal(1) }) it('tags', async function () { - const note = await this.createNote() - const tag = await this.createTag([note]) - const looseTag = await this.createTag([], 'loose') + const note = await createNote() + const tag = await createTag([note]) + const looseTag = await createTag([], 'loose') const matchingCriteria = { tags: [tag], @@ -210,8 +186,8 @@ describe('note display criteria', function () { expect( notesAndFilesMatchingOptions( matchingCriteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) @@ -221,125 +197,125 @@ describe('note display criteria', function () { expect( notesAndFilesMatchingOptions( nonmatchingCriteria, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) }) describe('smart views', function () { it('normal note', async function () { - await this.createNote() + await createNote() expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.archivedSmartView], + views: [itemManager.archivedSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) }) it('trashed note', async function () { - const normal = await this.createNote() - await this.mutator.changeItem(normal, (mutator) => { + const normal = await createNote() + await mutator.changeItem(normal, (mutator) => { mutator.trashed = true }) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeTrashed: false, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.archivedSmartView], + views: [itemManager.archivedSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) }) it('archived note', async function () { - const normal = await this.createNote() - await this.mutator.changeItem(normal, (mutator) => { + const normal = await createNote() + await mutator.changeItem(normal, (mutator) => { mutator.trashed = false mutator.archived = true }) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeArchived: false, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.archivedSmartView], + views: [itemManager.archivedSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) }) it('archived + trashed note', async function () { - const normal = await this.createNote() - await this.mutator.changeItem(normal, (mutator) => { + const normal = await createNote() + await mutator.changeItem(normal, (mutator) => { mutator.trashed = true mutator.archived = true }) @@ -347,30 +323,30 @@ describe('note display criteria', function () { expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.archivedSmartView], + views: [itemManager.archivedSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) }) @@ -378,87 +354,87 @@ describe('note display criteria', function () { describe('includeTrash', function () { it('normal note', async function () { - await this.createNote() + await createNote() expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeTrashed: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], includeTrashed: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) }) it('trashed note', async function () { - const normal = await this.createNote() + const normal = await createNote() - await this.mutator.changeItem(normal, (mutator) => { + await mutator.changeItem(normal, (mutator) => { mutator.trashed = true }) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeTrashed: false, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeTrashed: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], includeTrashed: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.archivedSmartView], + views: [itemManager.archivedSmartView], includeTrashed: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) }) it('archived + trashed note', async function () { - const normal = await this.createNote() + const normal = await createNote() - await this.mutator.changeItem(normal, (mutator) => { + await mutator.changeItem(normal, (mutator) => { mutator.trashed = true mutator.archived = true }) @@ -466,30 +442,30 @@ describe('note display criteria', function () { expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.archivedSmartView], + views: [itemManager.archivedSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) }) @@ -497,85 +473,85 @@ describe('note display criteria', function () { describe('includeArchived', function () { it('normal note', async function () { - await this.createNote() + await createNote() expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) }) it('archived note', async function () { - const normal = await this.createNote() - await this.mutator.changeItem(normal, (mutator) => { + const normal = await createNote() + await mutator.changeItem(normal, (mutator) => { mutator.archived = true }) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeArchived: false, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.archivedSmartView], + views: [itemManager.archivedSmartView], includeArchived: false, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) }) it('archived + trashed note', async function () { - const normal = await this.createNote() - await this.mutator.changeItem(normal, (mutator) => { + const normal = await createNote() + await mutator.changeItem(normal, (mutator) => { mutator.trashed = true mutator.archived = true }) @@ -583,33 +559,33 @@ describe('note display criteria', function () { expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.archivedSmartView], + views: [itemManager.archivedSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) }) @@ -617,7 +593,7 @@ describe('note display criteria', function () { describe.skip('multiple tags', function () { it('normal note', async function () { - await this.createNote() + await createNote() /** * This test presently fails because the compound predicate created * when using multiple views is an AND predicate instead of OR @@ -625,82 +601,78 @@ describe('note display criteria', function () { expect( notesAndFilesMatchingOptions( { - views: [ - this.itemManager.allNotesSmartView, - this.itemManager.archivedSmartView, - this.itemManager.trashSmartView, - ], + views: [itemManager.allNotesSmartView, itemManager.archivedSmartView, itemManager.trashSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) }) it('archived note', async function () { - const normal = await this.createNote() - await this.mutator.changeItem(normal, (mutator) => { + const normal = await createNote() + await mutator.changeItem(normal, (mutator) => { mutator.archived = true }) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeArchived: false, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.archivedSmartView], + views: [itemManager.archivedSmartView], includeArchived: false, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) }) it('archived + trashed note', async function () { - const normal = await this.createNote() - await this.mutator.changeItem(normal, (mutator) => { + const normal = await createNote() + await mutator.changeItem(normal, (mutator) => { mutator.trashed = true mutator.archived = true }) @@ -708,33 +680,33 @@ describe('note display criteria', function () { expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.allNotesSmartView], + views: [itemManager.allNotesSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.trashSmartView], + views: [itemManager.trashSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(1) expect( notesAndFilesMatchingOptions( { - views: [this.itemManager.archivedSmartView], + views: [itemManager.archivedSmartView], includeArchived: true, }, - this.itemManager.collection.all(ContentType.TYPES.Note), - this.itemManager.collection, + itemManager.collection.all(ContentType.TYPES.Note), + itemManager.collection, ).length, ).to.equal(0) }) diff --git a/packages/snjs/mocha/payload.test.js b/packages/snjs/mocha/payload.test.js index 4773e311b..97195b849 100644 --- a/packages/snjs/mocha/payload.test.js +++ b/packages/snjs/mocha/payload.test.js @@ -1,32 +1,29 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ -chai.use(chaiAsPromised) -const expect = chai.expect import * as Factory from './lib/factory.js' -describe('payload', () => { - beforeEach(async function () { - this.createBarePayload = () => { - return new DecryptedPayload({ - uuid: '123', - content_type: ContentType.TYPES.Note, - content: { - title: 'hello', - }, - }) - } +chai.use(chaiAsPromised) +const expect = chai.expect - this.createEncryptedPayload = () => { - return new EncryptedPayload({ - uuid: '123', - content_type: ContentType.TYPES.Note, - content: '004:foo:bar', - }) - } - }) +describe('payload', () => { + const createBarePayload = () => { + return new DecryptedPayload({ + uuid: '123', + content_type: ContentType.TYPES.Note, + content: { + title: 'hello', + }, + }) + } + + const createEncryptedPayload = () => { + return new EncryptedPayload({ + uuid: '123', + content_type: ContentType.TYPES.Note, + content: '004:foo:bar', + }) + } it('constructor should set expected fields', function () { - const payload = this.createBarePayload() + const payload = createBarePayload() expect(payload.uuid).to.be.ok expect(payload.content_type).to.be.ok @@ -46,25 +43,25 @@ describe('payload', () => { }) it('created at should default to present', function () { - const payload = this.createBarePayload() + const payload = createBarePayload() expect(payload.created_at - new Date()).to.be.below(1) }) it('updated at should default to epoch', function () { - const payload = this.createBarePayload() + const payload = createBarePayload() expect(payload.updated_at.getTime()).to.equal(0) }) it('payload format bare', function () { - const payload = this.createBarePayload() + const payload = createBarePayload() expect(isDecryptedPayload(payload)).to.equal(true) }) it('payload format encrypted string', function () { - const payload = this.createEncryptedPayload() + const payload = createEncryptedPayload() expect(isEncryptedPayload(payload)).to.equal(true) }) @@ -92,13 +89,13 @@ describe('payload', () => { }) it('payload version 004', function () { - const payload = this.createEncryptedPayload() + const payload = createEncryptedPayload() expect(payload.version).to.equal('004') }) it('merged with absent content', function () { - const payload = this.createBarePayload() + const payload = createBarePayload() const merged = payload.copy({ uuid: '123', content_type: ContentType.TYPES.Note, @@ -125,7 +122,7 @@ describe('payload', () => { }) it('should be immutable', async function () { - const payload = this.createBarePayload() + const payload = createBarePayload() await Factory.sleep(0.1) diff --git a/packages/snjs/mocha/payload_encryption.test.js b/packages/snjs/mocha/payload_encryption.test.js index dc24b387d..86e9afb8f 100644 --- a/packages/snjs/mocha/payload_encryption.test.js +++ b/packages/snjs/mocha/payload_encryption.test.js @@ -1,31 +1,33 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' import { createRelatedNoteTagPairPayload } from './lib/Items.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('payload encryption', function () { + let application + beforeEach(async function () { this.timeout(Factory.TenSecondTimeout) localStorage.clear() - this.application = await Factory.createInitAppWithFakeCrypto() + application = await Factory.createInitAppWithFakeCrypto() }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) localStorage.clear() + application = undefined }) it('creating payload from item should create copy not by reference', async function () { - const item = await Factory.createMappedNote(this.application) + const item = await Factory.createMappedNote(application) const payload = new DecryptedPayload(item.payload.ejected()) expect(item.content === payload.content).to.equal(false) expect(item.content.references === payload.content.references).to.equal(false) }) it('creating payload from item should preserve appData', async function () { - const item = await Factory.createMappedNote(this.application) + const item = await Factory.createMappedNote(application) const payload = new DecryptedPayload(item.payload.ejected()) expect(item.content.appData).to.be.ok expect(JSON.stringify(item.content)).to.equal(JSON.stringify(payload.content)) @@ -40,7 +42,7 @@ describe('payload encryption', function () { lastSyncBegan: new Date(), }) - const encryptedPayload = await this.application.encryption.encryptSplitSingle({ + const encryptedPayload = await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [notePayload], }, @@ -85,7 +87,7 @@ describe('payload encryption', function () { }) it('copying payload with override content should override completely', async function () { - const item = await Factory.createMappedNote(this.application) + const item = await Factory.createMappedNote(application) const payload = new DecryptedPayload(item.payload.ejected()) const mutated = new DecryptedPayload({ ...payload, @@ -114,7 +116,7 @@ describe('payload encryption', function () { it('returns valid encrypted params for syncing', async function () { const payload = Factory.createNotePayload() const encryptedPayload = CreateEncryptedServerSyncPushPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -126,7 +128,7 @@ describe('payload encryption', function () { expect(encryptedPayload.content_type).to.be.ok expect(encryptedPayload.created_at).to.be.ok expect(encryptedPayload.content).to.satisfy((string) => { - return string.startsWith(this.application.encryption.getLatestVersion()) + return string.startsWith(application.encryption.getLatestVersion()) }) }).timeout(5000) @@ -134,7 +136,7 @@ describe('payload encryption', function () { const payload = Factory.createNotePayload() const encryptedPayload = CreateEncryptedLocalStorageContextPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -150,14 +152,14 @@ describe('payload encryption', function () { expect(encryptedPayload.deleted).to.not.be.ok expect(encryptedPayload.errorDecrypting).to.not.be.ok expect(encryptedPayload.content).to.satisfy((string) => { - return string.startsWith(this.application.encryption.getLatestVersion()) + return string.startsWith(application.encryption.getLatestVersion()) }) }) it('omits deleted for export file', async function () { const payload = Factory.createNotePayload() const encryptedPayload = CreateEncryptedBackupFileContextPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -170,7 +172,7 @@ describe('payload encryption', function () { expect(encryptedPayload.created_at).to.be.ok expect(encryptedPayload.deleted).to.not.be.ok expect(encryptedPayload.content).to.satisfy((string) => { - return string.startsWith(this.application.encryption.getLatestVersion()) + return string.startsWith(application.encryption.getLatestVersion()) }) }) diff --git a/packages/snjs/mocha/payload_manager.test.js b/packages/snjs/mocha/payload_manager.test.js index 95a03db49..d25eb0899 100644 --- a/packages/snjs/mocha/payload_manager.test.js +++ b/packages/snjs/mocha/payload_manager.test.js @@ -1,14 +1,17 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('payload manager', () => { + let payloadManager + let createNotePayload + beforeEach(async function () { const logger = new Logger('test') - this.payloadManager = new PayloadManager(logger) - this.createNotePayload = async () => { + payloadManager = new PayloadManager(logger) + + createNotePayload = async () => { return new DecryptedPayload({ uuid: Factory.generateUuidish(), content_type: ContentType.TYPES.Note, @@ -21,15 +24,15 @@ describe('payload manager', () => { }) it('emit payload should create local record', async function () { - const payload = await this.createNotePayload() - await this.payloadManager.emitPayload(payload) + const payload = await createNotePayload() + await payloadManager.emitPayload(payload) - expect(this.payloadManager.collection.find(payload.uuid)).to.be.ok + expect(payloadManager.collection.find(payload.uuid)).to.be.ok }) it('merge payloads onto master', async function () { - const payload = await this.createNotePayload() - await this.payloadManager.emitPayload(payload) + const payload = await createNotePayload() + await payloadManager.emitPayload(payload) const newTitle = `${Math.random()}` const changedPayload = payload.copy({ @@ -38,19 +41,19 @@ describe('payload manager', () => { title: newTitle, }, }) - const { changed, inserted } = await this.payloadManager.applyPayloads([changedPayload]) + const { changed, inserted } = await payloadManager.applyPayloads([changedPayload]) expect(changed.length).to.equal(1) expect(inserted.length).to.equal(0) - expect(this.payloadManager.collection.find(payload.uuid).content.title).to.equal(newTitle) + expect(payloadManager.collection.find(payload.uuid).content.title).to.equal(newTitle) }) it('insertion observer', async function () { const observations = [] - this.payloadManager.addObserver(ContentType.TYPES.Any, ({ inserted }) => { + payloadManager.addObserver(ContentType.TYPES.Any, ({ inserted }) => { observations.push({ inserted }) }) - const payload = await this.createNotePayload() - await this.payloadManager.emitPayload(payload) + const payload = await createNotePayload() + await payloadManager.emitPayload(payload) expect(observations.length).equal(1) expect(observations[0].inserted[0]).equal(payload) @@ -58,14 +61,14 @@ describe('payload manager', () => { it('change observer', async function () { const observations = [] - this.payloadManager.addObserver(ContentType.TYPES.Any, ({ changed }) => { + payloadManager.addObserver(ContentType.TYPES.Any, ({ changed }) => { if (changed.length > 0) { observations.push({ changed }) } }) - const payload = await this.createNotePayload() - await this.payloadManager.emitPayload(payload) - await this.payloadManager.emitPayload( + const payload = await createNotePayload() + await payloadManager.emitPayload(payload) + await payloadManager.emitPayload( payload.copy({ content: { ...payload.content, @@ -79,12 +82,12 @@ describe('payload manager', () => { }) it('reset state', async function () { - this.payloadManager.addObserver(ContentType.TYPES.Any, ({}) => {}) - const payload = await this.createNotePayload() - await this.payloadManager.emitPayload(payload) - await this.payloadManager.resetState() + payloadManager.addObserver(ContentType.TYPES.Any, ({}) => {}) + const payload = await createNotePayload() + await payloadManager.emitPayload(payload) + await payloadManager.resetState() - expect(this.payloadManager.collection.all().length).to.equal(0) - expect(this.payloadManager.changeObservers.length).equal(1) + expect(payloadManager.collection.all().length).to.equal(0) + expect(payloadManager.changeObservers.length).equal(1) }) }) diff --git a/packages/snjs/mocha/preferences.test.js b/packages/snjs/mocha/preferences.test.js index ac82ccce3..63879c716 100644 --- a/packages/snjs/mocha/preferences.test.js +++ b/packages/snjs/mocha/preferences.test.js @@ -1,111 +1,116 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('preferences', function () { + let application + let email + let password + let context + beforeEach(async function () { localStorage.clear() - this.context = await Factory.createAppContext() - await this.context.launch() - this.application = this.context.application + context = await Factory.createAppContext() + await context.launch() + application = context.application - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) localStorage.clear() + context = undefined }) function register() { return Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) } it('sets preference', async function () { - await this.application.setPreference('editorLeft', 300) - expect(this.application.getPreference('editorLeft')).to.equal(300) + await application.setPreference('editorLeft', 300) + expect(application.getPreference('editorLeft')).to.equal(300) }) it('saves preference', async function () { await register.call(this) - await this.application.setPreference('editorLeft', 300) - await this.application.sync.sync() - this.application = await Factory.signOutAndBackIn(this.application, this.email, this.password) - const editorLeft = this.application.getPreference('editorLeft') + await application.setPreference('editorLeft', 300) + await application.sync.sync() + application = await Factory.signOutAndBackIn(application, email, password) + const editorLeft = application.getPreference('editorLeft') expect(editorLeft).to.equal(300) }).timeout(10000) it('clears preferences on signout', async function () { await register.call(this) - await this.application.setPreference('editorLeft', 300) - await this.application.sync.sync() - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - expect(this.application.getPreference('editorLeft')).to.equal(undefined) + await application.setPreference('editorLeft', 300) + await application.sync.sync() + application = await Factory.signOutApplicationAndReturnNew(application) + expect(application.getPreference('editorLeft')).to.equal(undefined) }) it('returns default value for non-existent preference', async function () { await register.call(this) - const editorLeft = this.application.getPreference('editorLeft', 100) + const editorLeft = application.getPreference('editorLeft', 100) expect(editorLeft).to.equal(100) }) it('emits an event when preferences change', async function () { const promise = new Promise((resolve) => { - this.application.addEventObserver(() => { + application.addEventObserver(() => { resolve() }, ApplicationEvent.PreferencesChanged) }) - await this.application.setPreference('editorLeft', 300) + await application.setPreference('editorLeft', 300) await promise expect(promise).to.be.fulfilled }) it('discards existing preferences when signing in', async function () { await register.call(this) - await this.application.setPreference('editorLeft', 300) - await this.application.sync.sync() + await application.setPreference('editorLeft', 300) + await application.sync.sync() - this.application = await this.context.signout() + application = await context.signout() - await this.application.setPreference('editorLeft', 200) - await this.application.signIn(this.email, this.password) + await application.setPreference('editorLeft', 200) + await application.signIn(email, password) - const promise = this.context.awaitUserPrefsSingletonResolution() - await this.application.sync.sync({ awaitAll: true }) + const promise = context.awaitUserPrefsSingletonResolution() + await application.sync.sync({ awaitAll: true }) await promise - const editorLeft = this.application.getPreference('editorLeft') + const editorLeft = application.getPreference('editorLeft') expect(editorLeft).to.equal(300) }) it('reads stored preferences on start without waiting for syncing to complete', async function () { const prefKey = 'editorLeft' const prefValue = 300 - const identifier = this.application.identifier + const identifier = application.identifier await register.call(this) - await this.application.setPreference(prefKey, prefValue) - await this.application.sync.sync() + await application.setPreference(prefKey, prefValue) + await application.sync.sync() - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) - this.application = Factory.createApplicationWithFakeCrypto(identifier) + application = Factory.createApplicationWithFakeCrypto(identifier) const willSyncPromise = new Promise((resolve) => { - this.application.addEventObserver(resolve, ApplicationEvent.WillSync) + application.addEventObserver(resolve, ApplicationEvent.WillSync) }) - Factory.initializeApplication(this.application) + Factory.initializeApplication(application) await willSyncPromise - expect(this.application.preferences.preferences).to.exist - expect(this.application.getPreference(prefKey)).to.equal(prefValue) + expect(application.preferences.preferences).to.exist + expect(application.getPreference(prefKey)).to.equal(prefValue) }) }) diff --git a/packages/snjs/mocha/protection.test.js b/packages/snjs/mocha/protection.test.js index 0ea3892ac..faa1ef3d6 100644 --- a/packages/snjs/mocha/protection.test.js +++ b/packages/snjs/mocha/protection.test.js @@ -1,6 +1,6 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ + import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect diff --git a/packages/snjs/mocha/protocol.test.js b/packages/snjs/mocha/protocol.test.js index 90bcfafea..6ce021926 100644 --- a/packages/snjs/mocha/protocol.test.js +++ b/packages/snjs/mocha/protocol.test.js @@ -1,61 +1,60 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('protocol', function () { + let application + beforeEach(async function () { localStorage.clear() - this.application = await Factory.createInitAppWithFakeCrypto() - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() + application = await Factory.createInitAppWithFakeCrypto() }) afterEach(async function () { - await Factory.safeDeinit(this.application) - this.application = null + await Factory.safeDeinit(application) + application = null localStorage.clear() }) it('checks version to make sure its 004', function () { - expect(this.application.encryption.getLatestVersion()).to.equal('004') + expect(application.encryption.getLatestVersion()).to.equal('004') }) it('checks supported versions to make sure it includes 001, 002, 003, 004', function () { - expect(this.application.encryption.supportedVersions()).to.eql(['001', '002', '003', '004']) + expect(application.encryption.supportedVersions()).to.eql(['001', '002', '003', '004']) }) it('platform derivation support', function () { expect( - this.application.encryption.platformSupportsKeyDerivation({ + application.encryption.platformSupportsKeyDerivation({ version: '001', }), ).to.equal(true) expect( - this.application.encryption.platformSupportsKeyDerivation({ + application.encryption.platformSupportsKeyDerivation({ version: '002', }), ).to.equal(true) expect( - this.application.encryption.platformSupportsKeyDerivation({ + application.encryption.platformSupportsKeyDerivation({ version: '003', }), ).to.equal(true) expect( - this.application.encryption.platformSupportsKeyDerivation({ + application.encryption.platformSupportsKeyDerivation({ version: '004', }), ).to.equal(true) expect( - this.application.encryption.platformSupportsKeyDerivation({ + application.encryption.platformSupportsKeyDerivation({ version: '005', }), ).to.equal(true) }) it('key params versions <= 002 should include pw_cost in portable value', function () { - const keyParams002 = this.application.encryption.createKeyParams({ + const keyParams002 = application.encryption.createKeyParams({ version: '002', pw_cost: 5000, }) @@ -63,15 +62,15 @@ describe('protocol', function () { }) it('version comparison of 002 should be older than library version', function () { - expect(this.application.encryption.isVersionNewerThanLibraryVersion('002')).to.equal(false) + expect(application.encryption.isVersionNewerThanLibraryVersion('002')).to.equal(false) }) it('version comparison of 005 should be newer than library version', function () { - expect(this.application.encryption.isVersionNewerThanLibraryVersion('005')).to.equal(true) + expect(application.encryption.isVersionNewerThanLibraryVersion('005')).to.equal(true) }) it('library version should not be outdated', function () { - var currentVersion = this.application.encryption.getLatestVersion() + var currentVersion = application.encryption.getLatestVersion() expect(isProtocolVersionExpired(currentVersion)).to.equal(false) }) @@ -91,7 +90,7 @@ describe('protocol', function () { const payload = Factory.createNotePayload() let error try { - await this.application.encryption.decryptSplitSingle({ + await application.encryption.decryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -103,10 +102,10 @@ describe('protocol', function () { }) it('ejected payload should not have meta fields', async function () { - await this.application.addPasscode('123') + await application.addPasscode('123') const payload = Factory.createNotePayload() const result = CreateEncryptedServerSyncPushPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -121,7 +120,7 @@ describe('protocol', function () { it('encrypted payload for server should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedServerSyncPushPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -134,7 +133,7 @@ describe('protocol', function () { it('ejected payload for server should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedServerSyncPushPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -147,7 +146,7 @@ describe('protocol', function () { it('encrypted payload for storage should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedLocalStorageContextPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -160,7 +159,7 @@ describe('protocol', function () { it('ejected payload for storage should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedLocalStorageContextPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -173,7 +172,7 @@ describe('protocol', function () { it('encrypted payload for file should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedBackupFileContextPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -186,7 +185,7 @@ describe('protocol', function () { it('ejected payload for file should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedBackupFileContextPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, diff --git a/packages/snjs/mocha/session.test.js b/packages/snjs/mocha/session.test.js index 7acba2e13..cb21d4670 100644 --- a/packages/snjs/mocha/session.test.js +++ b/packages/snjs/mocha/session.test.js @@ -1,14 +1,17 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ -import { BaseItemCounts } from './lib/BaseItemCounts.js' import * as Factory from './lib/factory.js' import WebDeviceInterface from './lib/web_device_interface.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('server session', function () { this.timeout(Factory.TenSecondTimeout) + let application + let email + let password + let newPassword + const syncOptions = { checkIntegrity: true, awaitAll: true, @@ -16,16 +19,15 @@ describe('server session', function () { beforeEach(async function () { localStorage.clear() - this.expectedItemCount = BaseItemCounts.DefaultItems - this.application = await Factory.createInitAppWithFakeCrypto() - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() - this.newPassword = Factory.randomString() + application = await Factory.createInitAppWithFakeCrypto() + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() + newPassword = Factory.randomString() }) afterEach(async function () { - await Factory.safeDeinit(this.application) - this.application = null + await Factory.safeDeinit(application) + application = null localStorage.clear() }) @@ -47,26 +49,26 @@ describe('server session', function () { it('should succeed when a sync request is perfomed with an expired access token', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - await sleepUntilSessionExpires(this.application) + await sleepUntilSessionExpires(application) - const response = await this.application.legacyApi.sync([]) + const response = await application.legacyApi.sync([]) expect(response.status).to.equal(200) }).timeout(Factory.TwentySecondTimeout) it('should return the new session in the response when refreshed', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const response = await this.application.legacyApi.refreshSession() + const response = await application.legacyApi.refreshSession() expect(response.status).to.equal(200) expect(response.data.session.access_token).to.be.a('string') @@ -77,22 +79,22 @@ describe('server session', function () { it('should be refreshed on any api call if access token is expired', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) // Saving the current session information for later... - const sessionBeforeSync = this.application.legacyApi.getSession() + const sessionBeforeSync = application.legacyApi.getSession() // Waiting enough time for the access token to expire, before performing a new sync request. - await sleepUntilSessionExpires(this.application) + await sleepUntilSessionExpires(application) // Performing a sync request with an expired access token. - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) // After the above sync request is completed, we obtain the session information. - const sessionAfterSync = this.application.legacyApi.getSession() + const sessionAfterSync = application.legacyApi.getSession() expect(sessionBeforeSync.accessToken.value).to.not.equal(sessionAfterSync.accessToken.value) expect(sessionBeforeSync.refreshToken.value).to.not.equal(sessionAfterSync.refreshToken.value) @@ -103,59 +105,59 @@ describe('server session', function () { it('should not deadlock while renewing session', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - await sleepUntilSessionExpires(this.application) + await sleepUntilSessionExpires(application) - // Apply a latency simulation so that ` this.inProgressRefreshSessionPromise = this.refreshSession()` does + // Apply a latency simulation so that ` inProgressRefreshSessionPromise = refreshSession()` does // not have the chance to complete before it is assigned to the variable. This test came along with a fix // where runHttp does not await a pending refreshSession promise if the request being made is itself a refreshSession request. - this.application.http.__latencySimulatorMs = 1000 - await this.application.sync.sync(syncOptions) + application.http.__latencySimulatorMs = 1000 + await application.sync.sync(syncOptions) - const sessionAfterSync = this.application.legacyApi.getSession() + const sessionAfterSync = application.legacyApi.getSession() expect(sessionAfterSync.accessToken.expiresAt).to.be.greaterThan(Date.now()) }).timeout(Factory.TwentySecondTimeout) it('should succeed when a sync request is perfomed after signing into an ephemeral session', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + application = await Factory.signOutApplicationAndReturnNew(application) - await this.application.signIn(this.email, this.password, false, true) + await application.signIn(email, password, false, true) - const response = await this.application.legacyApi.sync([]) + const response = await application.legacyApi.sync([]) expect(response.status).to.equal(200) }) it('should succeed when a sync request is perfomed after registering into an ephemeral session', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, ephemeral: true, }) - const response = await this.application.legacyApi.sync([]) + const response = await application.legacyApi.sync([]) expect(response.status).to.equal(200) }) it('should be consistent between storage and apiService', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const sessionFromStorage = await getSessionFromStorage(this.application) - const sessionFromApiService = this.application.legacyApi.getSession() + const sessionFromStorage = await getSessionFromStorage(application) + const sessionFromApiService = application.legacyApi.getSession() expect(sessionFromStorage.accessToken).to.equal(sessionFromApiService.accessToken.value) expect(sessionFromStorage.refreshToken).to.equal(sessionFromApiService.refreshToken.value) @@ -163,10 +165,10 @@ describe('server session', function () { expect(sessionFromStorage.refreshExpiration).to.equal(sessionFromApiService.refreshToken.expiresAt) expect(sessionFromStorage.readonlyAccess).to.equal(sessionFromApiService.isReadOnly()) - await this.application.legacyApi.refreshSession() + await application.legacyApi.refreshSession() - const updatedSessionFromStorage = await getSessionFromStorage(this.application) - const updatedSessionFromApiService = this.application.legacyApi.getSession() + const updatedSessionFromStorage = await getSessionFromStorage(application) + const updatedSessionFromApiService = application.legacyApi.getSession() expect(updatedSessionFromStorage.accessToken).to.equal(updatedSessionFromApiService.accessToken.value) expect(updatedSessionFromStorage.refreshToken).to.equal(updatedSessionFromApiService.refreshToken.value) @@ -177,16 +179,16 @@ describe('server session', function () { it('should be performed successfully and terminate session with a valid access token', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const signOutResponse = await this.application.legacyApi.signOut() + const signOutResponse = await application.legacyApi.signOut() expect(signOutResponse.status).to.equal(204) - Factory.ignoreChallenges(this.application) - const syncResponse = await this.application.legacyApi.sync([]) + Factory.ignoreChallenges(application) + const syncResponse = await application.legacyApi.sync([]) expect(syncResponse.status).to.equal(401) expect(syncResponse.data.error.tag).to.equal('invalid-auth') expect(syncResponse.data.error.message).to.equal('Invalid login credentials.') @@ -194,19 +196,19 @@ describe('server session', function () { it('sign out request should be performed successfully and terminate session with expired access token', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) // Waiting enough time for the access token to expire, before performing a sign out request. - await sleepUntilSessionExpires(this.application) + await sleepUntilSessionExpires(application) - const signOutResponse = await this.application.legacyApi.signOut() + const signOutResponse = await application.legacyApi.signOut() expect(signOutResponse.status).to.equal(204) - Factory.ignoreChallenges(this.application) - const syncResponse = await this.application.legacyApi.sync([]) + Factory.ignoreChallenges(application) + const syncResponse = await application.legacyApi.sync([]) expect(syncResponse.status).to.equal(401) expect(syncResponse.data.error.tag).to.equal('invalid-auth') expect(syncResponse.data.error.message).to.equal('Invalid login credentials.') @@ -276,20 +278,20 @@ describe('server session', function () { it('change password request should be successful with a valid access token', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const changePasswordResponse = await this.application.changePassword(this.password, this.newPassword) + const changePasswordResponse = await application.changePassword(password, newPassword) expect(changePasswordResponse.error).to.not.be.ok - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + application = await Factory.signOutApplicationAndReturnNew(application) const loginResponse = await Factory.loginToApplication({ - application: this.application, - email: this.email, - password: this.newPassword, + application: application, + email: email, + password: newPassword, }) expect(loginResponse).to.be.ok @@ -298,23 +300,23 @@ describe('server session', function () { it('change password request should be successful after the expired access token is refreshed', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) // Waiting enough time for the access token to expire. - await sleepUntilSessionExpires(this.application) + await sleepUntilSessionExpires(application) - const changePasswordResponse = await this.application.changePassword(this.password, this.newPassword) + const changePasswordResponse = await application.changePassword(password, newPassword) expect(changePasswordResponse.error).to.not.be.ok - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + application = await Factory.signOutApplicationAndReturnNew(application) const loginResponse = await Factory.loginToApplication({ - application: this.application, - email: this.email, - password: this.newPassword, + application: application, + email: email, + password: newPassword, }) expect(loginResponse).to.be.ok @@ -323,39 +325,39 @@ describe('server session', function () { it('change password request should fail with an invalid access token', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - this.application.storage.setValue(StorageKey.Session, { + application.storage.setValue(StorageKey.Session, { accessToken: 'this-is-a-fake-token-1234', refreshToken: 'this-is-a-fake-token-1234', accessExpiration: 999999999999999, refreshExpiration: 99999999999999, readonlyAccess: false, }) - this.application.sessions.initializeFromDisk() + application.sessions.initializeFromDisk() - Factory.ignoreChallenges(this.application) - const changePasswordResponse = await this.application.changePassword(this.password, this.newPassword) + Factory.ignoreChallenges(application) + const changePasswordResponse = await application.changePassword(password, newPassword) expect(changePasswordResponse.error.message).to.equal('Invalid login credentials.') }) it('change password request should fail with an expired refresh token', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - this.application.sync.lockSyncing() + application.sync.lockSyncing() /** Waiting for the refresh token to expire. */ - await sleepUntilSessionExpires(this.application, false) + await sleepUntilSessionExpires(application, false) - Factory.ignoreChallenges(this.application) - const changePasswordResponse = await this.application.changePassword(this.password, this.newPassword) + Factory.ignoreChallenges(application) + const changePasswordResponse = await application.changePassword(password, newPassword) expect(changePasswordResponse).to.be.ok expect(changePasswordResponse.error.message).to.equal('Invalid login credentials.') @@ -363,17 +365,17 @@ describe('server session', function () { it('should sign in successfully after signing out', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - await this.application.legacyApi.signOut() - this.application.legacyApi.session = undefined + await application.legacyApi.signOut() + application.legacyApi.session = undefined - await this.application.sessions.signIn(this.email, this.password) + await application.sessions.signIn(email, password) - const currentSession = this.application.legacyApi.getSession() + const currentSession = application.legacyApi.getSession() expect(currentSession).to.be.ok expect(currentSession.accessToken).to.be.ok @@ -383,16 +385,16 @@ describe('server session', function () { it('should fail when renewing a session with an expired refresh token', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - this.application.sync.lockSyncing() + application.sync.lockSyncing() - await sleepUntilSessionExpires(this.application, false) + await sleepUntilSessionExpires(application, false) - const refreshSessionResponse = await this.application.legacyApi.refreshSession() + const refreshSessionResponse = await application.legacyApi.refreshSession() expect(refreshSessionResponse.status).to.equal(400) expect(refreshSessionResponse.data.error.tag).to.equal('expired-refresh-token') @@ -402,8 +404,8 @@ describe('server session', function () { The access token and refresh token should be expired up to this point. Here we make sure that any subsequent requests will fail. */ - Factory.ignoreChallenges(this.application) - const syncResponse = await this.application.legacyApi.sync([]) + Factory.ignoreChallenges(application) + const syncResponse = await application.legacyApi.sync([]) expect(syncResponse.status).to.equal(401) expect(syncResponse.data.error.tag).to.equal('invalid-auth') expect(syncResponse.data.error.message).to.equal('Invalid login credentials.') @@ -411,42 +413,42 @@ describe('server session', function () { it('should fail when renewing a session with an invalid refresh token', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const originalSession = this.application.legacyApi.getSession() + const originalSession = application.legacyApi.getSession() - this.application.storage.setValue(StorageKey.Session, { + application.storage.setValue(StorageKey.Session, { accessToken: originalSession.accessToken.value, refreshToken: 'this-is-a-fake-token-1234', accessExpiration: originalSession.accessToken.expiresAt, refreshExpiration: originalSession.refreshToken.expiresAt, readonlyAccess: false, }) - this.application.sessions.initializeFromDisk() + application.sessions.initializeFromDisk() - const refreshSessionResponse = await this.application.legacyApi.refreshSession() + const refreshSessionResponse = await application.legacyApi.refreshSession() expect(refreshSessionResponse.status).to.equal(400) expect(refreshSessionResponse.data.error.tag).to.equal('invalid-refresh-token') expect(refreshSessionResponse.data.error.message).to.equal('The refresh token is not valid.') // Access token should remain valid. - const syncResponse = await this.application.legacyApi.sync([]) + const syncResponse = await application.legacyApi.sync([]) expect(syncResponse.status).to.equal(200) }) it('should fail if syncing while a session refresh is in progress', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const refreshPromise = this.application.legacyApi.refreshSession() - const syncResponse = await this.application.legacyApi.sync([]) + const refreshPromise = application.legacyApi.refreshSession() + const syncResponse = await application.legacyApi.sync([]) expect(syncResponse.data.error).to.be.ok @@ -458,40 +460,40 @@ describe('server session', function () { it('notes should be synced as expected after refreshing a session', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const notesBeforeSync = await Factory.createManyMappedNotes(this.application, 5) + const notesBeforeSync = await Factory.createManyMappedNotes(application, 5) - await sleepUntilSessionExpires(this.application) - await this.application.sync.sync(syncOptions) - expect(this.application.sync.isOutOfSync()).to.equal(false) + await sleepUntilSessionExpires(application) + await application.sync.sync(syncOptions) + expect(application.sync.isOutOfSync()).to.equal(false) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) + application = await Factory.signOutApplicationAndReturnNew(application) + await application.signIn(email, password, undefined, undefined, undefined, true) const expectedNotesUuids = notesBeforeSync.map((n) => n.uuid) - const notesResults = await this.application.items.findItems(expectedNotesUuids) + const notesResults = await application.items.findItems(expectedNotesUuids) expect(notesResults.length).to.equal(notesBeforeSync.length) for (const aNoteBeforeSync of notesBeforeSync) { - const noteResult = await this.application.items.findItem(aNoteBeforeSync.uuid) + const noteResult = await application.items.findItem(aNoteBeforeSync.uuid) expect(aNoteBeforeSync.isItemContentEqualWith(noteResult)).to.equal(true) } }).timeout(Factory.TwentySecondTimeout) it('should prompt user for account password and sign back in on invalid session', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const email = `${Math.random()}` - const password = `${Math.random()}` + email = `${Math.random()}` + password = `${Math.random()}` let didPromptForSignIn = false const receiveChallenge = async (challenge) => { didPromptForSignIn = true @@ -541,55 +543,55 @@ describe('server session', function () { it('should return current session in list of sessions', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const response = await this.application.legacyApi.getSessionsList() + const response = await application.legacyApi.getSessionsList() expect(response.data[0].current).to.equal(true) }) it('signing out should delete session from all list', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) /** Create new session aside from existing one */ const app2 = await Factory.createAndInitializeApplication('app2') - await app2.signIn(this.email, this.password) + await app2.signIn(email, password) - const response = await this.application.legacyApi.getSessionsList() + const response = await application.legacyApi.getSessionsList() expect(response.data.length).to.equal(2) await app2.user.signOut() - const response2 = await this.application.legacyApi.getSessionsList() + const response2 = await application.legacyApi.getSessionsList() expect(response2.data.length).to.equal(1) }) it('revoking a session should destroy local data', async function () { - Factory.handlePasswordChallenges(this.application, this.password) + Factory.handlePasswordChallenges(application, password) await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) const app2identifier = 'app2' const app2 = await Factory.createAndInitializeApplication(app2identifier) - await app2.signIn(this.email, this.password) + await app2.signIn(email, password) const app2Deinit = new Promise((resolve) => { app2.setOnDeinit(() => { resolve() }) }) - const { data: sessions } = await this.application.getSessions() + const { data: sessions } = await application.getSessions() const app2session = sessions.find((session) => !session.current) - await this.application.revokeSession(app2session.uuid) + await application.revokeSession(app2session.uuid) void app2.sync.sync() await app2Deinit @@ -599,23 +601,23 @@ describe('server session', function () { }).timeout(Factory.TwentySecondTimeout) it('revoking other sessions should destroy their local data', async function () { - Factory.handlePasswordChallenges(this.application, this.password) + Factory.handlePasswordChallenges(application, password) await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) const app2identifier = 'app2' const app2 = await Factory.createAndInitializeApplication(app2identifier) - await app2.signIn(this.email, this.password) + await app2.signIn(email, password) const app2Deinit = new Promise((resolve) => { app2.setOnDeinit(() => { resolve() }) }) - await this.application.revokeAllOtherSessions() + await application.revokeAllOtherSessions() void app2.sync.sync() await app2Deinit @@ -626,24 +628,24 @@ describe('server session', function () { it('signing out with invalid session token should still delete local data', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - this.application.storage.setValue(StorageKey.Session, { + application.storage.setValue(StorageKey.Session, { accessToken: undefined, refreshToken: undefined, accessExpiration: 999999999999999, refreshExpiration: 999999999999999, readonlyAccess: false, }) - this.application.sessions.initializeFromDisk() + application.sessions.initializeFromDisk() - const storageKey = this.application.storage.getPersistenceKey() + const storageKey = application.storage.getPersistenceKey() expect(localStorage.getItem(storageKey)).to.be.ok - await this.application.user.signOut() + await application.user.signOut() expect(localStorage.getItem(storageKey)).to.not.be.ok }) }) diff --git a/packages/snjs/mocha/settings.test.js b/packages/snjs/mocha/settings.test.js index 587295294..115541510 100644 --- a/packages/snjs/mocha/settings.test.js +++ b/packages/snjs/mocha/settings.test.js @@ -30,6 +30,11 @@ describe('settings service', function () { }) }) + afterEach(async function () { + await Factory.safeDeinit(application) + localStorage.clear() + }) + const reInitializeApplicationWithRealCrypto = async () => { await Factory.safeDeinit(application) @@ -46,11 +51,6 @@ describe('settings service', function () { }) } - afterEach(async function () { - await Factory.safeDeinit(application) - localStorage.clear() - }) - it('creates and reads a setting', async function () { await application.settings.updateSetting(validSetting, fakePayload) const responseCreate = await application.settings.getSetting(validSetting) diff --git a/packages/snjs/mocha/singletons.test.js b/packages/snjs/mocha/singletons.test.js index f84bf5456..f5898bf7a 100644 --- a/packages/snjs/mocha/singletons.test.js +++ b/packages/snjs/mocha/singletons.test.js @@ -1,8 +1,6 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import { BaseItemCounts } from './lib/BaseItemCounts.js' import * as Factory from './lib/factory.js' -import WebDeviceInterface from './lib/web_device_interface.js' + chai.use(chaiAsPromised) const expect = chai.expect @@ -28,48 +26,60 @@ describe('singletons', function () { return context.singletons.findOrCreateContentTypeSingleton(ContentType.TYPES.UserPrefs, FillItemContent({})) } + let expectedItemCount + let application + let context + let email + let password + let registerUser + let signIn + let signOut + let extManagerId + let extPred + let createExtMgr + beforeEach(async function () { localStorage.clear() - this.expectedItemCount = BaseItemCounts.DefaultItems + expectedItemCount = BaseItemCounts.DefaultItems - this.context = await Factory.createAppContext() - await this.context.launch() - this.application = this.context.application + context = await Factory.createAppContext() + await context.launch() + application = context.application - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() - this.registerUser = async () => { - this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount + registerUser = async () => { + expectedItemCount = BaseItemCounts.DefaultItemsWithAccount await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) } - this.signOut = async () => { - this.application = await this.context.signout() + signOut = async () => { + application = await context.signout() } - this.signIn = async () => { - await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) + signIn = async () => { + await application.signIn(email, password, undefined, undefined, undefined, true) } - this.extManagerId = 'org.standardnotes.extensions-manager' + extManagerId = 'org.standardnotes.extensions-manager' - this.extPred = new CompoundPredicate('and', [ + extPred = new CompoundPredicate('and', [ new Predicate('content_type', '=', ContentType.TYPES.Component), - new Predicate('package_info.identifier', '=', this.extManagerId), + new Predicate('package_info.identifier', '=', extManagerId), ]) - this.createExtMgr = () => { - return this.application.mutator.createItem( + createExtMgr = () => { + return application.mutator.createItem( ContentType.TYPES.Component, { package_info: { name: 'Extensions', - identifier: this.extManagerId, + identifier: extManagerId, }, }, true, @@ -78,13 +88,13 @@ describe('singletons', function () { }) afterEach(async function () { - expect(this.application.sync.isOutOfSync()).to.equal(false) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(application.sync.isOutOfSync()).to.equal(false) + expect(application.items.items.length).to.equal(expectedItemCount) - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) localStorage.clear() }) @@ -95,99 +105,99 @@ describe('singletons', function () { const prefs2 = createPrefsPayload() const prefs3 = createPrefsPayload() - const items = await this.application.mutator.emitItemsFromPayloads( + const items = await application.mutator.emitItemsFromPayloads( [prefs1, prefs2, prefs3], PayloadEmitSource.LocalChanged, ) - await this.application.mutator.setItemsDirty(items) - await this.application.sync.sync(syncOptions) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + await application.mutator.setItemsDirty(items) + await application.sync.sync(syncOptions) + expect(application.items.items.length).to.equal(expectedItemCount) }) it('duplicate components should auto-resolve to 1', async function () { - const extManager = await this.createExtMgr() - this.expectedItemCount += 1 + const extManager = await createExtMgr() + expectedItemCount += 1 /** Call needlessly */ - await this.createExtMgr() - await this.createExtMgr() - await this.createExtMgr() + await createExtMgr() + await createExtMgr() + await createExtMgr() expect(extManager).to.be.ok - const refreshedExtMgr = this.application.items.findItem(extManager.uuid) + const refreshedExtMgr = application.items.findItem(extManager.uuid) expect(refreshedExtMgr).to.be.ok - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) - expect(this.application.items.itemsMatchingPredicate(ContentType.TYPES.Component, this.extPred).length).to.equal(1) + expect(application.items.itemsMatchingPredicate(ContentType.TYPES.Component, extPred).length).to.equal(1) }) it('resolves via find or create', async function () { /* Set to never synced as singleton manager will attempt to sync before resolving */ - this.application.sync.ut_clearLastSyncDate() - this.application.sync.ut_setDatabaseLoaded(false) + application.sync.ut_clearLastSyncDate() + application.sync.ut_setDatabaseLoaded(false) const contentType = ContentType.TYPES.UserPrefs const predicate = new Predicate('content_type', '=', contentType) /* Start a sync right after we await singleton resolve below */ setTimeout(() => { - this.application.sync.ut_setDatabaseLoaded(true) - this.application.sync.sync({ + application.sync.ut_setDatabaseLoaded(true) + application.sync.sync({ /* Simulate the first sync occuring as that is handled specially by sync service */ mode: SyncMode.DownloadFirst, }) }) - const userPreferences = await this.context.singletons.findOrCreateContentTypeSingleton(contentType, {}) + const userPreferences = await context.singletons.findOrCreateContentTypeSingleton(contentType, {}) expect(userPreferences).to.be.ok - const refreshedUserPrefs = this.application.items.findItem(userPreferences.uuid) + const refreshedUserPrefs = application.items.findItem(userPreferences.uuid) expect(refreshedUserPrefs).to.be.ok - await this.application.sync.sync(syncOptions) - expect(this.application.items.itemsMatchingPredicate(contentType, predicate).length).to.equal(1) + await application.sync.sync(syncOptions) + expect(application.items.itemsMatchingPredicate(contentType, predicate).length).to.equal(1) }) it('resolves registered predicate with signing in/out', async function () { - await this.registerUser() + await registerUser() - await this.signOut() + await signOut() - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() - await this.createExtMgr() + await createExtMgr() - this.expectedItemCount += 1 + expectedItemCount += 1 await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - await this.signOut() + await signOut() - await this.createExtMgr() + await createExtMgr() - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) - const extraSync = this.application.sync.sync(syncOptions) + const extraSync = application.sync.sync(syncOptions) - await this.signIn() + await signIn() await extraSync }).timeout(15000) it('singletons that are deleted after download first sync should not sync to server', async function () { - await this.registerUser() - await this.createExtMgr() - await this.createExtMgr() - await this.createExtMgr() - this.expectedItemCount++ + await registerUser() + await createExtMgr() + await createExtMgr() + await createExtMgr() + expectedItemCount++ let didCompleteRelevantSync = false let beginCheckingResponse = false - this.application.sync.addEventObserver(async (eventName, data) => { + application.sync.addEventObserver(async (eventName, data) => { if (eventName === SyncEvent.DownloadFirstSyncCompleted) { beginCheckingResponse = true } @@ -202,59 +212,59 @@ describe('singletons', function () { expect(matching).to.not.be.ok } }) - await this.application.sync.sync({ mode: SyncMode.DownloadFirst }) + await application.sync.sync({ mode: SyncMode.DownloadFirst }) expect(didCompleteRelevantSync).to.equal(true) }).timeout(10000) it('signing into account and retrieving singleton shouldnt put us in deadlock', async function () { - await this.registerUser() + await registerUser() /** Create prefs */ - const ogPrefs = await findOrCreatePrefsSingleton(this.context) - await this.application.sync.sync(syncOptions) + const ogPrefs = await findOrCreatePrefsSingleton(context) + await application.sync.sync(syncOptions) - this.application = await this.context.signout() + application = await context.signout() /** Create another instance while signed out */ - await findOrCreatePrefsSingleton(this.context) + await findOrCreatePrefsSingleton(context) await Factory.loginToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) /** After signing in, the instance retrieved from the server should be the one kept */ - const latestPrefs = await findOrCreatePrefsSingleton(this.context) + const latestPrefs = await findOrCreatePrefsSingleton(context) expect(latestPrefs.uuid).to.equal(ogPrefs.uuid) - const allPrefs = this.application.items.getItems(ogPrefs.content_type) + const allPrefs = application.items.getItems(ogPrefs.content_type) expect(allPrefs.length).to.equal(1) }) it('resolving singleton before first sync, then signing in, should result in correct number of instances', async function () { - await this.registerUser() + await registerUser() /** Create prefs and associate them with account */ - const ogPrefs = await findOrCreatePrefsSingleton(this.context) - await this.application.sync.sync(syncOptions) - this.application = await this.context.signout() + const ogPrefs = await findOrCreatePrefsSingleton(context) + await application.sync.sync(syncOptions) + application = await context.signout() /** Create another instance while signed out */ - await findOrCreatePrefsSingleton(this.context) + await findOrCreatePrefsSingleton(context) await Factory.loginToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) /** After signing in, the instance retrieved from the server should be the one kept */ - const latestPrefs = await findOrCreatePrefsSingleton(this.context) + const latestPrefs = await findOrCreatePrefsSingleton(context) expect(latestPrefs.uuid).to.equal(ogPrefs.uuid) - const allPrefs = this.application.items.getItems(ogPrefs.content_type) + const allPrefs = application.items.getItems(ogPrefs.content_type) expect(allPrefs.length).to.equal(1) }) it('if only result is errorDecrypting, create new item', async function () { - const item = this.application.items.items.find((item) => item.content_type === ContentType.TYPES.UserPrefs) + const item = application.items.items.find((item) => item.content_type === ContentType.TYPES.UserPrefs) const erroredPayload = new EncryptedPayload({ ...item.payload.ejected(), @@ -262,13 +272,13 @@ describe('singletons', function () { errorDecrypting: true, }) - await this.application.payloads.emitPayload(erroredPayload) + await application.payloads.emitPayload(erroredPayload) - const resolvedItem = await this.context.singletons.findOrCreateContentTypeSingleton(item.content_type, item.content) + const resolvedItem = await context.singletons.findOrCreateContentTypeSingleton(item.content_type, item.content) - await this.application.sync.sync({ awaitAll: true }) + await application.sync.sync({ awaitAll: true }) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) expect(resolvedItem.uuid).to.not.equal(item.uuid) expect(resolvedItem.errorDecrypting).to.not.be.ok }) @@ -284,13 +294,13 @@ describe('singletons', function () { const sharedContent = { package_info: { name: 'Extensions', - identifier: this.extManagerId, + identifier: extManagerId, }, } const errorDecryptingFalse = false await Factory.insertItemWithOverride( - this.application, + application, ContentType.TYPES.Component, sharedContent, true, @@ -299,16 +309,16 @@ describe('singletons', function () { const errorDecryptingTrue = true const errored = await Factory.insertItemWithOverride( - this.application, + application, ContentType.TYPES.Component, sharedContent, true, errorDecryptingTrue, ) - this.expectedItemCount += 1 + expectedItemCount += 1 - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) /** Now mark errored as not errorDecrypting and sync */ const notErrored = new DecryptedPayload({ @@ -317,34 +327,31 @@ describe('singletons', function () { errorDecrypting: false, }) - await this.application.payloads.emitPayload(notErrored) + await application.payloads.emitPayload(notErrored) /** Item will get decrypted on current tick, so wait one before syncing */ await Factory.sleep(0) - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) - expect(this.application.items.itemsMatchingPredicate(ContentType.TYPES.Component, this.extPred).length).to.equal(1) + expect(application.items.itemsMatchingPredicate(ContentType.TYPES.Component, extPred).length).to.equal(1) }) it('alternating the uuid of a singleton should return correct result', async function () { const payload = createPrefsPayload() - const item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) - await this.application.sync.sync(syncOptions) + const item = await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) + await application.sync.sync(syncOptions) const predicate = new Predicate('content_type', '=', item.content_type) - let resolvedItem = await this.context.singletons.findOrCreateContentTypeSingleton( - payload.content_type, - payload.content, - ) + let resolvedItem = await context.singletons.findOrCreateContentTypeSingleton(payload.content_type, payload.content) const originalUuid = resolvedItem.uuid - await Factory.alternateUuidForItem(this.application, resolvedItem.uuid) - await this.application.sync.sync(syncOptions) - const resolvedItem2 = await this.context.singletons.findOrCreateContentTypeSingleton( + await Factory.alternateUuidForItem(application, resolvedItem.uuid) + await application.sync.sync(syncOptions) + const resolvedItem2 = await context.singletons.findOrCreateContentTypeSingleton( payload.content_type, payload.content, ) - resolvedItem = this.application.items.findItem(resolvedItem.uuid) + resolvedItem = application.items.findItem(resolvedItem.uuid) expect(resolvedItem).to.not.be.ok expect(resolvedItem2.uuid).to.not.equal(originalUuid) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) }) }) diff --git a/packages/snjs/mocha/storage.test.js b/packages/snjs/mocha/storage.test.js index 72edf1bbc..f4654d84a 100644 --- a/packages/snjs/mocha/storage.test.js +++ b/packages/snjs/mocha/storage.test.js @@ -1,7 +1,6 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import { BaseItemCounts } from './lib/BaseItemCounts.js' import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect @@ -13,128 +12,137 @@ describe('storage manager', function () { */ const BASE_KEY_COUNT = ['storage', 'snjs_version', 'keychain'].length + let application + let email + let password + let expectedKeyCount + beforeEach(async function () { localStorage.clear() - this.expectedKeyCount = BASE_KEY_COUNT + expectedKeyCount = BASE_KEY_COUNT - this.context = await Factory.createAppContext() - await this.context.launch() + context = await Factory.createAppContext() + await context.launch() - this.application = this.context.application - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() + application = context.application + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await context.deinit() + + application = undefined + context = undefined + localStorage.clear() }) it('should set and retrieve values', async function () { const key = 'foo' const value = 'bar' - await this.application.storage.setValue(key, value) - expect(await this.application.storage.getValue(key)).to.eql(value) + await application.storage.setValue(key, value) + expect(await application.storage.getValue(key)).to.eql(value) }) it('should set and retrieve items', async function () { const payload = Factory.createNotePayload() - await this.application.storage.savePayload(payload) - const payloads = await this.application.storage.getAllRawPayloads() + await application.storage.savePayload(payload) + const payloads = await application.storage.getAllRawPayloads() expect(payloads.length).to.equal(BaseItemCounts.DefaultItems + 1) }) it('should clear values', async function () { const key = 'foo' const value = 'bar' - await this.application.storage.setValue(key, value) - await this.application.storage.clearAllData() - expect(await this.application.storage.getValue(key)).to.not.be.ok + await application.storage.setValue(key, value) + await application.storage.clearAllData() + expect(await application.storage.getValue(key)).to.not.be.ok }) it('serverPassword should not be saved to keychain', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, ephemeral: false, }) - const keychainValue = await this.application.device.getNamespacedKeychainValue(this.application.identifier) + const keychainValue = await application.device.getNamespacedKeychainValue(application.identifier) expect(keychainValue.masterKey).to.be.ok expect(keychainValue.serverPassword).to.not.be.ok }) it('regular session should persist data', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, ephemeral: false, }) const key = 'foo' const value = 'bar' - await this.application.storage.setValue(key, value) + await application.storage.setValue(key, value) - expect(Object.keys(localStorage).length).to.equal(this.expectedKeyCount + BaseItemCounts.DefaultItemsWithAccount) - const retrievedValue = await this.application.storage.getValue(key) + expect(Object.keys(localStorage).length).to.equal(expectedKeyCount + BaseItemCounts.DefaultItemsWithAccount) + const retrievedValue = await application.storage.getValue(key) expect(retrievedValue).to.equal(value) }) it('ephemeral session should not persist data', async function () { this.retries(2) await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, ephemeral: true, }) const key = 'foo' const value = 'bar' - await this.application.storage.setValueAndAwaitPersist(key, value) + await application.storage.setValueAndAwaitPersist(key, value) const expectedKeys = ['keychain'] expect(Object.keys(localStorage).length).to.equal(expectedKeys.length) - const retrievedValue = await this.application.storage.getValue(key) + const retrievedValue = await application.storage.getValue(key) expect(retrievedValue).to.equal(value) }) it('ephemeral session should not persist to database', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, ephemeral: true, }) - await Factory.createSyncedNote(this.application) - const rawPayloads = await this.application.storage.getAllRawPayloads() + await Factory.createSyncedNote(application) + const rawPayloads = await application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(0) }) it('storage with no account and no passcode should not be encrypted', async function () { - await this.application.storage.setValueAndAwaitPersist('foo', 'bar') - const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] + await application.storage.setValueAndAwaitPersist('foo', 'bar') + const wrappedValue = application.storage.values[ValueModesKeys.Wrapped] const payload = new DecryptedPayload(wrappedValue) expect(payload.content).to.be.an.instanceof(Object) }) it('storage aftering adding passcode should be encrypted', async function () { - await this.application.storage.setValueAndAwaitPersist('foo', 'bar') - await this.application.addPasscode('123') - const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] + await application.storage.setValueAndAwaitPersist('foo', 'bar') + await application.addPasscode('123') + const wrappedValue = application.storage.values[ValueModesKeys.Wrapped] const payload = new EncryptedPayload(wrappedValue) expect(payload.content).to.be.a('string') }) it('storage after adding passcode then removing passcode should not be encrypted', async function () { const passcode = '123' - Factory.handlePasswordChallenges(this.application, passcode) - await this.application.storage.setValueAndAwaitPersist('foo', 'bar') - await this.application.addPasscode(passcode) - await this.application.storage.setValueAndAwaitPersist('bar', 'foo') - await this.application.removePasscode() - const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] + Factory.handlePasswordChallenges(application, passcode) + await application.storage.setValueAndAwaitPersist('foo', 'bar') + await application.addPasscode(passcode) + await application.storage.setValueAndAwaitPersist('bar', 'foo') + await application.removePasscode() + const wrappedValue = application.storage.values[ValueModesKeys.Wrapped] const payload = new DecryptedPayload(wrappedValue) expect(payload.content).to.be.an.instanceof(Object) }) @@ -147,80 +155,85 @@ describe('storage manager', function () { * the account keys to be moved to the keychain. * */ await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - expect(await this.application.device.getNamespacedKeychainValue(this.application.identifier)).to.be.ok - await this.application.storage.setValueAndAwaitPersist('foo', 'bar') - Factory.handlePasswordChallenges(this.application, this.password) - await this.application.addPasscode(passcode) - expect(await this.application.device.getNamespacedKeychainValue(this.application.identifier)).to.not.be.ok - await this.application.storage.setValueAndAwaitPersist('bar', 'foo') - Factory.handlePasswordChallenges(this.application, passcode) - await this.application.removePasscode() - expect(await this.application.device.getNamespacedKeychainValue(this.application.identifier)).to.be.ok + expect(await application.device.getNamespacedKeychainValue(application.identifier)).to.be.ok + await application.storage.setValueAndAwaitPersist('foo', 'bar') + Factory.handlePasswordChallenges(application, password) + await application.addPasscode(passcode) + expect(await application.device.getNamespacedKeychainValue(application.identifier)).to.not.be.ok + await application.storage.setValueAndAwaitPersist('bar', 'foo') + Factory.handlePasswordChallenges(application, passcode) + await application.removePasscode() + expect(await application.device.getNamespacedKeychainValue(application.identifier)).to.be.ok - const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] + const wrappedValue = application.storage.values[ValueModesKeys.Wrapped] const payload = new EncryptedPayload(wrappedValue) expect(payload.content).to.be.a('string') }) it('adding account should encrypt storage with account keys', async function () { - await this.application.storage.setValueAndAwaitPersist('foo', 'bar') + await application.storage.setValueAndAwaitPersist('foo', 'bar') await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, ephemeral: true, }) - const accountKey = await this.application.encryption.getRootKey() - expect(await this.application.storage.canDecryptWithKey(accountKey)).to.equal(true) + const accountKey = await application.encryption.getRootKey() + expect(await application.storage.canDecryptWithKey(accountKey)).to.equal(true) }) it('signing out of account should decrypt storage', async function () { - await this.application.storage.setValueAndAwaitPersist('foo', 'bar') + await application.storage.setValueAndAwaitPersist('foo', 'bar') await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, ephemeral: true, }) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - await this.application.storage.setValueAndAwaitPersist('bar', 'foo') - const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] + + application = await Factory.signOutApplicationAndReturnNew(application) + + await application.storage.setValueAndAwaitPersist('bar', 'foo') + const wrappedValue = application.storage.values[ValueModesKeys.Wrapped] const payload = new DecryptedPayload(wrappedValue) + expect(payload.content).to.be.an.instanceof(Object) + + await Factory.safeDeinit(application) }) it('adding account then passcode should encrypt storage with account keys', async function () { /** Should encrypt storage with account keys and encrypt account keys with passcode */ - await this.application.storage.setValueAndAwaitPersist('foo', 'bar') + await application.storage.setValueAndAwaitPersist('foo', 'bar') await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, ephemeral: true, }) /** Should not be wrapped root key yet */ - expect(await this.application.encryption.rootKeyManager.getWrappedRootKey()).to.not.be.ok + expect(await application.encryption.rootKeyManager.getWrappedRootKey()).to.not.be.ok const passcode = '123' - Factory.handlePasswordChallenges(this.application, this.password) - await this.application.addPasscode(passcode) - await this.application.storage.setValueAndAwaitPersist('bar', 'foo') + Factory.handlePasswordChallenges(application, password) + await application.addPasscode(passcode) + await application.storage.setValueAndAwaitPersist('bar', 'foo') /** Root key should now be wrapped */ - expect(await this.application.encryption.rootKeyManager.getWrappedRootKey()).to.be.ok + expect(await application.encryption.rootKeyManager.getWrappedRootKey()).to.be.ok - const accountKey = await this.application.encryption.getRootKey() - expect(await this.application.storage.canDecryptWithKey(accountKey)).to.equal(true) - const passcodeKey = await this.application.encryption.computeWrappingKey(passcode) - const wrappedRootKey = await this.application.encryption.rootKeyManager.getWrappedRootKey() + const accountKey = await application.encryption.getRootKey() + expect(await application.storage.canDecryptWithKey(accountKey)).to.equal(true) + const passcodeKey = await application.encryption.computeWrappingKey(passcode) + const wrappedRootKey = await application.encryption.rootKeyManager.getWrappedRootKey() /** Expect that we can decrypt wrapped root key with passcode key */ const payload = new EncryptedPayload(wrappedRootKey) - const decrypted = await this.application.encryption.decryptSplitSingle({ + const decrypted = await application.encryption.decryptSplitSingle({ usesRootKey: { items: [payload], key: passcodeKey, @@ -230,9 +243,9 @@ describe('storage manager', function () { }) it('stored payloads should not contain metadata fields', async function () { - await this.application.addPasscode('123') - await Factory.createSyncedNote(this.application) - const payloads = await this.application.storage.getAllRawPayloads() + await application.addPasscode('123') + await Factory.createSyncedNote(application) + const payloads = await application.storage.getAllRawPayloads() const payload = payloads[0] expect(payload.fields).to.not.be.ok expect(payload.source).to.not.be.ok @@ -240,9 +253,9 @@ describe('storage manager', function () { }) it('storing an offline synced payload should not include dirty flag', async function () { - await this.application.addPasscode('123') - await Factory.createSyncedNote(this.application) - const payloads = await this.application.storage.getAllRawPayloads() + await application.addPasscode('123') + await Factory.createSyncedNote(application) + const payloads = await application.storage.getAllRawPayloads() const payload = payloads[0] expect(payload.dirty).to.not.be.ok @@ -250,14 +263,14 @@ describe('storage manager', function () { it('storing an online synced payload should not include dirty flag', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, ephemeral: false, }) - await Factory.createSyncedNote(this.application) - const payloads = await this.application.storage.getAllRawPayloads() + await Factory.createSyncedNote(application) + const payloads = await application.storage.getAllRawPayloads() const payload = payloads[0] expect(payload.dirty).to.not.be.ok @@ -265,29 +278,36 @@ describe('storage manager', function () { it('signing out should clear unwrapped value store', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, ephemeral: false, }) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - const values = this.application.storage.values[ValueModesKeys.Unwrapped] + application = await Factory.signOutApplicationAndReturnNew(application) + + const values = application.storage.values[ValueModesKeys.Unwrapped] expect(Object.keys(values).length).to.equal(0) + + await Factory.safeDeinit(application) }) it('signing out should clear payloads', async function () { await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, ephemeral: false, }) - await Factory.createSyncedNote(this.application) - expect(await Factory.storagePayloadCount(this.application)).to.equal(BaseItemCounts.DefaultItemsWithAccount + 1) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + await Factory.createSyncedNote(application) + expect(await Factory.storagePayloadCount(application)).to.equal(BaseItemCounts.DefaultItemsWithAccount + 1) + + application = await Factory.signOutApplicationAndReturnNew(application) + await Factory.sleep(0.1, 'Allow all untrackable singleton syncs to complete') - expect(await Factory.storagePayloadCount(this.application)).to.equal(BaseItemCounts.DefaultItems) + expect(await Factory.storagePayloadCount(application)).to.equal(BaseItemCounts.DefaultItems) + + await Factory.safeDeinit(application) }) }) diff --git a/packages/snjs/mocha/subscriptions.test.js b/packages/snjs/mocha/subscriptions.test.js index cd56e947a..8fb0862de 100644 --- a/packages/snjs/mocha/subscriptions.test.js +++ b/packages/snjs/mocha/subscriptions.test.js @@ -10,11 +10,6 @@ describe('subscriptions', function () { let context let subscriptionManager - afterEach(async function () { - await Factory.safeDeinit(application) - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -34,6 +29,11 @@ describe('subscriptions', function () { await context.activatePaidSubscriptionForUser() }) + afterEach(async function () { + await Factory.safeDeinit(application) + localStorage.clear() + }) + it('should invite a user by email to a shared subscription', async () => { await subscriptionManager.inviteToSubscription('test@test.te') diff --git a/packages/snjs/mocha/sync_tests/conflicting.test.js b/packages/snjs/mocha/sync_tests/conflicting.test.js index d25211693..0a4dc5dd9 100644 --- a/packages/snjs/mocha/sync_tests/conflicting.test.js +++ b/packages/snjs/mocha/sync_tests/conflicting.test.js @@ -3,44 +3,48 @@ import { BaseItemCounts } from '../lib/BaseItemCounts.js' import * as Factory from '../lib/factory.js' import { createSyncedNoteWithTag } from '../lib/Items.js' import * as Utils from '../lib/Utils.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('online conflict handling', function () { this.timeout(Factory.TenSecondTimeout) + let context + let application + let sharedFinalAssertions + let expectedItemCount + const syncOptions = { checkIntegrity: true, awaitAll: true, } - beforeEach(async function () { + beforeEach(async () => { localStorage.clear() - this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount + expectedItemCount = BaseItemCounts.DefaultItemsWithAccount - this.context = await Factory.createAppContextWithFakeCrypto('AppA') - await this.context.launch() + context = await Factory.createAppContextWithFakeCrypto('AppA') + await context.launch() - this.application = this.context.application - this.email = this.context.email - this.password = this.context.password + application = context.application - Factory.disableIntegrityAutoHeal(this.application) + Factory.disableIntegrityAutoHeal(application) - await this.context.register() + await context.register() - this.sharedFinalAssertions = async function () { - expect(this.application.sync.isOutOfSync()).to.equal(false) - const items = this.application.items.items - expect(items.length).to.equal(this.expectedItemCount) - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) + sharedFinalAssertions = async () => { + expect(application.sync.isOutOfSync()).to.equal(false) + const items = application.items.items + expect(items.length).to.equal(expectedItemCount) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) } }) - afterEach(async function () { - if (!this.application.dealloced) { - await this.context.deinit() + afterEach(async () => { + if (!application.dealloced) { + await context.deinit() } localStorage.clear() }) @@ -61,22 +65,22 @@ describe('online conflict handling', function () { return payload } - it('components should not be duplicated under any circumstances', async function () { + it('components should not be duplicated under any circumstances', async () => { const payload = createDirtyPayload(ContentType.TYPES.Component) - const item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) + const item = await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) - this.expectedItemCount++ + expectedItemCount++ - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) /** First modify the item without saving so that our local contents digress from the server's */ - await this.application.mutator.changeItem(item, (mutator) => { + await application.mutator.changeItem(item, (mutator) => { mutator.mutableContent.foo = `${Math.random()}` }) await Factory.changePayloadTimeStampAndSync( - this.application, + application, item.payload, Factory.dateToMicroseconds(Factory.yesterday()), { @@ -85,23 +89,23 @@ describe('online conflict handling', function () { syncOptions, ) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) - await this.sharedFinalAssertions() + expect(application.items.items.length).to.equal(expectedItemCount) + await sharedFinalAssertions() }) - it('items keys should not be duplicated under any circumstances', async function () { + it('items keys should not be duplicated under any circumstances', async () => { const payload = createDirtyPayload(ContentType.TYPES.ItemsKey) - const item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) - this.expectedItemCount++ - await this.application.sync.sync(syncOptions) + const item = await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) + expectedItemCount++ + await application.sync.sync(syncOptions) /** First modify the item without saving so that * our local contents digress from the server's */ - await this.application.mutator.changeItem(item, (mutator) => { + await application.mutator.changeItem(item, (mutator) => { mutator.title = `${Math.random()}` }) await Factory.changePayloadTimeStampAndSync( - this.application, + application, item.payload, Factory.dateToMicroseconds(Factory.yesterday()), { @@ -110,29 +114,29 @@ describe('online conflict handling', function () { syncOptions, ) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) - await this.sharedFinalAssertions() + expect(application.items.items.length).to.equal(expectedItemCount) + await sharedFinalAssertions() }) - it('should create conflicted copy if incoming server item attempts to overwrite local dirty item', async function () { + it('should create conflicted copy if incoming server item attempts to overwrite local dirty item', async () => { // create an item and sync it - const note = await Factory.createMappedNote(this.application) - this.expectedItemCount++ - await this.application.mutator.setItemDirty(note) - await this.application.sync.sync(syncOptions) + const note = await Factory.createMappedNote(application) + expectedItemCount++ + await application.mutator.setItemDirty(note) + await application.sync.sync(syncOptions) - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) const originalValue = note.title const dirtyValue = `${Math.random()}` /** Modify nonsense first to get around strategyWhenConflictingWithItem with previousRevision check */ - await this.application.mutator.changeNote(note, (mutator) => { + await application.mutator.changeNote(note, (mutator) => { mutator.title = 'any' }) - await this.application.mutator.changeNote(note, (mutator) => { + await application.mutator.changeNote(note, (mutator) => { // modify this item locally to have differing contents from server mutator.title = dirtyValue // Intentionally don't change updated_at. We want to simulate a chaotic case where @@ -141,47 +145,47 @@ describe('online conflict handling', function () { }) // Download all items from the server, which will include this note. - await this.application.sync.clearSyncPositionTokens() - await this.application.sync.sync({ + await application.sync.clearSyncPositionTokens() + await application.sync.sync({ ...syncOptions, awaitAll: true, }) // We expect this item to be duplicated - this.expectedItemCount++ - expect(this.application.items.getDisplayableNotes().length).to.equal(2) + expectedItemCount++ + expect(application.items.getDisplayableNotes().length).to.equal(2) - const allItems = this.application.items.items - expect(allItems.length).to.equal(this.expectedItemCount) + const allItems = application.items.items + expect(allItems.length).to.equal(expectedItemCount) - const originalItem = this.application.items.findItem(note.uuid) + const originalItem = application.items.findItem(note.uuid) const duplicateItem = allItems.find((i) => i.content.conflict_of === note.uuid) expect(originalItem.title).to.equal(dirtyValue) expect(duplicateItem.title).to.equal(originalValue) expect(originalItem.title).to.not.equal(duplicateItem.title) - const newRawPayloads = await this.application.storage.getAllRawPayloads() - expect(newRawPayloads.length).to.equal(this.expectedItemCount) - await this.sharedFinalAssertions() + const newRawPayloads = await application.storage.getAllRawPayloads() + expect(newRawPayloads.length).to.equal(expectedItemCount) + await sharedFinalAssertions() }) - it('should handle sync conflicts by duplicating differing data', async function () { + it('should handle sync conflicts by duplicating differing data', async () => { // create an item and sync it - const note = await Factory.createMappedNote(this.application) - await Factory.markDirtyAndSyncItem(this.application, note) - this.expectedItemCount++ + const note = await Factory.createMappedNote(application) + await Factory.markDirtyAndSyncItem(application, note) + expectedItemCount++ - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) /** First modify the item without saving so that * our local contents digress from the server's */ - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.title = `${Math.random()}` }) await Factory.changePayloadTimeStampAndSync( - this.application, + application, note.payload, Factory.dateToMicroseconds(Factory.yesterday()), { @@ -191,28 +195,28 @@ describe('online conflict handling', function () { ) // We expect this item to be duplicated - this.expectedItemCount++ - const allItems = this.application.items.items - expect(allItems.length).to.equal(this.expectedItemCount) + expectedItemCount++ + const allItems = application.items.items + expect(allItems.length).to.equal(expectedItemCount) - const note1 = this.application.items.getDisplayableNotes()[0] - const note2 = this.application.items.getDisplayableNotes()[1] + const note1 = application.items.getDisplayableNotes()[0] + const note2 = application.items.getDisplayableNotes()[1] expect(note1.content.title).to.not.equal(note2.content.title) - await this.sharedFinalAssertions() + await sharedFinalAssertions() }) - it('basic conflict with clearing local state', async function () { - const note = await Factory.createMappedNote(this.application) - await Factory.markDirtyAndSyncItem(this.application, note) - this.expectedItemCount += 1 + it('basic conflict with clearing local state', async () => { + const note = await Factory.createMappedNote(application) + await Factory.markDirtyAndSyncItem(application, note) + expectedItemCount += 1 /** First modify the item without saving so that * our local contents digress from the server's */ - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.title = `${Math.random()}` }) await Factory.changePayloadTimeStampAndSync( - this.application, + application, note.payload, Factory.dateToMicroseconds(Factory.yesterday()), { @@ -221,37 +225,37 @@ describe('online conflict handling', function () { syncOptions, ) - this.expectedItemCount++ - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expectedItemCount++ + expect(application.items.items.length).to.equal(expectedItemCount) // clear sync token, clear storage, download all items, and ensure none of them have error decrypting - await this.application.sync.clearSyncPositionTokens() - await this.application.storage.clearAllPayloads() - await this.application.payloads.resetState() - await this.application.items.resetState() - await this.application.sync.sync(syncOptions) + await application.sync.clearSyncPositionTokens() + await application.storage.clearAllPayloads() + await application.payloads.resetState() + await application.items.resetState() + await application.sync.sync(syncOptions) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) - await this.sharedFinalAssertions() + expect(application.items.items.length).to.equal(expectedItemCount) + await sharedFinalAssertions() }) - it('should duplicate item if saving a modified item and clearing our sync token', async function () { - let note = await Factory.createMappedNote(this.application) + it('should duplicate item if saving a modified item and clearing our sync token', async () => { + let note = await Factory.createMappedNote(application) - await this.application.mutator.setItemDirty(note) - await this.application.sync.sync(syncOptions) + await application.mutator.setItemDirty(note) + await application.sync.sync(syncOptions) - this.expectedItemCount++ + expectedItemCount++ const newTitle = `${Math.random()}` /** First modify the item without saving so that our local contents digress from the server's */ - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.title = `${Math.random()}` }) await Factory.changePayloadTimeStamp( - this.application, + application, note.payload, Factory.dateToMicroseconds(Factory.yesterday()), { @@ -261,44 +265,44 @@ describe('online conflict handling', function () { ) // We expect this item to be duplicated - this.expectedItemCount++ + expectedItemCount++ - await this.application.sync.clearSyncPositionTokens() - await this.application.sync.sync(syncOptions) + await application.sync.clearSyncPositionTokens() + await application.sync.sync(syncOptions) - note = this.application.items.findItem(note.uuid) + note = application.items.findItem(note.uuid) // We expect the item title to be the new title, and not rolled back to original value expect(note.content.title).to.equal(newTitle) - const allItems = this.application.items.items - expect(allItems.length).to.equal(this.expectedItemCount) - await this.sharedFinalAssertions() + const allItems = application.items.items + expect(allItems.length).to.equal(expectedItemCount) + await sharedFinalAssertions() }) - it('should handle sync conflicts by not duplicating same data', async function () { - const note = await Factory.createMappedNote(this.application) - this.expectedItemCount++ - await this.application.mutator.setItemDirty(note) - await this.application.sync.sync(syncOptions) + it('should handle sync conflicts by not duplicating same data', async () => { + const note = await Factory.createMappedNote(application) + expectedItemCount++ + await application.mutator.setItemDirty(note) + await application.sync.sync(syncOptions) // keep item as is and set dirty - await this.application.mutator.setItemDirty(note) + await application.mutator.setItemDirty(note) // clear sync token so that all items are retrieved on next sync - this.application.sync.clearSyncPositionTokens() + application.sync.clearSyncPositionTokens() - await this.application.sync.sync(syncOptions) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) - await this.sharedFinalAssertions() + await application.sync.sync(syncOptions) + expect(application.items.items.length).to.equal(expectedItemCount) + await sharedFinalAssertions() }) - it('clearing conflict_of on two clients simultaneously should keep us in sync', async function () { - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) - this.expectedItemCount++ + it('clearing conflict_of on two clients simultaneously should keep us in sync', async () => { + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) + expectedItemCount++ - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( note, (mutator) => { // client A @@ -310,8 +314,8 @@ describe('online conflict handling', function () { ) // client B - await this.application.sync.clearSyncPositionTokens() - await this.application.mutator.changeItem( + await application.sync.clearSyncPositionTokens() + await application.mutator.changeItem( note, (mutator) => { mutator.mutableContent.conflict_of = 'bar' @@ -323,16 +327,16 @@ describe('online conflict handling', function () { // conflict_of is a key to ignore when comparing content, so item should // not be duplicated. - await this.application.sync.sync(syncOptions) - await this.sharedFinalAssertions() + await application.sync.sync(syncOptions) + await sharedFinalAssertions() }) - it('setting property on two clients simultaneously should create conflict', async function () { - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) - this.expectedItemCount++ + it('setting property on two clients simultaneously should create conflict', async () => { + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) + expectedItemCount++ - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( note, (mutator) => { // client A @@ -344,14 +348,14 @@ describe('online conflict handling', function () { ) /** First modify the item without saving so that * our local contents digress from the server's */ - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.title = `${Math.random()}` }) // client B - await this.application.sync.clearSyncPositionTokens() + await application.sync.clearSyncPositionTokens() await Factory.changePayloadTimeStampAndSync( - this.application, + application, note.payload, Factory.dateToMicroseconds(Factory.yesterday()), { @@ -360,85 +364,85 @@ describe('online conflict handling', function () { syncOptions, ) - this.expectedItemCount++ + expectedItemCount++ - await this.sharedFinalAssertions() + await sharedFinalAssertions() }) - it('if server says deleted but client says not deleted, keep server state', async function () { - const note = await Factory.createMappedNote(this.application) + it('if server says deleted but client says not deleted, keep server state', async () => { + const note = await Factory.createMappedNote(application) const originalPayload = note.payloadRepresentation() - this.expectedItemCount++ - await this.application.mutator.setItemDirty(note) - await this.application.sync.sync(syncOptions) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expectedItemCount++ + await application.mutator.setItemDirty(note) + await application.sync.sync(syncOptions) + expect(application.items.items.length).to.equal(expectedItemCount) // client A - await this.application.mutator.setItemToBeDeleted(note) - await this.application.sync.sync(syncOptions) - this.expectedItemCount-- - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + await application.mutator.setItemToBeDeleted(note) + await application.sync.sync(syncOptions) + expectedItemCount-- + expect(application.items.items.length).to.equal(expectedItemCount) // client B - await this.application.sync.clearSyncPositionTokens() + await application.sync.clearSyncPositionTokens() // Add the item back and say it's not deleted const mutatedPayload = new DecryptedPayload({ ...originalPayload, deleted: false, updated_at: Factory.yesterday(), }) - await this.application.mutator.emitItemsFromPayloads([mutatedPayload], PayloadEmitSource.LocalChanged) - const resultNote = this.application.items.findItem(note.uuid) + await application.mutator.emitItemsFromPayloads([mutatedPayload], PayloadEmitSource.LocalChanged) + const resultNote = application.items.findItem(note.uuid) expect(resultNote.uuid).to.equal(note.uuid) - await this.application.mutator.setItemDirty(resultNote) - await this.application.sync.sync(syncOptions) + await application.mutator.setItemDirty(resultNote) + await application.sync.sync(syncOptions) // We expect that this item is now gone for good, and a duplicate has not been created. - expect(this.application.items.items.length).to.equal(this.expectedItemCount) - await this.sharedFinalAssertions() + expect(application.items.items.length).to.equal(expectedItemCount) + await sharedFinalAssertions() }) - it('if server says not deleted but client says deleted, keep server state', async function () { - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) - this.expectedItemCount++ + it('if server says not deleted but client says deleted, keep server state', async () => { + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) + expectedItemCount++ // client A - await this.application.sync.sync(syncOptions) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + await application.sync.sync(syncOptions) + expect(application.items.items.length).to.equal(expectedItemCount) // client B - await this.application.sync.clearSyncPositionTokens() + await application.sync.clearSyncPositionTokens() // This client says this item is deleted, but the server is saying its not deleted. // In this case, we want to keep the server copy. await Factory.changePayloadTimeStampDeleteAndSync( - this.application, + application, note.payload, Factory.dateToMicroseconds(Factory.yesterday()), syncOptions, ) // We expect that this item maintained. - expect(this.application.items.items.length).to.equal(this.expectedItemCount) - await this.sharedFinalAssertions() + expect(application.items.items.length).to.equal(expectedItemCount) + await sharedFinalAssertions() }) - it('should create conflict if syncing an item that is stale', async function () { - let note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) - await this.application.sync.sync(syncOptions) - note = this.application.items.findItem(note.uuid) + it('should create conflict if syncing an item that is stale', async () => { + let note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) + await application.sync.sync(syncOptions) + note = application.items.findItem(note.uuid) expect(note.dirty).to.equal(false) - this.expectedItemCount++ + expectedItemCount++ /** First modify the item without saving so that * our local contents digress from the server's */ - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.title = `${Math.random()}` }) note = await Factory.changePayloadTimeStampAndSync( - this.application, + application, note.payload, Factory.dateToMicroseconds(Factory.yesterday()), { @@ -450,111 +454,111 @@ describe('online conflict handling', function () { expect(note.dirty).to.equal(false) // We expect now that the item was conflicted - this.expectedItemCount++ + expectedItemCount++ - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) for (const payload of rawPayloads) { expect(payload.dirty).to.not.be.ok } - await this.sharedFinalAssertions() + await sharedFinalAssertions() }) - it('creating conflict with exactly equal content should keep us in sync', async function () { - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) - this.expectedItemCount++ + it('creating conflict with exactly equal content should keep us in sync', async () => { + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) + expectedItemCount++ - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) await Factory.changePayloadTimeStampAndSync( - this.application, + application, note.payload, Factory.dateToMicroseconds(Factory.yesterday()), {}, syncOptions, ) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) - await this.sharedFinalAssertions() + expect(application.items.items.length).to.equal(expectedItemCount) + await sharedFinalAssertions() }) /** * This test takes over 60s in a CI environment when running in Docker server. * It's much faster in a home server environment but should still be skipped for now. */ - it.skip('handles stale data in bulk', async function () { + it.skip('handles stale data in bulk', async () => { /** This number must be greater than the pagination limit per sync request. * For example if the limit per request is 150 items sent/received, this number should * be something like 160. */ const largeItemCount = SyncUpDownLimit + 10 - await Factory.createManyMappedNotes(this.application, largeItemCount) + await Factory.createManyMappedNotes(application, largeItemCount) /** Upload */ - this.application.sync.sync(syncOptions) - await this.context.awaitNextSucessfulSync() + application.sync.sync(syncOptions) + await context.awaitNextSucessfulSync() - this.expectedItemCount += largeItemCount - const items = this.application.items.items - expect(items.length).to.equal(this.expectedItemCount) + expectedItemCount += largeItemCount + const items = application.items.items + expect(items.length).to.equal(expectedItemCount) /** * We want to see what will happen if we upload everything we have to * the server as dirty, with no sync token, so that the server also * gives us everything it has. */ - this.application.sync.lockSyncing() + application.sync.lockSyncing() const yesterday = Factory.yesterday() - for (const note of this.application.items.getDisplayableNotes()) { + for (const note of application.items.getDisplayableNotes()) { /** First modify the item without saving so that * our local contents digress from the server's */ - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.text = '1' }) - await Factory.changePayloadTimeStamp(this.application, note.payload, Factory.dateToMicroseconds(yesterday), { + await Factory.changePayloadTimeStamp(application, note.payload, Factory.dateToMicroseconds(yesterday), { text: '2', }) // We expect all the notes to be duplicated. - this.expectedItemCount++ + expectedItemCount++ } - this.application.sync.unlockSyncing() + application.sync.unlockSyncing() - await this.application.sync.clearSyncPositionTokens() - this.application.sync.sync(syncOptions) - await this.context.awaitNextSucessfulSync() + await application.sync.clearSyncPositionTokens() + application.sync.sync(syncOptions) + await context.awaitNextSucessfulSync() - expect(this.application.items.getDisplayableNotes().length).to.equal(largeItemCount * 2) - await this.sharedFinalAssertions() + expect(application.items.getDisplayableNotes().length).to.equal(largeItemCount * 2) + await sharedFinalAssertions() }).timeout(60000) - it('duplicating an item should maintian its relationships', async function () { + it('duplicating an item should maintian its relationships', async () => { const payload1 = Factory.createStorageItemPayload(ContentType.TYPES.Tag) const payload2 = Factory.createStorageItemPayload(ContentType.TYPES.UserPrefs) - this.expectedItemCount -= 1 /** auto-created user preferences */ - await this.application.mutator.emitItemsFromPayloads([payload1, payload2], PayloadEmitSource.LocalChanged) - this.expectedItemCount += 2 - let tag = this.application.items.getItems(ContentType.TYPES.Tag)[0] - let userPrefs = this.application.items.getItems(ContentType.TYPES.UserPrefs)[0] + expectedItemCount -= 1 /** auto-created user preferences */ + await application.mutator.emitItemsFromPayloads([payload1, payload2], PayloadEmitSource.LocalChanged) + expectedItemCount += 2 + let tag = application.items.getItems(ContentType.TYPES.Tag)[0] + let userPrefs = application.items.getItems(ContentType.TYPES.UserPrefs)[0] expect(tag).to.be.ok expect(userPrefs).to.be.ok - tag = await this.application.mutator.changeItem(tag, (mutator) => { + tag = await application.mutator.changeItem(tag, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(userPrefs) }) - await this.application.mutator.setItemDirty(userPrefs) - userPrefs = this.application.items.findItem(userPrefs.uuid) + await application.mutator.setItemDirty(userPrefs) + userPrefs = application.items.findItem(userPrefs.uuid) - expect(this.application.items.itemsReferencingItem(userPrefs).length).to.equal(1) - expect(this.application.items.itemsReferencingItem(userPrefs)).to.include(tag) + expect(application.items.itemsReferencingItem(userPrefs).length).to.equal(1) + expect(application.items.itemsReferencingItem(userPrefs)).to.include(tag) - await this.application.sync.sync(syncOptions) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + await application.sync.sync(syncOptions) + expect(application.items.items.length).to.equal(expectedItemCount) tag = await Factory.changePayloadTimeStamp( - this.application, + application, tag.payload, Factory.dateToMicroseconds(Factory.yesterday()), { @@ -562,48 +566,48 @@ describe('online conflict handling', function () { }, ) - await this.application.sync.sync({ ...syncOptions, awaitAll: true }) + await application.sync.sync({ ...syncOptions, awaitAll: true }) // fooItem should now be conflicted and a copy created - this.expectedItemCount++ - expect(this.application.items.items.length).to.equal(this.expectedItemCount) - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) + expectedItemCount++ + expect(application.items.items.length).to.equal(expectedItemCount) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) - const fooItems = this.application.items.getItems(ContentType.TYPES.Tag) + const fooItems = application.items.getItems(ContentType.TYPES.Tag) const fooItem2 = fooItems[1] expect(fooItem2.content.conflict_of).to.equal(tag.uuid) // Two items now link to this original object - const referencingItems = this.application.items.itemsReferencingItem(userPrefs) + const referencingItems = application.items.itemsReferencingItem(userPrefs) expect(referencingItems.length).to.equal(2) expect(referencingItems[0]).to.not.equal(referencingItems[1]) - expect(this.application.items.itemsReferencingItem(tag).length).to.equal(0) - expect(this.application.items.itemsReferencingItem(fooItem2).length).to.equal(0) + expect(application.items.itemsReferencingItem(tag).length).to.equal(0) + expect(application.items.itemsReferencingItem(fooItem2).length).to.equal(0) expect(tag.content.references.length).to.equal(1) expect(fooItem2.content.references.length).to.equal(1) expect(userPrefs.content.references.length).to.equal(0) - expect(this.application.items.getDirtyItems().length).to.equal(0) - for (const item of this.application.items.items) { + expect(application.items.getDirtyItems().length).to.equal(0) + for (const item of application.items.items) { expect(item.dirty).to.not.be.ok } - await this.sharedFinalAssertions() + await sharedFinalAssertions() }) - it('when a note is conflicted, its tags should not be duplicated.', async function () { + it('when a note is conflicted, its tags should not be duplicated.', async () => { /** * If you have a note and a tag, and the tag has 1 reference to the note, * and you import the same two items, except modify the note value so that * a duplicate is created, we expect only the note to be duplicated, * and the tag not to. */ - let tag = await Factory.createMappedTag(this.application) - let note = await Factory.createMappedNote(this.application) + let tag = await Factory.createMappedTag(application) + let note = await Factory.createMappedNote(application) tag = ( - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( tag, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(note) @@ -613,21 +617,21 @@ describe('online conflict handling', function () { syncOptions, ) ).getValue() - await this.application.mutator.setItemDirty(note) - this.expectedItemCount += 2 + await application.mutator.setItemDirty(note) + expectedItemCount += 2 - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) // conflict the note const newText = `${Math.random()}` /** First modify the item without saving so that our local contents digress from the server's */ - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.title = `${Math.random()}` }) note = await Factory.changePayloadTimeStampAndSync( - this.application, + application, note.payload, Factory.dateToMicroseconds(Factory.yesterday()), { @@ -638,7 +642,7 @@ describe('online conflict handling', function () { // conflict the tag but keep its content the same tag = await Factory.changePayloadTimeStampAndSync( - this.application, + application, tag.payload, Factory.dateToMicroseconds(Factory.yesterday()), {}, @@ -649,13 +653,13 @@ describe('online conflict handling', function () { * We expect now that the total item count has went up by just 1 (the note), * and not 2 (the note and tag) */ - this.expectedItemCount += 1 - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expectedItemCount += 1 + expect(application.items.items.length).to.equal(expectedItemCount) expect(tag.content.references.length).to.equal(2) - await this.sharedFinalAssertions() + await sharedFinalAssertions() }) - it('succesful server side saving but dropped packet response should not create sync conflict', async function () { + it('succesful server side saving but dropped packet response should not create sync conflict', async () => { /** * 1. Initiate a change locally that is successfully saved by the server, but the client * drops the server response. @@ -663,35 +667,35 @@ describe('online conflict handling', function () { * * Expected result: no sync conflict is created */ - const note = await Factory.createSyncedNote(this.application) - this.expectedItemCount++ + const note = await Factory.createSyncedNote(application) + expectedItemCount++ const baseTitle = 'base title' /** Change the note */ - const noteAfterChange = await this.application.mutator.changeItem(note, (mutator) => { + const noteAfterChange = await application.mutator.changeItem(note, (mutator) => { mutator.title = baseTitle }) - await this.application.sync.sync() + await application.sync.sync() /** Simulate a dropped response by reverting the note back its post-change, pre-sync state */ - const retroNote = await this.application.mutator.emitItemFromPayload(noteAfterChange.payload) + const retroNote = await application.mutator.emitItemFromPayload(noteAfterChange.payload) expect(retroNote.serverUpdatedAt.getTime()).to.equal(noteAfterChange.serverUpdatedAt.getTime()) /** Change the item to its final title and sync */ const finalTitle = 'final title' - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.title = finalTitle }) - await this.application.sync.sync() + await application.sync.sync() /** Expect that no duplicates have been created, and that the note's title is now finalTitle */ - expect(this.application.items.getDisplayableNotes().length).to.equal(1) - const finalNote = this.application.items.findItem(note.uuid) + expect(application.items.getDisplayableNotes().length).to.equal(1) + const finalNote = application.items.findItem(note.uuid) expect(finalNote.title).to.equal(finalTitle) - await this.sharedFinalAssertions() + await sharedFinalAssertions() }) - it('receiving a decrypted item while the current local item is errored and dirty should overwrite local value', async function () { + it('receiving a decrypted item while the current local item is errored and dirty should overwrite local value', async () => { /** * An item can be marked as dirty (perhaps via a bulk dirtying operation) even if it is errored, * but it can never be sent to the server if errored. If we retrieve an item from the server @@ -701,8 +705,8 @@ describe('online conflict handling', function () { /** * Create a note and sync it with the server while its valid */ - const note = await Factory.createSyncedNote(this.application) - this.expectedItemCount++ + const note = await Factory.createSyncedNote(application) + expectedItemCount++ /** * Mark the item as dirty and errored @@ -713,13 +717,13 @@ describe('online conflict handling', function () { errorDecrypting: true, dirty: true, }) - await this.application.mutator.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged) /** * Retrieve this note from the server by clearing sync token */ - await this.application.sync.clearSyncPositionTokens() - await this.application.sync.sync({ + await application.sync.clearSyncPositionTokens() + await application.sync.sync({ ...syncOptions, awaitAll: true, }) @@ -727,16 +731,16 @@ describe('online conflict handling', function () { /** * Expect that the final result is just 1 note that is not errored */ - const resultNote = await this.application.items.findItem(note.uuid) + const resultNote = await application.items.findItem(note.uuid) expect(resultNote.errorDecrypting).to.not.be.ok - expect(this.application.items.getDisplayableNotes().length).to.equal(1) - await this.sharedFinalAssertions() + expect(application.items.getDisplayableNotes().length).to.equal(1) + await sharedFinalAssertions() }) /** This test takes too long on Docker CI */ it.skip( 'registering for account with bulk offline data belonging to another account should be error-free', - async function () { + async () => { /** * When performing a multi-page sync request where we are uploading data imported from a backup, * if the first page of the sync request returns conflicted items keys, we rotate their UUID. @@ -752,12 +756,12 @@ describe('online conflict handling', function () { /** Create bulk data belonging to another account and sync */ const largeItemCount = SyncUpDownLimit + 10 - await Factory.createManyMappedNotes(this.application, largeItemCount) - await this.application.sync.sync(syncOptions) - const priorData = this.application.items.items + await Factory.createManyMappedNotes(application, largeItemCount) + await application.sync.sync(syncOptions) + const priorData = application.items.items /** Register new account and import this same data */ - const newApp = await Factory.signOutApplicationAndReturnNew(this.application) + const newApp = await Factory.signOutApplicationAndReturnNew(application) await Factory.registerUserToApplication({ application: newApp, email: Utils.generateUuid(), @@ -771,10 +775,10 @@ describe('online conflict handling', function () { }, ).timeout(80000) - it('importing data belonging to another account should not result in duplication', async function () { + it('importing data belonging to another account should not result in duplication', async () => { /** Create primary account and export data */ - await createSyncedNoteWithTag(this.application) - let backupFile = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() + await createSyncedNoteWithTag(application) + let backupFile = await application.createEncryptedBackupFileForAutomatedDesktopBackups() /** Sort matters, and is the cause of the original issue, where tag comes before the note */ backupFile.items = [ backupFile.items.find((i) => i.content_type === ContentType.TYPES.ItemsKey), @@ -783,8 +787,8 @@ describe('online conflict handling', function () { ] backupFile = JSON.parse(JSON.stringify(backupFile)) /** Register new account and import this same data */ - const newApp = await Factory.signOutApplicationAndReturnNew(this.application) - const password = this.password + const newApp = await Factory.signOutApplicationAndReturnNew(application) + const password = context.password await Factory.registerUserToApplication({ application: newApp, email: Utils.generateUuid(), @@ -797,19 +801,19 @@ describe('online conflict handling', function () { await Factory.safeDeinit(newApp) }).timeout(10000) - it('importing notes + tags belonging to another account should keep correct associations', async function () { + it('importing notes + tags belonging to another account should keep correct associations', async () => { /** * The original issue can be replicated when an export contains a tag with two notes, * where the two notes are first listed in the backup, then the tag. */ /** Create primary account and export data */ - await createSyncedNoteWithTag(this.application) - const tag = this.application.items.getDisplayableTags()[0] - const note2 = await Factory.createMappedNote(this.application) - await this.application.changeAndSaveItem.execute(tag, (mutator) => { + await createSyncedNoteWithTag(application) + const tag = application.items.getDisplayableTags()[0] + const note2 = await Factory.createMappedNote(application) + await application.changeAndSaveItem.execute(tag, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(note2) }) - let backupFile = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() + let backupFile = await application.createEncryptedBackupFileForAutomatedDesktopBackups() backupFile.items = [ backupFile.items.find((i) => i.content_type === ContentType.TYPES.ItemsKey), backupFile.items.filter((i) => i.content_type === ContentType.TYPES.Note)[0], @@ -818,8 +822,8 @@ describe('online conflict handling', function () { ] backupFile = JSON.parse(JSON.stringify(backupFile)) /** Register new account and import this same data */ - const newApp = await Factory.signOutApplicationAndReturnNew(this.application) - const password = this.password + const newApp = await Factory.signOutApplicationAndReturnNew(application) + const password = context.password await Factory.registerUserToApplication({ application: newApp, email: Utils.generateUuid(), @@ -833,18 +837,18 @@ describe('online conflict handling', function () { await Factory.safeDeinit(newApp) }).timeout(10000) - it('server should prioritize updated_at_timestamp over updated_at for sync, if provided', async function () { + it('server should prioritize updated_at_timestamp over updated_at for sync, if provided', async () => { /** * As part of SSRB to SSJS migration, server should prefer to use updated_at_timestamp * over updated_at for sync conflict logic. The timestamps are more accurate and support * microsecond precision, versus date objects which only go up to milliseconds. */ - const note = await Factory.createSyncedNote(this.application) - this.expectedItemCount++ + const note = await Factory.createSyncedNote(application) + expectedItemCount++ /** First modify the item without saving so that * our local contents digress from the server's */ - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.title = `${Math.random()}` }) /** @@ -860,19 +864,19 @@ describe('online conflict handling', function () { }, dirty: true, }) - await this.application.mutator.emitItemFromPayload(modified) - await this.application.sync.sync() - expect(this.application.items.getDisplayableNotes().length).to.equal(1) - await this.sharedFinalAssertions() + await application.mutator.emitItemFromPayload(modified) + await application.sync.sync() + expect(application.items.getDisplayableNotes().length).to.equal(1) + await sharedFinalAssertions() }) - it('conflict should be created if updated_at_timestamp is not exactly equal to servers', async function () { - const note = await Factory.createSyncedNote(this.application) - this.expectedItemCount++ + it('conflict should be created if updated_at_timestamp is not exactly equal to servers', async () => { + const note = await Factory.createSyncedNote(application) + expectedItemCount++ /** First modify the item without saving so that * our local contents digress from the server's */ - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.title = `${Math.random()}` }) const modified = note.payload.copy({ @@ -883,14 +887,14 @@ describe('online conflict handling', function () { }, dirty: true, }) - this.expectedItemCount++ - await this.application.mutator.emitItemFromPayload(modified) - await this.application.sync.sync() - expect(this.application.items.getDisplayableNotes().length).to.equal(2) - await this.sharedFinalAssertions() + expectedItemCount++ + await application.mutator.emitItemFromPayload(modified) + await application.sync.sync() + expect(application.items.getDisplayableNotes().length).to.equal(2) + await sharedFinalAssertions() }) - it('conflict where server updated_at_timestamp is less than base updated_at should not result in infinite loop', async function () { + it('conflict where server updated_at_timestamp is less than base updated_at should not result in infinite loop', async () => { /** * While this shouldn't happen, I've seen this happen locally where a single UserPrefs object has a timestamp of A * on the server, and A + 10 on the client side. Somehow the client had a newer timestamp than the server. The @@ -900,11 +904,11 @@ describe('online conflict handling', function () { * Because this was not the case, the client kept sending up its own base timestamp and the server kept rejecting it, * and it never resolved. The fix made here was to take the server's timestamp no matter what, even if it is less than client's. */ - const note = await Factory.createSyncedNote(this.application) - this.expectedItemCount++ + const note = await Factory.createSyncedNote(application) + expectedItemCount++ /** First modify the item without saving so that our local contents digress from the server's */ - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.title = `${Math.random()}` }) const modified = note.payload.copy({ @@ -915,21 +919,21 @@ describe('online conflict handling', function () { }, dirty: true, }) - this.expectedItemCount++ - await this.application.mutator.emitItemFromPayload(modified) - await this.application.sync.sync() - expect(this.application.items.getDisplayableNotes().length).to.equal(2) - await this.sharedFinalAssertions() + expectedItemCount++ + await application.mutator.emitItemFromPayload(modified) + await application.sync.sync() + expect(application.items.getDisplayableNotes().length).to.equal(2) + await sharedFinalAssertions() }) - it('conflicting should not over resolve', async function () { + it('conflicting should not over resolve', async () => { /** * Before refactoring to use dirtyIndex instead of dirtiedDate, sometimes an item could be dirtied * and begin sync at the exact same millisecond count (at least in the tests). In which case, the item * would be stillDirty after sync and would sync again. This test ensures that an item is only synced once * after it saves changes from conflicted items. */ - const contextA = this.context + const contextA = context const contextB = await Factory.createAppContextWithFakeCrypto('AppB', contextA.email, contextA.password) contextA.disableIntegrityAutoHeal() @@ -944,7 +948,7 @@ describe('online conflict handling', function () { await contextA.changeNoteTitleAndSync(note, 'title-A') await contextB.changeNoteTitleAndSync(note, 'title-B') - this.expectedItemCount += 2 + expectedItemCount += 2 const noteAExpectedTimestamp = contextB.findNoteByTitle('title-A').payload.updated_at_timestamp const noteBExpectedTimestamp = contextB.findNoteByTitle('title-B').payload.updated_at_timestamp @@ -954,12 +958,12 @@ describe('online conflict handling', function () { expect(contextA.findNoteByTitle('title-A').payload.updated_at_timestamp).to.equal(noteAExpectedTimestamp) expect(contextA.findNoteByTitle('title-B').payload.updated_at_timestamp).to.equal(noteBExpectedTimestamp) - await this.sharedFinalAssertions() + await sharedFinalAssertions() await contextB.deinit() }).timeout(20000) - it('editing original note many times after conflict on other client should only result in 2 cumulative notes', async function () { - const contextA = this.context + it('editing original note many times after conflict on other client should only result in 2 cumulative notes', async () => { + const contextA = context const contextB = await Factory.createAppContextWithFakeCrypto('AppB', contextA.email, contextA.password) contextA.disableIntegrityAutoHeal() contextB.disableIntegrityAutoHeal() @@ -968,7 +972,7 @@ describe('online conflict handling', function () { await contextB.signIn() const { original } = await contextA.createConflictedNotes(contextB) - this.expectedItemCount += 2 + expectedItemCount += 2 expect(contextA.noteCount).to.equal(2) expect(contextB.noteCount).to.equal(2) @@ -984,7 +988,7 @@ describe('online conflict handling', function () { expect(contextA.noteCount).to.equal(2) expect(contextB.noteCount).to.equal(2) - await this.sharedFinalAssertions() + await sharedFinalAssertions() await contextB.deinit() }).timeout(20000) }) diff --git a/packages/snjs/mocha/sync_tests/integrity.test.js b/packages/snjs/mocha/sync_tests/integrity.test.js index 704ec3095..1c32cf9c4 100644 --- a/packages/snjs/mocha/sync_tests/integrity.test.js +++ b/packages/snjs/mocha/sync_tests/integrity.test.js @@ -1,31 +1,37 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import { BaseItemCounts } from '../lib/BaseItemCounts.js' import * as Factory from '../lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('sync integrity', () => { - before(function () { - localStorage.clear() - }) - - after(function () { - localStorage.clear() - }) + let application + let email + let password + let expectedItemCount beforeEach(async function () { - this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount - this.application = await Factory.createInitAppWithFakeCrypto() - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() + localStorage.clear() + expectedItemCount = BaseItemCounts.DefaultItemsWithAccount + application = await Factory.createInitAppWithFakeCrypto() + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) }) + afterEach(async function () { + expect(application.sync.isOutOfSync()).to.equal(false) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) + await Factory.safeDeinit(application) + localStorage.clear() + application = undefined + }) + const awaitSyncEventPromise = (application, targetEvent) => { return new Promise((resolve) => { application.sync.addEventObserver((event) => { @@ -36,44 +42,37 @@ describe('sync integrity', () => { }) } - afterEach(async function () { - expect(this.application.sync.isOutOfSync()).to.equal(false) - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) - await Factory.safeDeinit(this.application) - }) - it('should detect when out of sync', async function () { - const item = await this.application.mutator.emitItemFromPayload( + const item = await application.mutator.emitItemFromPayload( Factory.createNotePayload(), PayloadEmitSource.LocalChanged, ) - this.expectedItemCount++ + expectedItemCount++ - const didEnterOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.EnterOutOfSync) - await this.application.sync.sync({ checkIntegrity: true }) + const didEnterOutOfSync = awaitSyncEventPromise(application, SyncEvent.EnterOutOfSync) + await application.sync.sync({ checkIntegrity: true }) - await this.application.items.removeItemFromMemory(item) - await this.application.sync.sync({ checkIntegrity: true, awaitAll: true }) + await application.items.removeItemFromMemory(item) + await application.sync.sync({ checkIntegrity: true, awaitAll: true }) await didEnterOutOfSync }) it('should self heal after out of sync', async function () { - const item = await this.application.mutator.emitItemFromPayload( + const item = await application.mutator.emitItemFromPayload( Factory.createNotePayload(), PayloadEmitSource.LocalChanged, ) - this.expectedItemCount++ + expectedItemCount++ - const didEnterOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.EnterOutOfSync) - const didExitOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.ExitOutOfSync) + const didEnterOutOfSync = awaitSyncEventPromise(application, SyncEvent.EnterOutOfSync) + const didExitOutOfSync = awaitSyncEventPromise(application, SyncEvent.ExitOutOfSync) - await this.application.sync.sync({ checkIntegrity: true }) - await this.application.items.removeItemFromMemory(item) - await this.application.sync.sync({ checkIntegrity: true, awaitAll: true }) + await application.sync.sync({ checkIntegrity: true }) + await application.items.removeItemFromMemory(item) + await application.sync.sync({ checkIntegrity: true, awaitAll: true }) await Promise.all([didEnterOutOfSync, didExitOutOfSync]) - expect(this.application.sync.isOutOfSync()).to.equal(false) + expect(application.sync.isOutOfSync()).to.equal(false) }) }) diff --git a/packages/snjs/mocha/sync_tests/notes_tags.test.js b/packages/snjs/mocha/sync_tests/notes_tags.test.js index 8e234d588..db6f57e51 100644 --- a/packages/snjs/mocha/sync_tests/notes_tags.test.js +++ b/packages/snjs/mocha/sync_tests/notes_tags.test.js @@ -1,11 +1,12 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from '../lib/factory.js' import { createRelatedNoteTagPairPayload } from '../lib/Items.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('notes + tags syncing', function () { + let application + const syncOptions = { checkIntegrity: true, awaitAll: true, @@ -16,30 +17,31 @@ describe('notes + tags syncing', function () { }) beforeEach(async function () { - this.application = await Factory.createInitAppWithFakeCrypto() - Factory.disableIntegrityAutoHeal(this.application) + application = await Factory.createInitAppWithFakeCrypto() + Factory.disableIntegrityAutoHeal(application) const email = UuidGenerator.GenerateUuid() const password = UuidGenerator.GenerateUuid() await Factory.registerUserToApplication({ - application: this.application, + application: application, email, password, }) }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) + application = undefined }) it('syncing an item then downloading it should include items_key_id', async function () { - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) - await this.application.sync.sync(syncOptions) - await this.application.payloads.resetState() - await this.application.items.resetState() - await this.application.sync.clearSyncPositionTokens() - await this.application.sync.sync(syncOptions) - const downloadedNote = this.application.items.getDisplayableNotes()[0] + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) + await application.sync.sync(syncOptions) + await application.payloads.resetState() + await application.items.resetState() + await application.sync.clearSyncPositionTokens() + await application.sync.sync(syncOptions) + const downloadedNote = application.items.getDisplayableNotes()[0] expect(downloadedNote.items_key_id).to.not.be.ok // Allow time for waitingForKey await Factory.sleep(0.1) @@ -52,21 +54,21 @@ describe('notes + tags syncing', function () { const notePayload = pair[0] const tagPayload = pair[1] - await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - const note = this.application.items.getItems([ContentType.TYPES.Note])[0] - const tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] - expect(this.application.items.getDisplayableNotes().length).to.equal(1) - expect(this.application.items.getDisplayableTags().length).to.equal(1) + await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) + const note = application.items.getItems([ContentType.TYPES.Note])[0] + const tag = application.items.getItems([ContentType.TYPES.Tag])[0] + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableTags().length).to.equal(1) for (let i = 0; i < 9; i++) { - await this.application.mutator.setItemsDirty([note, tag]) - await this.application.sync.sync(syncOptions) - this.application.sync.clearSyncPositionTokens() + await application.mutator.setItemsDirty([note, tag]) + await application.sync.sync(syncOptions) + application.sync.clearSyncPositionTokens() expect(tag.content.references.length).to.equal(1) - expect(this.application.items.itemsReferencingItem(note).length).to.equal(1) + expect(application.items.itemsReferencingItem(note).length).to.equal(1) expect(tag.noteCount).to.equal(1) - expect(this.application.items.getDisplayableNotes().length).to.equal(1) - expect(this.application.items.getDisplayableTags().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableTags().length).to.equal(1) console.warn('Waiting 0.1s...') await Factory.sleep(0.1) } @@ -76,59 +78,59 @@ describe('notes + tags syncing', function () { const pair = createRelatedNoteTagPairPayload() const notePayload = pair[0] const tagPayload = pair[1] - await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - const originalNote = this.application.items.getDisplayableNotes()[0] - const originalTag = this.application.items.getDisplayableTags()[0] - await this.application.mutator.setItemsDirty([originalNote, originalTag]) + await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) + const originalNote = application.items.getDisplayableNotes()[0] + const originalTag = application.items.getDisplayableTags()[0] + await application.mutator.setItemsDirty([originalNote, originalTag]) - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) expect(originalTag.content.references.length).to.equal(1) expect(originalTag.noteCount).to.equal(1) - expect(this.application.items.itemsReferencingItem(originalNote).length).to.equal(1) + expect(application.items.itemsReferencingItem(originalNote).length).to.equal(1) // when signing in, all local items are cleared from storage (but kept in memory; to clear desktop logs), // then resaved with alternated uuids. - await this.application.storage.clearAllPayloads() - await this.application.sync.markAllItemsAsNeedingSyncAndPersist() + await application.storage.clearAllPayloads() + await application.sync.markAllItemsAsNeedingSyncAndPersist() - expect(this.application.items.getDisplayableNotes().length).to.equal(1) - expect(this.application.items.getDisplayableTags().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableTags().length).to.equal(1) - const note = this.application.items.getDisplayableNotes()[0] - const tag = this.application.items.getDisplayableTags()[0] + const note = application.items.getDisplayableNotes()[0] + const tag = application.items.getDisplayableTags()[0] expect(tag.content.references.length).to.equal(1) expect(note.content.references.length).to.equal(0) expect(tag.noteCount).to.equal(1) - expect(this.application.items.itemsReferencingItem(note).length).to.equal(1) + expect(application.items.itemsReferencingItem(note).length).to.equal(1) }) it('duplicating a tag should maintian its relationships', async function () { const pair = createRelatedNoteTagPairPayload() const notePayload = pair[0] const tagPayload = pair[1] - await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - let note = this.application.items.getDisplayableNotes()[0] - let tag = this.application.items.getDisplayableTags()[0] - expect(this.application.items.itemsReferencingItem(note).length).to.equal(1) + await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) + let note = application.items.getDisplayableNotes()[0] + let tag = application.items.getDisplayableTags()[0] + expect(application.items.itemsReferencingItem(note).length).to.equal(1) - await this.application.mutator.setItemsDirty([note, tag]) - await this.application.sync.sync(syncOptions) - await this.application.sync.clearSyncPositionTokens() + await application.mutator.setItemsDirty([note, tag]) + await application.sync.sync(syncOptions) + await application.sync.clearSyncPositionTokens() - note = this.application.items.findItem(note.uuid) - tag = this.application.items.findItem(tag.uuid) + note = application.items.findItem(note.uuid) + tag = application.items.findItem(tag.uuid) expect(note.dirty).to.equal(false) expect(tag.dirty).to.equal(false) - expect(this.application.items.getDisplayableNotes().length).to.equal(1) - expect(this.application.items.getDisplayableTags().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableTags().length).to.equal(1) await Factory.changePayloadTimeStampAndSync( - this.application, + application, tag.payload, Factory.dateToMicroseconds(Factory.yesterday()), { @@ -137,13 +139,13 @@ describe('notes + tags syncing', function () { syncOptions, ) - tag = this.application.items.findItem(tag.uuid) + tag = application.items.findItem(tag.uuid) // tag should now be conflicted and a copy created - expect(this.application.items.getDisplayableNotes().length).to.equal(1) - expect(this.application.items.getDisplayableTags().length).to.equal(2) + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableTags().length).to.equal(2) - const tags = this.application.items.getDisplayableTags() + const tags = application.items.getDisplayableTags() const conflictedTag = tags.find((tag) => { return !!tag.content.conflict_of }) @@ -157,11 +159,11 @@ describe('notes + tags syncing', function () { expect(conflictedTag.content.conflict_of).to.equal(originalTag.uuid) expect(conflictedTag.noteCount).to.equal(originalTag.noteCount) - expect(this.application.items.itemsReferencingItem(conflictedTag).length).to.equal(0) - expect(this.application.items.itemsReferencingItem(originalTag).length).to.equal(0) + expect(application.items.itemsReferencingItem(conflictedTag).length).to.equal(0) + expect(application.items.itemsReferencingItem(originalTag).length).to.equal(0) // Two tags now link to this note - const referencingItems = this.application.items.itemsReferencingItem(note) + const referencingItems = application.items.itemsReferencingItem(note) expect(referencingItems.length).to.equal(2) expect(referencingItems[0]).to.not.equal(referencingItems[1]) }).timeout(10000) diff --git a/packages/snjs/mocha/sync_tests/offline.test.js b/packages/snjs/mocha/sync_tests/offline.test.js index 6456f13a5..c236f0c3e 100644 --- a/packages/snjs/mocha/sync_tests/offline.test.js +++ b/packages/snjs/mocha/sync_tests/offline.test.js @@ -1,71 +1,70 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import { BaseItemCounts } from '../lib/BaseItemCounts.js' import * as Factory from '../lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('offline syncing', () => { + let context + let application + let expectedItemCount + const syncOptions = { checkIntegrity: true, awaitAll: true, } beforeEach(async function () { - this.expectedItemCount = BaseItemCounts.DefaultItems - this.context = await Factory.createAppContext() - await this.context.launch() - this.application = this.context.application + localStorage.clear() + expectedItemCount = BaseItemCounts.DefaultItems + context = await Factory.createAppContext() + await context.launch() + application = context.application }) afterEach(async function () { - expect(this.application.sync.isOutOfSync()).to.equal(false) - await Factory.safeDeinit(this.application) - }) - - before(async function () { - localStorage.clear() - }) - - after(async function () { + expect(application.sync.isOutOfSync()).to.equal(false) + await Factory.safeDeinit(application) localStorage.clear() + application = undefined + context = undefined }) it('uuid alternation should delete original payload', async function () { - const note = await Factory.createMappedNote(this.application) - this.expectedItemCount++ + const note = await Factory.createMappedNote(application) + expectedItemCount++ - await Factory.alternateUuidForItem(this.application, note.uuid) - await this.application.sync.sync(syncOptions) + await Factory.alternateUuidForItem(application, note.uuid) + await application.sync.sync(syncOptions) - const notes = this.application.items.getDisplayableNotes() + const notes = application.items.getDisplayableNotes() expect(notes.length).to.equal(1) expect(notes[0].uuid).to.not.equal(note.uuid) - const items = this.application.items.allTrackedItems() - expect(items.length).to.equal(this.expectedItemCount) + const items = application.items.allTrackedItems() + expect(items.length).to.equal(expectedItemCount) }) it('should sync item with no passcode', async function () { - let note = await Factory.createMappedNote(this.application) - expect(Uuids(this.application.items.getDirtyItems()).includes(note.uuid)) + let note = await Factory.createMappedNote(application) + expect(Uuids(application.items.getDirtyItems()).includes(note.uuid)) - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) - note = this.application.items.findItem(note.uuid) + note = application.items.findItem(note.uuid) /** In rare cases a sync can complete so fast that the dates are equal; this is ok. */ expect(note.lastSyncEnd).to.be.at.least(note.lastSyncBegan) - this.expectedItemCount++ + expectedItemCount++ - expect(this.application.items.getDirtyItems().length).to.equal(0) + expect(application.items.getDirtyItems().length).to.equal(0) - const rawPayloads2 = await this.application.storage.getAllRawPayloads() - expect(rawPayloads2.length).to.equal(this.expectedItemCount) + const rawPayloads2 = await application.storage.getAllRawPayloads() + expect(rawPayloads2.length).to.equal(expectedItemCount) - const itemsKeyRaw = (await Factory.getStoragePayloadsOfType(this.application, ContentType.TYPES.ItemsKey))[0] - const noteRaw = (await Factory.getStoragePayloadsOfType(this.application, ContentType.TYPES.Note))[0] + const itemsKeyRaw = (await Factory.getStoragePayloadsOfType(application, ContentType.TYPES.ItemsKey))[0] + const noteRaw = (await Factory.getStoragePayloadsOfType(application, ContentType.TYPES.Note))[0] /** Encrypts with default items key */ expect(typeof noteRaw.content).to.equal('string') @@ -75,30 +74,30 @@ describe('offline syncing', () => { }) it('should sync item encrypted with passcode', async function () { - await this.application.addPasscode('foobar') - await Factory.createMappedNote(this.application) - expect(this.application.items.getDirtyItems().length).to.equal(1) - const rawPayloads1 = await this.application.storage.getAllRawPayloads() - expect(rawPayloads1.length).to.equal(this.expectedItemCount) + await application.addPasscode('foobar') + await Factory.createMappedNote(application) + expect(application.items.getDirtyItems().length).to.equal(1) + const rawPayloads1 = await application.storage.getAllRawPayloads() + expect(rawPayloads1.length).to.equal(expectedItemCount) - await this.application.sync.sync(syncOptions) - this.expectedItemCount++ + await application.sync.sync(syncOptions) + expectedItemCount++ - expect(this.application.items.getDirtyItems().length).to.equal(0) - const rawPayloads2 = await this.application.storage.getAllRawPayloads() - expect(rawPayloads2.length).to.equal(this.expectedItemCount) + expect(application.items.getDirtyItems().length).to.equal(0) + const rawPayloads2 = await application.storage.getAllRawPayloads() + expect(rawPayloads2.length).to.equal(expectedItemCount) const payload = rawPayloads2[0] expect(typeof payload.content).to.equal('string') - expect(payload.content.startsWith(this.application.encryption.getLatestVersion())).to.equal(true) + expect(payload.content.startsWith(application.encryption.getLatestVersion())).to.equal(true) }) it('signing out while offline should succeed', async function () { - await Factory.createMappedNote(this.application) - this.expectedItemCount++ - await this.application.sync.sync(syncOptions) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - expect(this.application.sessions.isSignedIn()).to.equal(false) - expect(this.application.sessions.getUser()).to.not.be.ok + await Factory.createMappedNote(application) + expectedItemCount++ + await application.sync.sync(syncOptions) + application = await Factory.signOutApplicationAndReturnNew(application) + expect(application.sessions.isSignedIn()).to.equal(false) + expect(application.sessions.getUser()).to.not.be.ok }) }) diff --git a/packages/snjs/mocha/sync_tests/online.test.js b/packages/snjs/mocha/sync_tests/online.test.js index ff7d8220b..2dd6c639e 100644 --- a/packages/snjs/mocha/sync_tests/online.test.js +++ b/packages/snjs/mocha/sync_tests/online.test.js @@ -1,13 +1,19 @@ -/* eslint-disable no-undef */ import { BaseItemCounts } from '../lib/BaseItemCounts.js' import * as Factory from '../lib/factory.js' import * as Utils from '../lib/Utils.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('online syncing', function () { this.timeout(Factory.TenSecondTimeout) + let application + let email + let password + let expectedItemCount + let context + const syncOptions = { checkIntegrity: true, awaitAll: true, @@ -15,42 +21,37 @@ describe('online syncing', function () { beforeEach(async function () { localStorage.clear() - this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount + expectedItemCount = BaseItemCounts.DefaultItemsWithAccount - this.context = await Factory.createAppContext() - await this.context.launch() + context = await Factory.createAppContext() + await context.launch() - this.application = this.context.application - this.email = this.context.email - this.password = this.context.password + application = context.application + email = context.email + password = context.password - Factory.disableIntegrityAutoHeal(this.application) + Factory.disableIntegrityAutoHeal(application) await Factory.registerUserToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - - this.signOut = async () => { - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - } - - this.signIn = async () => { - await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) - } }) afterEach(async function () { - expect(this.application.sync.isOutOfSync()).to.equal(false) + expect(application.sync.isOutOfSync()).to.equal(false) - const items = this.application.items.allTrackedItems() - expect(items.length).to.equal(this.expectedItemCount) + const items = application.items.allTrackedItems() + expect(items.length).to.equal(expectedItemCount) - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) - await Factory.safeDeinit(this.application) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) + await Factory.safeDeinit(application) localStorage.clear() + + application = undefined + context = undefined }) function noteObjectsFromObjects(items) { @@ -58,13 +59,13 @@ describe('online syncing', function () { } it('should register and sync basic model online', async function () { - let note = await Factory.createSyncedNote(this.application) - this.expectedItemCount++ - expect(this.application.items.getDirtyItems().length).to.equal(0) - note = this.application.items.findItem(note.uuid) + let note = await Factory.createSyncedNote(application) + expectedItemCount++ + expect(application.items.getDirtyItems().length).to.equal(0) + note = application.items.findItem(note.uuid) expect(note.dirty).to.not.be.ok - const rawPayloads = await this.application.storage.getAllRawPayloads() + const rawPayloads = await application.storage.getAllRawPayloads() const notePayloads = noteObjectsFromObjects(rawPayloads) expect(notePayloads.length).to.equal(1) for (const rawNote of notePayloads) { @@ -73,17 +74,17 @@ describe('online syncing', function () { }) it('should login and retrieve synced item', async function () { - const note = await Factory.createSyncedNote(this.application) - this.expectedItemCount++ - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + const note = await Factory.createSyncedNote(application) + expectedItemCount++ + application = await Factory.signOutApplicationAndReturnNew(application) await Factory.loginToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - const notes = this.application.items.getDisplayableNotes() + const notes = application.items.getDisplayableNotes() expect(notes.length).to.equal(1) expect(notes[0].title).to.equal(note.title) }) @@ -91,27 +92,27 @@ describe('online syncing', function () { it('can complete multipage sync on sign in', async function () { const count = 0 - await Factory.createManyMappedNotes(this.application, count) + await Factory.createManyMappedNotes(application, count) - this.expectedItemCount += count + expectedItemCount += count - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) - this.application = await this.context.signout() + application = await context.signout() - expect(this.application.items.items.length).to.equal(BaseItemCounts.DefaultItems) + expect(application.items.items.length).to.equal(BaseItemCounts.DefaultItems) const promise = Factory.loginToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) /** Throw in some random syncs to cause trouble */ const syncCount = 30 for (let i = 0; i < syncCount; i++) { - this.application.sync.sync(syncOptions) + application.sync.sync(syncOptions) await Factory.sleep(0.01) } await promise @@ -122,17 +123,17 @@ describe('online syncing', function () { }).timeout(20000) it('having offline data then signing in should not alternate uuid and merge with account', async function () { - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - const note = await Factory.createMappedNote(this.application) - this.expectedItemCount++ + application = await Factory.signOutApplicationAndReturnNew(application) + const note = await Factory.createMappedNote(application) + expectedItemCount++ await Factory.loginToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, mergeLocal: true, }) - const notes = this.application.items.getDisplayableNotes() + const notes = application.items.getDisplayableNotes() expect(notes.length).to.equal(1) /** uuid should have been alternated */ expect(notes[0].uuid).to.equal(note.uuid) @@ -143,8 +144,8 @@ describe('online syncing', function () { let successes = 0 let events = 0 - this.application.sync.ut_beginLatencySimulator(250) - this.application.sync.addEventObserver((event, data) => { + application.sync.ut_beginLatencySimulator(250) + application.sync.addEventObserver((event, data) => { if (event === SyncEvent.SyncCompletedWithAllItemsUploaded) { events++ } @@ -153,7 +154,7 @@ describe('online syncing', function () { const promises = [] for (let i = 0; i < syncCount; i++) { promises.push( - this.application.sync + application.sync .sync({ queueStrategy: SyncQueueStrategy.ResolveOnNext, }) @@ -169,7 +170,7 @@ describe('online syncing', function () { // We don't know how many will execute above. expect(events).to.be.at.least(1) - this.application.sync.ut_endLatencySimulator() + application.sync.ut_endLatencySimulator() // Since the syncs all happen after one another, extra syncs may be queued on that we are not awaiting. await Factory.sleep(0.5) }) @@ -179,9 +180,9 @@ describe('online syncing', function () { let successes = 0 let events = 0 - this.application.sync.ut_beginLatencySimulator(250) + application.sync.ut_beginLatencySimulator(250) - this.application.sync.addEventObserver((event, data) => { + application.sync.addEventObserver((event, data) => { if (event === SyncEvent.SyncCompletedWithAllItemsUploaded) { events++ } @@ -190,7 +191,7 @@ describe('online syncing', function () { const promises = [] for (let i = 0; i < syncCount; i++) { promises.push( - this.application.sync + application.sync .sync({ queueStrategy: SyncQueueStrategy.ForceSpawnNew, }) @@ -202,18 +203,18 @@ describe('online syncing', function () { await Promise.all(promises) expect(successes).to.equal(syncCount) expect(events).to.equal(syncCount) - this.application.sync.ut_endLatencySimulator() + application.sync.ut_endLatencySimulator() }) it('retrieving new items should not mark them as dirty', async function () { - const originalNote = await Factory.createSyncedNote(this.application) - this.expectedItemCount++ + const originalNote = await Factory.createSyncedNote(application) + expectedItemCount++ - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + application = await Factory.signOutApplicationAndReturnNew(application) const promise = new Promise((resolve) => { - this.application.sync.addEventObserver(async (event) => { + application.sync.addEventObserver(async (event) => { if (event === SyncEvent.PaginatedSyncRequestCompleted) { - const note = this.application.items.findItem(originalNote.uuid) + const note = application.items.findItem(originalNote.uuid) if (note) { expect(note.dirty).to.not.be.ok resolve() @@ -221,45 +222,45 @@ describe('online syncing', function () { } }) }) - await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) + await application.signIn(email, password, undefined, undefined, undefined, true) await promise }) it('allows saving of data after sign out', async function () { - expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) - const note = await Factory.createMappedNote(this.application) - this.expectedItemCount++ - await this.application.mutator.setItemDirty(note) - await this.application.sync.sync(syncOptions) - const rawPayloads = await this.application.storage.getAllRawPayloads() + expect(application.items.getDisplayableItemsKeys().length).to.equal(1) + application = await Factory.signOutApplicationAndReturnNew(application) + expect(application.items.getDisplayableItemsKeys().length).to.equal(1) + const note = await Factory.createMappedNote(application) + expectedItemCount++ + await application.mutator.setItemDirty(note) + await application.sync.sync(syncOptions) + const rawPayloads = await application.storage.getAllRawPayloads() const notePayload = noteObjectsFromObjects(rawPayloads) expect(notePayload.length).to.equal(1) - expect(this.application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(1) // set item to be merged for when sign in occurs - await this.application.sync.markAllItemsAsNeedingSyncAndPersist() - expect(this.application.sync.isOutOfSync()).to.equal(false) - expect(this.application.items.getDirtyItems().length).to.equal(BaseItemCounts.DefaultItems + 1) + await application.sync.markAllItemsAsNeedingSyncAndPersist() + expect(application.sync.isOutOfSync()).to.equal(false) + expect(application.items.getDirtyItems().length).to.equal(BaseItemCounts.DefaultItems + 1) // Sign back in for next tests await Factory.loginToApplication({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, }) - expect(this.application.items.getDirtyItems().length).to.equal(0) - expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) - expect(this.application.sync.isOutOfSync()).to.equal(false) - expect(this.application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDirtyItems().length).to.equal(0) + expect(application.items.getDisplayableItemsKeys().length).to.equal(1) + expect(application.sync.isOutOfSync()).to.equal(false) + expect(application.items.getDisplayableNotes().length).to.equal(1) - for (const item of this.application.items.getDisplayableNotes()) { + for (const item of application.items.getDisplayableNotes()) { expect(item.content.title).to.be.ok } - const updatedRawPayloads = await this.application.storage.getAllRawPayloads() + const updatedRawPayloads = await application.storage.getAllRawPayloads() for (const payload of updatedRawPayloads) { // if an item comes back from the server, it is saved to disk immediately without a dirty value. expect(payload.dirty).to.not.be.ok @@ -267,17 +268,17 @@ describe('online syncing', function () { }) it('mapping should not mutate items with error decrypting state', async function () { - const note = await Factory.createMappedNote(this.application) + const note = await Factory.createMappedNote(application) - this.expectedItemCount++ + expectedItemCount++ const originalTitle = note.content.title - await this.application.mutator.setItemDirty(note) - await this.application.sync.sync(syncOptions) + await application.mutator.setItemDirty(note) + await application.sync.sync(syncOptions) const encrypted = CreateEncryptedServerSyncPushPayload( - await this.application.encryption.encryptSplitSingle({ + await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note.payloadRepresentation()], }, @@ -289,19 +290,19 @@ describe('online syncing', function () { errorDecrypting: true, }) - const items = await this.application.mutator.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged) + const items = await application.mutator.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged) - const mappedItem = this.application.items.findAnyItem(errorred.uuid) + const mappedItem = application.items.findAnyItem(errorred.uuid) expect(typeof mappedItem.content).to.equal('string') - const decryptedPayload = await this.application.encryption.decryptSplitSingle({ + const decryptedPayload = await application.encryption.decryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [errorred], }, }) - const mappedItems2 = await this.application.mutator.emitItemsFromPayloads( + const mappedItems2 = await application.mutator.emitItemsFromPayloads( [decryptedPayload], PayloadEmitSource.LocalChanged, ) @@ -312,135 +313,135 @@ describe('online syncing', function () { }) it('signing into account with pre-existing items', async function () { - const note = await Factory.createMappedNote(this.application) - await Factory.markDirtyAndSyncItem(this.application, note) - this.expectedItemCount += 1 + const note = await Factory.createMappedNote(application) + await Factory.markDirtyAndSyncItem(application, note) + expectedItemCount += 1 - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) + application = await Factory.signOutApplicationAndReturnNew(application) + await application.signIn(email, password, undefined, undefined, undefined, true) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) }) it('removes item from storage upon deletion', async function () { - let note = await Factory.createMappedNote(this.application) - this.expectedItemCount++ + let note = await Factory.createMappedNote(application) + expectedItemCount++ - await this.application.mutator.setItemDirty(note) - await this.application.sync.sync(syncOptions) + await application.mutator.setItemDirty(note) + await application.sync.sync(syncOptions) - note = this.application.items.findItem(note.uuid) + note = application.items.findItem(note.uuid) expect(note.dirty).to.equal(false) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) - await this.application.mutator.setItemToBeDeleted(note) - note = this.application.items.findAnyItem(note.uuid) + await application.mutator.setItemToBeDeleted(note) + note = application.items.findAnyItem(note.uuid) expect(note.dirty).to.equal(true) - this.expectedItemCount-- + expectedItemCount-- - await this.application.sync.sync(syncOptions) - note = this.application.items.findItem(note.uuid) + await application.sync.sync(syncOptions) + note = application.items.findItem(note.uuid) expect(note).to.not.be.ok // We expect that this item is now gone for good, and no duplicate has been created. - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) await Factory.sleep(0.5) - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) }) it('retrieving item with no content should correctly map local state', async function () { - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) - await this.application.sync.sync(syncOptions) + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) + await application.sync.sync(syncOptions) - const syncToken = await this.application.sync.getLastSyncToken() + const syncToken = await application.sync.getLastSyncToken() - this.expectedItemCount++ - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expectedItemCount++ + expect(application.items.items.length).to.equal(expectedItemCount) // client A - await this.application.mutator.setItemToBeDeleted(note) - await this.application.sync.sync(syncOptions) + await application.mutator.setItemToBeDeleted(note) + await application.sync.sync(syncOptions) // Subtract 1 - this.expectedItemCount-- + expectedItemCount-- // client B // Clearing sync tokens wont work as server wont return deleted items. // Set saved sync token instead - await this.application.sync.setLastSyncToken(syncToken) - await this.application.sync.sync(syncOptions) + await application.sync.setLastSyncToken(syncToken) + await application.sync.sync(syncOptions) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) }) it('changing an item while it is being synced should sync again', async function () { - const note = await Factory.createMappedNote(this.application) + const note = await Factory.createMappedNote(application) - this.expectedItemCount++ + expectedItemCount++ /** Begin syncing it with server but introduce latency so we can sneak in a delete */ - this.application.sync.ut_beginLatencySimulator(500) + application.sync.ut_beginLatencySimulator(500) - const sync = this.application.sync.sync() + const sync = application.sync.sync() /** Sleep so sync call can begin preparations but not fully begin */ await Factory.sleep(0.1) - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.title = 'latest title' }) await sync - this.application.sync.ut_endLatencySimulator() + application.sync.ut_endLatencySimulator() - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) - const latestNote = this.application.items.findItem(note.uuid) + const latestNote = application.items.findItem(note.uuid) expect(latestNote.title).to.equal('latest title') }) it('deleting an item while it is being synced should keep deletion state', async function () { - const note = await Factory.createMappedNote(this.application) + const note = await Factory.createMappedNote(application) - this.expectedItemCount++ + expectedItemCount++ /** Begin syncing it with server but introduce latency so we can sneak in a delete */ - this.application.sync.ut_beginLatencySimulator(500) + application.sync.ut_beginLatencySimulator(500) - const sync = this.application.sync.sync() + const sync = application.sync.sync() /** Sleep so sync call can begin preparations but not fully begin */ await Factory.sleep(0.1) - await this.application.mutator.setItemToBeDeleted(note) + await application.mutator.setItemToBeDeleted(note) - this.expectedItemCount-- + expectedItemCount-- await sync - this.application.sync.ut_endLatencySimulator() + application.sync.ut_endLatencySimulator() - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) /** We expect that item has been deleted */ - const allItems = this.application.items.items - expect(allItems.length).to.equal(this.expectedItemCount) + const allItems = application.items.items + expect(allItems.length).to.equal(expectedItemCount) }) it('items that are never synced and deleted should not be uploaded to server', async function () { - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) - await this.application.mutator.setItemToBeDeleted(note) + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) + await application.mutator.setItemToBeDeleted(note) let success = true let didCompleteRelevantSync = false let beginCheckingResponse = false - this.application.sync.addEventObserver((eventName, data) => { + application.sync.addEventObserver((eventName, data) => { if (eventName === SyncEvent.DownloadFirstSyncCompleted) { beginCheckingResponse = true } @@ -456,22 +457,22 @@ describe('online syncing', function () { } } }) - await this.application.sync.sync({ mode: SyncMode.DownloadFirst }) + await application.sync.sync({ mode: SyncMode.DownloadFirst }) expect(didCompleteRelevantSync).to.equal(true) expect(success).to.equal(true) }) it('items that are deleted after download first sync complete should not be uploaded to server', async function () { /** The singleton manager may delete items are download first. We dont want those uploaded to server. */ - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) let success = true let didCompleteRelevantSync = false let beginCheckingResponse = false - this.application.sync.addEventObserver(async (eventName, data) => { + application.sync.addEventObserver(async (eventName, data) => { if (eventName === SyncEvent.DownloadFirstSyncCompleted) { - await this.application.mutator.setItemToBeDeleted(note) + await application.mutator.setItemToBeDeleted(note) beginCheckingResponse = true } if (!beginCheckingResponse) { @@ -486,26 +487,26 @@ describe('online syncing', function () { } } }) - await this.application.sync.sync({ mode: SyncMode.DownloadFirst }) + await application.sync.sync({ mode: SyncMode.DownloadFirst }) expect(didCompleteRelevantSync).to.equal(true) expect(success).to.equal(true) }) it('marking an item dirty then saving to disk should retain that dirty state when restored', async function () { - const note = await Factory.createMappedNote(this.application) + const note = await Factory.createMappedNote(application) - this.expectedItemCount++ + expectedItemCount++ - await this.application.sync.markAllItemsAsNeedingSyncAndPersist() + await application.sync.markAllItemsAsNeedingSyncAndPersist() - this.application.items.resetState() - this.application.payloads.resetState() + application.items.resetState() + application.payloads.resetState() - await this.application.sync.clearSyncPositionTokens() + await application.sync.clearSyncPositionTokens() - expect(this.application.items.items.length).to.equal(0) + expect(application.items.items.length).to.equal(0) - const rawPayloads = await this.application.storage.getAllRawPayloads() + const rawPayloads = await application.storage.getAllRawPayloads() const encryptedPayloads = rawPayloads.map((rawPayload) => { return new EncryptedPayload(rawPayload) @@ -515,92 +516,92 @@ describe('online syncing', function () { const keyedSplit = CreateDecryptionSplitWithKeyLookup(encryptionSplit) - const decryptionResults = await this.application.encryption.decryptSplit(keyedSplit) + const decryptionResults = await application.encryption.decryptSplit(keyedSplit) - await this.application.mutator.emitItemsFromPayloads(decryptionResults, PayloadEmitSource.LocalChanged) + await application.mutator.emitItemsFromPayloads(decryptionResults, PayloadEmitSource.LocalChanged) - expect(this.application.items.allTrackedItems().length).to.equal(this.expectedItemCount) + expect(application.items.allTrackedItems().length).to.equal(expectedItemCount) - const foundNote = this.application.items.findAnyItem(note.uuid) + const foundNote = application.items.findAnyItem(note.uuid) expect(foundNote.dirty).to.equal(true) - await this.application.sync.sync(syncOptions) + await application.sync.sync(syncOptions) }) /** This test takes 30s+ on a Docker server environment and should be skipped for now */ it.skip('should handle uploading with sync pagination', async function () { const largeItemCount = SyncUpDownLimit + 10 for (let i = 0; i < largeItemCount; i++) { - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) } - this.expectedItemCount += largeItemCount + expectedItemCount += largeItemCount - await this.application.sync.sync(syncOptions) - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) + await application.sync.sync(syncOptions) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) }).timeout(Factory.SixtySecondTimeout) /** This test takes 30s+ on a Docker server environment and should be skipped for now */ it.skip('should handle downloading with sync pagination', async function () { const largeItemCount = SyncUpDownLimit + 10 for (let i = 0; i < largeItemCount; i++) { - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) } /** Upload */ - this.application.sync.sync({ awaitAll: true, checkIntegrity: false }) - await this.context.awaitNextSucessfulSync() - this.expectedItemCount += largeItemCount + application.sync.sync({ awaitAll: true, checkIntegrity: false }) + await context.awaitNextSucessfulSync() + expectedItemCount += largeItemCount /** Clear local data */ - await this.application.payloads.resetState() - await this.application.items.resetState() - await this.application.sync.clearSyncPositionTokens() - await this.application.storage.clearAllPayloads() - expect(this.application.items.items.length).to.equal(0) + await application.payloads.resetState() + await application.items.resetState() + await application.sync.clearSyncPositionTokens() + await application.storage.clearAllPayloads() + expect(application.items.items.length).to.equal(0) /** Download all data */ - this.application.sync.sync(syncOptions) - await this.context.awaitNextSucessfulSync() - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + application.sync.sync(syncOptions) + await context.awaitNextSucessfulSync() + expect(application.items.items.length).to.equal(expectedItemCount) - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) }).timeout(Factory.SixtySecondTimeout) it('syncing an item should storage it encrypted', async function () { - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) - await this.application.sync.sync(syncOptions) - this.expectedItemCount++ - const rawPayloads = await this.application.storage.getAllRawPayloads() + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) + await application.sync.sync(syncOptions) + expectedItemCount++ + const rawPayloads = await application.storage.getAllRawPayloads() const notePayload = rawPayloads.find((p) => p.content_type === ContentType.TYPES.Note) expect(typeof notePayload.content).to.equal('string') }) it('syncing an item before data load should storage it encrypted', async function () { - const note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) - this.expectedItemCount++ + const note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) + expectedItemCount++ /** Simulate database not loaded */ - await this.application.sync.clearSyncPositionTokens() - this.application.sync.ut_setDatabaseLoaded(false) - this.application.sync.sync(syncOptions) + await application.sync.clearSyncPositionTokens() + application.sync.ut_setDatabaseLoaded(false) + application.sync.sync(syncOptions) await Factory.sleep(0.3) - const rawPayloads = await this.application.storage.getAllRawPayloads() + const rawPayloads = await application.storage.getAllRawPayloads() const notePayload = rawPayloads.find((p) => p.content_type === ContentType.TYPES.Note) expect(typeof notePayload.content).to.equal('string') }) it('saving an item after sync should persist it with content property', async function () { - const note = await Factory.createMappedNote(this.application) + const note = await Factory.createMappedNote(application) const text = Factory.randomString(10000) - await this.application.changeAndSaveItem.execute( + await application.changeAndSaveItem.execute( note, (mutator) => { mutator.text = text @@ -609,52 +610,52 @@ describe('online syncing', function () { undefined, syncOptions, ) - this.expectedItemCount++ - const rawPayloads = await this.application.storage.getAllRawPayloads() + expectedItemCount++ + const rawPayloads = await application.storage.getAllRawPayloads() const notePayload = rawPayloads.find((p) => p.content_type === ContentType.TYPES.Note) expect(typeof notePayload.content).to.equal('string') expect(notePayload.content.length).to.be.above(text.length) }) it('syncing a new item before local data has loaded should still persist the item to disk', async function () { - this.application.sync.ut_setDatabaseLoaded(false) + application.sync.ut_setDatabaseLoaded(false) /** You don't want to clear model manager state as we'll lose encrypting items key */ - // await this.application.payloads.resetState(); - await this.application.sync.clearSyncPositionTokens() - expect(this.application.items.getDirtyItems().length).to.equal(0) + // await application.payloads.resetState(); + await application.sync.clearSyncPositionTokens() + expect(application.items.getDirtyItems().length).to.equal(0) - let note = await Factory.createMappedNote(this.application) - note = await this.application.mutator.changeItem(note, (mutator) => { + let note = await Factory.createMappedNote(application) + note = await application.mutator.changeItem(note, (mutator) => { mutator.text = `${Math.random()}` }) /** This sync request should exit prematurely as we called ut_setDatabaseNotLoaded */ /** Do not await. Sleep instead. */ - this.application.sync.sync(syncOptions) + application.sync.sync(syncOptions) await Factory.sleep(0.3) - this.expectedItemCount++ + expectedItemCount++ /** Item should still be dirty */ expect(note.dirty).to.equal(true) - expect(this.application.items.getDirtyItems().length).to.equal(1) + expect(application.items.getDirtyItems().length).to.equal(1) - const rawPayloads = await this.application.storage.getAllRawPayloads() - expect(rawPayloads.length).to.equal(this.expectedItemCount) + const rawPayloads = await application.storage.getAllRawPayloads() + expect(rawPayloads.length).to.equal(expectedItemCount) const rawPayload = rawPayloads.find((p) => p.uuid === note.uuid) expect(rawPayload.uuid).to.equal(note.uuid) expect(rawPayload.dirty).equal(true) expect(typeof rawPayload.content).to.equal('string') /** Clear state data and upload item from storage to server */ - await this.application.sync.clearSyncPositionTokens() - await this.application.payloads.resetState() - await this.application.items.resetState() - await this.application.sync.loadDatabasePayloads() - await this.application.sync.sync(syncOptions) + await application.sync.clearSyncPositionTokens() + await application.payloads.resetState() + await application.items.resetState() + await application.sync.loadDatabasePayloads() + await application.sync.sync(syncOptions) - const newRawPayloads = await this.application.storage.getAllRawPayloads() - expect(newRawPayloads.length).to.equal(this.expectedItemCount) + const newRawPayloads = await application.storage.getAllRawPayloads() + expect(newRawPayloads.length).to.equal(expectedItemCount) - const currentItem = this.application.items.findItem(note.uuid) + const currentItem = application.items.findItem(note.uuid) expect(currentItem.content.text).to.equal(note.content.text) expect(currentItem.text).to.equal(note.text) expect(currentItem.dirty).to.not.be.ok @@ -678,38 +679,38 @@ describe('online syncing', function () { it('should sign in and retrieve large number of items', async function () { const largeItemCount = 50 - await Factory.createManyMappedNotes(this.application, largeItemCount) - this.expectedItemCount += largeItemCount - await this.application.sync.sync(syncOptions) + await Factory.createManyMappedNotes(application, largeItemCount) + expectedItemCount += largeItemCount + await application.sync.sync(syncOptions) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) + application = await Factory.signOutApplicationAndReturnNew(application) + await application.signIn(email, password, undefined, undefined, undefined, true) - this.application.sync.ut_setDatabaseLoaded(false) - await this.application.sync.loadDatabasePayloads() - await this.application.sync.sync(syncOptions) + application.sync.ut_setDatabaseLoaded(false) + await application.sync.loadDatabasePayloads() + await application.sync.sync(syncOptions) - const items = await this.application.items.items - expect(items.length).to.equal(this.expectedItemCount) + const items = await application.items.items + expect(items.length).to.equal(expectedItemCount) }).timeout(20000) it('valid sync date tracking', async function () { - let note = await Factory.createMappedNote(this.application) - note = await this.application.mutator.setItemDirty(note) - this.expectedItemCount++ + let note = await Factory.createMappedNote(application) + note = await application.mutator.setItemDirty(note) + expectedItemCount++ expect(note.dirty).to.equal(true) expect(note.payload.dirtyIndex).to.be.at.most(getCurrentDirtyIndex()) - note = await this.application.mutator.changeItem(note, (mutator) => { + note = await application.mutator.changeItem(note, (mutator) => { mutator.text = `${Math.random()}` }) - const sync = this.application.sync.sync(syncOptions) + const sync = application.sync.sync(syncOptions) await Factory.sleep(0.1) - note = this.application.items.findItem(note.uuid) + note = application.items.findItem(note.uuid) expect(note.lastSyncBegan).to.be.below(new Date()) await sync - note = this.application.items.findItem(note.uuid) + note = application.items.findItem(note.uuid) expect(note.dirty).to.equal(false) expect(note.lastSyncEnd).to.be.at.least(note.lastSyncBegan) }) @@ -717,13 +718,13 @@ describe('online syncing', function () { it('syncing twice without waiting should only execute 1 online sync', async function () { const expectedEvents = 1 let actualEvents = 0 - this.application.sync.addEventObserver((event, data) => { + application.sync.addEventObserver((event, data) => { if (event === SyncEvent.SyncCompletedWithAllItemsUploaded && data.source === SyncSource.External) { actualEvents++ } }) - const first = this.application.sync.sync() - const second = this.application.sync.sync() + const first = application.sync.sync() + const second = application.sync.sync() await Promise.all([first, second]) /** Sleep so that any automatic syncs that are triggered are also sent to handler above */ await Factory.sleep(0.5) @@ -737,47 +738,47 @@ describe('online syncing', function () { * When that completes, it will decide whether an item is still dirty or not. * It will do based on comparing whether item.dirtyIndex > item.globalDirtyIndexAtLastSync */ - let note = await Factory.createMappedNote(this.application) - await this.application.mutator.setItemDirty(note) - this.expectedItemCount++ + let note = await Factory.createMappedNote(application) + await application.mutator.setItemDirty(note) + expectedItemCount++ // client A. Don't await, we want to do other stuff. - this.application.sync.ut_beginLatencySimulator(1500) - const slowSync = this.application.sync.sync(syncOptions) + application.sync.ut_beginLatencySimulator(1500) + const slowSync = application.sync.sync(syncOptions) await Factory.sleep(0.1) expect(note.dirty).to.equal(true) // While that sync is going on, we want to modify this item many times. const text = `${Math.random()}` - note = await this.application.mutator.changeItem(note, (mutator) => { + note = await application.mutator.changeItem(note, (mutator) => { mutator.text = text }) - await this.application.mutator.setItemDirty(note) - await this.application.mutator.setItemDirty(note) - await this.application.mutator.setItemDirty(note) + await application.mutator.setItemDirty(note) + await application.mutator.setItemDirty(note) + await application.mutator.setItemDirty(note) expect(note.payload.dirtyIndex).to.be.above(note.payload.globalDirtyIndexAtLastSync) // Now do a regular sync with no latency. - this.application.sync.ut_endLatencySimulator() - const midSync = this.application.sync.sync(syncOptions) + application.sync.ut_endLatencySimulator() + const midSync = application.sync.sync(syncOptions) await slowSync await midSync - note = this.application.items.findItem(note.uuid) + note = application.items.findItem(note.uuid) expect(note.dirty).to.equal(false) expect(note.lastSyncEnd).to.be.above(note.lastSyncBegan) expect(note.content.text).to.equal(text) // client B - await this.application.payloads.resetState() - await this.application.items.resetState() - await this.application.sync.clearSyncPositionTokens() - await this.application.sync.sync(syncOptions) + await application.payloads.resetState() + await application.items.resetState() + await application.sync.clearSyncPositionTokens() + await application.sync.sync(syncOptions) // Expect that the server value and client value match, and no conflicts are created. - expect(this.application.items.items.length).to.equal(this.expectedItemCount) - const foundItem = this.application.items.findItem(note.uuid) + expect(application.items.items.length).to.equal(expectedItemCount) + const foundItem = application.items.findItem(note.uuid) expect(foundItem.content.text).to.equal(text) expect(foundItem.text).to.equal(text) }) @@ -788,26 +789,26 @@ describe('online syncing', function () { let actualSaveCount = 0 /** Create an item and sync it */ - let note = await Factory.createMappedNote(this.application) + let note = await Factory.createMappedNote(application) - this.application.items.addObserver(ContentType.TYPES.Note, ({ source }) => { + application.items.addObserver(ContentType.TYPES.Note, ({ source }) => { if (source === PayloadEmitSource.RemoteSaved) { actualSaveCount++ } }) - this.expectedItemCount++ - this.application.sync.ut_beginLatencySimulator(150) + expectedItemCount++ + application.sync.ut_beginLatencySimulator(150) /** Dont await */ - const syncRequest = this.application.sync.sync(syncOptions) + const syncRequest = application.sync.sync(syncOptions) /** Dirty the item 100ms into 150ms request */ const newText = `${Math.random()}` setTimeout( async function () { - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.text = newText }) }.bind(this), @@ -820,7 +821,7 @@ describe('online syncing', function () { */ await syncRequest expect(actualSaveCount).to.equal(expectedSaveCount) - note = this.application.items.findItem(note.uuid) + note = application.items.findItem(note.uuid) expect(note.text).to.equal(newText) }) @@ -835,26 +836,26 @@ describe('online syncing', function () { let actualSaveCount = 0 /** Create an item and sync it */ - let note = await Factory.createMappedNote(this.application) + let note = await Factory.createMappedNote(application) - this.application.items.addObserver(ContentType.TYPES.Note, ({ source }) => { + application.items.addObserver(ContentType.TYPES.Note, ({ source }) => { if (source === PayloadEmitSource.RemoteSaved) { actualSaveCount++ } }) - this.expectedItemCount++ + expectedItemCount++ /** Dont await */ - const syncRequest = this.application.sync.sync(syncOptions) + const syncRequest = application.sync.sync(syncOptions) /** Dirty the item before lastSyncBegan is set */ let didPerformMutatation = false const newText = `${Math.random()}` - this.application.sync.addEventObserver(async (eventName) => { + application.sync.addEventObserver(async (eventName) => { if (eventName === SyncEvent.SyncDidBeginProcessing && !didPerformMutatation) { didPerformMutatation = true - await this.application.mutator.changeItem(note, (mutator) => { + await application.mutator.changeItem(note, (mutator) => { mutator.text = newText }) } @@ -863,7 +864,7 @@ describe('online syncing', function () { await syncRequest expect(actualSaveCount).to.equal(expectedSaveCount) - note = this.application.items.findItem(note.uuid) + note = application.items.findItem(note.uuid) expect(note.text).to.equal(newText) }) @@ -872,11 +873,11 @@ describe('online syncing', function () { let actualSaveCount = 0 /** Create an item and sync it */ - let note = await Factory.createMappedNote(this.application) + let note = await Factory.createMappedNote(application) let didPerformMutatation = false const newText = `${Math.random()}` - this.application.items.addObserver(ContentType.TYPES.Note, async ({ changed, source }) => { + application.items.addObserver(ContentType.TYPES.Note, async ({ changed, source }) => { if (source === PayloadEmitSource.RemoteSaved) { actualSaveCount++ } else if (source === PayloadEmitSource.PreSyncSave && !didPerformMutatation) { @@ -888,37 +889,37 @@ describe('online syncing', function () { dirtyIndex: changed[0].payload.globalDirtyIndexAtLastSync + 1, }) - await this.application.mutator.emitItemFromPayload(mutated) + await application.mutator.emitItemFromPayload(mutated) } }) - this.expectedItemCount++ + expectedItemCount++ /** Dont await */ - const syncRequest = this.application.sync.sync(syncOptions) + const syncRequest = application.sync.sync(syncOptions) await syncRequest expect(actualSaveCount).to.equal(expectedSaveCount) - note = this.application.items.findItem(note.uuid) + note = application.items.findItem(note.uuid) expect(note.text).to.equal(newText) }) it('retreiving a remote deleted item should succeed', async function () { - const note = await Factory.createSyncedNote(this.application) - const preDeleteSyncToken = await this.application.sync.getLastSyncToken() - await this.application.mutator.deleteItem(note) - await this.application.sync.sync() - await this.application.sync.setLastSyncToken(preDeleteSyncToken) - await this.application.sync.sync(syncOptions) - expect(this.application.items.items.length).to.equal(this.expectedItemCount) + const note = await Factory.createSyncedNote(application) + const preDeleteSyncToken = await application.sync.getLastSyncToken() + await application.mutator.deleteItem(note) + await application.sync.sync() + await application.sync.setLastSyncToken(preDeleteSyncToken) + await application.sync.sync(syncOptions) + expect(application.items.items.length).to.equal(expectedItemCount) }) it('errored items should not be synced', async function () { - const note = await Factory.createSyncedNote(this.application) - this.expectedItemCount++ + const note = await Factory.createSyncedNote(application) + expectedItemCount++ const lastSyncBegan = note.lastSyncBegan const lastSyncEnd = note.lastSyncEnd - const encrypted = await this.application.encryption.encryptSplitSingle({ + const encrypted = await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note.payload], }, @@ -929,10 +930,10 @@ describe('online syncing', function () { dirty: true, }) - await this.application.payloads.emitPayload(errored) - await this.application.sync.sync(syncOptions) + await application.payloads.emitPayload(errored) + await application.sync.sync(syncOptions) - const updatedNote = this.application.items.findAnyItem(note.uuid) + const updatedNote = application.items.findAnyItem(note.uuid) expect(updatedNote.lastSyncBegan.getTime()).to.equal(lastSyncBegan.getTime()) expect(updatedNote.lastSyncEnd.getTime()).to.equal(lastSyncEnd.getTime()) }) @@ -949,7 +950,7 @@ describe('online syncing', function () { content: '004:...', }) - this.expectedItemCount++ + expectedItemCount++ const response = new ServerSyncResponse({ data: { @@ -957,16 +958,16 @@ describe('online syncing', function () { }, }) - await this.application.sync.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response) + await application.sync.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response) - expect(this.application.payloads.findOne(invalidPayload.uuid)).to.not.be.ok - expect(this.application.payloads.findOne(validPayload.uuid)).to.be.ok + expect(application.payloads.findOne(invalidPayload.uuid)).to.not.be.ok + expect(application.payloads.findOne(validPayload.uuid)).to.be.ok }) it('retrieved items should have both updated_at and updated_at_timestamps', async function () { - const note = await Factory.createSyncedNote(this.application) + const note = await Factory.createSyncedNote(application) - this.expectedItemCount++ + expectedItemCount++ expect(note.payload.created_at_timestamp).to.be.ok expect(note.payload.created_at).to.be.ok @@ -985,24 +986,24 @@ describe('online syncing', function () { dirty: true, content: {}, }) - this.expectedItemCount++ - await this.application.mutator.emitItemsFromPayloads([payload]) - await this.application.sync.sync(syncOptions) + expectedItemCount++ + await application.mutator.emitItemsFromPayloads([payload]) + await application.sync.sync(syncOptions) /** Item should no longer be dirty, otherwise it would keep syncing */ - const item = this.application.items.findItem(payload.uuid) + const item = application.items.findItem(payload.uuid) expect(item.dirty).to.not.be.ok }) it('should call onPresyncSave before sync begins', async function () { const events = [] - this.application.sync.addEventObserver((event) => { + application.sync.addEventObserver((event) => { if (event === SyncEvent.SyncDidBeginProcessing) { events.push('sync-will-begin') } }) - await this.application.sync.sync({ + await application.sync.sync({ onPresyncSave: () => { events.push('on-presync-save') }, @@ -1015,21 +1016,21 @@ describe('online syncing', function () { it('deleting an item permanently should include it in PayloadEmitSource.PreSyncSave item change observer', async function () { let conditionMet = false - this.application.items.streamItems([ContentType.TYPES.Note], async ({ removed, source }) => { + application.items.streamItems([ContentType.TYPES.Note], async ({ removed, source }) => { if (source === PayloadEmitSource.PreSyncSave && removed.length === 1) { conditionMet = true } }) - const note = await Factory.createSyncedNote(this.application) - await this.application.mutator.deleteItem(note) - await this.application.sync.sync() + const note = await Factory.createSyncedNote(application) + await application.mutator.deleteItem(note) + await application.sync.sync() expect(conditionMet).to.equal(true) }) it('deleting a note on one client should update notes count on the other', async function () { - const contextA = this.context + const contextA = context const contextB = await Factory.createAppContextWithFakeCrypto('AppB', contextA.email, contextA.password) await contextB.launch() @@ -1049,7 +1050,7 @@ describe('online syncing', function () { }) it('should sync note when running raw sync request for external use', async function () { - const contextA = this.context + const contextA = context const contextB = await Factory.createAppContextWithFakeCrypto('AppB', contextA.email, contextA.password) await contextB.launch() @@ -1057,15 +1058,17 @@ describe('online syncing', function () { const notePayload = Factory.createNote() - const rawSyncRequest = await this.application.sync.getRawSyncRequestForExternalUse([notePayload]) + const rawSyncRequest = await application.sync.getRawSyncRequestForExternalUse([notePayload]) expect(rawSyncRequest).to.be.ok - const response = await this.application.http.runHttp(rawSyncRequest) + const response = await application.http.runHttp(rawSyncRequest) expect(response.status).to.equal(200) await contextB.sync() const note = contextB.application.items.findItem(notePayload.uuid) expect(note).to.be.ok + + await contextB.deinit() }) }) diff --git a/packages/snjs/mocha/upgrading.test.js b/packages/snjs/mocha/upgrading.test.js index a0ce3adc2..7aa23be4c 100644 --- a/packages/snjs/mocha/upgrading.test.js +++ b/packages/snjs/mocha/upgrading.test.js @@ -1,41 +1,49 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ import * as Factory from './lib/factory.js' + chai.use(chaiAsPromised) const expect = chai.expect describe('upgrading', () => { + let application + let context + let email + let password + let passcode + let receiveChallenge + let receiveChallengeWithApp + beforeEach(async function () { localStorage.clear() - this.context = await Factory.createAppContext() - await this.context.launch() + context = await Factory.createAppContext() + await context.launch() - this.application = this.context.application - this.email = UuidGenerator.GenerateUuid() - this.password = UuidGenerator.GenerateUuid() - this.passcode = '1234' + application = context.application + email = UuidGenerator.GenerateUuid() + password = UuidGenerator.GenerateUuid() + passcode = '1234' const promptValueReply = (prompts) => { const values = [] for (const prompt of prompts) { if (prompt.validation === ChallengeValidation.LocalPasscode) { - values.push(CreateChallengeValue(prompt, this.passcode)) + values.push(CreateChallengeValue(prompt, passcode)) } else { - values.push(CreateChallengeValue(prompt, this.password)) + values.push(CreateChallengeValue(prompt, password)) } } return values } - this.receiveChallenge = (challenge) => { - void this.receiveChallengeWithApp(this.application, challenge) + + receiveChallenge = (challenge) => { + void receiveChallengeWithApp(application, challenge) } - this.receiveChallengeWithApp = (application, challenge) => { + + receiveChallengeWithApp = (application, challenge) => { application.addChallengeObserver(challenge, { onInvalidValue: (value) => { const values = promptValueReply([value.prompt]) application.submitValuesForChallenge(challenge, values) - numPasscodeAttempts++ }, }) const initialValues = promptValueReply(challenge.prompts) @@ -44,7 +52,7 @@ describe('upgrading', () => { }) afterEach(async function () { - await Factory.safeDeinit(this.application) + await Factory.safeDeinit(application) localStorage.clear() }) @@ -52,76 +60,72 @@ describe('upgrading', () => { const oldVersion = ProtocolVersion.V003 /** Register with 003 version */ await Factory.registerOldUser({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, version: oldVersion, }) - expect(await this.application.protocolUpgradeAvailable()).to.equal(true) + expect(await application.protocolUpgradeAvailable()).to.equal(true) }) it('upgrade should be available when passcode only', async function () { const oldVersion = ProtocolVersion.V003 await Factory.setOldVersionPasscode({ - application: this.application, - passcode: this.passcode, + application: application, + passcode: passcode, version: oldVersion, }) - expect(await this.application.protocolUpgradeAvailable()).to.equal(true) + expect(await application.protocolUpgradeAvailable()).to.equal(true) }) it('upgrades application protocol from 003 to 004', async function () { const oldVersion = ProtocolVersion.V003 const newVersion = ProtocolVersion.V004 - await Factory.createMappedNote(this.application) + await Factory.createMappedNote(application) /** Register with 003 version */ await Factory.registerOldUser({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, version: oldVersion, }) await Factory.setOldVersionPasscode({ - application: this.application, - passcode: this.passcode, + application: application, + passcode: passcode, version: oldVersion, }) - expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( - oldVersion, - ) - expect((await this.application.encryption.getRootKeyParams()).version).to.equal(oldVersion) - expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(oldVersion) + expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(oldVersion) + expect((await application.encryption.getRootKeyParams()).version).to.equal(oldVersion) + expect((await application.encryption.getRootKey()).keyVersion).to.equal(oldVersion) - this.application.setLaunchCallback({ - receiveChallenge: this.receiveChallenge, + application.setLaunchCallback({ + receiveChallenge: receiveChallenge, }) - const result = await this.application.upgradeProtocolVersion() + const result = await application.upgradeProtocolVersion() expect(result).to.deep.equal({ success: true }) - const wrappedRootKey = await this.application.encryption.rootKeyManager.getWrappedRootKey() + const wrappedRootKey = await application.encryption.rootKeyManager.getWrappedRootKey() const payload = new EncryptedPayload(wrappedRootKey) expect(payload.version).to.equal(newVersion) - expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( - newVersion, - ) - expect((await this.application.encryption.getRootKeyParams()).version).to.equal(newVersion) - expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(newVersion) + expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(newVersion) + expect((await application.encryption.getRootKeyParams()).version).to.equal(newVersion) + expect((await application.encryption.getRootKey()).keyVersion).to.equal(newVersion) /** * Immediately logging out ensures we don't rely on subsequent * sync events to complete the upgrade */ - this.application = await Factory.signOutApplicationAndReturnNew(this.application) - await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) - expect(this.application.items.getDisplayableNotes().length).to.equal(1) - expect(this.application.payloads.invalidPayloads).to.be.empty + application = await Factory.signOutApplicationAndReturnNew(application) + await application.signIn(email, password, undefined, undefined, undefined, true) + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.payloads.invalidPayloads).to.be.empty }).timeout(15000) it('upgrading from 003 to 004 with passcode only then reiniting app should create valid state', async function () { @@ -133,23 +137,23 @@ describe('upgrading', () => { const oldVersion = ProtocolVersion.V003 await Factory.setOldVersionPasscode({ - application: this.application, - passcode: this.passcode, + application: application, + passcode: passcode, version: oldVersion, }) - await Factory.createSyncedNote(this.application) + await Factory.createSyncedNote(application) - this.application.setLaunchCallback({ - receiveChallenge: this.receiveChallenge, + application.setLaunchCallback({ + receiveChallenge: receiveChallenge, }) - const identifier = this.application.identifier + const identifier = application.identifier /** Recreate the app once */ const appFirst = Factory.createApplicationWithFakeCrypto(identifier) await appFirst.prepareForLaunch({ receiveChallenge: (challenge) => { - this.receiveChallengeWithApp(appFirst, challenge) + receiveChallengeWithApp(appFirst, challenge) }, }) await appFirst.launch(true) @@ -162,7 +166,7 @@ describe('upgrading', () => { const appSecond = Factory.createApplicationWithFakeCrypto(identifier) await appSecond.prepareForLaunch({ receiveChallenge: (challenge) => { - this.receiveChallengeWithApp(appSecond, challenge) + receiveChallengeWithApp(appSecond, challenge) }, }) await appSecond.launch(true) @@ -172,46 +176,46 @@ describe('upgrading', () => { it('protocol version should be upgraded on password change', async function () { /** Delete default items key that is created on launch */ - const itemsKey = await this.application.encryption.getSureDefaultItemsKey() - await this.application.mutator.setItemToBeDeleted(itemsKey) - expect(Uuids(this.application.items.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false) + const itemsKey = await application.encryption.getSureDefaultItemsKey() + await application.mutator.setItemToBeDeleted(itemsKey) + expect(Uuids(application.items.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false) - Factory.createMappedNote(this.application) + Factory.createMappedNote(application) /** Register with 003 version */ await Factory.registerOldUser({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, version: ProtocolVersion.V003, }) - expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) + expect(application.items.getDisplayableItemsKeys().length).to.equal(1) - expect((await this.application.encryption.getRootKeyParams()).version).to.equal(ProtocolVersion.V003) - expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(ProtocolVersion.V003) + expect((await application.encryption.getRootKeyParams()).version).to.equal(ProtocolVersion.V003) + expect((await application.encryption.getRootKey()).keyVersion).to.equal(ProtocolVersion.V003) /** Ensure note is encrypted with 003 */ - const notePayloads = await Factory.getStoragePayloadsOfType(this.application, ContentType.TYPES.Note) + const notePayloads = await Factory.getStoragePayloadsOfType(application, ContentType.TYPES.Note) expect(notePayloads.length).to.equal(1) expect(notePayloads[0].version).to.equal(ProtocolVersion.V003) - const { error } = await this.application.changePassword(this.password, 'foobarfoo') + const { error } = await application.changePassword(password, 'foobarfoo') expect(error).to.not.exist - const latestVersion = this.application.encryption.getLatestVersion() - expect((await this.application.encryption.getRootKeyParams()).version).to.equal(latestVersion) - expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(latestVersion) + const latestVersion = application.encryption.getLatestVersion() + expect((await application.encryption.getRootKeyParams()).version).to.equal(latestVersion) + expect((await application.encryption.getRootKey()).keyVersion).to.equal(latestVersion) - const defaultItemsKey = await this.application.encryption.getSureDefaultItemsKey() + const defaultItemsKey = await application.encryption.getSureDefaultItemsKey() expect(defaultItemsKey.keyVersion).to.equal(latestVersion) /** After change, note should now be encrypted with latest protocol version */ - const note = this.application.items.getDisplayableNotes()[0] - await Factory.markDirtyAndSyncItem(this.application, note) + const note = application.items.getDisplayableNotes()[0] + await Factory.markDirtyAndSyncItem(application, note) - const refreshedNotePayloads = await Factory.getStoragePayloadsOfType(this.application, ContentType.TYPES.Note) + const refreshedNotePayloads = await Factory.getStoragePayloadsOfType(application, ContentType.TYPES.Note) const refreshedNotePayload = refreshedNotePayloads[0] expect(refreshedNotePayload.version).to.equal(latestVersion) }).timeout(5000) @@ -221,19 +225,19 @@ describe('upgrading', () => { const oldVersion = ProtocolVersion.V003 beforeEach(async function () { - await Factory.createMappedNote(this.application) + await Factory.createMappedNote(application) /** Register with 003 version */ await Factory.registerOldUser({ - application: this.application, - email: this.email, - password: this.password, + application: application, + email: email, + password: password, version: oldVersion, }) await Factory.setOldVersionPasscode({ - application: this.application, - passcode: this.passcode, + application: application, + passcode: passcode, version: oldVersion, }) }) @@ -243,44 +247,36 @@ describe('upgrading', () => { }) it('rolls back the local protocol upgrade if syncing fails', async function () { - sinon.replace(this.application.sync, 'sync', sinon.fake()) - this.application.setLaunchCallback({ - receiveChallenge: this.receiveChallenge, + sinon.replace(application.sync, 'sync', sinon.fake()) + application.setLaunchCallback({ + receiveChallenge: receiveChallenge, }) - expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( - oldVersion, - ) - const errors = await this.application.upgradeProtocolVersion() + expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(oldVersion) + const errors = await application.upgradeProtocolVersion() expect(errors).to.not.be.empty /** Ensure we're still on 003 */ - expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( - oldVersion, - ) - expect((await this.application.encryption.getRootKeyParams()).version).to.equal(oldVersion) - expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(oldVersion) - expect((await this.application.encryption.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion) + expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(oldVersion) + expect((await application.encryption.getRootKeyParams()).version).to.equal(oldVersion) + expect((await application.encryption.getRootKey()).keyVersion).to.equal(oldVersion) + expect((await application.encryption.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion) }) it('rolls back the local protocol upgrade if the server responds with an error', async function () { - sinon.replace(this.application.sessions, 'changeCredentials', () => [Error()]) + sinon.replace(application.sessions, 'changeCredentials', () => [Error()]) - this.application.setLaunchCallback({ - receiveChallenge: this.receiveChallenge, + application.setLaunchCallback({ + receiveChallenge: receiveChallenge, }) - expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( - oldVersion, - ) - const errors = await this.application.upgradeProtocolVersion() + expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(oldVersion) + const errors = await application.upgradeProtocolVersion() expect(errors).to.not.be.empty /** Ensure we're still on 003 */ - expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( - oldVersion, - ) - expect((await this.application.encryption.getRootKeyParams()).version).to.equal(oldVersion) - expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(oldVersion) - expect((await this.application.encryption.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion) + expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(oldVersion) + expect((await application.encryption.getRootKeyParams()).version).to.equal(oldVersion) + expect((await application.encryption.getRootKey()).keyVersion).to.equal(oldVersion) + expect((await application.encryption.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion) }) }) }) diff --git a/packages/snjs/mocha/utils.test.js b/packages/snjs/mocha/utils.test.js index a525a8fe6..1acb6e006 100644 --- a/packages/snjs/mocha/utils.test.js +++ b/packages/snjs/mocha/utils.test.js @@ -1,5 +1,4 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ + chai.use(chaiAsPromised) const expect = chai.expect diff --git a/packages/snjs/mocha/vaults/asymmetric-messages.test.js b/packages/snjs/mocha/vaults/asymmetric-messages.test.js index 1d918773f..b2864c892 100644 --- a/packages/snjs/mocha/vaults/asymmetric-messages.test.js +++ b/packages/snjs/mocha/vaults/asymmetric-messages.test.js @@ -9,11 +9,6 @@ describe('asymmetric messages', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -23,6 +18,12 @@ describe('asymmetric messages', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + it('should not trust message if the trusted payload data recipientUuid does not match the message user uuid', async () => { const { sharedVault, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(context) diff --git a/packages/snjs/mocha/vaults/conflicts.test.js b/packages/snjs/mocha/vaults/conflicts.test.js index ff45780cf..39d8879ac 100644 --- a/packages/snjs/mocha/vaults/conflicts.test.js +++ b/packages/snjs/mocha/vaults/conflicts.test.js @@ -9,11 +9,6 @@ describe('shared vault conflicts', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -23,6 +18,12 @@ describe('shared vault conflicts', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + it('after being removed from shared vault, attempting to sync previous vault item should result in SharedVaultNotMemberError. The item should be duplicated then removed.', async () => { const { sharedVault, note, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) @@ -81,7 +82,10 @@ describe('shared vault conflicts', function () { it('attempting to modify note as read user should result in SharedVaultInsufficientPermissionsError', async () => { const { note, contactContext, deinitContactContext } = - await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context, SharedVaultUserPermission.PERMISSIONS.Read) + await Collaboration.createSharedVaultWithAcceptedInviteAndNote( + context, + SharedVaultUserPermission.PERMISSIONS.Read, + ) const promise = contactContext.resolveWithConflicts() await contactContext.changeNoteTitleAndSync(note, 'new title') diff --git a/packages/snjs/mocha/vaults/contacts.test.js b/packages/snjs/mocha/vaults/contacts.test.js index 4361aef4d..7ba28f2a3 100644 --- a/packages/snjs/mocha/vaults/contacts.test.js +++ b/packages/snjs/mocha/vaults/contacts.test.js @@ -9,11 +9,6 @@ describe('contacts', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -23,6 +18,12 @@ describe('contacts', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + it('should create contact', async () => { const contact = await context.contacts.createOrEditTrustedContact({ name: 'John Doe', diff --git a/packages/snjs/mocha/vaults/crypto.test.js b/packages/snjs/mocha/vaults/crypto.test.js index 0d98caa48..1275d8953 100644 --- a/packages/snjs/mocha/vaults/crypto.test.js +++ b/packages/snjs/mocha/vaults/crypto.test.js @@ -9,11 +9,6 @@ describe('shared vault crypto', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -23,6 +18,12 @@ describe('shared vault crypto', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + describe('root key', () => { it('root key loaded from disk should have keypairs', async () => { const appIdentifier = context.identifier diff --git a/packages/snjs/mocha/vaults/deletion.test.js b/packages/snjs/mocha/vaults/deletion.test.js index 896f8abeb..1539dc979 100644 --- a/packages/snjs/mocha/vaults/deletion.test.js +++ b/packages/snjs/mocha/vaults/deletion.test.js @@ -10,11 +10,6 @@ describe('shared vault deletion', function () { let context let sharedVaults - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -26,6 +21,12 @@ describe('shared vault deletion', function () { sharedVaults = context.sharedVaults }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + it('should remove item from all user devices when item is deleted permanently', async () => { const { note, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) @@ -102,7 +103,10 @@ describe('shared vault deletion', function () { it('leaving a shared vault should remove its items locally', async () => { const { sharedVault, note, contactContext, deinitContactContext } = - await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context, SharedVaultUserPermission.PERMISSIONS.Admin) + await Collaboration.createSharedVaultWithAcceptedInviteAndNote( + context, + SharedVaultUserPermission.PERMISSIONS.Admin, + ) const originalNote = contactContext.items.findItem(note.uuid) expect(originalNote).to.not.be.undefined diff --git a/packages/snjs/mocha/vaults/files.test.js b/packages/snjs/mocha/vaults/files.test.js index ab5264859..d29658e90 100644 --- a/packages/snjs/mocha/vaults/files.test.js +++ b/packages/snjs/mocha/vaults/files.test.js @@ -11,11 +11,6 @@ describe('shared vault files', function () { let context let vaults - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -28,6 +23,12 @@ describe('shared vault files', function () { await context.activatePaidSubscriptionForUser() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + describe('private vaults', () => { it('should be able to upload and download file to private vault as owner', async () => { const vault = await Collaboration.createPrivateVault(context) diff --git a/packages/snjs/mocha/vaults/importing.test.js b/packages/snjs/mocha/vaults/importing.test.js index 45a1c7d43..8b1a64d35 100644 --- a/packages/snjs/mocha/vaults/importing.test.js +++ b/packages/snjs/mocha/vaults/importing.test.js @@ -9,11 +9,6 @@ describe.skip('vault importing', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -23,6 +18,12 @@ describe.skip('vault importing', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + it('should import vaulted items with synced root key', async () => { console.error('TODO: implement') }) diff --git a/packages/snjs/mocha/vaults/invites.test.js b/packages/snjs/mocha/vaults/invites.test.js index 777aae856..6ccd9e90c 100644 --- a/packages/snjs/mocha/vaults/invites.test.js +++ b/packages/snjs/mocha/vaults/invites.test.js @@ -9,11 +9,6 @@ describe('shared vault invites', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -22,6 +17,12 @@ describe('shared vault invites', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + it('should invite contact to vault', async () => { const sharedVault = await Collaboration.createSharedVault(context) const { contactContext, deinitContactContext } = await Collaboration.createContactContext() diff --git a/packages/snjs/mocha/vaults/items.test.js b/packages/snjs/mocha/vaults/items.test.js index c23e39bdf..f1d17cf34 100644 --- a/packages/snjs/mocha/vaults/items.test.js +++ b/packages/snjs/mocha/vaults/items.test.js @@ -9,11 +9,6 @@ describe('shared vault items', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -23,6 +18,12 @@ describe('shared vault items', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + it('should add item to shared vault with no other members', async () => { const note = await context.createSyncedNote('foo', 'bar') diff --git a/packages/snjs/mocha/vaults/key-management.test.js b/packages/snjs/mocha/vaults/key-management.test.js index 70f44f5f2..a304f0a53 100644 --- a/packages/snjs/mocha/vaults/key-management.test.js +++ b/packages/snjs/mocha/vaults/key-management.test.js @@ -8,11 +8,6 @@ describe('vault key management', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -21,6 +16,12 @@ describe('vault key management', function () { await context.launch() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + describe('locking', () => { it('should throw if attempting to add item to locked vault', async () => { const vault = await context.vaults.createUserInputtedPasswordVault({ diff --git a/packages/snjs/mocha/vaults/key-rotation.test.js b/packages/snjs/mocha/vaults/key-rotation.test.js index 6094ce192..d5bfb29fd 100644 --- a/packages/snjs/mocha/vaults/key-rotation.test.js +++ b/packages/snjs/mocha/vaults/key-rotation.test.js @@ -9,11 +9,6 @@ describe('vault key rotation', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -23,6 +18,12 @@ describe('vault key rotation', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + it('should reencrypt all items keys belonging to key system', async () => { const { sharedVault, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(context) diff --git a/packages/snjs/mocha/vaults/key-sharing.test.js b/packages/snjs/mocha/vaults/key-sharing.test.js index 34a1d96b9..a8be0bbb7 100644 --- a/packages/snjs/mocha/vaults/key-sharing.test.js +++ b/packages/snjs/mocha/vaults/key-sharing.test.js @@ -9,11 +9,6 @@ describe('vault key sharing', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -23,6 +18,12 @@ describe('vault key sharing', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + it('sharing a vault with user inputted and ephemeral password should share the key as synced for the recipient', async () => { const privateVault = await context.vaults.createUserInputtedPasswordVault({ name: 'My Private Vault', diff --git a/packages/snjs/mocha/vaults/keypair-change.test.js b/packages/snjs/mocha/vaults/keypair-change.test.js index 323e17160..16814446c 100644 --- a/packages/snjs/mocha/vaults/keypair-change.test.js +++ b/packages/snjs/mocha/vaults/keypair-change.test.js @@ -9,11 +9,6 @@ describe('keypair change', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -23,6 +18,12 @@ describe('keypair change', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + it('contacts should be able to handle receiving multiple keypair changed messages and trust them in order', async () => { const { note, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) diff --git a/packages/snjs/mocha/vaults/permissions.test.js b/packages/snjs/mocha/vaults/permissions.test.js index bfa031275..f229c01fa 100644 --- a/packages/snjs/mocha/vaults/permissions.test.js +++ b/packages/snjs/mocha/vaults/permissions.test.js @@ -9,11 +9,6 @@ describe('shared vault permissions', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -23,6 +18,12 @@ describe('shared vault permissions', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + }) + it('non-admin user should not be able to invite user', async () => { context.anticipateConsoleError('Could not create invite') @@ -42,6 +43,8 @@ describe('shared vault permissions', function () { expect(result.isFailed()).to.be.true + await thirdParty.deinitContactContext() + await deinitContactContext() }) diff --git a/packages/snjs/mocha/vaults/pkc.test.js b/packages/snjs/mocha/vaults/pkc.test.js index 70eaf9934..703ac30ac 100644 --- a/packages/snjs/mocha/vaults/pkc.test.js +++ b/packages/snjs/mocha/vaults/pkc.test.js @@ -7,24 +7,23 @@ describe('public key cryptography', function () { this.timeout(Factory.TwentySecondTimeout) let context - let sessions - let encryption - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - - beforeEach(async function () { + beforeEach(async () => { localStorage.clear() context = await Factory.createVaultsContextWithRealCrypto() await context.launch() await context.register() + }) - sessions = context.application.sessions - encryption = context.encryption + afterEach(async () => { + await context.deinit() + localStorage.clear() + + sinon.restore() + + context = undefined }) it('should create keypair during registration', () => { @@ -38,7 +37,7 @@ describe('public key cryptography', function () { it('should populate keypair during sign in', async () => { const email = context.email const password = context.password - await context.signout() + await context.deinit() const recreatedContext = await Factory.createVaultsContextWithRealCrypto() await recreatedContext.launch() diff --git a/packages/snjs/mocha/vaults/shared_vaults.test.js b/packages/snjs/mocha/vaults/shared_vaults.test.js index 8335eca8b..54984b82a 100644 --- a/packages/snjs/mocha/vaults/shared_vaults.test.js +++ b/packages/snjs/mocha/vaults/shared_vaults.test.js @@ -24,6 +24,8 @@ describe('shared vaults', function () { afterEach(async function () { await context.deinit() localStorage.clear() + sinon.restore() + context = undefined }) it('should update vault name and description', async () => { diff --git a/packages/snjs/mocha/vaults/signatures.test.js b/packages/snjs/mocha/vaults/signatures.test.js index 433578b3c..8c021e9a0 100644 --- a/packages/snjs/mocha/vaults/signatures.test.js +++ b/packages/snjs/mocha/vaults/signatures.test.js @@ -9,11 +9,6 @@ describe('signatures', function () { let context - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() @@ -23,6 +18,13 @@ describe('signatures', function () { await context.register() }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + context = undefined + }) + describe('item decryption signature verification', () => { it('should have failing signature if contact public key does not match', async () => { const { note, contactContext, deinitContactContext } = diff --git a/packages/snjs/mocha/vaults/vaults.test.js b/packages/snjs/mocha/vaults/vaults.test.js index de7c80399..04b4206af 100644 --- a/packages/snjs/mocha/vaults/vaults.test.js +++ b/packages/snjs/mocha/vaults/vaults.test.js @@ -9,21 +9,24 @@ describe('vaults', function () { let context let vaults - afterEach(async function () { - await context.deinit() - localStorage.clear() - }) - beforeEach(async function () { localStorage.clear() - context = await Factory.createVaultsContextWithRealCrypto() + context = await Factory.createVaultsContextWithFakeCrypto() await context.launch() vaults = context.vaults }) + afterEach(async function () { + await context.deinit() + localStorage.clear() + sinon.restore() + context = undefined + vaults = undefined + }) + describe('offline', function () { it('should be able to create an offline vault', async () => { const vault = await vaults.createRandomizedVault({ @@ -77,7 +80,7 @@ describe('vaults', function () { await vaults.moveItemToVault(vault, note) await context.deinit() - const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier) + const recreatedContext = await Factory.createVaultsContextWithFakeCrypto(appIdentifier) await recreatedContext.launch() const updatedNote = recreatedContext.items.findItem(note.uuid) @@ -101,7 +104,7 @@ describe('vaults', function () { await context.deinit() - const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier) + const recreatedContext = await Factory.createVaultsContextWithFakeCrypto(appIdentifier) await recreatedContext.launch() const notes = recreatedContext.notes @@ -128,7 +131,7 @@ describe('vaults', function () { await context.deinit() - const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier) + const recreatedContext = await Factory.createVaultsContextWithFakeCrypto(appIdentifier) await recreatedContext.launch() const updatedNote = recreatedContext.items.findItem(note.uuid) @@ -181,7 +184,7 @@ describe('vaults', function () { await vaults.moveItemToVault(vault, note) await context.deinit() - const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier) + const recreatedContext = await Factory.createVaultsContextWithFakeCrypto(appIdentifier) await recreatedContext.launch() const updatedNote = recreatedContext.items.findItem(note.uuid)