tests: fix memory leaks (#2389)

This commit is contained in:
Mo
2023-08-06 15:23:31 -05:00
committed by GitHub
parent d59e1befff
commit 8655bdb5dd
76 changed files with 3904 additions and 3840 deletions

View File

@@ -1,5 +1,4 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import './vendor/chai-as-promised-built.js' import './vendor/chai-as-promised-built.js'
import './vendor/buffer@5.6.0.js' import './vendor/buffer@5.6.0.js'

View File

@@ -1,5 +1,4 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import './vendor/chai-as-promised-built.js' import './vendor/chai-as-promised-built.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)

View File

@@ -1,5 +1,4 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import './vendor/chai-as-promised-built.js' import './vendor/chai-as-promised-built.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)

View File

@@ -1,19 +1,17 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('000 legacy protocol operations', () => { describe('000 legacy protocol operations', () => {
const application = Factory.createApplicationWithRealCrypto() let protocol004
const protocol004 = new SNProtocolOperator004(new SNWebCrypto())
before(async () => { beforeEach(async () => {
await Factory.initializeApplication(application) localStorage.clear()
protocol004 = new SNProtocolOperator004(new SNWebCrypto())
}) })
after(async () => { afterEach(async () => {
await Factory.safeDeinit(application) localStorage.clear()
}) })
it('cannot decode 000 item', function () { it('cannot decode 000 item', function () {

View File

@@ -1,27 +1,32 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('001 protocol operations', () => { describe('001 protocol operations', () => {
const application = Factory.createApplicationWithRealCrypto() let application
const protocol001 = new SNProtocolOperator001(new SNWebCrypto()) let protocol001
const _identifier = 'hello@test.com' let _identifier = 'hello@test.com'
const _password = 'password' let _password = 'password'
let _keyParams, _key let _keyParams, _key
// runs once before all tests in this block beforeEach(async () => {
before(async () => {
localStorage.clear() localStorage.clear()
application = Factory.createApplicationWithRealCrypto()
protocol001 = new SNProtocolOperator001(new SNWebCrypto())
await Factory.initializeApplication(application) await Factory.initializeApplication(application)
_key = await protocol001.createRootKey(_identifier, _password, KeyParamsOrigination.Registration) _key = await protocol001.createRootKey(_identifier, _password, KeyParamsOrigination.Registration)
_keyParams = _key.keyParams _keyParams = _key.keyParams
}) })
after(async () => { afterEach(async () => {
await Factory.safeDeinit(application) await Factory.safeDeinit(application)
localStorage.clear()
application = undefined
}) })
it('generates random key', async () => { it('generates random key', async () => {

View File

@@ -1,26 +1,30 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('002 protocol operations', () => { describe('002 protocol operations', () => {
const _identifier = 'hello@test.com' let _identifier = 'hello@test.com'
const _password = 'password' let _password = 'password'
let _keyParams, _key let _keyParams, _key
const application = Factory.createApplicationWithRealCrypto() let application
const protocol002 = new SNProtocolOperator002(new SNWebCrypto()) let protocol002
// runs once before all tests in this block beforeEach(async () => {
before(async () => {
localStorage.clear() localStorage.clear()
application = Factory.createApplicationWithRealCrypto()
protocol002 = new SNProtocolOperator002(new SNWebCrypto())
await Factory.initializeApplication(application) await Factory.initializeApplication(application)
_key = await protocol002.createRootKey(_identifier, _password, KeyParamsOrigination.Registration) _key = await protocol002.createRootKey(_identifier, _password, KeyParamsOrigination.Registration)
_keyParams = _key.keyParams _keyParams = _key.keyParams
}) })
after(async () => { afterEach(async () => {
await Factory.safeDeinit(application) await Factory.safeDeinit(application)
localStorage.clear()
application = undefined
}) })
it('generates random key', async () => { it('generates random key', async () => {

View File

@@ -1,34 +1,31 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('003 protocol operations', () => { describe('003 protocol operations', () => {
before(async () => { let _identifier = 'hello@test.com'
localStorage.clear() let _password = 'password'
})
after(async () => {
localStorage.clear()
})
const _identifier = 'hello@test.com'
const _password = 'password'
let _keyParams, _key let _keyParams, _key
const sharedApplication = Factory.createApplicationWithRealCrypto() let application
const protocol003 = new SNProtocolOperator003(new SNWebCrypto()) let protocol003
// runs once before all tests in this block beforeEach(async () => {
before(async () => { localStorage.clear()
await Factory.initializeApplication(sharedApplication)
application = Factory.createApplicationWithRealCrypto()
protocol003 = new SNProtocolOperator003(new SNWebCrypto())
await Factory.initializeApplication(application)
_key = await protocol003.createRootKey(_identifier, _password, KeyParamsOrigination.Registration) _key = await protocol003.createRootKey(_identifier, _password, KeyParamsOrigination.Registration)
_keyParams = _key.keyParams _keyParams = _key.keyParams
}) })
after(async () => { afterEach(async () => {
await Factory.safeDeinit(sharedApplication) await Factory.safeDeinit(application)
localStorage.clear()
application = undefined
}) })
it('generates random key', async () => { it('generates random key', async () => {
@@ -39,7 +36,7 @@ describe('003 protocol operations', () => {
it('cost minimum should throw', () => { it('cost minimum should throw', () => {
expect(() => { expect(() => {
sharedApplication.encryption.costMinimumForVersion('003') application.encryption.costMinimumForVersion('003')
}).to.throw('Cost minimums only apply to versions <= 002') }).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 () => { it('computes proper keys for sign in', async () => {
const identifier = 'foo@bar.com' const identifier = 'foo@bar.com'
const password = 'very_secure' const password = 'very_secure'
const keyParams = sharedApplication.encryption.createKeyParams({ const keyParams = application.encryption.createKeyParams({
pw_nonce: 'baaec0131d677cf993381367eb082fe377cefe70118c1699cb9b38f0bc850e7b', pw_nonce: 'baaec0131d677cf993381367eb082fe377cefe70118c1699cb9b38f0bc850e7b',
identifier: identifier, identifier: identifier,
version: '003', version: '003',
@@ -73,7 +70,7 @@ describe('003 protocol operations', () => {
it('can decrypt item generated with web version 3.3.6', async () => { it('can decrypt item generated with web version 3.3.6', async () => {
const identifier = 'demo@standardnotes.org' const identifier = 'demo@standardnotes.org'
const password = 'password' const password = 'password'
const keyParams = sharedApplication.encryption.createKeyParams({ const keyParams = application.encryption.createKeyParams({
pw_nonce: '31107837b44d86179140b7c602a55d694243e2e9ced0c4c914ac21ad90215055', pw_nonce: '31107837b44d86179140b7c602a55d694243e2e9ced0c4c914ac21ad90215055',
identifier: identifier, identifier: identifier,
version: '003', version: '003',

View File

@@ -1,26 +1,33 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('004 protocol operations', function () { describe('004 protocol operations', function () {
const _identifier = 'hello@test.com' let _identifier = 'hello@test.com'
const _password = 'password' let _password = 'password'
let rootKeyParams let rootKeyParams
let rootKey let rootKey
const application = Factory.createApplicationWithRealCrypto() let application
const protocol004 = new SNProtocolOperator004(new SNWebCrypto()) let protocol004
beforeEach(async function () {
localStorage.clear()
application = Factory.createApplicationWithRealCrypto()
protocol004 = new SNProtocolOperator004(new SNWebCrypto())
before(async function () {
await Factory.initializeApplication(application) await Factory.initializeApplication(application)
rootKey = await protocol004.createRootKey(_identifier, _password, KeyParamsOrigination.Registration) rootKey = await protocol004.createRootKey(_identifier, _password, KeyParamsOrigination.Registration)
rootKeyParams = rootKey.keyParams rootKeyParams = rootKey.keyParams
}) })
after(async function () { afterEach(async function () {
await Factory.safeDeinit(application) await Factory.safeDeinit(application)
application = undefined
localStorage.clear()
}) })
it('cost minimum should throw', function () { it('cost minimum should throw', function () {

View File

@@ -1,41 +1,46 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
import * as Utils from './lib/Utils.js' import * as Utils from './lib/Utils.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('actions service', () => { describe('actions service', () => {
const errorProcessingActionMessage = 'An issue occurred while processing this action. Please try again.' 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) this.timeout(20000)
localStorage.clear() localStorage.clear()
this.application = await Factory.createInitAppWithFakeCrypto() application = await Factory.createInitAppWithFakeCrypto()
this.itemManager = this.application.items itemManager = application.items
this.actionsManager = this.application.actions actionsManager = application.actions
this.email = UuidGenerator.GenerateUuid() email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid() password = UuidGenerator.GenerateUuid()
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
const rootKey = await this.application.encryption.createRootKey( const rootKey = await application.encryption.createRootKey(email, password, KeyParamsOrigination.Registration)
this.email, authParams = rootKey.keyParams.content
this.password,
KeyParamsOrigination.Registration,
)
this.authParams = rootKey.keyParams.content
this.fakeServer = sinon.fakeServer.create() fakeServer = sinon.fakeServer.create()
this.fakeServer.respondImmediately = true fakeServer.respondImmediately = true
this.actionsExtension = { actionsExtension = {
identifier: 'org.standardnotes.testing', identifier: 'org.standardnotes.testing',
name: 'Test extension', name: 'Test extension',
content_type: '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 urlParams = new URLSearchParams(params)
const extension = Copy(this.actionsExtension) const extension = Copy(actionsExtension)
if (urlParams.has('item_uuid')) { if (urlParams.has('item_uuid')) {
extension.actions.push({ extension.actions.push({
@@ -117,31 +122,31 @@ describe('actions service', () => {
}) })
const encryptedPayload = CreateEncryptedServerSyncPushPayload( const encryptedPayload = CreateEncryptedServerSyncPushPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], 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( request.respond(
200, 200,
{ 'Content-Type': 'application/json' }, { 'Content-Type': 'application/json' },
JSON.stringify({ JSON.stringify({
item: encryptedPayload, 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, 200,
{ 'Content-Type': 'text/html; charset=utf-8' }, { 'Content-Type': 'text/html; charset=utf-8' },
'<h2>Action #3</h2>', '<h2>Action #3</h2>',
]) ])
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 requestBody = JSON.parse(request.requestBody)
const response = { const response = {
@@ -152,7 +157,7 @@ describe('actions service', () => {
request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(response)) 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) const encryptedPayloadClone = Copy(encryptedPayload)
encryptedPayloadClone.items_key_id = undefined encryptedPayloadClone.items_key_id = undefined
@@ -163,53 +168,56 @@ describe('actions service', () => {
const payload = { const payload = {
item: encryptedPayloadClone, item: encryptedPayloadClone,
auth_params: this.authParams, auth_params: authParams,
} }
request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(payload)) request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(payload))
}) })
// Extension item // Extension item
const extensionItem = await this.application.mutator.createItem( const extensionItem = await application.mutator.createItem(ContentType.TYPES.ActionsExtension, actionsExtension)
ContentType.TYPES.ActionsExtension, extensionItemUuid = extensionItem.uuid
this.actionsExtension,
)
this.extensionItemUuid = extensionItem.uuid
}) })
after(async function () { afterEach(async function () {
this.fakeServer.restore() fakeServer.restore()
await Factory.safeDeinit(this.application)
this.application = null await Factory.safeDeinit(application)
application = undefined
itemManager = undefined
actionsManager = undefined
fakeServer = undefined
localStorage.clear() localStorage.clear()
}) })
it('should get extension items', async function () { 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', title: 'A simple note',
text: 'Standard Notes rocks! lml.', text: 'Standard Notes rocks! lml.',
}) })
const extensions = this.actionsManager.getExtensions() const extensions = actionsManager.getExtensions()
expect(extensions.length).to.eq(1) expect(extensions.length).to.eq(1)
}) })
it('should get extensions in context of item', async function () { 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', title: 'Another note',
text: 'Whiskey In The Jar', text: 'Whiskey In The Jar',
}) })
const noteItemExtensions = this.actionsManager.extensionsInContextOfItem(noteItem) const noteItemExtensions = actionsManager.extensionsInContextOfItem(noteItem)
expect(noteItemExtensions.length).to.eq(1) expect(noteItemExtensions.length).to.eq(1)
expect(noteItemExtensions[0].supported_types).to.include(noteItem.content_type) expect(noteItemExtensions[0].supported_types).to.include(noteItem.content_type)
}) })
it('should get actions based on item context', async function () { 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', title: 'Music',
}) })
const extensionItem = await this.itemManager.findItem(this.extensionItemUuid) const extensionItem = await itemManager.findItem(extensionItemUuid)
const tagActions = extensionItem.actionsWithContextForItem(tagItem) const tagActions = extensionItem.actionsWithContextForItem(tagItem)
expect(tagActions.length).to.eq(1) expect(tagActions.length).to.eq(1)
@@ -217,15 +225,15 @@ describe('actions service', () => {
}) })
it('should load extension in context of item', async function () { 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', title: 'Yet another note',
text: 'And all things will end ♫', 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) 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.length).to.be.eq(7)
expect(extensionWithItem.actions.map((action) => action.label)).to.include.members([ 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. // 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 expectedActions = extensionItem.actions.map((action) => {
const { id, ...rest } = action const { id, ...rest } = action
return rest return rest
@@ -248,18 +256,22 @@ describe('actions service', () => {
describe('render action', async function () { describe('render action', async function () {
const sandbox = sinon.createSandbox() const sandbox = sinon.createSandbox()
before(async function () { let noteItem
this.noteItem = await this.application.mutator.createItem(ContentType.TYPES.Note, { let renderAction
let alertServiceAlert
let windowAlert
let httpServiceGetAbsolute
beforeEach(async function () {
noteItem = await application.mutator.createItem(ContentType.TYPES.Note, {
title: 'Hey', title: 'Hey',
text: 'Welcome To Paradise', text: 'Welcome To Paradise',
}) })
const extensionItem = await this.itemManager.findItem(this.extensionItemUuid) const extensionItem = await itemManager.findItem(extensionItemUuid)
this.renderAction = extensionItem.actions.filter((action) => action.verb === 'render')[0]
})
beforeEach(async function () { renderAction = extensionItem.actions.filter((action) => action.verb === 'render')[0]
this.alertServiceAlert = sandbox.spy(this.actionsManager.alertService, 'alert') alertServiceAlert = sandbox.spy(actionsManager.alertService, 'alert')
this.windowAlert = sandbox.stub(window, 'alert').callsFake((message) => message) windowAlert = sandbox.stub(window, 'alert').callsFake((message) => message)
}) })
afterEach(async function () { afterEach(async function () {
@@ -267,35 +279,35 @@ describe('actions service', () => {
}) })
it('should show an alert if the request fails', async function () { it('should show an alert if the request fails', async function () {
this.httpServiceGetAbsolute = sandbox httpServiceGetAbsolute = sandbox
.stub(this.actionsManager.httpService, 'getAbsolute') .stub(actionsManager.httpService, 'getAbsolute')
.callsFake((url) => Promise.reject(new Error('Dummy error.'))) .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(httpServiceGetAbsolute, renderAction.url)
sinon.assert.calledOnceWithExactly(this.alertServiceAlert, errorProcessingActionMessage) sinon.assert.calledOnceWithExactly(alertServiceAlert, errorProcessingActionMessage)
expect(actionResponse.error.message).to.eq(errorProcessingActionMessage) expect(actionResponse.error.message).to.eq(errorProcessingActionMessage)
}) })
it('should return a response if payload is valid', async function () { 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).to.have.property('item')
expect(actionResponse.item.payload.content.title).to.eq('Testing') expect(actionResponse.item.payload.content.title).to.eq('Testing')
}) })
it('should return undefined if payload is invalid', async function () { 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 expect(actionResponse).to.be.undefined
}) })
it('should return decrypted payload if password is valid', async function () { it('should return decrypted payload if password is valid', async function () {
const extensionItem = await this.itemManager.findItem(this.extensionItemUuid) const extensionItem = await itemManager.findItem(extensionItemUuid)
this.renderAction = extensionItem.actions.filter((action) => action.verb === 'render')[0] renderAction = extensionItem.actions.filter((action) => action.verb === 'render')[0]
const actionResponse = await this.actionsManager.runAction(this.renderAction, this.noteItem) const actionResponse = await actionsManager.runAction(renderAction, noteItem)
expect(actionResponse.item).to.be.ok expect(actionResponse.item).to.be.ok
expect(actionResponse.item.title).to.be.equal('Testing') expect(actionResponse.item.title).to.be.equal('Testing')
@@ -305,24 +317,24 @@ describe('actions service', () => {
describe('show action', async function () { describe('show action', async function () {
const sandbox = sinon.createSandbox() const sandbox = sinon.createSandbox()
before(async function () { let showAction
const extensionItem = await this.itemManager.findItem(this.extensionItemUuid) let deviceInterfaceOpenUrl
this.showAction = extensionItem.actions[2]
})
beforeEach(async function () { beforeEach(async function () {
this.actionsManager.device.openUrl = (url) => url const extensionItem = await itemManager.findItem(extensionItemUuid)
this.deviceInterfaceOpenUrl = sandbox.spy(this.actionsManager.device, 'openUrl') showAction = extensionItem.actions[2]
actionsManager.device.openUrl = (url) => url
deviceInterfaceOpenUrl = sandbox.spy(actionsManager.device, 'openUrl')
}) })
this.afterEach(async function () { afterEach(async function () {
sandbox.restore() sandbox.restore()
}) })
it('should open the action url', async function () { 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({}) expect(response).to.eql({})
}) })
}) })
@@ -330,28 +342,32 @@ describe('actions service', () => {
describe('post action', async function () { describe('post action', async function () {
const sandbox = sinon.createSandbox() const sandbox = sinon.createSandbox()
before(async function () { let noteItem
this.noteItem = await this.application.mutator.createItem(ContentType.TYPES.Note, { let extensionItem
let decryptedPostAction
let encryptedPostAction
let alertServiceAlert
let httpServicePostAbsolute
beforeEach(async function () {
noteItem = await application.mutator.createItem(ContentType.TYPES.Note, {
title: 'Excuse Me', title: 'Excuse Me',
text: 'Time To Be King 8)', text: 'Time To Be King 8)',
}) })
this.extensionItem = await this.itemManager.findItem(this.extensionItemUuid) extensionItem = await itemManager.findItem(extensionItemUuid)
this.extensionItem = await this.actionsManager.loadExtensionInContextOfItem(this.extensionItem, this.noteItem) extensionItem = await actionsManager.loadExtensionInContextOfItem(extensionItem, noteItem)
this.decryptedPostAction = this.extensionItem.actions.filter( decryptedPostAction = extensionItem.actions.filter(
(action) => action.access_type === 'decrypted' && action.verb === 'post', (action) => action.access_type === 'decrypted' && action.verb === 'post',
)[0] )[0]
this.encryptedPostAction = this.extensionItem.actions.filter( encryptedPostAction = extensionItem.actions.filter(
(action) => action.access_type === 'encrypted' && action.verb === 'post', (action) => action.access_type === 'encrypted' && action.verb === 'post',
)[0] )[0]
}) alertServiceAlert = sandbox.spy(actionsManager.alertService, 'alert')
sandbox.stub(window, 'alert').callsFake((message) => message)
beforeEach(async function () { httpServicePostAbsolute = sandbox.stub(actionsManager.httpService, 'postAbsolute')
this.alertServiceAlert = sandbox.spy(this.actionsManager.alertService, 'alert') httpServicePostAbsolute.callsFake((url, params) => Promise.resolve(params))
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))
}) })
afterEach(async function () { afterEach(async function () {
@@ -359,52 +375,50 @@ describe('actions service', () => {
}) })
it('should include generic encrypted payload within request body', async function () { 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) => { 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].auth_hash).to.not.be.ok
expect(response.items[0].content_type).to.be.ok expect(response.items[0].content_type).to.be.ok
expect(response.items[0].created_at).to.be.ok expect(response.items[0].created_at).to.be.ok
expect(response.items[0].content).to.satisfy((string) => { 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 () { 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].enc_item_key).to.not.be.ok
expect(response.items[0].auth_hash).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].content_type).to.be.ok
expect(response.items[0].created_at).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.title).to.eq(noteItem.title)
expect(response.items[0].content.text).to.eq(this.noteItem.text) expect(response.items[0].content.text).to.eq(noteItem.text)
}) })
it('should post to the action url', async function () { it('should post to the action url', async function () {
this.httpServicePostAbsolute.restore() httpServicePostAbsolute.restore()
const response = await this.actionsManager.runAction(this.decryptedPostAction, this.noteItem) const response = await actionsManager.runAction(decryptedPostAction, noteItem)
expect(response).to.be.ok 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.') expect(response.result).to.eq('Action POSTed successfully.')
}) })
it('should alert if an error occurred while processing the action', async function () { it('should alert if an error occurred while processing the action', async function () {
this.httpServicePostAbsolute.restore() httpServicePostAbsolute.restore()
const dummyError = new Error('Dummy error.') const dummyError = new Error('Dummy error.')
sandbox sandbox.stub(actionsManager.httpService, 'postAbsolute').callsFake((url, params) => Promise.reject(dummyError))
.stub(this.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) expect(response).to.be.eq(dummyError)
}) })
}) })
@@ -412,15 +426,17 @@ describe('actions service', () => {
describe('nested action', async function () { describe('nested action', async function () {
const sandbox = sinon.createSandbox() const sandbox = sinon.createSandbox()
before(async function () { let nestedAction
const extensionItem = await this.itemManager.findItem(this.extensionItemUuid) let actionsManagerRunAction
this.nestedAction = extensionItem.actions.filter((action) => action.verb === 'nested')[0] let httpServiceRunHttp
}) let actionResponse
beforeEach(async function () { beforeEach(async function () {
this.actionsManagerRunAction = sandbox.spy(this.actionsManager, 'runAction') const extensionItem = await itemManager.findItem(extensionItemUuid)
this.httpServiceRunHttp = sandbox.spy(this.actionsManager.httpService, 'runHttp') nestedAction = extensionItem.actions.filter((action) => action.verb === 'nested')[0]
this.actionResponse = await this.actionsManager.runAction(this.nestedAction) actionsManagerRunAction = sandbox.spy(actionsManager, 'runAction')
httpServiceRunHttp = sandbox.spy(actionsManager.httpService, 'runHttp')
actionResponse = await actionsManager.runAction(nestedAction)
}) })
afterEach(async function () { afterEach(async function () {
@@ -428,15 +444,15 @@ describe('actions service', () => {
}) })
it('should return undefined', async function () { it('should return undefined', async function () {
expect(this.actionResponse).to.be.undefined expect(actionResponse).to.be.undefined
}) })
it('should call runAction once', async function () { 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 () { it('should not make any http requests', async function () {
sandbox.assert.notCalled(this.httpServiceRunHttp) sandbox.assert.notCalled(httpServiceRunHttp)
}) })
}) })
}) })

View File

@@ -1,23 +1,32 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import WebDeviceInterface from './lib/web_device_interface.js' import WebDeviceInterface from './lib/web_device_interface.js'
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('application group', function () { describe('application group', function () {
const globalDevice = new WebDeviceInterface(setTimeout.bind(window), setInterval.bind(window)) let device
let group
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
device = new WebDeviceInterface(setTimeout.bind(window), setInterval.bind(window))
group = new SNApplicationGroup(device)
}) })
afterEach(async function () { afterEach(async function () {
if (group.primaryApplication) {
await Factory.safeDeinit(group.primaryApplication)
}
device.deinit()
localStorage.clear() localStorage.clear()
group = undefined
}) })
it('initializing a group should result with primary application', async function () { it('initializing a group should result with primary application', async function () {
const group = new SNApplicationGroup(globalDevice)
await group.initialize({ await group.initialize({
applicationCreator: (descriptor, deviceInterface) => { applicationCreator: (descriptor, deviceInterface) => {
return Factory.createApplicationWithFakeCrypto(descriptor.identifier, deviceInterface) return Factory.createApplicationWithFakeCrypto(descriptor.identifier, deviceInterface)
@@ -25,12 +34,9 @@ describe('application group', function () {
}) })
expect(group.primaryApplication).to.be.ok expect(group.primaryApplication).to.be.ok
expect(group.primaryApplication.identifier).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 () { it('initializing a group should result with proper descriptor setup', async function () {
const group = new SNApplicationGroup(globalDevice)
await group.initialize({ await group.initialize({
applicationCreator: (descriptor, deviceInterface) => { applicationCreator: (descriptor, deviceInterface) => {
return Factory.createApplicationWithFakeCrypto(descriptor.identifier, deviceInterface) return Factory.createApplicationWithFakeCrypto(descriptor.identifier, deviceInterface)
@@ -38,12 +44,9 @@ describe('application group', function () {
}) })
const identifier = group.primaryApplication.identifier const identifier = group.primaryApplication.identifier
expect(group.descriptorRecord[identifier].identifier).to.equal(identifier) expect(group.descriptorRecord[identifier].identifier).to.equal(identifier)
await Factory.safeDeinit(group.primaryApplication)
}) })
it('should persist descriptor record after changes', async function () { it('should persist descriptor record after changes', async function () {
const group = new SNApplicationGroup(globalDevice)
await group.initialize({ await group.initialize({
applicationCreator: (descriptor, device) => { applicationCreator: (descriptor, device) => {
return Factory.createInitAppWithFakeCryptoWithOptions({ return Factory.createInitAppWithFakeCryptoWithOptions({
@@ -59,14 +62,13 @@ describe('application group', function () {
expect(descriptorRecord[identifier].primary).to.equal(true) expect(descriptorRecord[identifier].primary).to.equal(true)
await group.unloadCurrentAndCreateNewDescriptor() 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(Object.keys(descriptorRecord2).length).to.equal(2)
expect(descriptorRecord2[identifier].primary).to.equal(false) expect(descriptorRecord2[identifier].primary).to.equal(false)
}) })
it('adding new application should incrememnt total descriptor count', async function () { it('adding new application should incrememnt total descriptor count', async function () {
const group = new SNApplicationGroup(globalDevice)
await group.initialize({ await group.initialize({
applicationCreator: (descriptor, device) => { applicationCreator: (descriptor, device) => {
return Factory.createInitAppWithFakeCryptoWithOptions({ return Factory.createInitAppWithFakeCryptoWithOptions({
@@ -82,7 +84,6 @@ describe('application group', function () {
}) })
it('should be notified when application changes', async function () { it('should be notified when application changes', async function () {
const group = new SNApplicationGroup(globalDevice)
let notifyCount = 0 let notifyCount = 0
const expectedCount = 2 const expectedCount = 2
// eslint-disable-next-line no-async-promise-executor // eslint-disable-next-line no-async-promise-executor

View File

@@ -1,7 +1,6 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import { BaseItemCounts } from './lib/BaseItemCounts.js' import { BaseItemCounts } from './lib/BaseItemCounts.js'
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
@@ -133,6 +132,12 @@ describe('application instances', () => {
deinit = sinon.spy(testSNApp, 'deinit') 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 () => { it('shows confirmation dialog when there are unsaved changes', async () => {
await testSNApp.mutator.setItemDirty(testNote1) await testSNApp.mutator.setItemDirty(testNote1)
await testSNApp.user.signOut() await testSNApp.user.signOut()

View File

@@ -1,14 +1,16 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import { BaseItemCounts } from './lib/BaseItemCounts.js' import { BaseItemCounts } from './lib/BaseItemCounts.js'
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('auth fringe cases', () => { describe('auth fringe cases', () => {
const createContext = async () => { let context
beforeEach(async function () {
localStorage.clear()
const application = await Factory.createInitAppWithFakeCrypto() const application = await Factory.createInitAppWithFakeCrypto()
return { context = {
expectedItemCount: BaseItemCounts.DefaultItems, expectedItemCount: BaseItemCounts.DefaultItems,
application: application, application: application,
email: UuidGenerator.GenerateUuid(), email: UuidGenerator.GenerateUuid(),
@@ -17,10 +19,6 @@ describe('auth fringe cases', () => {
await Factory.safeDeinit(application) await Factory.safeDeinit(application)
}, },
} }
}
beforeEach(async function () {
localStorage.clear()
}) })
afterEach(async function () { afterEach(async function () {
@@ -40,7 +38,6 @@ describe('auth fringe cases', () => {
describe('localStorage improperly cleared with 1 item', function () { describe('localStorage improperly cleared with 1 item', function () {
it('item should be errored', async function () { it('item should be errored', async function () {
const context = await createContext()
await context.application.register(context.email, context.password) await context.application.register(context.email, context.password)
const note = await Factory.createSyncedNote(context.application) const note = await Factory.createSyncedNote(context.application)
clearApplicationLocalStorageOfNonItems() clearApplicationLocalStorageOfNonItems()
@@ -55,7 +52,6 @@ describe('auth fringe cases', () => {
}) })
it('signing in again should decrypt item', async function () { it('signing in again should decrypt item', async function () {
const context = await createContext()
await context.application.register(context.email, context.password) await context.application.register(context.email, context.password)
const note = await Factory.createSyncedNote(context.application) const note = await Factory.createSyncedNote(context.application)
clearApplicationLocalStorageOfNonItems() clearApplicationLocalStorageOfNonItems()
@@ -76,7 +72,6 @@ describe('auth fringe cases', () => {
describe('having offline item matching remote item uuid', function () { 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 () { 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) await context.application.register(context.email, context.password)
const staleText = 'stale text' const staleText = 'stale text'

View File

@@ -13,11 +13,7 @@ describe('basic auth', function () {
} }
let context let context
let expectedItemCount
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -26,12 +22,23 @@ describe('basic auth', function () {
await context.launch() 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 () { it('successfully register new account', async function () {
const response = await context.register() const response = await context.register()
expect(response).to.be.ok expect(response).to.be.ok
expect(await context.application.encryption.getRootKey()).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 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(await context.application.encryption.getRootKey()).to.not.be.ok
expect(context.application.encryption.rootKeyManager.getKeyMode()).to.equal(KeyMode.RootKeyNone) expect(context.application.encryption.rootKeyManager.getKeyMode()).to.equal(KeyMode.RootKeyNone)
@@ -233,7 +240,7 @@ describe('basic auth', function () {
await specContext.launch() await specContext.launch()
await specContext.register() await specContext.register()
await specContext.signout() await specContext.deinit()
specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), uppercase, password) specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), uppercase, password)
@@ -255,10 +262,11 @@ describe('basic auth', function () {
* with an uppercase email * with an uppercase email
*/ */
const password = UuidGenerator.GenerateUuid() const password = UuidGenerator.GenerateUuid()
let specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), nospace, password) let specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), nospace, password)
await specContext.launch() await specContext.launch()
await specContext.register() await specContext.register()
await specContext.signout() await specContext.deinit()
specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), withspace, password) specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), withspace, password)
await specContext.launch() await specContext.launch()
@@ -272,7 +280,8 @@ describe('basic auth', function () {
it('fails login with wrong password', async function () { it('fails login with wrong password', async function () {
await context.register() await context.register()
context.application = await Factory.signOutApplicationAndReturnNew(context.application) await context.signout()
const response = await context.application.signIn( const response = await context.application.signIn(
context.email, context.email,
'wrongpassword', 'wrongpassword',
@@ -299,7 +308,8 @@ describe('basic auth', function () {
expect(response.error).to.be.ok expect(response.error).to.be.ok
/** Ensure we can still log in */ /** 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) }).timeout(20000)
it('registering for new account and completing first after download sync should not put us out of sync', async function () { 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 const noteCount = 5
await Factory.createManyMappedNotes(context.application, noteCount) await Factory.createManyMappedNotes(context.application, noteCount)
this.expectedItemCount += noteCount expectedItemCount += noteCount
await context.sync() 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 newPassword = 'newpassword'
const response = await context.application.changePassword(context.password, newPassword) const response = await context.application.changePassword(context.password, newPassword)
expect(response.error).to.not.be.ok 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)
expect(context.application.payloads.invalidPayloads.length).to.equal(0) expect(context.application.payloads.invalidPayloads.length).to.equal(0)
await context.application.sync.markAllItemsAsNeedingSyncAndPersist() await context.application.sync.markAllItemsAsNeedingSyncAndPersist()
await context.sync(syncOptions) 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) }).timeout(40000)
it('should sign into account after changing password', async function () { 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) const response = await context.application.changePassword(context.password, newPassword)
expect(response.error).to.not.be.ok expect(response.error).to.not.be.ok
this.expectedItemCount += ['new items key'].length expectedItemCount += ['new items key'].length
await context.signout() await context.signout()
@@ -382,7 +392,7 @@ describe('basic auth', function () {
expect(signinResponse.data.error).to.not.be.ok expect(signinResponse.data.error).to.not.be.ok
expect(await context.application.encryption.getRootKey()).to.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) expect(context.application.payloads.invalidPayloads.length).to.equal(0)
}) })
@@ -393,7 +403,7 @@ describe('basic auth', function () {
const noteCount = 3 const noteCount = 3
await Factory.createManyMappedNotes(context.application, noteCount) await Factory.createManyMappedNotes(context.application, noteCount)
this.expectedItemCount += noteCount expectedItemCount += noteCount
await context.sync() await context.sync()
@@ -401,9 +411,9 @@ describe('basic auth', function () {
const response = await context.application.changePassword(context.password, newPassword) const response = await context.application.changePassword(context.password, newPassword)
expect(response.error).to.not.be.ok 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 () { it('changes password many times', async function () {
@@ -411,7 +421,7 @@ describe('basic auth', function () {
const noteCount = 10 const noteCount = 10
await Factory.createManyMappedNotes(context.application, noteCount) await Factory.createManyMappedNotes(context.application, noteCount)
this.expectedItemCount += noteCount expectedItemCount += noteCount
await context.application.sync.sync(syncOptions) await context.application.sync.sync(syncOptions)
const numTimesToChangePw = 3 const numTimesToChangePw = 3
@@ -422,12 +432,12 @@ describe('basic auth', function () {
await context.application.changePassword(currentPassword, newPassword) await context.application.changePassword(currentPassword, newPassword)
/** New items key */ /** New items key */
this.expectedItemCount++ expectedItemCount++
currentPassword = newPassword currentPassword = newPassword
newPassword = Factory.randomString() 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) expect(context.application.payloads.invalidPayloads.length).to.equal(0)
await context.application.sync.markAllItemsAsNeedingSyncAndPersist() await context.application.sync.markAllItemsAsNeedingSyncAndPersist()
@@ -460,7 +470,7 @@ describe('basic auth', function () {
email: context.email, email: context.email,
password: context.password, password: context.password,
}) })
context.application = await Factory.signOutApplicationAndReturnNew(context.application) await context.signout()
const performSignIn = sinon.spy(context.application.sessions, 'performSignIn') const performSignIn = sinon.spy(context.application.sessions, 'performSignIn')
await context.application.signIn(context.email, 'wrong password', undefined, undefined, undefined, true) await context.application.signIn(context.email, 'wrong password', undefined, undefined, undefined, true)
expect(performSignIn.callCount).to.equal(1) expect(performSignIn.callCount).to.equal(1)

View File

@@ -1,41 +1,38 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import { BaseItemCounts } from './lib/BaseItemCounts.js' import { BaseItemCounts } from './lib/BaseItemCounts.js'
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('backups', function () { describe('backups', function () {
before(function () { let application
localStorage.clear() let email
}) let password
after(function () {
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
this.application = await Factory.createInitAppWithFakeCrypto() localStorage.clear()
this.email = UuidGenerator.GenerateUuid() application = await Factory.createInitAppWithFakeCrypto()
this.password = UuidGenerator.GenerateUuid() email = UuidGenerator.GenerateUuid()
password = UuidGenerator.GenerateUuid()
}) })
afterEach(async function () { afterEach(async function () {
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
this.application = null application = null
localStorage.clear()
}) })
it('backup file should have a version number', async function () { it('backup file should have a version number', async function () {
let data = await this.application.createDecryptedBackupFile() let data = await application.createDecryptedBackupFile()
expect(data.version).to.equal(this.application.encryption.getLatestVersion()) expect(data.version).to.equal(application.encryption.getLatestVersion())
await this.application.addPasscode('passcode') await application.addPasscode('passcode')
data = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() data = await application.createEncryptedBackupFileForAutomatedDesktopBackups()
expect(data.version).to.equal(this.application.encryption.getLatestVersion()) expect(data.version).to.equal(application.encryption.getLatestVersion())
}) })
it('no passcode + no account backup file should have correct number of items', async function () { 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)]) await Promise.all([Factory.createSyncedNote(application), Factory.createSyncedNote(application)])
const data = await this.application.createDecryptedBackupFile() const data = await application.createDecryptedBackupFile()
const offsetForNewItems = 2 const offsetForNewItems = 2
const offsetForNoItemsKey = -1 const offsetForNoItemsKey = -1
expect(data.items.length).to.equal(BaseItemCounts.DefaultItems + offsetForNewItems + offsetForNoItemsKey) 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 () { it('passcode + no account backup file should have correct number of items', async function () {
const passcode = 'passcode' const passcode = 'passcode'
await this.application.addPasscode(passcode) await application.addPasscode(passcode)
await Promise.all([Factory.createSyncedNote(this.application), Factory.createSyncedNote(this.application)]) await Promise.all([Factory.createSyncedNote(application), Factory.createSyncedNote(application)])
// Encrypted backup without authorization // Encrypted backup without authorization
const encryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() const encryptedData = await application.createEncryptedBackupFileForAutomatedDesktopBackups()
expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItems + 2) expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItems + 2)
// Encrypted backup with authorization // Encrypted backup with authorization
Factory.handlePasswordChallenges(this.application, passcode) Factory.handlePasswordChallenges(application, passcode)
const authorizedEncryptedData = await this.application.createEncryptedBackupFile() const authorizedEncryptedData = await application.createEncryptedBackupFile()
expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItems + 2) expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItems + 2)
}) })
it('no passcode + account backup file should have correct number of items', async function () { it('no passcode + account backup file should have correct number of items', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, 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 // Encrypted backup without authorization
const encryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() const encryptedData = await application.createEncryptedBackupFileForAutomatedDesktopBackups()
expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2) expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2)
Factory.handlePasswordChallenges(this.application, this.password) Factory.handlePasswordChallenges(application, password)
// Decrypted backup // Decrypted backup
const decryptedData = await this.application.createDecryptedBackupFile() const decryptedData = await application.createDecryptedBackupFile()
expect(decryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccountWithoutItemsKey + 2) expect(decryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccountWithoutItemsKey + 2)
// Encrypted backup with authorization // Encrypted backup with authorization
const authorizedEncryptedData = await this.application.createEncryptedBackupFile() const authorizedEncryptedData = await application.createEncryptedBackupFile()
expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2) expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2)
}) })
it('passcode + account backup file should have correct number of items', async function () { it('passcode + account backup file should have correct number of items', async function () {
const passcode = 'passcode' const passcode = 'passcode'
await this.application.register(this.email, this.password) await application.register(email, password)
Factory.handlePasswordChallenges(this.application, this.password) Factory.handlePasswordChallenges(application, password)
await this.application.addPasscode(passcode) await application.addPasscode(passcode)
await Promise.all([Factory.createSyncedNote(this.application), Factory.createSyncedNote(this.application)]) await Promise.all([Factory.createSyncedNote(application), Factory.createSyncedNote(application)])
// Encrypted backup without authorization // Encrypted backup without authorization
const encryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() const encryptedData = await application.createEncryptedBackupFileForAutomatedDesktopBackups()
expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2) expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2)
Factory.handlePasswordChallenges(this.application, passcode) Factory.handlePasswordChallenges(application, passcode)
// Decrypted backup // Decrypted backup
const decryptedData = await this.application.createDecryptedBackupFile() const decryptedData = await application.createDecryptedBackupFile()
expect(decryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccountWithoutItemsKey + 2) expect(decryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccountWithoutItemsKey + 2)
// Encrypted backup with authorization // Encrypted backup with authorization
const authorizedEncryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() const authorizedEncryptedData = await application.createEncryptedBackupFileForAutomatedDesktopBackups()
expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2) expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2)
}).timeout(10000) }).timeout(10000)
it('backup file item should have correct fields', async function () { it('backup file item should have correct fields', async function () {
await Factory.createSyncedNote(this.application) await Factory.createSyncedNote(application)
let backupData = await this.application.createDecryptedBackupFile() let backupData = await application.createDecryptedBackupFile()
let rawItem = backupData.items.find((i) => i.content_type === ContentType.TYPES.Note) let rawItem = backupData.items.find((i) => i.content_type === ContentType.TYPES.Note)
expect(rawItem.fields).to.not.be.ok expect(rawItem.fields).to.not.be.ok
@@ -118,12 +115,12 @@ describe('backups', function () {
expect(rawItem.updated_at).to.be.ok expect(rawItem.updated_at).to.be.ok
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
backupData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() backupData = await application.createEncryptedBackupFileForAutomatedDesktopBackups()
rawItem = backupData.items.find((i) => i.content_type === ContentType.TYPES.Note) rawItem = backupData.items.find((i) => i.content_type === ContentType.TYPES.Note)
expect(rawItem.fields).to.not.be.ok 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 () { 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: { usesItemsKeyWithKeyLookup: {
items: [note.payload], items: [note.payload],
}, },
@@ -152,19 +149,19 @@ describe('backups', function () {
errorDecrypting: true, 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) 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) expect(backupData.items.length).to.equal(BaseItemCounts.DefaultItemsNoAccounNoItemsKey + 2)
}) })
it('decrypted backup file should not have keyParams', async function () { 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') expect(backup).to.not.haveOwnProperty('keyParams')
}) })
@@ -187,31 +184,31 @@ describe('backups', function () {
}) })
it('encrypted backup file should have keyParams', async function () { it('encrypted backup file should have keyParams', async function () {
await this.application.addPasscode('passcode') await application.addPasscode('passcode')
const backup = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() const backup = await application.createEncryptedBackupFileForAutomatedDesktopBackups()
expect(backup).to.haveOwnProperty('keyParams') expect(backup).to.haveOwnProperty('keyParams')
}) })
it('decrypted backup file should not have itemsKeys', async function () { 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 expect(backup.items.some((item) => item.content_type === ContentType.TYPES.ItemsKey)).to.be.false
}) })
it('encrypted backup file should have itemsKeys', async function () { it('encrypted backup file should have itemsKeys', async function () {
await this.application.addPasscode('passcode') await application.addPasscode('passcode')
const backup = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() const backup = await application.createEncryptedBackupFileForAutomatedDesktopBackups()
expect(backup.items.some((item) => item.content_type === ContentType.TYPES.ItemsKey)).to.be.true 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 () { it('backup file with no account and no passcode should be decrypted', async function () {
const note = await Factory.createSyncedNote(this.application) const note = await Factory.createSyncedNote(application)
const backup = await this.application.createDecryptedBackupFile() const backup = await application.createDecryptedBackupFile()
expect(backup).to.not.haveOwnProperty('keyParams') expect(backup).to.not.haveOwnProperty('keyParams')
expect(backup.items.some((item) => item.content_type === ContentType.TYPES.ItemsKey)).to.be.false 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) expect(backup.items.find((item) => item.content_type === ContentType.TYPES.Note).uuid).to.equal(note.uuid)
let error let error
try { try {
await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() await application.createEncryptedBackupFileForAutomatedDesktopBackups()
} catch (e) { } catch (e) {
error = e error = e
} }

View File

@@ -1,16 +1,15 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
import { createRelatedNoteTagPairPayload } from './lib/Items.js' import { createRelatedNoteTagPairPayload } from './lib/Items.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('payload collections', () => { describe('payload collections', () => {
before(async () => { beforeEach(async () => {
localStorage.clear() localStorage.clear()
}) })
after(async () => { afterEach(async () => {
localStorage.clear() localStorage.clear()
}) })

View File

@@ -1,6 +1,5 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect

View File

@@ -1,6 +1,6 @@
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('features', () => { describe('features', () => {
@@ -9,6 +9,7 @@ describe('features', () => {
let password let password
beforeEach(async function () { beforeEach(async function () {
localStorage.clear()
application = await Factory.createInitAppWithFakeCrypto() application = await Factory.createInitAppWithFakeCrypto()
sinon.spy(application.mutator, 'createItem') sinon.spy(application.mutator, 'createItem')
@@ -28,6 +29,8 @@ describe('features', () => {
afterEach(async function () { afterEach(async function () {
Factory.safeDeinit(application) Factory.safeDeinit(application)
sinon.restore() sinon.restore()
localStorage.clear()
application = undefined
}) })
describe('new user roles received on api response meta', () => { describe('new user roles received on api response meta', () => {

View File

@@ -17,6 +17,14 @@ describe('files', function () {
localStorage.clear() localStorage.clear()
}) })
afterEach(async function () {
await Factory.safeDeinit(application)
localStorage.clear()
application = undefined
context = undefined
})
const setup = async ({ fakeCrypto, subscription = true }) => { const setup = async ({ fakeCrypto, subscription = true }) => {
if (fakeCrypto) { if (fakeCrypto) {
context = await Factory.createAppContextWithFakeCrypto() 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 () { it('should create valet token from server', async function () {
await setup({ fakeCrypto: true, subscription: true }) await setup({ fakeCrypto: true, subscription: true })

View File

@@ -1,13 +1,14 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
import { createNoteParams } from './lib/Items.js' import { createNoteParams } from './lib/Items.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('history manager', () => { describe('history manager', () => {
const largeCharacterChange = 25 const largeCharacterChange = 25
let application, history, email, password
const syncOptions = { const syncOptions = {
checkIntegrity: true, checkIntegrity: true,
awaitAll: true, awaitAll: true,
@@ -23,15 +24,15 @@ describe('history manager', () => {
describe('session', function () { describe('session', function () {
beforeEach(async function () { beforeEach(async function () {
this.application = await Factory.createInitAppWithFakeCrypto() application = await Factory.createInitAppWithFakeCrypto()
this.history = this.application.dependencies.get(TYPES.HistoryManager) history = application.dependencies.get(TYPES.HistoryManager)
this.payloadManager = this.application.payloads
/** Automatically optimize after every revision by setting this to 0 */ /** Automatically optimize after every revision by setting this to 0 */
this.history.itemRevisionThreshold = 0 history.itemRevisionThreshold = 0
}) })
afterEach(async function () { afterEach(async function () {
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
}) })
async function setTextAndSync(application, item, text) { async function setTextAndSync(application, item, text) {
@@ -53,15 +54,15 @@ describe('history manager', () => {
} }
it('create basic history entries 1', async function () { it('create basic history entries 1', async function () {
const item = await Factory.createSyncedNote(this.application) const item = await Factory.createSyncedNote(application)
expect(this.history.sessionHistoryForItem(item).length).to.equal(0) expect(history.sessionHistoryForItem(item).length).to.equal(0)
/** Sync with same contents, should not create new entry */ /** Sync with same contents, should not create new entry */
await Factory.markDirtyAndSyncItem(this.application, item) await Factory.markDirtyAndSyncItem(application, item)
expect(this.history.sessionHistoryForItem(item).length).to.equal(0) expect(history.sessionHistoryForItem(item).length).to.equal(0)
/** Sync with different contents, should create new entry */ /** Sync with different contents, should create new entry */
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item, item,
(mutator) => { (mutator) => {
mutator.title = Math.random() mutator.title = Math.random()
@@ -70,12 +71,12 @@ describe('history manager', () => {
undefined, undefined,
syncOptions, 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 () { it('first change should create revision with previous value', async function () {
const identifier = this.application.identifier const identifier = application.identifier
const item = await Factory.createSyncedNote(this.application) const item = await Factory.createSyncedNote(application)
/** Simulate loading new application session */ /** Simulate loading new application session */
const context = await Factory.createAppContext({ identifier }) const context = await Factory.createAppContext({ identifier })
@@ -119,53 +120,41 @@ describe('history manager', () => {
}) })
it('should optimize basic entries', async function () { 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 * 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. * won't here because it's the first change, which we want to keep.
*/ */
await setTextAndSync(this.application, item, item.content.text + '1') await setTextAndSync(application, item, item.content.text + '1')
expect(this.history.sessionHistoryForItem(item).length).to.equal(1) expect(history.sessionHistoryForItem(item).length).to.equal(1)
/** /**
* Changing it by one character should keep this entry, * Changing it by one character should keep this entry,
* since it's now the last (and will keep the first) * since it's now the last (and will keep the first)
*/ */
item = await setTextAndSync(this.application, item, item.content.text + '2') item = await setTextAndSync(application, item, item.content.text + '2')
expect(this.history.sessionHistoryForItem(item).length).to.equal(2) expect(history.sessionHistoryForItem(item).length).to.equal(2)
/** /**
* Change it over the largeCharacterChange threshold. It should keep this * Change it over the largeCharacterChange threshold. It should keep this
* revision, but now remove the previous revision, since it's no longer * revision, but now remove the previous revision, since it's no longer
* the last, and is a small change. * the last, and is a small change.
*/ */
item = await setTextAndSync( item = await setTextAndSync(application, item, item.content.text + Factory.randomString(largeCharacterChange + 1))
this.application, expect(history.sessionHistoryForItem(item).length).to.equal(2)
item,
item.content.text + Factory.randomString(largeCharacterChange + 1),
)
expect(this.history.sessionHistoryForItem(item).length).to.equal(2)
item = await setTextAndSync( item = await setTextAndSync(application, item, item.content.text + Factory.randomString(largeCharacterChange + 1))
this.application, expect(history.sessionHistoryForItem(item).length).to.equal(2)
item,
item.content.text + Factory.randomString(largeCharacterChange + 1),
)
expect(this.history.sessionHistoryForItem(item).length).to.equal(2)
/** Delete over threshold text. */ /** Delete over threshold text. */
item = await setTextAndSync( item = await setTextAndSync(application, item, deleteCharsFromString(item.content.text, largeCharacterChange + 1))
this.application, expect(history.sessionHistoryForItem(item).length).to.equal(3)
item,
deleteCharsFromString(item.content.text, largeCharacterChange + 1),
)
expect(this.history.sessionHistoryForItem(item).length).to.equal(3)
/** /**
* Delete just 1 character. It should now retain the previous revision, as well as the * Delete just 1 character. It should now retain the previous revision, as well as the
* one previous to that. * one previous to that.
*/ */
item = await setTextAndSync(this.application, item, deleteCharsFromString(item.content.text, 1)) item = await setTextAndSync(application, item, deleteCharsFromString(item.content.text, 1))
expect(this.history.sessionHistoryForItem(item).length).to.equal(4) expect(history.sessionHistoryForItem(item).length).to.equal(4)
item = await setTextAndSync(this.application, item, deleteCharsFromString(item.content.text, 1)) item = await setTextAndSync(application, item, deleteCharsFromString(item.content.text, 1))
expect(this.history.sessionHistoryForItem(item).length).to.equal(5) expect(history.sessionHistoryForItem(item).length).to.equal(5)
}) })
it('should keep the entry right before a large deletion, regardless of its delta', async function () { 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), text: Factory.randomString(100),
}), }),
) )
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 application.mutator.setItemDirty(item)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
/** It should keep the first and last by default */ /** It should keep the first and last by default */
item = await setTextAndSync(this.application, item, item.content.text) item = await setTextAndSync(application, item, item.content.text)
item = await setTextAndSync(this.application, item, item.content.text + Factory.randomString(1)) item = await setTextAndSync(application, item, item.content.text + Factory.randomString(1))
expect(this.history.sessionHistoryForItem(item).length).to.equal(2) expect(history.sessionHistoryForItem(item).length).to.equal(2)
item = await setTextAndSync( item = await setTextAndSync(application, item, deleteCharsFromString(item.content.text, largeCharacterChange + 1))
this.application, expect(history.sessionHistoryForItem(item).length).to.equal(2)
item, item = await setTextAndSync(application, item, item.content.text + Factory.randomString(1))
deleteCharsFromString(item.content.text, largeCharacterChange + 1), expect(history.sessionHistoryForItem(item).length).to.equal(3)
) item = await setTextAndSync(application, item, item.content.text + Factory.randomString(largeCharacterChange + 1))
expect(this.history.sessionHistoryForItem(item).length).to.equal(2) expect(history.sessionHistoryForItem(item).length).to.equal(4)
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)
}) })
it('entries should be ordered from newest to oldest', async function () { 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 application.mutator.setItemDirty(item)
await this.application.sync.sync(syncOptions) 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( item = await setTextAndSync(application, item, deleteCharsFromString(item.content.text, largeCharacterChange + 1))
this.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( item = await setTextAndSync(application, item, item.content.text + Factory.randomString(largeCharacterChange + 1))
this.application,
item,
item.content.text + Factory.randomString(largeCharacterChange + 1),
)
/** First entry should be the latest revision. */ /** 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. */ /** Last entry should be the initial revision. */
const initialRevision = const initialRevision = history.sessionHistoryForItem(item)[history.sessionHistoryForItem(item).length - 1]
this.history.sessionHistoryForItem(item)[this.history.sessionHistoryForItem(item).length - 1]
expect(latestRevision).to.not.equal(initialRevision) 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 () { it('unsynced entries should use payload created_at for preview titles', async function () {
const payload = Factory.createNotePayload() const payload = Factory.createNotePayload()
await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
const item = this.application.items.findItem(payload.uuid) const item = application.items.findItem(payload.uuid)
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item, item,
(mutator) => { (mutator) => {
mutator.title = Math.random() mutator.title = Math.random()
@@ -254,7 +226,7 @@ describe('history manager', () => {
undefined, undefined,
syncOptions, syncOptions,
) )
const historyItem = this.history.sessionHistoryForItem(item)[0] const historyItem = history.sessionHistoryForItem(item)[0]
expect(historyItem.previewTitle()).to.equal(historyItem.payload.created_at.toLocaleString()) expect(historyItem.previewTitle()).to.equal(historyItem.payload.created_at.toLocaleString())
}) })
}) })
@@ -263,52 +235,54 @@ describe('history manager', () => {
this.timeout(Factory.TwentySecondTimeout) this.timeout(Factory.TwentySecondTimeout)
beforeEach(async function () { beforeEach(async function () {
this.application = await Factory.createInitAppWithFakeCrypto() localStorage.clear()
this.history = this.application.dependencies.get(TYPES.HistoryManager)
this.payloadManager = this.application.payloads application = await Factory.createInitAppWithFakeCrypto()
this.email = UuidGenerator.GenerateUuid() history = application.dependencies.get(TYPES.HistoryManager)
this.password = UuidGenerator.GenerateUuid() email = UuidGenerator.GenerateUuid()
password = UuidGenerator.GenerateUuid()
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
}) })
afterEach(async function () { 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 () { it('response from server should be failed if not signed in', async function () {
await this.application.user.signOut() await application.user.signOut()
this.application = await Factory.createInitAppWithFakeCrypto() application = await Factory.createInitAppWithFakeCrypto()
this.history = this.application.dependencies.get(TYPES.HistoryManager) history = application.dependencies.get(TYPES.HistoryManager)
this.payloadManager = this.application.payloads
const item = await Factory.createSyncedNote(this.application) const item = await Factory.createSyncedNote(application)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
const itemHistoryOrError = await this.application.listRevisions.execute({ itemUuid: item.uuid }) const itemHistoryOrError = await application.listRevisions.execute({ itemUuid: item.uuid })
expect(itemHistoryOrError.isFailed()).to.equal(true) expect(itemHistoryOrError.isFailed()).to.equal(true)
}) })
it('create basic history entries 2', async function () { 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) 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() let itemHistory = itemHistoryOrError.getValue()
/** Server history should save initial revision */ /** Server history should save initial revision */
expect(itemHistory.length).to.equal(1) expect(itemHistory.length).to.equal(1)
/** Sync within 5 seconds (ENV VAR dependend on self-hosted setup), should not create a new entry */ /** Sync within 5 seconds (ENV VAR dependend on self-hosted setup), should not create a new entry */
await Factory.markDirtyAndSyncItem(this.application, item) await Factory.markDirtyAndSyncItem(application, item)
itemHistoryOrError = await this.application.listRevisions.execute({ itemUuid: item.uuid }) itemHistoryOrError = await application.listRevisions.execute({ itemUuid: item.uuid })
itemHistory = itemHistoryOrError.getValue() itemHistory = itemHistoryOrError.getValue()
expect(itemHistory.length).to.equal(1) expect(itemHistory.length).to.equal(1)
/** Sync with different contents, should not create a new entry */ /** Sync with different contents, should not create a new entry */
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item, item,
(mutator) => { (mutator) => {
mutator.title = Math.random() mutator.title = Math.random()
@@ -318,18 +292,18 @@ describe('history manager', () => {
syncOptions, syncOptions,
) )
await Factory.sleep(Factory.ServerRevisionCreationDelay) 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() itemHistory = itemHistoryOrError.getValue()
expect(itemHistory.length).to.equal(1) expect(itemHistory.length).to.equal(1)
}) })
it('returns revisions from server', async function () { 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) await Factory.sleep(Factory.ServerRevisionFrequency)
/** Sync with different contents, should create new entry */ /** Sync with different contents, should create new entry */
const newTitleAfterFirstChange = `The title should be: ${Math.random()}` const newTitleAfterFirstChange = `The title should be: ${Math.random()}`
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item, item,
(mutator) => { (mutator) => {
mutator.title = newTitleAfterFirstChange mutator.title = newTitleAfterFirstChange
@@ -340,12 +314,12 @@ describe('history manager', () => {
) )
await Factory.sleep(Factory.ServerRevisionCreationDelay) 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() const itemHistory = itemHistoryOrError.getValue()
expect(itemHistory.length).to.equal(2) expect(itemHistory.length).to.equal(2)
const oldestEntry = lastElement(itemHistory) const oldestEntry = lastElement(itemHistory)
let revisionFromServerOrError = await this.application.getRevision.execute({ let revisionFromServerOrError = await application.getRevision.execute({
itemUuid: item.uuid, itemUuid: item.uuid,
revisionUuid: oldestEntry.uuid, revisionUuid: oldestEntry.uuid,
}) })
@@ -357,22 +331,22 @@ describe('history manager', () => {
expect(payloadFromServer.uuid).to.eq(item.payload.uuid) expect(payloadFromServer.uuid).to.eq(item.payload.uuid)
expect(payloadFromServer.content).to.eql(item.payload.content) 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) expect(payloadFromServer.content).to.not.eql(item.payload.content)
}) })
it('duplicate revisions should not have the originals uuid', async function () { it('duplicate revisions should not have the originals uuid', async function () {
const note = await Factory.createSyncedNote(this.application) const note = await Factory.createSyncedNote(application)
await Factory.markDirtyAndSyncItem(this.application, note) await Factory.markDirtyAndSyncItem(application, note)
const dupe = await this.application.mutator.duplicateItem(note, true) const dupe = await application.mutator.duplicateItem(note, true)
await Factory.markDirtyAndSyncItem(this.application, dupe) await Factory.markDirtyAndSyncItem(application, dupe)
await Factory.sleep(Factory.ServerRevisionCreationDelay) 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 dupeHistory = dupeHistoryOrError.getValue()
const dupeRevisionOrError = await this.application.getRevision.execute({ const dupeRevisionOrError = await application.getRevision.execute({
itemUuid: dupe.uuid, itemUuid: dupe.uuid,
revisionUuid: dupeHistory[0].uuid, revisionUuid: dupeHistory[0].uuid,
}) })
@@ -381,54 +355,54 @@ describe('history manager', () => {
}) })
it('revisions count matches original for duplicated items', async function () { 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.sleep(Factory.ServerRevisionFrequency)
await Factory.markDirtyAndSyncItem(this.application, note) await Factory.markDirtyAndSyncItem(application, note)
await Factory.sleep(Factory.ServerRevisionFrequency) await Factory.sleep(Factory.ServerRevisionFrequency)
await Factory.markDirtyAndSyncItem(this.application, note) await Factory.markDirtyAndSyncItem(application, note)
await Factory.sleep(Factory.ServerRevisionFrequency) 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) const dupe = await application.mutator.duplicateItem(note, true)
await Factory.markDirtyAndSyncItem(this.application, dupe) await Factory.markDirtyAndSyncItem(application, dupe)
await Factory.sleep(Factory.ServerRevisionCreationDelay) await Factory.sleep(Factory.ServerRevisionCreationDelay)
const expectedRevisions = 4 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 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() const dupeHistory = dupeHistoryOrError.getValue()
expect(noteHistory.length).to.equal(expectedRevisions) expect(noteHistory.length).to.equal(expectedRevisions)
expect(dupeHistory.length).to.equal(expectedRevisions + 1) expect(dupeHistory.length).to.equal(expectedRevisions + 1)
}).timeout(Factory.ThirtySecondTimeout) }).timeout(Factory.SixtySecondTimeout)
it('can decrypt revisions for duplicate_of items', async function () { 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) await Factory.sleep(Factory.ServerRevisionFrequency)
const changedText = `${Math.random()}` const changedText = `${Math.random()}`
await this.application.changeAndSaveItem.execute(note, (mutator) => { await application.changeAndSaveItem.execute(note, (mutator) => {
mutator.title = changedText mutator.title = changedText
}) })
await Factory.markDirtyAndSyncItem(this.application, note) await Factory.markDirtyAndSyncItem(application, note)
const dupe = await this.application.mutator.duplicateItem(note, true) const dupe = await application.mutator.duplicateItem(note, true)
await Factory.markDirtyAndSyncItem(this.application, dupe) await Factory.markDirtyAndSyncItem(application, dupe)
await Factory.sleep(Factory.ServerRevisionCreationDelay) 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() const itemHistory = itemHistoryOrError.getValue()
expect(itemHistory.length).to.be.above(1) expect(itemHistory.length).to.be.above(1)
const newestRevision = itemHistory[0] const newestRevision = itemHistory[0]
const fetchedOrError = await this.application.getRevision.execute({ const fetchedOrError = await application.getRevision.execute({
itemUuid: dupe.uuid, itemUuid: dupe.uuid,
revisionUuid: newestRevision.uuid, revisionUuid: newestRevision.uuid,
}) })

View File

@@ -1,45 +1,43 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('item', () => { const createBarePayload = () => {
beforeEach(async function () { return new DecryptedPayload({
this.createBarePayload = () => { uuid: '123',
return new DecryptedPayload({ content_type: ContentType.TYPES.Note,
uuid: '123', content: {
content_type: ContentType.TYPES.Note, title: 'hello',
content: { },
title: 'hello', })
}, }
})
}
this.createNote = () => { const createNote = () => {
return new DecryptedItem(this.createBarePayload()) return new DecryptedItem(createBarePayload())
} }
this.createTag = (notes = []) => { const createTag = (notes = []) => {
const references = notes.map((note) => { const references = notes.map((note) => {
return { return {
uuid: note.uuid, uuid: note.uuid,
content_type: note.content_type, content_type: note.content_type,
}
})
return new SNTag(
new DecryptedPayload({
uuid: Factory.generateUuidish(),
content_type: ContentType.TYPES.Tag,
content: {
title: 'thoughts',
references: references,
},
}),
)
} }
}) })
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 () { it('constructing without uuid should throw', function () {
let error let error
@@ -53,40 +51,40 @@ describe('item', () => {
}) })
it('healthy constructor', function () { it('healthy constructor', function () {
const item = this.createNote() const item = createNote()
expect(item).to.be.ok expect(item).to.be.ok
expect(item.payload).to.be.ok expect(item.payload).to.be.ok
}) })
it('user modified date should be ok', function () { it('user modified date should be ok', function () {
const item = this.createNote() const item = createNote()
expect(item.userModifiedDate).to.be.ok expect(item.userModifiedDate).to.be.ok
}) })
it('has relationship with item true', function () { it('has relationship with item true', function () {
const note = this.createNote() const note = createNote()
const tag = this.createTag() const tag = createTag()
expect(tag.isReferencingItem(note)).to.equal(false) expect(tag.isReferencingItem(note)).to.equal(false)
}) })
it('has relationship with item true', function () { it('has relationship with item true', function () {
const note = this.createNote() const note = createNote()
const tag = this.createTag([note]) const tag = createTag([note])
expect(tag.isReferencingItem(note)).to.equal(true) expect(tag.isReferencingItem(note)).to.equal(true)
}) })
it('getDomainData for random domain should return undefined', function () { it('getDomainData for random domain should return undefined', function () {
const note = this.createNote() const note = createNote()
expect(note.getDomainData('random')).to.not.be.ok expect(note.getDomainData('random')).to.not.be.ok
}) })
it('getDomainData for app domain should return object', function () { it('getDomainData for app domain should return object', function () {
const note = this.createNote() const note = createNote()
expect(note.getDomainData(DecryptedItem.DefaultAppDomain())).to.be.ok expect(note.getDomainData(DecryptedItem.DefaultAppDomain())).to.be.ok
}) })

View File

@@ -1,5 +1,3 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
import { BaseItemCounts } from './lib/BaseItemCounts.js' import { BaseItemCounts } from './lib/BaseItemCounts.js'
@@ -10,11 +8,6 @@ describe('item manager', function () {
let context let context
let application let application
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -24,6 +17,14 @@ describe('item manager', function () {
await context.launch() await context.launch()
}) })
afterEach(async function () {
await context.deinit()
localStorage.clear()
context = undefined
application = undefined
})
const createNote = async () => { const createNote = async () => {
return application.mutator.createItem(ContentType.TYPES.Note, { return application.mutator.createItem(ContentType.TYPES.Note, {
title: 'hello', title: 'hello',

View File

@@ -1,17 +1,17 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('key params', function () { describe('key params', function () {
this.timeout(Factory.TenSecondTimeout) this.timeout(Factory.TenSecondTimeout)
before(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
}) })
after(async function () { afterEach(async function () {
localStorage.clear() localStorage.clear()
}) })

View File

@@ -1,23 +1,18 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('key recovery service', function () { describe('key recovery service', function () {
this.timeout(Factory.TwentySecondTimeout) this.timeout(Factory.TwentySecondTimeout)
const syncOptions = {
checkIntegrity: true,
awaitAll: true,
}
beforeEach(function () { beforeEach(function () {
localStorage.clear() localStorage.clear()
}) })
afterEach(function () { afterEach(function () {
localStorage.clear() localStorage.clear()
sinon.restore()
}) })
it('when encountering an undecryptable items key, should recover through recovery wizard', async function () { 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, KeyParamsOrigination.Registration,
ProtocolVersion.V003, ProtocolVersion.V003,
) )
const randomItemsKey = await context.operators const randomItemsKey = await context.operators.operatorForVersion(ProtocolVersion.V003).createItemsKey()
.operatorForVersion(ProtocolVersion.V003)
.createItemsKey()
const encrypted = await application.encryption.encryptSplitSingle({ const encrypted = await application.encryption.encryptSplitSingle({
usesRootKey: { usesRootKey: {

View File

@@ -1,35 +1,40 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
import * as Utils from './lib/Utils.js' import * as Utils from './lib/Utils.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('keys', function () { describe('keys', function () {
this.timeout(Factory.TwentySecondTimeout) this.timeout(Factory.TwentySecondTimeout)
let context
let application
let email
let password
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
this.context = await Factory.createAppContext() context = await Factory.createAppContext()
await this.context.launch() await context.launch()
this.application = this.context.application application = context.application
this.email = UuidGenerator.GenerateUuid() email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid() password = UuidGenerator.GenerateUuid()
}) })
afterEach(async function () { afterEach(async function () {
if (!this.application.dealloced) { if (!application.dealloced) {
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
} }
this.application = undefined application = undefined
context = undefined
localStorage.clear() localStorage.clear()
}) })
it('should not have root key by default', async function () { 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 () { it('validates content types requiring root encryption', function () {
@@ -43,7 +48,7 @@ describe('keys', function () {
/** Items key available by default */ /** Items key available by default */
const payload = Factory.createNotePayload() const payload = Factory.createNotePayload()
const processedPayload = CreateEncryptedLocalStorageContextPayload( const processedPayload = CreateEncryptedLocalStorageContextPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
@@ -53,45 +58,41 @@ describe('keys', function () {
}) })
it('has root key and one items key after registering user', async function () { it('has root key and one items key after registering user', async function () {
await Factory.registerUserToApplication({ application: this.application }) await Factory.registerUserToApplication({ application: application })
expect(this.application.encryption.getRootKey()).to.be.ok expect(application.encryption.getRootKey()).to.be.ok
expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) expect(application.items.getDisplayableItemsKeys().length).to.equal(1)
}) })
it('changing root key with passcode should re-wrap root key', async function () { it('changing root key with passcode should re-wrap root key', async function () {
const email = 'foo' const email = 'foo'
const password = 'bar' const password = 'bar'
const key = await this.application.encryption.createRootKey(email, password, KeyParamsOrigination.Registration) const key = await application.encryption.createRootKey(email, password, KeyParamsOrigination.Registration)
await this.application.encryption.setRootKey(key) await application.encryption.setRootKey(key)
Factory.handlePasswordChallenges(this.application, password) Factory.handlePasswordChallenges(application, password)
await this.application.addPasscode(password) await application.addPasscode(password)
/** We should be able to decrypt wrapped root key with passcode */ /** We should be able to decrypt wrapped root key with passcode */
const wrappingKeyParams = await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams() const wrappingKeyParams = await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()
const wrappingKey = await this.application.encryption.computeRootKey(password, wrappingKeyParams) const wrappingKey = await application.encryption.computeRootKey(password, wrappingKeyParams)
await this.application.encryption.unwrapRootKey(wrappingKey).catch((error) => { await application.encryption.unwrapRootKey(wrappingKey).catch((error) => {
expect(error).to.not.be.ok expect(error).to.not.be.ok
}) })
const newPassword = 'bar' const newPassword = 'bar'
const newKey = await this.application.encryption.createRootKey( const newKey = await application.encryption.createRootKey(email, newPassword, KeyParamsOrigination.Registration)
email, await application.encryption.setRootKey(newKey, wrappingKey)
newPassword, await application.encryption.unwrapRootKey(wrappingKey).catch((error) => {
KeyParamsOrigination.Registration,
)
await this.application.encryption.setRootKey(newKey, wrappingKey)
await this.application.encryption.unwrapRootKey(wrappingKey).catch((error) => {
expect(error).to.not.be.ok expect(error).to.not.be.ok
}) })
}) })
it('items key should be encrypted with root key', async function () { it('items key should be encrypted with root key', 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()
const rootKey = await this.application.encryption.getRootKey() const rootKey = await application.encryption.getRootKey()
/** Encrypt items key */ /** Encrypt items key */
const encryptedPayload = await this.application.encryption.encryptSplitSingle({ const encryptedPayload = await application.encryption.encryptSplitSingle({
usesRootKey: { usesRootKey: {
items: [itemsKey.payloadRepresentation()], items: [itemsKey.payloadRepresentation()],
key: rootKey, key: rootKey,
@@ -102,7 +103,7 @@ describe('keys', function () {
expect(encryptedPayload.items_key_id).to.not.be.ok expect(encryptedPayload.items_key_id).to.not.be.ok
/** Attempt to decrypt with root key. Should succeed. */ /** Attempt to decrypt with root key. Should succeed. */
const decryptedPayload = await this.application.encryption.decryptSplitSingle({ const decryptedPayload = await application.encryption.decryptSplitSingle({
usesRootKey: { usesRootKey: {
items: [encryptedPayload], items: [encryptedPayload],
key: rootKey, key: rootKey,
@@ -114,7 +115,7 @@ describe('keys', function () {
}) })
it('should create random items key if no account and no passcode', async 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) expect(itemsKeys.length).to.equal(1)
const notePayload = Factory.createNotePayload() const notePayload = Factory.createNotePayload()
@@ -122,55 +123,55 @@ describe('keys', function () {
dirty: true, dirty: true,
dirtyIndex: getIncrementedDirtyIndex(), dirtyIndex: getIncrementedDirtyIndex(),
}) })
await this.application.payloads.emitPayload(dirtied, PayloadEmitSource.LocalChanged) await application.payloads.emitPayload(dirtied, PayloadEmitSource.LocalChanged)
await this.application.sync.sync() 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) const rawNotePayload = rawPayloads.find((r) => r.content_type === ContentType.TYPES.Note)
expect(typeof rawNotePayload.content).to.equal('string') expect(typeof rawNotePayload.content).to.equal('string')
}) })
it('should keep offline created items key upon registration', async function () { it('should keep offline created items key upon registration', async function () {
expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) expect(application.items.getDisplayableItemsKeys().length).to.equal(1)
const originalItemsKey = this.application.items.getDisplayableItemsKeys()[0] const originalItemsKey = application.items.getDisplayableItemsKeys()[0]
await this.application.register(this.email, this.password) await application.register(email, password)
expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) expect(application.items.getDisplayableItemsKeys().length).to.equal(1)
const newestItemsKey = this.application.items.getDisplayableItemsKeys()[0] const newestItemsKey = application.items.getDisplayableItemsKeys()[0]
expect(newestItemsKey.uuid).to.equal(originalItemsKey.uuid) expect(newestItemsKey.uuid).to.equal(originalItemsKey.uuid)
}) })
it('should use items key for encryption of note', async function () { it('should use items key for encryption of note', async function () {
const notePayload = Factory.createNotePayload() 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) expect(keyToUse.content_type).to.equal(ContentType.TYPES.ItemsKey)
}) })
it('encrypting an item should associate an items key to it', async function () { it('encrypting an item should associate an items key to it', async function () {
const note = Factory.createNotePayload() const note = Factory.createNotePayload()
const encryptedPayload = await this.application.encryption.encryptSplitSingle({ const encryptedPayload = await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [note], items: [note],
}, },
}) })
const itemsKey = this.application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) const itemsKey = application.encryption.itemsKeyForEncryptedPayload(encryptedPayload)
expect(itemsKey).to.be.ok expect(itemsKey).to.be.ok
}) })
it('decrypt encrypted item with associated key', async function () { it('decrypt encrypted item with associated key', async function () {
const note = Factory.createNotePayload() const note = Factory.createNotePayload()
const title = note.content.title const title = note.content.title
const encryptedPayload = await this.application.encryption.encryptSplitSingle({ const encryptedPayload = await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [note], items: [note],
}, },
}) })
const itemsKey = this.application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) const itemsKey = application.encryption.itemsKeyForEncryptedPayload(encryptedPayload)
expect(itemsKey).to.be.ok expect(itemsKey).to.be.ok
const decryptedPayload = await this.application.encryption.decryptSplitSingle({ const decryptedPayload = await application.encryption.decryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [encryptedPayload], items: [encryptedPayload],
}, },
@@ -182,30 +183,30 @@ describe('keys', function () {
it('decrypts items waiting for keys', async function () { it('decrypts items waiting for keys', async function () {
const notePayload = Factory.createNotePayload() const notePayload = Factory.createNotePayload()
const title = notePayload.content.title const title = notePayload.content.title
const encryptedPayload = await this.application.encryption.encryptSplitSingle({ const encryptedPayload = await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [notePayload], 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: { usesItemsKeyWithKeyLookup: {
items: [encryptedPayload], 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.errorDecrypting).to.equal(true)
expect(note.waitingForKey).to.equal(true) expect(note.waitingForKey).to.equal(true)
const keyPayload = new DecryptedPayload(itemsKey.payload.ejected()) 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, * Sleeping is required to trigger asyncronous encryptionService.decryptItemsWaitingForKeys,
@@ -213,7 +214,7 @@ describe('keys', function () {
*/ */
await Factory.sleep(0.2) 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.errorDecrypting).to.not.be.ok
expect(updatedNote.waitingForKey).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 () { 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 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.errorDecrypting).to.not.be.ok
expect(refreshedKey.content.itemsKey).to.be.ok expect(refreshedKey.content.itemsKey).to.be.ok
}) })
it('generating export params with logged in account should produce encrypted payload', async function () { 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 payload = Factory.createNotePayload()
const encryptedPayload = await this.application.encryption.encryptSplitSingle({ const encryptedPayload = await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
}) })
expect(typeof encryptedPayload.content).to.equal('string') 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 () { it('When setting passcode, should encrypt items keys', async function () {
await this.application.addPasscode('foo') await application.addPasscode('foo')
const itemsKey = this.application.items.getDisplayableItemsKeys()[0] const itemsKey = application.items.getDisplayableItemsKeys()[0]
const rawPayloads = await this.application.storage.getAllRawPayloads() const rawPayloads = await application.storage.getAllRawPayloads()
const itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === itemsKey.uuid) const itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === itemsKey.uuid)
const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload) const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload)
expect(itemsKeyPayload.enc_item_key).to.be.ok expect(itemsKeyPayload.enc_item_key).to.be.ok
}) })
it('items key encrypted payload should contain root key params', async function () { it('items key encrypted payload should contain root key params', async function () {
await this.application.addPasscode('foo') await application.addPasscode('foo')
const itemsKey = this.application.items.getDisplayableItemsKeys()[0] const itemsKey = application.items.getDisplayableItemsKeys()[0]
const rawPayloads = await this.application.storage.getAllRawPayloads() const rawPayloads = await application.storage.getAllRawPayloads()
const itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === itemsKey.uuid) const itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === itemsKey.uuid)
const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload) const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload)
const authenticatedData = this.context.encryption.getEmbeddedPayloadAuthenticatedData(itemsKeyPayload) const authenticatedData = context.encryption.getEmbeddedPayloadAuthenticatedData(itemsKeyPayload)
const rootKeyParams = await this.application.encryption.getRootKeyParams() const rootKeyParams = await application.encryption.getRootKeyParams()
expect(authenticatedData.kp).to.be.ok expect(authenticatedData.kp).to.be.ok
expect(authenticatedData.kp).to.eql(rootKeyParams.getPortableValue()) expect(authenticatedData.kp).to.eql(rootKeyParams.getPortableValue())
@@ -285,9 +286,9 @@ describe('keys', function () {
it('correctly validates local passcode', async function () { it('correctly validates local passcode', async function () {
const passcode = 'foo' const passcode = 'foo'
await this.application.addPasscode('foo') await application.addPasscode('foo')
expect((await this.application.encryption.validatePasscode('wrong')).valid).to.equal(false) expect((await application.encryption.validatePasscode('wrong')).valid).to.equal(false)
expect((await this.application.encryption.validatePasscode(passcode)).valid).to.equal(true) 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 () { 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, * 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. * and create a new default items key that is the default for a given protocol version.
*/ */
const defaultItemsKey = await this.application.encryption.getSureDefaultItemsKey() const defaultItemsKey = await application.encryption.getSureDefaultItemsKey()
const latestVersion = this.application.encryption.getLatestVersion() const latestVersion = application.encryption.getLatestVersion()
expect(defaultItemsKey.keyVersion).to.equal(latestVersion) expect(defaultItemsKey.keyVersion).to.equal(latestVersion)
/** Register with 003 version */ /** Register with 003 version */
await Factory.registerOldUser({ await Factory.registerOldUser({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
version: ProtocolVersion.V003, version: ProtocolVersion.V003,
}) })
const itemsKeys = this.application.items.getDisplayableItemsKeys() const itemsKeys = application.items.getDisplayableItemsKeys()
expect(itemsKeys.length).to.equal(1) expect(itemsKeys.length).to.equal(1)
const newestItemsKey = itemsKeys[0] const newestItemsKey = itemsKeys[0]
expect(newestItemsKey.keyVersion).to.equal(ProtocolVersion.V003) 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.itemsKey).to.equal(rootKey.masterKey)
expect(newestItemsKey.dataAuthenticationKey).to.equal(rootKey.dataAuthenticationKey) expect(newestItemsKey.dataAuthenticationKey).to.equal(rootKey.dataAuthenticationKey)
}) })
it('reencrypts existing notes when logging into an 003 account', async function () { 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({ await Factory.registerOldUser({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
version: ProtocolVersion.V003, version: ProtocolVersion.V003,
}) })
expect(this.application.payloads.invalidPayloads.length).to.equal(0) expect(application.payloads.invalidPayloads.length).to.equal(0)
expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) expect(application.items.getDisplayableItemsKeys().length).to.equal(1)
expect(this.application.items.getDisplayableItemsKeys()[0].dirty).to.equal(false) expect(application.items.getDisplayableItemsKeys()[0].dirty).to.equal(false)
/** Sign out and back in */ /** Sign out and back in */
this.application = await Factory.signOutApplicationAndReturnNew(this.application) application = await Factory.signOutApplicationAndReturnNew(application)
await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) await application.signIn(email, password, undefined, undefined, undefined, true)
expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) expect(application.items.getDisplayableItemsKeys().length).to.equal(1)
expect(this.application.items.getDisplayableNotes().length).to.equal(10) expect(application.items.getDisplayableNotes().length).to.equal(10)
expect(this.application.payloads.invalidPayloads.length).to.equal(0) expect(application.payloads.invalidPayloads.length).to.equal(0)
}) })
it('When root key changes, all items keys must be re-encrypted', async function () { it('When root key changes, all items keys must be re-encrypted', async function () {
const passcode = 'foo' const passcode = 'foo'
await this.application.addPasscode(passcode) await application.addPasscode(passcode)
await Factory.createSyncedNote(this.application) await Factory.createSyncedNote(application)
const itemsKeys = this.application.items.getDisplayableItemsKeys() const itemsKeys = application.items.getDisplayableItemsKeys()
expect(itemsKeys.length).to.equal(1) expect(itemsKeys.length).to.equal(1)
const originalItemsKey = itemsKeys[0] 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 */ /** 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 itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === originalItemsKey.uuid)
const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload) const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload)
const decrypted = await this.application.encryption.decryptSplitSingle({ const decrypted = await application.encryption.decryptSplitSingle({
usesRootKey: { usesRootKey: {
items: [itemsKeyPayload], items: [itemsKeyPayload],
key: originalRootKey, key: originalRootKey,
@@ -363,10 +364,10 @@ describe('keys', function () {
expect(decrypted.content).to.eql(originalItemsKey.content) expect(decrypted.content).to.eql(originalItemsKey.content)
/** Change passcode */ /** Change passcode */
Factory.handlePasswordChallenges(this.application, passcode) Factory.handlePasswordChallenges(application, passcode)
await this.application.changePasscode('bar') 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).to.not.equal(originalRootKey)
expect(newRootKey.masterKey).to.not.equal(originalRootKey.masterKey) expect(newRootKey.masterKey).to.not.equal(originalRootKey.masterKey)
@@ -374,12 +375,12 @@ describe('keys', function () {
* Expect that originalRootKey can no longer decrypt originalItemsKey * Expect that originalRootKey can no longer decrypt originalItemsKey
* as items key has been re-encrypted with new root key * 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) const itemsKeyRawPayload2 = rawPayloads2.find((p) => p.uuid === originalItemsKey.uuid)
expect(itemsKeyRawPayload2.content).to.not.equal(itemsKeyRawPayload.content) expect(itemsKeyRawPayload2.content).to.not.equal(itemsKeyRawPayload.content)
const itemsKeyPayload2 = new EncryptedPayload(itemsKeyRawPayload2) const itemsKeyPayload2 = new EncryptedPayload(itemsKeyRawPayload2)
const decrypted2 = await this.application.encryption.decryptSplitSingle({ const decrypted2 = await application.encryption.decryptSplitSingle({
usesRootKey: { usesRootKey: {
items: [itemsKeyPayload2], items: [itemsKeyPayload2],
key: originalRootKey, key: originalRootKey,
@@ -388,7 +389,7 @@ describe('keys', function () {
expect(decrypted2.errorDecrypting).to.equal(true) expect(decrypted2.errorDecrypting).to.equal(true)
/** Should be able to decrypt with new root key */ /** Should be able to decrypt with new root key */
const decrypted3 = await this.application.encryption.decryptSplitSingle({ const decrypted3 = await application.encryption.decryptSplitSingle({
usesRootKey: { usesRootKey: {
items: [itemsKeyPayload2], items: [itemsKeyPayload2],
key: newRootKey, 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 () { it('changing account password should create new items key and encrypt items with that key', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
const itemsKeys = this.application.items.getDisplayableItemsKeys() const itemsKeys = application.items.getDisplayableItemsKeys()
expect(itemsKeys.length).to.equal(1) 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(result.error).to.not.be.ok
expect(this.application.items.getDisplayableItemsKeys().length).to.equal(2) expect(application.items.getDisplayableItemsKeys().length).to.equal(2)
const newDefaultItemsKey = await this.application.encryption.getSureDefaultItemsKey() const newDefaultItemsKey = await application.encryption.getSureDefaultItemsKey()
expect(newDefaultItemsKey.uuid).to.not.equal(defaultItemsKey.uuid) expect(newDefaultItemsKey.uuid).to.not.equal(defaultItemsKey.uuid)
const note = await Factory.createSyncedNote(this.application) const note = await Factory.createSyncedNote(application)
const payload = await this.application.encryption.encryptSplitSingle({ const payload = await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [note.payload], items: [note.payload],
}, },
@@ -480,29 +481,29 @@ describe('keys', function () {
}) })
it('loading the keychain root key should also load its key params', async function () { it('loading the keychain root key should also load its key params', async function () {
await Factory.registerUserToApplication({ application: this.application }) await Factory.registerUserToApplication({ application: application })
const rootKey = await this.application.encryption.rootKeyManager.getRootKeyFromKeychain() const rootKey = await application.encryption.rootKeyManager.getRootKeyFromKeychain()
expect(rootKey.keyParams).to.be.ok expect(rootKey.keyParams).to.be.ok
}) })
it('key params should be persisted separately and not as part of root key', async function () { it('key params should be persisted separately and not as part of root key', async function () {
await Factory.registerUserToApplication({ application: this.application }) await Factory.registerUserToApplication({ application: application })
const rawKey = await this.application.device.getNamespacedKeychainValue(this.application.identifier) const rawKey = await application.device.getNamespacedKeychainValue(application.identifier)
expect(rawKey.keyParams).to.not.be.ok 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 expect(rawKeyParams).to.be.ok
}) })
it('persisted key params should exactly equal in memory rootKey.keyParams', async function () { it('persisted key params should exactly equal in memory rootKey.keyParams', async function () {
await Factory.registerUserToApplication({ application: this.application }) await Factory.registerUserToApplication({ application: application })
const rootKey = await this.application.encryption.getRootKey() const rootKey = await application.encryption.getRootKey()
const rawKeyParams = await this.application.storage.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped) const rawKeyParams = await application.storage.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
expect(rootKey.keyParams.content).to.eql(rawKeyParams) expect(rootKey.keyParams.content).to.eql(rawKeyParams)
}) })
it('key params should have expected values', async function () { it('key params should have expected values', async function () {
await Factory.registerUserToApplication({ application: this.application }) await Factory.registerUserToApplication({ application: application })
const keyParamsObject = await this.application.encryption.getRootKeyParams() const keyParamsObject = await application.encryption.getRootKeyParams()
const keyParams = keyParamsObject.content const keyParams = keyParamsObject.content
expect(keyParams.identifier).to.be.ok expect(keyParams.identifier).to.be.ok
expect(keyParams.pw_nonce).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 () { 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({ await Factory.registerUserToApplication({
application: this.application, application: application,
email, email,
password, password,
}) })
this.application = await Factory.signOutApplicationAndReturnNew(this.application) application = await Factory.signOutApplicationAndReturnNew(application)
await Factory.loginToApplication({ await Factory.loginToApplication({
application: this.application, application: application,
email, email,
password, password,
}) })
const keyParamsObject = await this.application.encryption.getRootKeyParams() const keyParamsObject = await application.encryption.getRootKeyParams()
const keyParams = keyParamsObject.content const keyParams = keyParamsObject.content
expect(keyParams.created).to.be.ok expect(keyParams.created).to.be.ok
@@ -541,12 +540,12 @@ describe('keys', function () {
/** Register with 003 version */ /** Register with 003 version */
await Factory.registerOldUser({ await Factory.registerOldUser({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
version: ProtocolVersion.V003, version: ProtocolVersion.V003,
}) })
const keyParamsObject = await this.application.encryption.getRootKeyParams() const keyParamsObject = await application.encryption.getRootKeyParams()
const keyParams = keyParamsObject.content const keyParams = keyParamsObject.content
expect(keyParams.created).to.be.ok 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 () { it('encryption name should be dependent on key params version', async function () {
/** Register with 003 account */ /** Register with 003 account */
await Factory.registerOldUser({ await Factory.registerOldUser({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
version: ProtocolVersion.V003, 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 */ /** Register with 004 account */
await this.application.register(this.email + 'new', this.password) await application.register(email + 'new', password)
expect(await this.application.encryption.getEncryptionDisplayName()).to.equal('XChaCha20-Poly1305') 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 () { 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. * 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. * 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({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
/** Simulate empty keychain */ /** Simulate empty keychain */
await this.application.device.clearRawKeychainValue() await application.device.clearRawKeychainValue()
const recreatedApp = await Factory.createApplicationWithFakeCrypto(id) const recreatedApp = await Factory.createApplicationWithFakeCrypto(id)
let totalChallenges = 0 let totalChallenges = 0
const expectedChallenges = 1 const expectedChallenges = 1
const receiveChallenge = (challenge) => { const receiveChallenge = (challenge) => {
totalChallenges++ totalChallenges++
recreatedApp.submitValuesForChallenge(challenge, [CreateChallengeValue(challenge.prompts[0], this.password)]) recreatedApp.submitValuesForChallenge(challenge, [CreateChallengeValue(challenge.prompts[0], password)])
} }
await recreatedApp.prepareForLaunch({ receiveChallenge }) await recreatedApp.prepareForLaunch({ receiveChallenge })
await recreatedApp.launch(true) await recreatedApp.launch(true)
@@ -610,13 +609,13 @@ describe('keys', function () {
*/ */
it.skip('should add new items key', async function () { it.skip('should add new items key', async function () {
this.timeout(Factory.TwentySecondTimeout * 3) this.timeout(Factory.TwentySecondTimeout * 3)
let oldClient = this.application let oldClient = application
/** Register an 003 account */ /** Register an 003 account */
await Factory.registerOldUser({ await Factory.registerOldUser({
application: oldClient, application: oldClient,
email: this.email, email: email,
password: this.password, password: password,
version: ProtocolVersion.V003, version: ProtocolVersion.V003,
}) })
@@ -626,8 +625,8 @@ describe('keys', function () {
receiveChallenge: (challenge) => { receiveChallenge: (challenge) => {
/** Reauth session challenge */ /** Reauth session challenge */
newClient.submitValuesForChallenge(challenge, [ newClient.submitValuesForChallenge(challenge, [
CreateChallengeValue(challenge.prompts[0], this.email), CreateChallengeValue(challenge.prompts[0], email),
CreateChallengeValue(challenge.prompts[1], this.password), CreateChallengeValue(challenge.prompts[1], password),
]) ])
}, },
}) })
@@ -636,11 +635,11 @@ describe('keys', function () {
/** Change password through session manager directly instead of application, /** Change password through session manager directly instead of application,
* as not to create any items key (to simulate 003 client behavior) */ * as not to create any items key (to simulate 003 client behavior) */
const currentRootKey = await oldClient.encryption.computeRootKey( const currentRootKey = await oldClient.encryption.computeRootKey(
this.password, password,
await oldClient.encryption.getRootKeyParams(), await oldClient.encryption.getRootKeyParams(),
) )
const operator = this.context.operators.operatorForVersion(ProtocolVersion.V003) const operator = context.operators.operatorForVersion(ProtocolVersion.V003)
const newRootKey = await operator.createRootKey(this.email, this.password) const newRootKey = await operator.createRootKey(email, password)
Object.defineProperty(oldClient.legacyApi, 'apiVersion', { Object.defineProperty(oldClient.legacyApi, 'apiVersion', {
get: function () { get: function () {
return '20190520' return '20190520'
@@ -650,7 +649,7 @@ describe('keys', function () {
/** /**
* Sign in as late as possible on new client to prevent session timeouts * 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({ await oldClient.sessions.changeCredentials({
currentServerPassword: currentRootKey.serverPassword, 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 () { 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') context.anticipateConsoleError('Shared vault network errors due to not accepting JWT-based token')
this.context.anticipateConsoleError( context.anticipateConsoleError(
'Cannot find items key to use for encryption', 'Cannot find items key to use for encryption',
'No items keys being created in this test', 'No items keys being created in this test',
) )
/** Register an 003 account */ /** Register an 003 account */
await Factory.registerOldUser({ await Factory.registerOldUser({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
version: ProtocolVersion.V003, version: ProtocolVersion.V003,
}) })
/** Change password through session manager directly instead of application, /** Change password through session manager directly instead of application,
* as not to create any items key (to simulate 003 client behavior) */ * as not to create any items key (to simulate 003 client behavior) */
const currentRootKey = await this.application.encryption.computeRootKey( const currentRootKey = await application.encryption.computeRootKey(
this.password, password,
await this.application.encryption.getRootKeyParams(), await application.encryption.getRootKeyParams(),
) )
const operator = this.context.operators.operatorForVersion(ProtocolVersion.V003) const operator = context.operators.operatorForVersion(ProtocolVersion.V003)
const newRootKeyTemplate = await operator.createRootKey(this.email, this.password) const newRootKeyTemplate = await operator.createRootKey(email, password)
const newRootKey = CreateNewRootKey({ const newRootKey = CreateNewRootKey({
...newRootKeyTemplate.content, ...newRootKeyTemplate.content,
...{ ...{
@@ -699,28 +698,28 @@ describe('keys', function () {
}, },
}) })
Object.defineProperty(this.application.legacyApi, 'apiVersion', { Object.defineProperty(application.legacyApi, 'apiVersion', {
get: function () { get: function () {
return '20190520' return '20190520'
}, },
}) })
/** Renew session to prevent timeouts */ /** 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, currentServerPassword: currentRootKey.serverPassword,
newRootKey, 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 */ /** 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 */ /** 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 */ /** Set to pre 2.0.15 version so migration runs */
await this.application.device.setRawStorageValue(`${identifier}-snjs_version`, '2.0.14') await application.device.setRawStorageValue(`${identifier}-snjs_version`, '2.0.14')
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
const refreshedApp = await Factory.createApplicationWithFakeCrypto(identifier) const refreshedApp = await Factory.createApplicationWithFakeCrypto(identifier)
await Factory.initializeApplication(refreshedApp) await Factory.initializeApplication(refreshedApp)
@@ -740,16 +739,16 @@ describe('keys', function () {
* The corrective action was to do a final check in encryptionService.handleDownloadFirstSyncCompletion * 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. * to ensure there exists an items key corresponding to the user's account version.
*/ */
const promise = this.context.awaitNextSucessfulSync() const promise = context.awaitNextSucessfulSync()
await this.context.sync() await context.sync()
await promise await promise
await this.application.items.removeAllItemsFromMemory() await application.items.removeAllItemsFromMemory()
expect(this.application.encryption.getSureDefaultItemsKey()).to.not.be.ok expect(application.encryption.getSureDefaultItemsKey()).to.not.be.ok
const protocol003 = new SNProtocolOperator003(new SNWebCrypto()) const protocol003 = new SNProtocolOperator003(new SNWebCrypto())
const key = await protocol003.createItemsKey() const key = await protocol003.createItemsKey()
await this.application.mutator.emitItemFromPayload( await application.mutator.emitItemFromPayload(
key.payload.copy({ key.payload.copy({
content: { content: {
...key.payload.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.keyVersion).to.equal(ProtocolVersion.V003)
expect(defaultKey.uuid).to.equal(key.uuid) expect(defaultKey.uuid).to.equal(key.uuid)
await Factory.registerUserToApplication({ application: this.application }) await Factory.registerUserToApplication({ application: application })
const notePayload = Factory.createNotePayload() 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 () { it('having unsynced items keys should resync them upon download first sync completion', async function () {
await Factory.registerUserToApplication({ application: this.application }) await Factory.registerUserToApplication({ application: application })
const itemsKey = this.application.items.getDisplayableItemsKeys()[0] const itemsKey = application.items.getDisplayableItemsKeys()[0]
await this.application.mutator.emitItemFromPayload( await application.mutator.emitItemFromPayload(
itemsKey.payload.copy({ itemsKey.payload.copy({
dirty: false, dirty: false,
updated_at: new Date(0), updated_at: new Date(0),
deleted: false, deleted: false,
}), }),
) )
await this.application.sync.sync({ await application.sync.sync({
mode: SyncMode.DownloadFirst, mode: SyncMode.DownloadFirst,
}) })
const updatedKey = this.application.items.findItem(itemsKey.uuid) const updatedKey = application.items.findItem(itemsKey.uuid)
expect(updatedKey.neverSynced).to.equal(false) expect(updatedKey.neverSynced).to.equal(false)
}) })
@@ -794,18 +793,18 @@ describe('keys', function () {
otherClient.items.itemsKeyDisplayController.setDisplayOptions({ sortBy: 'dsc' }) otherClient.items.itemsKeyDisplayController.setDisplayOptions({ sortBy: 'dsc' })
/** On client A, create account and note */ /** On client A, create account and note */
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
await Factory.createSyncedNote(this.application) await Factory.createSyncedNote(application)
const itemsKey = this.application.items.getItems(ContentType.TYPES.ItemsKey)[0] const itemsKey = application.items.getItems(ContentType.TYPES.ItemsKey)[0]
/** Create another client and sign into account */ /** Create another client and sign into account */
await Factory.loginToApplication({ await Factory.loginToApplication({
application: otherClient, application: otherClient,
email: this.email, email: email,
password: this.password, password: password,
}) })
const defaultKeys = otherClient.encryption.itemsEncryption.getItemsKeys().filter((key) => { const defaultKeys = otherClient.encryption.itemsEncryption.getItemsKeys().filter((key) => {
return key.isDefault return key.isDefault

View File

@@ -1,5 +1,4 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import FakeWebCrypto from './fake_web_crypto.js' import FakeWebCrypto from './fake_web_crypto.js'
import { AppContext } from './AppContext.js' import { AppContext } from './AppContext.js'
import { VaultsContext } from './VaultsContext.js' import { VaultsContext } from './VaultsContext.js'
@@ -63,6 +62,10 @@ export async function createVaultsContextWithRealCrypto(identifier) {
return createVaultsContext({ identifier, crypto: new SNWebCrypto() }) 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 } = {}) { export async function createVaultsContext({ identifier, crypto, email, password, host } = {}) {
const context = new VaultsContext({ identifier, crypto, email, password, host }) const context = new VaultsContext({ identifier, crypto, email, password, host })
await context.initialize() await context.initialize()

View File

@@ -1,6 +1,5 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
@@ -9,6 +8,7 @@ const expect = chai.expect
* Then check browser Memory tool to make sure there are no leaks. * Then check browser Memory tool to make sure there are no leaks.
*/ */
describe('memory', function () { describe('memory', function () {
let application
before(async function () { before(async function () {
localStorage.clear() localStorage.clear()
}) })
@@ -18,12 +18,12 @@ describe('memory', function () {
}) })
beforeEach(async function () { beforeEach(async function () {
this.application = await Factory.createInitAppWithFakeCrypto() application = await Factory.createInitAppWithFakeCrypto()
}) })
afterEach(async function () { afterEach(async function () {
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
this.application = null application = undefined
}) })
it('passes', async function () { it('passes', async function () {

View File

@@ -1,68 +1,76 @@
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
const createApp = async () => Factory.createInitAppWithFakeCrypto(Environment.Web, Platform.MacWeb)
const accountPassword = 'password' const accountPassword = 'password'
const registerApp = async (snApp) => { const registerApp = async (application) => {
const email = UuidGenerator.GenerateUuid() const email = UuidGenerator.GenerateUuid()
const password = accountPassword const password = accountPassword
const ephemeral = false const ephemeral = false
const mergeLocal = true const mergeLocal = true
await snApp.register(email, password, ephemeral, mergeLocal) await application.register(email, password, ephemeral, mergeLocal)
return snApp return application
} }
describe('mfa service', () => { 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 () => { it('generates 160 bit base32-encoded mfa secret', async () => {
const RFC4648 = /[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]/g const RFC4648 = /[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]/g
const snApp = await createApp() const secret = await application.generateMfaSecret()
const secret = await snApp.generateMfaSecret()
expect(secret).to.have.lengthOf(32) expect(secret).to.have.lengthOf(32)
expect(secret.replace(RFC4648, '')).to.have.lengthOf(0) expect(secret.replace(RFC4648, '')).to.have.lengthOf(0)
Factory.safeDeinit(snApp)
}) })
it('activates mfa, checks if enabled, deactivates mfa', async () => { it('activates mfa, checks if enabled, deactivates mfa', async () => {
const snApp = await createApp().then(registerApp) await registerApp(application)
Factory.handlePasswordChallenges(snApp, accountPassword)
expect(await snApp.isMfaActivated()).to.equal(false) Factory.handlePasswordChallenges(application, accountPassword)
const secret = await snApp.generateMfaSecret() expect(await application.isMfaActivated()).to.equal(false)
const token = await snApp.getOtpToken(secret)
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) }).timeout(Factory.TenSecondTimeout)
it('prompts for account password when disabling mfa', async () => { it('prompts for account password when disabling mfa', async () => {
const snApp = await createApp().then(registerApp) await registerApp(application)
Factory.handlePasswordChallenges(snApp, accountPassword)
const secret = await snApp.generateMfaSecret()
const token = await snApp.getOtpToken(secret)
sinon.spy(snApp.challenges, 'sendChallenge') Factory.handlePasswordChallenges(application, accountPassword)
await snApp.enableMfa(secret, token) const secret = await application.generateMfaSecret()
await snApp.disableMfa() 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 const challenge = spyCall.firstArg
expect(challenge.prompts).to.have.lengthOf(2) expect(challenge.prompts).to.have.lengthOf(2)
expect(challenge.prompts[0].validation).to.equal(ChallengeValidation.AccountPassword) expect(challenge.prompts[0].validation).to.equal(ChallengeValidation.AccountPassword)
Factory.safeDeinit(snApp)
}).timeout(Factory.TenSecondTimeout) }).timeout(Factory.TenSecondTimeout)
}) })

View File

@@ -1,4 +1,5 @@
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect

View File

@@ -1,6 +1,5 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
@@ -11,25 +10,29 @@ const setupRandomUuid = () => {
} }
describe('web native folders migration', () => { describe('web native folders migration', () => {
let application
beforeEach(async function () { beforeEach(async function () {
this.application = await Factory.createInitAppWithFakeCrypto() localStorage.clear()
application = await Factory.createInitAppWithFakeCrypto()
setupRandomUuid() setupRandomUuid()
}) })
afterEach(async function () { afterEach(async function () {
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
// TODO: cleanup uuid behind us or we'll mess other tests. application = undefined
localStorage.clear()
}) })
it('migration with flat tag folders', async function () { it('migration with flat tag folders', async function () {
const titles = ['a', 'b', 'c'] const titles = ['a', 'b', 'c']
await makeTags(this.application, titles) await makeTags(application, titles)
// Run the migration // Run the migration
await this.application.mutator.migrateTagsToFolders() await application.mutator.migrateTagsToFolders()
// Check new tags // Check new tags
const result = extractTagHierarchy(this.application) const result = extractTagHierarchy(application)
expect(result).to.deep.equal({ expect(result).to.deep.equal({
a: { _uuid: 'a' }, a: { _uuid: 'a' },
@@ -40,13 +43,13 @@ describe('web native folders migration', () => {
it('migration with simple tag folders', async function () { it('migration with simple tag folders', async function () {
const titles = ['a.b.c', 'b', 'a.b'] const titles = ['a.b.c', 'b', 'a.b']
await makeTags(this.application, titles) await makeTags(application, titles)
// Run the migration // Run the migration
await this.application.mutator.migrateTagsToFolders() await application.mutator.migrateTagsToFolders()
// Check new tags // Check new tags
const result = extractTagHierarchy(this.application) const result = extractTagHierarchy(application)
expect(result).to.deep.equal({ expect(result).to.deep.equal({
a: { a: {
@@ -62,13 +65,13 @@ describe('web native folders migration', () => {
it('migration with more complex cases', async function () { it('migration with more complex cases', async function () {
const titles = ['a.b.c', 'b', 'a.b'] const titles = ['a.b.c', 'b', 'a.b']
await makeTags(this.application, titles) await makeTags(application, titles)
// Run the migration // Run the migration
await this.application.mutator.migrateTagsToFolders() await application.mutator.migrateTagsToFolders()
// Check new tags // Check new tags
const result = extractTagHierarchy(this.application) const result = extractTagHierarchy(application)
expect(result).to.deep.equal({ expect(result).to.deep.equal({
a: { 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 () { it('should produce a valid hierarchy cases with missing intermediate tags or unordered', async function () {
const titles = ['y.2', 'w.3', 'y'] const titles = ['y.2', 'w.3', 'y']
await makeTags(this.application, titles) await makeTags(application, titles)
// Run the migration // Run the migration
await this.application.mutator.migrateTagsToFolders() await application.mutator.migrateTagsToFolders()
// Check new tags // Check new tags
const result = extractTagHierarchy(this.application) const result = extractTagHierarchy(application)
expect(result).to.deep.equal({ expect(result).to.deep.equal({
w: { w: {
@@ -105,13 +108,13 @@ describe('web native folders migration', () => {
it('skip prefixed names', async function () { it('skip prefixed names', async function () {
const titles = ['.something', '.something...something', 'something.a.b.c'] const titles = ['.something', '.something...something', 'something.a.b.c']
await makeTags(this.application, titles) await makeTags(application, titles)
// Run the migration // Run the migration
await this.application.mutator.migrateTagsToFolders() await application.mutator.migrateTagsToFolders()
// Check new tags // Check new tags
const result = extractTagHierarchy(this.application) const result = extractTagHierarchy(application)
expect(result).to.deep.equal({ expect(result).to.deep.equal({
'.something': { _uuid: '.something' }, '.something': { _uuid: '.something' },
@@ -132,13 +135,13 @@ describe('web native folders migration', () => {
'a', 'a',
'something..another.thing..anyway', 'something..another.thing..anyway',
] ]
await makeTags(this.application, titles) await makeTags(application, titles)
// Run the migration // Run the migration
await this.application.mutator.migrateTagsToFolders() await application.mutator.migrateTagsToFolders()
// Check new tags // Check new tags
const result = extractTagHierarchy(this.application) const result = extractTagHierarchy(application)
expect(result).to.deep.equal({ expect(result).to.deep.equal({
'something.': { _uuid: 'something.' }, 'something.': { _uuid: 'something.' },

View File

@@ -1,37 +1,26 @@
/* eslint-disable camelcase */
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import { BaseItemCounts } from '../lib/BaseItemCounts.js' import { BaseItemCounts } from '../lib/BaseItemCounts.js'
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('app models', () => { describe('app models', () => {
const sharedApplication = Factory.createApplicationWithFakeCrypto() let application
let expectedItemCount
before(async function () { let context
localStorage.clear()
await Factory.initializeApplication(sharedApplication)
})
after(async function () {
localStorage.clear()
await Factory.safeDeinit(sharedApplication)
})
beforeEach(async function () { beforeEach(async function () {
this.expectedItemCount = BaseItemCounts.DefaultItems localStorage.clear()
this.context = await Factory.createAppContext() expectedItemCount = BaseItemCounts.DefaultItems
this.application = this.context.application context = await Factory.createAppContext()
await this.context.launch() application = context.application
await context.launch()
}) })
afterEach(async function () { afterEach(async function () {
await Factory.safeDeinit(this.application) await context.deinit()
}) localStorage.clear()
sinon.restore()
it('payloads should be defined', () => {
expect(sharedApplication.payloads).to.be.ok
}) })
it('item should be defined', () => { it('item should be defined', () => {
@@ -72,17 +61,17 @@ describe('app models', () => {
}, },
}) })
await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
await this.application.mutator.emitItemsFromPayloads([params2], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([params2], PayloadEmitSource.LocalChanged)
const item1 = this.application.items.findItem(params1.uuid) const item1 = application.items.findItem(params1.uuid)
const item2 = this.application.items.findItem(params2.uuid) const item2 = application.items.findItem(params2.uuid)
expect(item1.content.references.length).to.equal(1) expect(item1.content.references.length).to.equal(1)
expect(item2.content.references.length).to.equal(0) expect(item2.content.references.length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0) expect(application.items.itemsReferencingItem(item1).length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1) expect(application.items.itemsReferencingItem(item2).length).to.equal(1)
}) })
it('mapping an item twice shouldnt cause problems', async function () { 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] let item = items[0]
expect(item).to.be.ok 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] item = items[0]
expect(item.content.foo).to.equal('bar') 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 () { it('mapping item twice should preserve references', async function () {
const item1 = await Factory.createMappedNote(this.application) const item1 = await Factory.createMappedNote(application)
const item2 = await Factory.createMappedNote(this.application) const item2 = await Factory.createMappedNote(application)
await this.application.mutator.changeItem(item1, (mutator) => { await application.mutator.changeItem(item1, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2) mutator.e2ePendingRefactor_addItemAsRelationship(item2)
}) })
await this.application.mutator.changeItem(item2, (mutator) => { await application.mutator.changeItem(item2, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item1) 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) expect(refreshedItem.content.references.length).to.equal(1)
}) })
it('fixes relationship integrity', async function () { it('fixes relationship integrity', async function () {
var item1 = await Factory.createMappedNote(this.application) var item1 = await Factory.createMappedNote(application)
var item2 = await Factory.createMappedNote(this.application) var item2 = await Factory.createMappedNote(application)
await this.application.mutator.changeItem(item1, (mutator) => { await application.mutator.changeItem(item1, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2) mutator.e2ePendingRefactor_addItemAsRelationship(item2)
}) })
await this.application.mutator.changeItem(item2, (mutator) => { await application.mutator.changeItem(item2, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item1) mutator.e2ePendingRefactor_addItemAsRelationship(item1)
}) })
const refreshedItem1 = this.application.items.findItem(item1.uuid) const refreshedItem1 = application.items.findItem(item1.uuid)
const refreshedItem2 = this.application.items.findItem(item2.uuid) const refreshedItem2 = application.items.findItem(item2.uuid)
expect(refreshedItem1.content.references.length).to.equal(1) expect(refreshedItem1.content.references.length).to.equal(1)
expect(refreshedItem2.content.references.length).to.equal(1) expect(refreshedItem2.content.references.length).to.equal(1)
@@ -145,54 +134,54 @@ describe('app models', () => {
references: [], 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 refreshedItem1_2 = application.items.findItem(item1.uuid)
const refreshedItem2_2 = this.application.items.findItem(item2.uuid) const refreshedItem2_2 = application.items.findItem(item2.uuid)
expect(refreshedItem1_2.content.references.length).to.equal(0) expect(refreshedItem1_2.content.references.length).to.equal(0)
expect(refreshedItem2_2.content.references.length).to.equal(1) expect(refreshedItem2_2.content.references.length).to.equal(1)
}) })
it('creating and removing relationships between two items should have valid references', async function () { it('creating and removing relationships between two items should have valid references', async function () {
var item1 = await Factory.createMappedNote(this.application) var item1 = await Factory.createMappedNote(application)
var item2 = await Factory.createMappedNote(this.application) var item2 = await Factory.createMappedNote(application)
await this.application.mutator.changeItem(item1, (mutator) => { await application.mutator.changeItem(item1, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2) mutator.e2ePendingRefactor_addItemAsRelationship(item2)
}) })
await this.application.mutator.changeItem(item2, (mutator) => { await application.mutator.changeItem(item2, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item1) mutator.e2ePendingRefactor_addItemAsRelationship(item1)
}) })
const refreshedItem1 = this.application.items.findItem(item1.uuid) const refreshedItem1 = application.items.findItem(item1.uuid)
const refreshedItem2 = this.application.items.findItem(item2.uuid) const refreshedItem2 = application.items.findItem(item2.uuid)
expect(refreshedItem1.content.references.length).to.equal(1) expect(refreshedItem1.content.references.length).to.equal(1)
expect(refreshedItem2.content.references.length).to.equal(1) expect(refreshedItem2.content.references.length).to.equal(1)
expect(this.application.items.itemsReferencingItem(item1)).to.include(refreshedItem2) expect(application.items.itemsReferencingItem(item1)).to.include(refreshedItem2)
expect(this.application.items.itemsReferencingItem(item2)).to.include(refreshedItem1) expect(application.items.itemsReferencingItem(item2)).to.include(refreshedItem1)
await this.application.mutator.changeItem(item1, (mutator) => { await application.mutator.changeItem(item1, (mutator) => {
mutator.removeItemAsRelationship(item2) mutator.removeItemAsRelationship(item2)
}) })
await this.application.mutator.changeItem(item2, (mutator) => { await application.mutator.changeItem(item2, (mutator) => {
mutator.removeItemAsRelationship(item1) mutator.removeItemAsRelationship(item1)
}) })
const refreshedItem1_2 = this.application.items.findItem(item1.uuid) const refreshedItem1_2 = application.items.findItem(item1.uuid)
const refreshedItem2_2 = this.application.items.findItem(item2.uuid) const refreshedItem2_2 = application.items.findItem(item2.uuid)
expect(refreshedItem1_2.content.references.length).to.equal(0) expect(refreshedItem1_2.content.references.length).to.equal(0)
expect(refreshedItem2_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(application.items.itemsReferencingItem(item1).length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item2).length).to.equal(0) expect(application.items.itemsReferencingItem(item2).length).to.equal(0)
}) })
it('properly duplicates item with no relationships', async function () { it('properly duplicates item with no relationships', async function () {
const item = await Factory.createMappedNote(this.application) const item = await Factory.createMappedNote(application)
const duplicate = await this.application.mutator.duplicateItem(item) const duplicate = await application.mutator.duplicateItem(item)
expect(duplicate.uuid).to.not.equal(item.uuid) expect(duplicate.uuid).to.not.equal(item.uuid)
expect(item.isItemContentEqualWith(duplicate)).to.equal(true) expect(item.isItemContentEqualWith(duplicate)).to.equal(true)
expect(item.created_at.toISOString()).to.equal(duplicate.created_at.toISOString()) 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 () { it('properly duplicates item with relationships', async function () {
const item1 = await Factory.createMappedNote(this.application) const item1 = await Factory.createMappedNote(application)
const item2 = await Factory.createMappedNote(this.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) mutator.e2ePendingRefactor_addItemAsRelationship(item2)
}) })
expect(refreshedItem1.content.references.length).to.equal(1) 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.uuid).to.not.equal(item1.uuid)
expect(duplicate.content.references.length).to.equal(1) expect(duplicate.content.references.length).to.equal(1)
expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0) expect(application.items.itemsReferencingItem(item1).length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item2).length).to.equal(2) 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.isItemContentEqualWith(duplicate)).to.equal(true)
expect(refreshedItem1_2.created_at.toISOString()).to.equal(duplicate.created_at.toISOString()) expect(refreshedItem1_2.created_at.toISOString()).to.equal(duplicate.created_at.toISOString())
expect(refreshedItem1_2.content_type).to.equal(duplicate.content_type) expect(refreshedItem1_2.content_type).to.equal(duplicate.content_type)
}) })
it('removing references should update cross-refs', async function () { it('removing references should update cross-refs', async function () {
const item1 = await Factory.createMappedNote(this.application) const item1 = await Factory.createMappedNote(application)
const item2 = await Factory.createMappedNote(this.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) mutator.e2ePendingRefactor_addItemAsRelationship(item2)
}) })
const refreshedItem1_2 = await this.application.mutator.emitItemFromPayload( const refreshedItem1_2 = await application.mutator.emitItemFromPayload(
refreshedItem1.payloadRepresentation({ refreshedItem1.payloadRepresentation({
deleted: true, deleted: true,
content: { content: {
@@ -240,49 +229,49 @@ describe('app models', () => {
PayloadEmitSource.LocalChanged, PayloadEmitSource.LocalChanged,
) )
expect(this.application.items.itemsReferencingItem(item2).length).to.equal(0) expect(application.items.itemsReferencingItem(item2).length).to.equal(0)
expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0) expect(application.items.itemsReferencingItem(item1).length).to.equal(0)
expect(refreshedItem1_2.content.references.length).to.equal(0) expect(refreshedItem1_2.content.references.length).to.equal(0)
}) })
it('properly handles single item uuid alternation', async function () { it('properly handles single item uuid alternation', async function () {
const item1 = await Factory.createMappedNote(this.application) const item1 = await Factory.createMappedNote(application)
const item2 = await Factory.createMappedNote(this.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) mutator.e2ePendingRefactor_addItemAsRelationship(item2)
}) })
expect(refreshedItem1.content.references.length).to.equal(1) 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 alternatedItem = await Factory.alternateUuidForItem(application, item1.uuid)
const refreshedItem1_2 = this.application.items.findItem(item1.uuid) const refreshedItem1_2 = application.items.findItem(item1.uuid)
expect(refreshedItem1_2).to.not.be.ok 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(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.isReferencingItem(item2)).to.equal(true)
expect(alternatedItem.dirty).to.equal(true) expect(alternatedItem.dirty).to.equal(true)
}) })
it('alterating uuid of item should fill its duplicateOf value', async function () { it('alterating uuid of item should fill its duplicateOf value', async function () {
const item1 = await Factory.createMappedNote(this.application) const item1 = await Factory.createMappedNote(application)
const alternatedItem = await Factory.alternateUuidForItem(this.application, item1.uuid) const alternatedItem = await Factory.alternateUuidForItem(application, item1.uuid)
expect(alternatedItem.duplicateOf).to.equal(item1.uuid) expect(alternatedItem.duplicateOf).to.equal(item1.uuid)
}) })
it('alterating itemskey uuid should update errored items encrypted with that key', async function () { it('alterating itemskey uuid should update errored items encrypted with that key', async function () {
const item1 = await Factory.createMappedNote(this.application) const item1 = await Factory.createMappedNote(application)
const itemsKey = this.application.items.getDisplayableItemsKeys()[0] const itemsKey = application.items.getDisplayableItemsKeys()[0]
/** Encrypt item1 and emit as errored so it persists with items_key_id */ /** 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: { usesItemsKeyWithKeyLookup: {
items: [item1.payload], items: [item1.payload],
}, },
@@ -292,46 +281,46 @@ describe('app models', () => {
waitingForKey: true, 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(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).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 // prevent auto decryption
}) })
const alternatedKey = await Factory.alternateUuidForItem(this.application, itemsKey.uuid) const alternatedKey = await Factory.alternateUuidForItem(application, itemsKey.uuid)
const updatedPayload = this.application.payloads.findOne(item1.uuid) const updatedPayload = application.payloads.findOne(item1.uuid)
expect(updatedPayload.items_key_id).to.equal(alternatedKey.uuid) expect(updatedPayload.items_key_id).to.equal(alternatedKey.uuid)
}) })
it('properly handles mutli item uuid alternation', async function () { it('properly handles mutli item uuid alternation', async function () {
const item1 = await Factory.createMappedNote(this.application) const item1 = await Factory.createMappedNote(application)
const item2 = await Factory.createMappedNote(this.application) const item2 = await Factory.createMappedNote(application)
this.expectedItemCount += 2 expectedItemCount += 2
await this.application.mutator.changeItem(item1, (mutator) => { await application.mutator.changeItem(item1, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2) 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 alternatedItem1 = await Factory.alternateUuidForItem(application, item1.uuid)
const alternatedItem2 = await Factory.alternateUuidForItem(this.application, item2.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(item1.uuid).to.not.equal(alternatedItem1.uuid)
expect(item2.uuid).to.not.equal(alternatedItem2.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.length).to.equal(1)
expect(refreshedAltItem1.content.references[0].uuid).to.equal(alternatedItem2.uuid) expect(refreshedAltItem1.content.references[0].uuid).to.equal(alternatedItem2.uuid)
expect(alternatedItem2.content.references.length).to.equal(0) 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(refreshedAltItem1.isReferencingItem(alternatedItem2)).to.equal(true)
expect(alternatedItem2.isReferencingItem(refreshedAltItem1)).to.equal(false) expect(alternatedItem2.isReferencingItem(refreshedAltItem1)).to.equal(false)
@@ -339,39 +328,39 @@ describe('app models', () => {
}) })
it('maintains referencing relationships when duplicating', async function () { it('maintains referencing relationships when duplicating', async function () {
const tag = await Factory.createMappedTag(this.application) const tag = await Factory.createMappedTag(application)
const note = await Factory.createMappedNote(this.application) const note = await Factory.createMappedNote(application)
const refreshedTag = await this.application.mutator.changeItem(tag, (mutator) => { const refreshedTag = await application.mutator.changeItem(tag, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(note) mutator.e2ePendingRefactor_addItemAsRelationship(note)
}) })
expect(refreshedTag.content.references.length).to.equal(1) 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(note.uuid).to.not.equal(noteCopy.uuid)
expect(this.application.items.getDisplayableNotes().length).to.equal(2) expect(application.items.getDisplayableNotes().length).to.equal(2)
expect(this.application.items.getDisplayableTags().length).to.equal(1) expect(application.items.getDisplayableTags().length).to.equal(1)
expect(note.content.references.length).to.equal(0) expect(note.content.references.length).to.equal(0)
expect(noteCopy.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) expect(refreshedTag_2.content.references.length).to.equal(2)
}) })
it('maintains editor reference when duplicating note', async function () { 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, ContentType.TYPES.Component,
{ area: ComponentArea.Editor, package_info: { identifier: 'foo-editor' } }, { area: ComponentArea.Editor, package_info: { identifier: 'foo-editor' } },
true, true,
) )
const note = await Factory.insertItemWithOverride(this.application, ContentType.TYPES.Note, { const note = await Factory.insertItemWithOverride(application, ContentType.TYPES.Note, {
editorIdentifier: 'foo-editor', 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) const duplicate = await application.mutator.duplicateItem(note, true)
expect(this.application.componentManager.editorForNote(duplicate).uniqueIdentifier.value).to.equal(component.uuid) expect(application.componentManager.editorForNote(duplicate).uniqueIdentifier.value).to.equal(component.uuid)
}) })
}) })

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import { BaseItemCounts } from '../lib/BaseItemCounts.js' import { BaseItemCounts } from '../lib/BaseItemCounts.js'
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
@@ -11,47 +10,52 @@ describe('items', () => {
awaitAll: true, awaitAll: true,
} }
let application
let expectedItemCount
beforeEach(async function () { beforeEach(async function () {
this.expectedItemCount = BaseItemCounts.DefaultItems localStorage.clear()
this.application = await Factory.createInitAppWithFakeCrypto() expectedItemCount = BaseItemCounts.DefaultItems
application = await Factory.createInitAppWithFakeCrypto()
}) })
afterEach(async function () { 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 () { it('setting an item as dirty should update its client updated at', async function () {
const params = Factory.createNotePayload() 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]
const prevDate = item.userModifiedDate.getTime() const prevDate = item.userModifiedDate.getTime()
await Factory.sleep(0.1) await Factory.sleep(0.1)
await this.application.mutator.setItemDirty(item, true) await application.mutator.setItemDirty(item, true)
const refreshedItem = this.application.items.findItem(item.uuid) const refreshedItem = application.items.findItem(item.uuid)
const newDate = refreshedItem.userModifiedDate.getTime() const newDate = refreshedItem.userModifiedDate.getTime()
expect(prevDate).to.not.equal(newDate) expect(prevDate).to.not.equal(newDate)
}) })
it('setting an item as dirty with option to skip client updated at', async function () { it('setting an item as dirty with option to skip client updated at', async function () {
const params = Factory.createNotePayload() 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]
const prevDate = item.userModifiedDate.getTime() const prevDate = item.userModifiedDate.getTime()
await Factory.sleep(0.1) await Factory.sleep(0.1)
await this.application.mutator.setItemDirty(item) await application.mutator.setItemDirty(item)
const newDate = item.userModifiedDate.getTime() const newDate = item.userModifiedDate.getTime()
expect(prevDate).to.equal(newDate) expect(prevDate).to.equal(newDate)
}) })
it('properly pins, archives, and locks', async function () { it('properly pins, archives, and locks', async function () {
const params = Factory.createNotePayload() 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 expect(item.pinned).to.not.be.ok
const refreshedItem = ( const refreshedItem = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item, item,
(mutator) => { (mutator) => {
mutator.pinned = true mutator.pinned = true
@@ -71,16 +75,16 @@ describe('items', () => {
it('properly compares item equality', async function () { it('properly compares item equality', async function () {
const params1 = Factory.createNotePayload() const params1 = Factory.createNotePayload()
const params2 = 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 item1 = application.items.getDisplayableNotes()[0]
let item2 = this.application.items.getDisplayableNotes()[1] let item2 = application.items.getDisplayableNotes()[1]
expect(item1.isItemContentEqualWith(item2)).to.equal(true) expect(item1.isItemContentEqualWith(item2)).to.equal(true)
// items should ignore this field when checking for equality // items should ignore this field when checking for equality
item1 = ( item1 = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item1, item1,
(mutator) => { (mutator) => {
mutator.userModifiedDate = new Date() mutator.userModifiedDate = new Date()
@@ -91,7 +95,7 @@ describe('items', () => {
) )
).getValue() ).getValue()
item2 = ( item2 = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item2, item2,
(mutator) => { (mutator) => {
mutator.userModifiedDate = undefined mutator.userModifiedDate = undefined
@@ -105,7 +109,7 @@ describe('items', () => {
expect(item1.isItemContentEqualWith(item2)).to.equal(true) expect(item1.isItemContentEqualWith(item2)).to.equal(true)
item1 = ( item1 = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item1, item1,
(mutator) => { (mutator) => {
mutator.mutableContent.foo = 'bar' mutator.mutableContent.foo = 'bar'
@@ -119,7 +123,7 @@ describe('items', () => {
expect(item1.isItemContentEqualWith(item2)).to.equal(false) expect(item1.isItemContentEqualWith(item2)).to.equal(false)
item2 = ( item2 = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item2, item2,
(mutator) => { (mutator) => {
mutator.mutableContent.foo = 'bar' mutator.mutableContent.foo = 'bar'
@@ -134,7 +138,7 @@ describe('items', () => {
expect(item2.isItemContentEqualWith(item1)).to.equal(true) expect(item2.isItemContentEqualWith(item1)).to.equal(true)
item1 = ( item1 = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item1, item1,
(mutator) => { (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item2) mutator.e2ePendingRefactor_addItemAsRelationship(item2)
@@ -145,7 +149,7 @@ describe('items', () => {
) )
).getValue() ).getValue()
item2 = ( item2 = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item2, item2,
(mutator) => { (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(item1) mutator.e2ePendingRefactor_addItemAsRelationship(item1)
@@ -162,7 +166,7 @@ describe('items', () => {
expect(item1.isItemContentEqualWith(item2)).to.equal(false) expect(item1.isItemContentEqualWith(item2)).to.equal(false)
item1 = ( item1 = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item1, item1,
(mutator) => { (mutator) => {
mutator.removeItemAsRelationship(item2) mutator.removeItemAsRelationship(item2)
@@ -173,7 +177,7 @@ describe('items', () => {
) )
).getValue() ).getValue()
item2 = ( item2 = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item2, item2,
(mutator) => { (mutator) => {
mutator.removeItemAsRelationship(item1) mutator.removeItemAsRelationship(item1)
@@ -192,13 +196,13 @@ describe('items', () => {
it('content equality should not have side effects', async function () { it('content equality should not have side effects', async function () {
const params1 = Factory.createNotePayload() const params1 = Factory.createNotePayload()
const params2 = 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 item1 = application.items.getDisplayableNotes()[0]
const item2 = this.application.items.getDisplayableNotes()[1] const item2 = application.items.getDisplayableNotes()[1]
item1 = ( item1 = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
item1, item1,
(mutator) => { (mutator) => {
mutator.mutableContent.foo = 'bar' 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 // There was an issue where calling that function would modify values directly to omit keys
// in contentKeysToIgnoreWhenCheckingEquality. // in contentKeysToIgnoreWhenCheckingEquality.
await this.application.mutator.setItemsDirty([item1, item2]) await application.mutator.setItemsDirty([item1, item2])
expect(item1.userModifiedDate).to.be.ok expect(item1.userModifiedDate).to.be.ok
expect(item2.userModifiedDate).to.be.ok expect(item2.userModifiedDate).to.be.ok

View File

@@ -1,28 +1,35 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import { BaseItemCounts } from '../lib/BaseItemCounts.js' import { BaseItemCounts } from '../lib/BaseItemCounts.js'
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
import { createNoteParams } from '../lib/Items.js' import { createNoteParams } from '../lib/Items.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('model manager mapping', () => { describe('model manager mapping', () => {
let application
let expectedItemCount
let context
beforeEach(async function () { beforeEach(async function () {
this.expectedItemCount = BaseItemCounts.DefaultItems localStorage.clear()
this.context = await Factory.createAppContext() expectedItemCount = BaseItemCounts.DefaultItems
await this.context.launch() context = await Factory.createAppContext()
this.application = this.context.application await context.launch()
application = context.application
}) })
afterEach(async function () { 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 () { it('mapping nonexistent item creates it', async function () {
const payload = Factory.createNotePayload() 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)
}) })
it('mapping nonexistent deleted item doesnt create it', async function () { it('mapping nonexistent deleted item doesnt create it', async function () {
@@ -31,17 +38,17 @@ describe('model manager mapping', () => {
dirty: false, dirty: false,
deleted: true, deleted: true,
}) })
await this.application.payloads.emitPayload(payload, PayloadEmitSource.LocalChanged) await application.payloads.emitPayload(payload, PayloadEmitSource.LocalChanged)
expect(this.application.items.items.length).to.equal(this.expectedItemCount) expect(application.items.items.length).to.equal(expectedItemCount)
}) })
it('mapping and deleting nonexistent item creates and deletes it', async function () { it('mapping and deleting nonexistent item creates and deletes it', async function () {
const payload = Factory.createNotePayload() 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({ const changedParams = new DeletedPayload({
...payload, ...payload,
@@ -49,32 +56,32 @@ describe('model manager mapping', () => {
deleted: true, 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 () { it('mapping deleted but dirty item should not delete it', async function () {
const payload = Factory.createNotePayload() 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 () { it('mapping existing item updates its properties', async function () {
const payload = Factory.createNotePayload() const payload = Factory.createNotePayload()
await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
const newTitle = 'updated title' const newTitle = 'updated title'
const mutated = new DecryptedPayload({ const mutated = new DecryptedPayload({
@@ -84,45 +91,45 @@ describe('model manager mapping', () => {
title: newTitle, title: newTitle,
}, },
}) })
await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
const item = this.application.items.getDisplayableNotes()[0] const item = application.items.getDisplayableNotes()[0]
expect(item.content.title).to.equal(newTitle) expect(item.content.title).to.equal(newTitle)
}) })
it('setting an item dirty should retrieve it in dirty items', async function () { it('setting an item dirty should retrieve it in dirty items', async function () {
const payload = Factory.createNotePayload() const payload = Factory.createNotePayload()
await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
const note = this.application.items.getDisplayableNotes()[0] const note = application.items.getDisplayableNotes()[0]
await this.application.mutator.setItemDirty(note) await application.mutator.setItemDirty(note)
const dirtyItems = this.application.items.getDirtyItems() const dirtyItems = application.items.getDirtyItems()
expect(Uuids(dirtyItems).includes(note.uuid)) expect(Uuids(dirtyItems).includes(note.uuid))
}) })
it('set all items dirty', async function () { it('set all items dirty', async function () {
const count = 10 const count = 10
this.expectedItemCount += count expectedItemCount += count
const payloads = [] const payloads = []
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
payloads.push(Factory.createNotePayload()) payloads.push(Factory.createNotePayload())
} }
await this.application.mutator.emitItemsFromPayloads(payloads, PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads(payloads, PayloadEmitSource.LocalChanged)
await this.application.sync.markAllItemsAsNeedingSyncAndPersist() await application.sync.markAllItemsAsNeedingSyncAndPersist()
const dirtyItems = this.application.items.getDirtyItems() const dirtyItems = application.items.getDirtyItems()
expect(dirtyItems.length).to.equal(this.expectedItemCount) expect(dirtyItems.length).to.equal(expectedItemCount)
}) })
it('sync observers should be notified of changes', async function () { it('sync observers should be notified of changes', async function () {
const payload = Factory.createNotePayload() const payload = Factory.createNotePayload()
await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
const item = this.application.items.items[0] const item = application.items.items[0]
return new Promise((resolve) => { 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) expect(changed[0].uuid === item.uuid)
resolve() resolve()
}) })
this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
}) })
}) })
}) })

View File

@@ -1,5 +1,3 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
@@ -26,48 +24,53 @@ const titles = (items) => {
} }
describe('notes and smart views', () => { describe('notes and smart views', () => {
let application
beforeEach(async function () { beforeEach(async function () {
this.application = await Factory.createInitAppWithFakeCrypto() localStorage.clear()
application = await Factory.createInitAppWithFakeCrypto()
}) })
afterEach(async function () { 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 () { it('lets me create a smart view and use it', async function () {
// ## The user creates 3 notes // ## The user creates 3 notes
const [note_1, note_2, note_3] = await Promise.all([ const [note_1, note_2, note_3] = await Promise.all([
Factory.createMappedNote(this.application, 'long & pinned', generateLongString()), Factory.createMappedNote(application, 'long & pinned', generateLongString()),
Factory.createMappedNote(this.application, 'long & !pinned', generateLongString()), Factory.createMappedNote(application, 'long & !pinned', generateLongString()),
Factory.createMappedNote(this.application, 'pinned', 'this is a pinned note'), Factory.createMappedNote(application, 'pinned', 'this is a pinned note'),
]) ])
// The user pin 2 notes // 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) // ## The user creates smart views (long & pinned)
const not_pinned = '!["Not Pinned", "pinned", "=", false]' const not_pinned = '!["Not Pinned", "pinned", "=", false]'
const long = '!["Long", "text.length", ">", 500]' const long = '!["Long", "text.length", ">", 500]'
const tag_not_pinned = await this.application.mutator.createTagOrSmartView(not_pinned) const tag_not_pinned = await application.mutator.createTagOrSmartView(not_pinned)
const tag_long = await this.application.mutator.createTagOrSmartView(long) const tag_long = await application.mutator.createTagOrSmartView(long)
// ## The user can filter and see the pinned notes // ## 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], views: [tag_not_pinned],
}) })
expect(titles(notes_not_pinned)).to.eql(['long & !pinned']) expect(titles(notes_not_pinned)).to.eql(['long & !pinned'])
// ## The user can filter and see the long notes // ## 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']) expect(titles(notes_long)).to.eql(['long & !pinned', 'long & pinned'])
// ## The user creates a new long note // ## 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 // ## The user can filter and see the new long note
const notes_long2 = getFilteredNotes(this.application, { const notes_long2 = getFilteredNotes(application, {
views: [tag_long], views: [tag_long],
}) })
expect(titles(notes_long2)).to.eql(['long & !pinned', 'long & pinned', 'new long']) expect(titles(notes_long2)).to.eql(['long & !pinned', 'long & pinned', 'new long'])

View File

@@ -1,39 +1,44 @@
/* eslint-disable no-undef */
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
import * as Utils from '../lib/Utils.js' import * as Utils from '../lib/Utils.js'
import { createRelatedNoteTagPairPayload } from '../lib/Items.js' import { createRelatedNoteTagPairPayload } from '../lib/Items.js'
import { BaseItemCounts } from '../lib/BaseItemCounts.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('notes and tags', () => { describe('notes and tags', () => {
let application
let context
const syncOptions = { const syncOptions = {
checkIntegrity: true, checkIntegrity: true,
awaitAll: true, awaitAll: true,
} }
beforeEach(async function () { beforeEach(async function () {
this.expectedItemCount = BaseItemCounts.DefaultItems localStorage.clear()
this.context = await Factory.createAppContext() context = await Factory.createAppContext()
await this.context.launch() await context.launch()
this.application = this.context.application application = context.application
}) })
afterEach(async function () { 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 () { it('uses proper class for note', async function () {
const payload = Factory.createNotePayload() const payload = Factory.createNotePayload()
await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
const note = this.application.items.getItems([ContentType.TYPES.Note])[0] const note = application.items.getItems([ContentType.TYPES.Note])[0]
expect(note.constructor === SNNote).to.equal(true) expect(note.constructor === SNNote).to.equal(true)
}) })
it('properly constructs syncing params', async function () { it('properly constructs syncing params', async function () {
const title = 'Foo' const title = 'Foo'
const text = 'Bar' const text = 'Bar'
const note = await this.application.items.createTemplateItem(ContentType.TYPES.Note, { const note = await application.items.createTemplateItem(ContentType.TYPES.Note, {
title, title,
text, text,
}) })
@@ -41,7 +46,7 @@ describe('notes and tags', () => {
expect(note.content.title).to.equal(title) expect(note.content.title).to.equal(title)
expect(note.content.text).to.equal(text) 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, title,
}) })
@@ -73,12 +78,12 @@ describe('notes and tags', () => {
}, },
}) })
await this.application.mutator.emitItemsFromPayloads([mutatedNote, mutatedTag], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([mutatedNote, mutatedTag], PayloadEmitSource.LocalChanged)
const note = this.application.items.getItems([ContentType.TYPES.Note])[0] const note = application.items.getItems([ContentType.TYPES.Note])[0]
const tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] const tag = application.items.getItems([ContentType.TYPES.Tag])[0]
expect(note.content.references.length).to.equal(1) 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 () { 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(notePayload.content.references.length).to.equal(0)
expect(tagPayload.content.references.length).to.equal(1) expect(tagPayload.content.references.length).to.equal(1)
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
let note = this.application.items.getDisplayableNotes()[0] let note = application.items.getDisplayableNotes()[0]
let tag = this.application.items.getDisplayableTags()[0] let tag = application.items.getDisplayableTags()[0]
expect(note.dirty).to.not.be.ok expect(note.dirty).to.not.be.ok
expect(tag.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(note.isReferencingItem(tag)).to.equal(false)
expect(tag.isReferencingItem(note)).to.equal(true) 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(note.payload.references.length).to.equal(0)
expect(tag.noteCount).to.equal(1) 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(deletedNotePayload.dirty).to.be.true
expect(tag.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(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(tag.noteCount).to.equal(0)
tag = this.application.items.getDisplayableTags()[0] tag = application.items.getDisplayableTags()[0]
expect(this.application.items.getDisplayableNotes().length).to.equal(0) expect(application.items.getDisplayableNotes().length).to.equal(0)
expect(tag.dirty).to.be.false expect(tag.dirty).to.be.false
}) })
@@ -130,14 +135,14 @@ describe('notes and tags', () => {
const notePayload = pair[0] const notePayload = pair[0]
const tagPayload = pair[1] const tagPayload = pair[1]
await this.application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
let note = this.application.items.getItems([ContentType.TYPES.Note])[0] let note = application.items.getItems([ContentType.TYPES.Note])[0]
let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] let tag = application.items.getItems([ContentType.TYPES.Tag])[0]
expect(note.content.references.length).to.equal(0) expect(note.content.references.length).to.equal(0)
expect(tag.content.references.length).to.equal(1) expect(tag.content.references.length).to.equal(1)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
const mutatedTag = new DecryptedPayload({ const mutatedTag = new DecryptedPayload({
...tagPayload, ...tagPayload,
@@ -147,13 +152,13 @@ describe('notes and tags', () => {
references: [], references: [],
}, },
}) })
await this.application.mutator.emitItemsFromPayloads([mutatedTag], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([mutatedTag], PayloadEmitSource.LocalChanged)
note = this.application.items.findItem(note.uuid) note = application.items.findItem(note.uuid)
tag = this.application.items.findItem(tag.uuid) tag = application.items.findItem(tag.uuid)
expect(tag.content.references.length).to.equal(0) 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(tag.noteCount).to.equal(0)
// expect to be false // expect to be false
@@ -162,13 +167,13 @@ describe('notes and tags', () => {
}) })
it('creating basic note should have text set', async function () { 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.title).to.be.ok
expect(note.text).to.be.ok expect(note.text).to.be.ok
}) })
it('creating basic tag should have title', async function () { 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 expect(tag.title).to.be.ok
}) })
@@ -177,15 +182,15 @@ describe('notes and tags', () => {
const notePayload = pair[0] const notePayload = pair[0]
const tagPayload = pair[1] const tagPayload = pair[1]
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
const note = this.application.items.getItems([ContentType.TYPES.Note])[0] const note = application.items.getItems([ContentType.TYPES.Note])[0]
let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] let tag = application.items.getItems([ContentType.TYPES.Tag])[0]
expect(note.content.references.length).to.equal(0) expect(note.content.references.length).to.equal(0)
expect(tag.content.references.length).to.equal(1) expect(tag.content.references.length).to.equal(1)
tag = ( tag = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
tag, tag,
(mutator) => { (mutator) => {
mutator.removeItemAsRelationship(note) mutator.removeItemAsRelationship(note)
@@ -196,21 +201,21 @@ describe('notes and tags', () => {
) )
).getValue() ).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) expect(tag.noteCount).to.equal(0)
}) })
it('properly handles tag duplication', async function () { it('properly handles tag duplication', async function () {
const pair = createRelatedNoteTagPairPayload() const pair = createRelatedNoteTagPairPayload()
await this.application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
let note = this.application.items.getDisplayableNotes()[0] let note = application.items.getDisplayableNotes()[0]
let tag = this.application.items.getDisplayableTags()[0] let tag = application.items.getDisplayableTags()[0]
const duplicateTag = await this.application.mutator.duplicateItem(tag, true) const duplicateTag = await application.mutator.duplicateItem(tag, true)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
note = this.application.items.findItem(note.uuid) note = application.items.findItem(note.uuid)
tag = this.application.items.findItem(tag.uuid) tag = application.items.findItem(tag.uuid)
expect(tag.uuid).to.not.equal(duplicateTag.uuid) expect(tag.uuid).to.not.equal(duplicateTag.uuid)
expect(tag.content.references.length).to.equal(1) 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.content.references.length).to.equal(1)
expect(duplicateTag.noteCount).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) expect(noteTags.length).to.equal(2)
const noteTag1 = noteTags[0] const noteTag1 = noteTags[0]
@@ -234,13 +239,13 @@ describe('notes and tags', () => {
const pair = createRelatedNoteTagPairPayload() const pair = createRelatedNoteTagPairPayload()
const notePayload = pair[0] const notePayload = pair[0]
const tagPayload = pair[1] const tagPayload = pair[1]
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
const note = this.application.items.getItems([ContentType.TYPES.Note])[0] const note = application.items.getItems([ContentType.TYPES.Note])[0]
const duplicateNote = await this.application.mutator.duplicateItem(note, true) const duplicateNote = await application.mutator.duplicateItem(note, true)
expect(note.uuid).to.not.equal(duplicateNote.uuid) expect(note.uuid).to.not.equal(duplicateNote.uuid)
expect(this.application.items.itemsReferencingItem(duplicateNote).length).to.equal( expect(application.items.itemsReferencingItem(duplicateNote).length).to.equal(
this.application.items.itemsReferencingItem(note).length, application.items.itemsReferencingItem(note).length,
) )
}) })
@@ -248,27 +253,27 @@ describe('notes and tags', () => {
const pair = createRelatedNoteTagPairPayload() const pair = createRelatedNoteTagPairPayload()
const notePayload = pair[0] const notePayload = pair[0]
const tagPayload = pair[1] const tagPayload = pair[1]
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
const note = this.application.items.getItems([ContentType.TYPES.Note])[0] const note = application.items.getItems([ContentType.TYPES.Note])[0]
let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] let tag = application.items.getItems([ContentType.TYPES.Tag])[0]
expect(tag.content.references.length).to.equal(1) expect(tag.content.references.length).to.equal(1)
expect(tag.noteCount).to.equal(1) expect(tag.noteCount).to.equal(1)
expect(note.content.references.length).to.equal(0) 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) await application.mutator.setItemToBeDeleted(tag)
tag = this.application.items.findItem(tag.uuid) tag = application.items.findItem(tag.uuid)
expect(tag).to.not.be.ok expect(tag).to.not.be.ok
}) })
it('modifying item content should not modify payload content', async function () { it('modifying item content should not modify payload content', async function () {
const notePayload = Factory.createNotePayload() const notePayload = Factory.createNotePayload()
await this.application.mutator.emitItemsFromPayloads([notePayload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([notePayload], PayloadEmitSource.LocalChanged)
let note = this.application.items.getItems([ContentType.TYPES.Note])[0] let note = application.items.getItems([ContentType.TYPES.Note])[0]
note = ( note = (
await this.application.changeAndSaveItem.execute( await application.changeAndSaveItem.execute(
note, note,
(mutator) => { (mutator) => {
mutator.mutableContent.title = Math.random() mutator.mutableContent.title = Math.random()
@@ -289,15 +294,15 @@ describe('notes and tags', () => {
const notePayload = pair[0] const notePayload = pair[0]
const tagPayload = pair[1] const tagPayload = pair[1]
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
let note = this.application.items.getItems([ContentType.TYPES.Note])[0] let note = application.items.getItems([ContentType.TYPES.Note])[0]
let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] let tag = application.items.getItems([ContentType.TYPES.Tag])[0]
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
await this.application.mutator.setItemToBeDeleted(tag) await application.mutator.setItemToBeDeleted(tag)
note = this.application.items.findItem(note.uuid) note = application.items.findItem(note.uuid)
this.application.items.findItem(tag.uuid) application.items.findItem(tag.uuid)
expect(note.dirty).to.not.be.ok expect(note.dirty).to.not.be.ok
}) })
@@ -305,26 +310,26 @@ describe('notes and tags', () => {
it('should sort notes', async function () { it('should sort notes', async function () {
await Promise.all( await Promise.all(
['Y', 'Z', 'A', 'B'].map(async (title) => { ['Y', 'Z', 'A', 'B'].map(async (title) => {
return this.application.mutator.insertItem( return application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { title }), await application.items.createTemplateItem(ContentType.TYPES.Note, { title }),
) )
}), }),
) )
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'dsc', 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 */ /** setPrimaryItemDisplayOptions inverses sort for title */
expect(titles).to.deep.equal(['A', 'B', 'Y', 'Z']) expect(titles).to.deep.equal(['A', 'B', 'Y', 'Z'])
}) })
it('setting a note dirty should collapse its properties into content', async function () { 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', title: 'Foo',
}) })
await this.application.mutator.insertItem(note) await application.mutator.insertItem(note)
note = this.application.items.findItem(note.uuid) note = application.items.findItem(note.uuid)
expect(note.content.title).to.equal('Foo') 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 () { it('should sort tags in ascending alphabetical order by default', async function () {
const titles = ['1', 'A', 'b', '2'] const titles = ['1', 'A', 'b', '2']
const sortedTitles = titles.sort((a, b) => a.localeCompare(b)) const sortedTitles = titles.sort((a, b) => a.localeCompare(b))
await Promise.all(titles.map((title) => this.application.mutator.findOrCreateTag(title))) await Promise.all(titles.map((title) => application.mutator.findOrCreateTag(title)))
expect(this.application.items.tagDisplayController.items().map((t) => t.title)).to.deep.equal(sortedTitles) expect(application.items.tagDisplayController.items().map((t) => t.title)).to.deep.equal(sortedTitles)
}) })
it('should match a tag', async function () { it('should match a tag', async function () {
const taggedNote = await Factory.createMappedNote(this.application) const taggedNote = await Factory.createMappedNote(application)
const tag = await this.application.mutator.findOrCreateTag('A') const tag = await application.mutator.findOrCreateTag('A')
await this.application.mutator.changeItem(tag, (mutator) => { await application.mutator.changeItem(tag, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote) mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote)
}) })
await this.application.mutator.insertItem( await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
}), }),
) )
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'dsc', sortDirection: 'dsc',
tags: [tag], tags: [tag],
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes.length).to.equal(1) expect(displayedNotes.length).to.equal(1)
expect(displayedNotes[0].uuid).to.equal(taggedNote.uuid) expect(displayedNotes[0].uuid).to.equal(taggedNote.uuid)
}) })
it('should not show trashed notes when displaying a tag', async function () { it('should not show trashed notes when displaying a tag', async function () {
const taggedNote = await Factory.createMappedNote(this.application) const taggedNote = await Factory.createMappedNote(application)
const trashedNote = await Factory.createMappedNote(this.application) const trashedNote = await Factory.createMappedNote(application)
const tag = await this.application.mutator.findOrCreateTag('A') const tag = await application.mutator.findOrCreateTag('A')
await this.application.mutator.changeItem(tag, (mutator) => { await application.mutator.changeItem(tag, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote) mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote)
mutator.e2ePendingRefactor_addItemAsRelationship(trashedNote) mutator.e2ePendingRefactor_addItemAsRelationship(trashedNote)
}) })
await this.application.mutator.changeItem(trashedNote, (mutator) => { await application.mutator.changeItem(trashedNote, (mutator) => {
mutator.trashed = true mutator.trashed = true
}) })
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'dsc', sortDirection: 'dsc',
tags: [tag], tags: [tag],
includeTrashed: false, includeTrashed: false,
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes.length).to.equal(1) expect(displayedNotes.length).to.equal(1)
expect(displayedNotes[0].uuid).to.equal(taggedNote.uuid) 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 () { it('should sort notes when displaying tag', async function () {
await Promise.all( await Promise.all(
['Y', 'Z', 'A', 'B'].map(async (title) => { ['Y', 'Z', 'A', 'B'].map(async (title) => {
return this.application.mutator.insertItem( return application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title, title,
}), }),
) )
}), }),
) )
const pinnedNote = this.application.items.getDisplayableNotes().find((note) => note.title === 'B') const pinnedNote = application.items.getDisplayableNotes().find((note) => note.title === 'B')
await this.application.mutator.changeItem(pinnedNote, (mutator) => { await application.mutator.changeItem(pinnedNote, (mutator) => {
mutator.pinned = true mutator.pinned = true
}) })
const tag = await this.application.mutator.findOrCreateTag('A') const tag = await application.mutator.findOrCreateTag('A')
await this.application.mutator.changeItem(tag, (mutator) => { await application.mutator.changeItem(tag, (mutator) => {
for (const note of this.application.items.getDisplayableNotes()) { for (const note of application.items.getDisplayableNotes()) {
mutator.e2ePendingRefactor_addItemAsRelationship(note) mutator.e2ePendingRefactor_addItemAsRelationship(note)
} }
}) })
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'dsc', sortDirection: 'dsc',
tags: [tag], tags: [tag],
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes).to.have.length(4) expect(displayedNotes).to.have.length(4)
/** setPrimaryItemDisplayOptions inverses sort for title */ /** setPrimaryItemDisplayOptions inverses sort for title */
expect(displayedNotes[0].title).to.equal('B') expect(displayedNotes[0].title).to.equal('B')
@@ -416,18 +421,18 @@ describe('notes and tags', () => {
describe('Smart views', function () { describe('Smart views', function () {
it('"title", "startsWith", "Foo"', async function () { it('"title", "startsWith", "Foo"', async function () {
const note = await this.application.mutator.insertItem( const note = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'Foo 🎲', title: 'Foo 🎲',
}), }),
) )
await this.application.mutator.insertItem( await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'Not Foo 🎲', title: 'Not Foo 🎲',
}), }),
) )
const view = await this.application.mutator.insertItem( const view = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { await application.items.createTemplateItem(ContentType.TYPES.SmartView, {
title: 'Foo Notes', title: 'Foo Notes',
predicate: { predicate: {
keypath: 'title', keypath: 'title',
@@ -436,36 +441,36 @@ describe('notes and tags', () => {
}, },
}), }),
) )
const matches = this.application.items.notesMatchingSmartView(view) const matches = application.items.notesMatchingSmartView(view)
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'asc', sortDirection: 'asc',
views: [view], views: [view],
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes).to.deep.equal(matches) expect(displayedNotes).to.deep.equal(matches)
expect(matches.length).to.equal(1) expect(matches.length).to.equal(1)
expect(matches[0].uuid).to.equal(note.uuid) expect(matches[0].uuid).to.equal(note.uuid)
}) })
it('"pinned", "=", true', async function () { it('"pinned", "=", true', async function () {
const note = await this.application.mutator.insertItem( const note = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
}), }),
) )
await this.application.mutator.changeItem(note, (mutator) => { await application.mutator.changeItem(note, (mutator) => {
mutator.pinned = true mutator.pinned = true
}) })
await this.application.mutator.insertItem( await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'B', title: 'B',
pinned: false, pinned: false,
}), }),
) )
const view = await this.application.mutator.insertItem( const view = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { await application.items.createTemplateItem(ContentType.TYPES.SmartView, {
title: 'Pinned', title: 'Pinned',
predicate: { predicate: {
keypath: 'pinned', keypath: 'pinned',
@@ -474,35 +479,35 @@ describe('notes and tags', () => {
}, },
}), }),
) )
const matches = this.application.items.notesMatchingSmartView(view) const matches = application.items.notesMatchingSmartView(view)
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'asc', sortDirection: 'asc',
views: [view], views: [view],
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes).to.deep.equal(matches) expect(displayedNotes).to.deep.equal(matches)
expect(matches.length).to.equal(1) expect(matches.length).to.equal(1)
expect(matches[0].uuid).to.equal(note.uuid) expect(matches[0].uuid).to.equal(note.uuid)
}) })
it('"pinned", "=", false', async function () { it('"pinned", "=", false', async function () {
const pinnedNote = await this.application.mutator.insertItem( const pinnedNote = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
}), }),
) )
await this.application.mutator.changeItem(pinnedNote, (mutator) => { await application.mutator.changeItem(pinnedNote, (mutator) => {
mutator.pinned = true mutator.pinned = true
}) })
const unpinnedNote = await this.application.mutator.insertItem( const unpinnedNote = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'B', title: 'B',
}), }),
) )
const view = await this.application.mutator.insertItem( const view = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { await application.items.createTemplateItem(ContentType.TYPES.SmartView, {
title: 'Not pinned', title: 'Not pinned',
predicate: { predicate: {
keypath: 'pinned', keypath: 'pinned',
@@ -511,34 +516,34 @@ describe('notes and tags', () => {
}, },
}), }),
) )
const matches = this.application.items.notesMatchingSmartView(view) const matches = application.items.notesMatchingSmartView(view)
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'asc', sortDirection: 'asc',
views: [view], views: [view],
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes).to.deep.equal(matches) expect(displayedNotes).to.deep.equal(matches)
expect(matches.length).to.equal(1) expect(matches.length).to.equal(1)
expect(matches[0].uuid).to.equal(unpinnedNote.uuid) expect(matches[0].uuid).to.equal(unpinnedNote.uuid)
}) })
it('"text.length", ">", 500', async function () { it('"text.length", ">", 500', async function () {
const longNote = await this.application.mutator.insertItem( const longNote = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
text: Array(501).fill(0).join(''), text: Array(501).fill(0).join(''),
}), }),
) )
await this.application.mutator.insertItem( await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'B', title: 'B',
text: 'b', text: 'b',
}), }),
) )
const view = await this.application.mutator.insertItem( const view = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { await application.items.createTemplateItem(ContentType.TYPES.SmartView, {
title: 'Long', title: 'Long',
predicate: { predicate: {
keypath: 'text.length', keypath: 'text.length',
@@ -547,13 +552,13 @@ describe('notes and tags', () => {
}, },
}), }),
) )
const matches = this.application.items.notesMatchingSmartView(view) const matches = application.items.notesMatchingSmartView(view)
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'asc', sortDirection: 'asc',
views: [view], views: [view],
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes).to.deep.equal(matches) expect(displayedNotes).to.deep.equal(matches)
expect(matches.length).to.equal(1) expect(matches.length).to.equal(1)
expect(matches[0].uuid).to.equal(longNote.uuid) expect(matches[0].uuid).to.equal(longNote.uuid)
@@ -561,22 +566,22 @@ describe('notes and tags', () => {
it('"updated_at", ">", "1.days.ago"', async function () { it('"updated_at", ">", "1.days.ago"', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: Utils.generateUuid(), email: Utils.generateUuid(),
password: Utils.generateUuid(), password: Utils.generateUuid(),
}) })
const recentNote = await this.application.mutator.insertItem( const recentNote = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
}), }),
true, true,
) )
await this.application.sync.sync() await application.sync.sync()
const olderNote = await this.application.mutator.insertItem( const olderNote = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'B', title: 'B',
text: 'b', text: 'b',
}), }),
@@ -584,17 +589,17 @@ describe('notes and tags', () => {
) )
const threeDays = 3 * 24 * 60 * 60 * 1000 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 */ /** Create an unsynced note which shouldn't get an updated_at */
await this.application.mutator.insertItem( await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'B', title: 'B',
text: 'b', text: 'b',
}), }),
) )
const view = await this.application.mutator.insertItem( const view = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { await application.items.createTemplateItem(ContentType.TYPES.SmartView, {
title: 'One day ago', title: 'One day ago',
predicate: { predicate: {
keypath: 'serverUpdatedAt', 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.length).to.equal(1)
expect(matches[0].uuid).to.equal(recentNote.uuid) expect(matches[0].uuid).to.equal(recentNote.uuid)
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'asc', sortDirection: 'asc',
views: [view], views: [view],
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes).to.deep.equal(matches) expect(displayedNotes).to.deep.equal(matches)
}) })
it('"tags.length", "=", 0', async function () { it('"tags.length", "=", 0', async function () {
const untaggedNote = await this.application.mutator.insertItem( const untaggedNote = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
}), }),
) )
const taggedNote = await Factory.createMappedNote(this.application) const taggedNote = await Factory.createMappedNote(application)
const tag = await this.application.mutator.findOrCreateTag('A') const tag = await application.mutator.findOrCreateTag('A')
await this.application.mutator.changeItem(tag, (mutator) => { await application.mutator.changeItem(tag, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote) mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote)
}) })
const view = await this.application.mutator.insertItem( const view = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { await application.items.createTemplateItem(ContentType.TYPES.SmartView, {
title: 'Untagged', title: 'Untagged',
predicate: { predicate: {
keypath: 'tags.length', keypath: 'tags.length',
@@ -638,32 +643,32 @@ describe('notes and tags', () => {
}, },
}), }),
) )
const matches = this.application.items.notesMatchingSmartView(view) const matches = application.items.notesMatchingSmartView(view)
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'asc', sortDirection: 'asc',
views: [view], views: [view],
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes).to.deep.equal(matches) expect(displayedNotes).to.deep.equal(matches)
expect(matches.length).to.equal(1) expect(matches.length).to.equal(1)
expect(matches[0].uuid).to.equal(untaggedNote.uuid) expect(matches[0].uuid).to.equal(untaggedNote.uuid)
}) })
it('"tags", "includes", ["title", "startsWith", "b"]', async function () { it('"tags", "includes", ["title", "startsWith", "b"]', async function () {
const taggedNote = await Factory.createMappedNote(this.application) const taggedNote = await Factory.createMappedNote(application)
const tag = await this.application.mutator.findOrCreateTag('B') const tag = await application.mutator.findOrCreateTag('B')
await this.application.mutator.changeItem(tag, (mutator) => { await application.mutator.changeItem(tag, (mutator) => {
mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote) mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote)
}) })
await this.application.mutator.insertItem( await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
}), }),
) )
const view = await this.application.mutator.insertItem( const view = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { await application.items.createTemplateItem(ContentType.TYPES.SmartView, {
title: 'B-tags', title: 'B-tags',
predicate: { predicate: {
keypath: 'tags', keypath: 'tags',
@@ -672,45 +677,45 @@ describe('notes and tags', () => {
}, },
}), }),
) )
const matches = this.application.items.notesMatchingSmartView(view) const matches = application.items.notesMatchingSmartView(view)
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'asc', sortDirection: 'asc',
views: [view], views: [view],
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes).to.deep.equal(matches) expect(displayedNotes).to.deep.equal(matches)
expect(matches.length).to.equal(1) expect(matches.length).to.equal(1)
expect(matches[0].uuid).to.equal(taggedNote.uuid) expect(matches[0].uuid).to.equal(taggedNote.uuid)
}) })
it('"ignored", "and", [["pinned", "=", true], ["locked", "=", true]]', async function () { it('"ignored", "and", [["pinned", "=", true], ["locked", "=", true]]', async function () {
const pinnedAndLockedNote = await Factory.createMappedNote(this.application) const pinnedAndLockedNote = await Factory.createMappedNote(application)
await this.application.mutator.changeItem(pinnedAndLockedNote, (mutator) => { await application.mutator.changeItem(pinnedAndLockedNote, (mutator) => {
mutator.pinned = true mutator.pinned = true
mutator.locked = true mutator.locked = true
}) })
const pinnedNote = await this.application.mutator.insertItem( const pinnedNote = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
}), }),
) )
await this.application.mutator.changeItem(pinnedNote, (mutator) => { await application.mutator.changeItem(pinnedNote, (mutator) => {
mutator.pinned = true mutator.pinned = true
}) })
const lockedNote = await this.application.mutator.insertItem( const lockedNote = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
}), }),
) )
await this.application.mutator.changeItem(lockedNote, (mutator) => { await application.mutator.changeItem(lockedNote, (mutator) => {
mutator.locked = true mutator.locked = true
}) })
const view = await this.application.mutator.insertItem( const view = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { await application.items.createTemplateItem(ContentType.TYPES.SmartView, {
title: 'Pinned & Locked', title: 'Pinned & Locked',
predicate: { predicate: {
operator: 'and', operator: 'and',
@@ -721,51 +726,51 @@ describe('notes and tags', () => {
}, },
}), }),
) )
const matches = this.application.items.notesMatchingSmartView(view) const matches = application.items.notesMatchingSmartView(view)
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'asc', sortDirection: 'asc',
views: [view], views: [view],
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes).to.deep.equal(matches) expect(displayedNotes).to.deep.equal(matches)
expect(matches.length).to.equal(1) expect(matches.length).to.equal(1)
expect(matches[0].uuid).to.equal(pinnedAndLockedNote.uuid) expect(matches[0].uuid).to.equal(pinnedAndLockedNote.uuid)
}) })
it('"ignored", "or", [["content.protected", "=", true], ["pinned", "=", true]]', async function () { it('"ignored", "or", [["content.protected", "=", true], ["pinned", "=", true]]', async function () {
const protectedNote = await Factory.createMappedNote(this.application) const protectedNote = await Factory.createMappedNote(application)
await this.application.mutator.changeItem(protectedNote, (mutator) => { await application.mutator.changeItem(protectedNote, (mutator) => {
mutator.protected = true mutator.protected = true
}) })
const pinnedNote = await this.application.mutator.insertItem( const pinnedNote = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
}), }),
) )
await this.application.mutator.changeItem(pinnedNote, (mutator) => { await application.mutator.changeItem(pinnedNote, (mutator) => {
mutator.pinned = true mutator.pinned = true
}) })
const pinnedAndProtectedNote = await this.application.mutator.insertItem( const pinnedAndProtectedNote = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
}), }),
) )
await this.application.mutator.changeItem(pinnedAndProtectedNote, (mutator) => { await application.mutator.changeItem(pinnedAndProtectedNote, (mutator) => {
mutator.pinned = true mutator.pinned = true
mutator.protected = true mutator.protected = true
}) })
await this.application.mutator.insertItem( await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.Note, { await application.items.createTemplateItem(ContentType.TYPES.Note, {
title: 'A', title: 'A',
}), }),
) )
const view = await this.application.mutator.insertItem( const view = await application.mutator.insertItem(
await this.application.items.createTemplateItem(ContentType.TYPES.SmartView, { await application.items.createTemplateItem(ContentType.TYPES.SmartView, {
title: 'Protected or Pinned', title: 'Protected or Pinned',
predicate: { predicate: {
operator: 'or', operator: 'or',
@@ -776,13 +781,13 @@ describe('notes and tags', () => {
}, },
}), }),
) )
const matches = this.application.items.notesMatchingSmartView(view) const matches = application.items.notesMatchingSmartView(view)
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'created_at', sortBy: 'created_at',
sortDirection: 'asc', sortDirection: 'asc',
views: [view], views: [view],
}) })
const displayedNotes = this.application.items.getDisplayableNotes() const displayedNotes = application.items.getDisplayableNotes()
expect(displayedNotes.length).to.equal(matches.length) expect(displayedNotes.length).to.equal(matches.length)
expect(matches.length).to.equal(3) expect(matches.length).to.equal(3)
expect(matches.find((note) => note.uuid === protectedNote.uuid)).to.exist expect(matches.find((note) => note.uuid === protectedNote.uuid)).to.exist
@@ -801,12 +806,12 @@ describe('notes and tags', () => {
const notePayload3 = Factory.createNotePayload('Bar') const notePayload3 = Factory.createNotePayload('Bar')
const notePayload4 = Factory.createNotePayload('Testing') const notePayload4 = Factory.createNotePayload('Testing')
await this.application.mutator.emitItemsFromPayloads( await application.mutator.emitItemsFromPayloads(
[notePayload1, notePayload2, notePayload3, notePayload4, tagPayload1], [notePayload1, notePayload2, notePayload3, notePayload4, tagPayload1],
PayloadEmitSource.LocalChanged, PayloadEmitSource.LocalChanged,
) )
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'dsc', sortDirection: 'dsc',
searchQuery: { 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) expect(displayedNotes.length).to.equal(2)
/** setPrimaryItemDisplayOptions inverses sort for title */ /** setPrimaryItemDisplayOptions inverses sort for title */
expect(displayedNotes[0].uuid).to.equal(notePayload1.uuid) expect(displayedNotes[0].uuid).to.equal(notePayload1.uuid)
@@ -831,12 +836,12 @@ describe('notes and tags', () => {
const notePayload3 = Factory.createNotePayload('Testing FOO (Bar)') const notePayload3 = Factory.createNotePayload('Testing FOO (Bar)')
const notePayload4 = Factory.createNotePayload('This should not match') const notePayload4 = Factory.createNotePayload('This should not match')
await this.application.mutator.emitItemsFromPayloads( await application.mutator.emitItemsFromPayloads(
[notePayload1, notePayload2, notePayload3, notePayload4, tagPayload1], [notePayload1, notePayload2, notePayload3, notePayload4, tagPayload1],
PayloadEmitSource.LocalChanged, PayloadEmitSource.LocalChanged,
) )
this.application.items.setPrimaryItemDisplayOptions({ application.items.setPrimaryItemDisplayOptions({
sortBy: 'title', sortBy: 'title',
sortDirection: 'dsc', sortDirection: 'dsc',
searchQuery: { 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) expect(displayedNotes.length).to.equal(3)
/** setPrimaryItemDisplayOptions inverses sort for title */ /** setPrimaryItemDisplayOptions inverses sort for title */
expect(displayedNotes[0].uuid).to.equal(notePayload1.uuid) expect(displayedNotes[0].uuid).to.equal(notePayload1.uuid)

View File

@@ -1,86 +1,91 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('tags as folders', () => { describe('tags as folders', () => {
let context
let application
beforeEach(async function () { beforeEach(async function () {
this.context = await Factory.createAppContext() localStorage.clear()
await this.context.launch() context = await Factory.createAppContext()
this.application = this.context.application await context.launch()
application = context.application
}) })
afterEach(async function () { 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 () { 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 // ## The user creates four tags
let tagChildren = await Factory.createMappedTag(this.application, { let tagChildren = await Factory.createMappedTag(application, {
title: 'children', title: 'children',
}) })
let tagParent = await Factory.createMappedTag(this.application, { let tagParent = await Factory.createMappedTag(application, {
title: 'parent', title: 'parent',
}) })
let tagGrandParent = await Factory.createMappedTag(this.application, { let tagGrandParent = await Factory.createMappedTag(application, {
title: 'grandparent', title: 'grandparent',
}) })
let tagGrandParent2 = await Factory.createMappedTag(this.application, { let tagGrandParent2 = await Factory.createMappedTag(application, {
title: 'grandparent2', title: 'grandparent2',
}) })
// ## Now the users moves the tag children into the parent // ## 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(application.items.getTagParent(tagChildren)).to.equal(tagParent)
expect(Uuids(this.application.items.getTagChildren(tagParent))).deep.to.equal(Uuids([tagChildren])) expect(Uuids(application.items.getTagChildren(tagParent))).deep.to.equal(Uuids([tagChildren]))
// ## Now the user moves the tag parent into the grand parent // ## 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(application.items.getTagParent(tagParent)).to.equal(tagGrandParent)
expect(Uuids(this.application.items.getTagChildren(tagGrandParent))).deep.to.equal(Uuids([tagParent])) expect(Uuids(application.items.getTagChildren(tagGrandParent))).deep.to.equal(Uuids([tagParent]))
// ## Now the user moves the tag parent into another grand parent // ## 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(application.items.getTagParent(tagParent)).to.equal(tagGrandParent2)
expect(this.application.items.getTagChildren(tagGrandParent)).deep.to.equal([]) expect(application.items.getTagChildren(tagGrandParent)).deep.to.equal([])
expect(Uuids(this.application.items.getTagChildren(tagGrandParent2))).deep.to.equal(Uuids([tagParent])) expect(Uuids(application.items.getTagChildren(tagGrandParent2))).deep.to.equal(Uuids([tagParent]))
// ## Now the user tries to move the tag into one of its children // ## 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(application.items.getTagParent(tagParent)).to.equal(tagGrandParent2)
expect(this.application.items.getTagChildren(tagGrandParent)).deep.to.equal([]) expect(application.items.getTagChildren(tagGrandParent)).deep.to.equal([])
expect(Uuids(this.application.items.getTagChildren(tagGrandParent2))).deep.to.equal(Uuids([tagParent])) expect(Uuids(application.items.getTagChildren(tagGrandParent2))).deep.to.equal(Uuids([tagParent]))
// ## Now the user move the tag outside any hierarchy // ## 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(application.items.getTagParent(tagParent)).to.equal(undefined)
expect(this.application.items.getTagChildren(tagGrandParent2)).deep.to.equals([]) expect(application.items.getTagChildren(tagGrandParent2)).deep.to.equals([])
}) })
it('lets me add a note to a tag hierarchy', async function () { it('lets me add a note to a tag hierarchy', async function () {
// ## The user creates four tags hierarchy // ## The user creates four tags hierarchy
const tags = await Factory.createTags(this.application, { const tags = await Factory.createTags(application, {
grandparent: { parent: { child: true } }, grandparent: { parent: { child: true } },
another: true, another: true,
}) })
const note1 = await Factory.createMappedNote(this.application, 'my first note') const note1 = await Factory.createMappedNote(application, 'my first note')
const note2 = await Factory.createMappedNote(this.application, 'my second note') const note2 = await Factory.createMappedNote(application, 'my second note')
// ## The user add a note to the child tag // ## The user add a note to the child tag
await this.application.mutator.addTagToNote(note1, tags.child, true) await application.mutator.addTagToNote(note1, tags.child, true)
await this.application.mutator.addTagToNote(note2, tags.another, true) await application.mutator.addTagToNote(note2, tags.another, true)
// ## The note has been added to other tags // ## The note has been added to other tags
const note1Tags = await this.application.items.getSortedTagsForItem(note1) const note1Tags = await application.items.getSortedTagsForItem(note1)
const note2Tags = await this.application.items.getSortedTagsForItem(note2) const note2Tags = await application.items.getSortedTagsForItem(note2)
expect(note1Tags.length).to.equal(3) expect(note1Tags.length).to.equal(3)
expect(note2Tags.length).to.equal(1) expect(note2Tags.length).to.equal(1)

View File

@@ -1,10 +1,17 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('mapping performance', () => { describe('mapping performance', () => {
beforeEach(function () {
localStorage.clear()
})
afterEach(async function () {
localStorage.clear()
})
it('shouldnt take a long time', async () => { 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), There was an issue with mapping where we were using arrays for everything instead of hashes (like items, missedReferences),

View File

@@ -1,47 +1,44 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('item mutator', () => { describe('item mutator', () => {
beforeEach(async function () { const createBarePayload = () => {
this.createBarePayload = () => { return new DecryptedPayload({
return new DecryptedPayload({ uuid: '123',
uuid: '123', content_type: ContentType.TYPES.Note,
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: { 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 () { it('mutate set domain data key', function () {
const item = this.createNote() const item = createNote()
const mutator = new DecryptedItemMutator(item) const mutator = new DecryptedItemMutator(item)
mutator.setDomainDataKey('somekey', 'somevalue', 'somedomain') mutator.setDomainDataKey('somekey', 'somevalue', 'somedomain')
const payload = mutator.getResult() const payload = mutator.getResult()
@@ -50,7 +47,7 @@ describe('item mutator', () => {
}) })
it('mutate set pinned', function () { it('mutate set pinned', function () {
const item = this.createNote() const item = createNote()
const mutator = new DecryptedItemMutator(item) const mutator = new DecryptedItemMutator(item)
mutator.pinned = true mutator.pinned = true
const payload = mutator.getResult() const payload = mutator.getResult()
@@ -59,7 +56,7 @@ describe('item mutator', () => {
}) })
it('mutate set archived', function () { it('mutate set archived', function () {
const item = this.createNote() const item = createNote()
const mutator = new DecryptedItemMutator(item) const mutator = new DecryptedItemMutator(item)
mutator.archived = true mutator.archived = true
const payload = mutator.getResult() const payload = mutator.getResult()
@@ -68,7 +65,7 @@ describe('item mutator', () => {
}) })
it('mutate set locked', function () { it('mutate set locked', function () {
const item = this.createNote() const item = createNote()
const mutator = new DecryptedItemMutator(item) const mutator = new DecryptedItemMutator(item)
mutator.locked = true mutator.locked = true
const payload = mutator.getResult() const payload = mutator.getResult()
@@ -77,7 +74,7 @@ describe('item mutator', () => {
}) })
it('mutate set protected', function () { it('mutate set protected', function () {
const item = this.createNote() const item = createNote()
const mutator = new DecryptedItemMutator(item) const mutator = new DecryptedItemMutator(item)
mutator.protected = true mutator.protected = true
const payload = mutator.getResult() const payload = mutator.getResult()
@@ -86,7 +83,7 @@ describe('item mutator', () => {
}) })
it('mutate set trashed', function () { it('mutate set trashed', function () {
const item = this.createNote() const item = createNote()
const mutator = new DecryptedItemMutator(item) const mutator = new DecryptedItemMutator(item)
mutator.trashed = true mutator.trashed = true
const payload = mutator.getResult() const payload = mutator.getResult()
@@ -95,7 +92,7 @@ describe('item mutator', () => {
}) })
it('calling get result should set us dirty', function () { it('calling get result should set us dirty', function () {
const item = this.createNote() const item = createNote()
const mutator = new DecryptedItemMutator(item) const mutator = new DecryptedItemMutator(item)
const payload = mutator.getResult() const payload = mutator.getResult()
@@ -103,7 +100,7 @@ describe('item mutator', () => {
}) })
it('get result should always have userModifiedDate', function () { it('get result should always have userModifiedDate', function () {
const item = this.createNote() const item = createNote()
const mutator = new DecryptedItemMutator(item) const mutator = new DecryptedItemMutator(item)
const payload = mutator.getResult() const payload = mutator.getResult()
const resultItem = CreateDecryptedItemFromPayload(payload) const resultItem = CreateDecryptedItemFromPayload(payload)
@@ -111,7 +108,7 @@ describe('item mutator', () => {
}) })
it('mutate set deleted', function () { it('mutate set deleted', function () {
const item = this.createNote() const item = createNote()
const mutator = new DeleteItemMutator(item) const mutator = new DeleteItemMutator(item)
const payload = mutator.getDeletedResult() const payload = mutator.getDeletedResult()
@@ -121,7 +118,7 @@ describe('item mutator', () => {
}) })
it('mutate app data', function () { it('mutate app data', function () {
const item = this.createNote() const item = createNote()
const mutator = new DecryptedItemMutator(item, MutationType.UpdateUserTimestamps) const mutator = new DecryptedItemMutator(item, MutationType.UpdateUserTimestamps)
mutator.setAppDataItem('foo', 'bar') mutator.setAppDataItem('foo', 'bar')
mutator.setAppDataItem('bar', 'foo') mutator.setAppDataItem('bar', 'foo')
@@ -131,8 +128,8 @@ describe('item mutator', () => {
}) })
it('mutate add item as relationship', function () { it('mutate add item as relationship', function () {
const note = this.createNote() const note = createNote()
const tag = this.createTag() const tag = createTag()
const mutator = new DecryptedItemMutator(tag) const mutator = new DecryptedItemMutator(tag)
mutator.e2ePendingRefactor_addItemAsRelationship(note) mutator.e2ePendingRefactor_addItemAsRelationship(note)
const payload = mutator.getResult() const payload = mutator.getResult()
@@ -142,8 +139,8 @@ describe('item mutator', () => {
}) })
it('mutate remove item as relationship', function () { it('mutate remove item as relationship', function () {
const note = this.createNote() const note = createNote()
const tag = this.createTag([note]) const tag = createTag([note])
const mutator = new DecryptedItemMutator(tag) const mutator = new DecryptedItemMutator(tag)
mutator.removeItemAsRelationship(note) mutator.removeItemAsRelationship(note)
const payload = mutator.getResult() const payload = mutator.getResult()

View File

@@ -1,7 +1,6 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
import { BaseItemCounts } from './lib/BaseItemCounts.js' import { BaseItemCounts } from './lib/BaseItemCounts.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
@@ -12,11 +11,6 @@ describe('mutator service', function () {
let application let application
let mutator let mutator
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -27,6 +21,12 @@ describe('mutator service', function () {
await context.launch() await context.launch()
}) })
afterEach(async function () {
await context.deinit()
localStorage.clear()
context = undefined
})
const createNote = async () => { const createNote = async () => {
return mutator.createItem(ContentType.TYPES.Note, { return mutator.createItem(ContentType.TYPES.Note, {
title: 'hello', title: 'hello',
@@ -154,8 +154,10 @@ describe('mutator service', function () {
describe('duplicateItem', async function () { describe('duplicateItem', async function () {
const sandbox = sinon.createSandbox() const sandbox = sinon.createSandbox()
let emitPayloads
beforeEach(async function () { beforeEach(async function () {
this.emitPayloads = sandbox.spy(application.payloads, 'emitPayloads') emitPayloads = sandbox.spy(application.payloads, 'emitPayloads')
}) })
afterEach(async function () { afterEach(async function () {
@@ -165,7 +167,7 @@ describe('mutator service', function () {
it('should duplicate the item and set the duplicate_of property', async function () { it('should duplicate the item and set the duplicate_of property', async function () {
const note = await createNote() const note = await createNote()
await mutator.duplicateItem(note) await mutator.duplicateItem(note)
sinon.assert.calledTwice(this.emitPayloads) sinon.assert.calledTwice(emitPayloads)
const originalNote = application.items.getDisplayableNotes()[0] const originalNote = application.items.getDisplayableNotes()[0]
const duplicatedNote = application.items.getDisplayableNotes()[1] 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 () { it('should duplicate the item and set the duplicate_of and conflict_of properties', async function () {
const note = await createNote() const note = await createNote()
await mutator.duplicateItem(note, true) await mutator.duplicateItem(note, true)
sinon.assert.calledTwice(this.emitPayloads) sinon.assert.calledTwice(emitPayloads)
const originalNote = application.items.getDisplayableNotes()[0] const originalNote = application.items.getDisplayableNotes()[0]
const duplicatedNote = application.items.getDisplayableNotes()[1] const duplicatedNote = application.items.getDisplayableNotes()[1]

View File

@@ -1,30 +1,36 @@
/* eslint-disable no-undef */
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('note display criteria', function () { describe('note display criteria', function () {
let payloadManager
let itemManager
let mutator
let createNote
let createTag
beforeEach(async function () { beforeEach(async function () {
const logger = new Logger('test') const logger = new Logger('test')
this.payloadManager = new PayloadManager(logger) payloadManager = new PayloadManager(logger)
this.itemManager = new ItemManager(this.payloadManager) itemManager = new ItemManager(payloadManager)
this.mutator = new MutatorService(this.itemManager, this.payloadManager) mutator = new MutatorService(itemManager, payloadManager)
this.createNote = async (title = 'hello', text = 'world') => { createNote = async (title = 'hello', text = 'world') => {
return this.mutator.createItem(ContentType.TYPES.Note, { return mutator.createItem(ContentType.TYPES.Note, {
title: title, title: title,
text: text, text: text,
}) })
} }
this.createTag = async (notes = [], title = 'thoughts') => { createTag = async (notes = [], title = 'thoughts') => {
const references = notes.map((note) => { const references = notes.map((note) => {
return { return {
uuid: note.uuid, uuid: note.uuid,
content_type: note.content_type, content_type: note.content_type,
} }
}) })
return this.mutator.createItem(ContentType.TYPES.Tag, { return mutator.createItem(ContentType.TYPES.Tag, {
title: title, title: title,
references: references, references: references,
}) })
@@ -32,177 +38,147 @@ describe('note display criteria', function () {
}) })
it('includePinned off', async function () { it('includePinned off', async function () {
await this.createNote() await createNote()
const pendingPin = await this.createNote() const pendingPin = await createNote()
await this.mutator.changeItem(pendingPin, (mutator) => { await mutator.changeItem(pendingPin, (mutator) => {
mutator.pinned = true mutator.pinned = true
}) })
const criteria = { const criteria = {
includePinned: false, includePinned: false,
} }
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection)
criteria, .length,
this.itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection,
).length,
).to.equal(1) ).to.equal(1)
}) })
it('includePinned on', async function () { it('includePinned on', async function () {
await this.createNote() await createNote()
const pendingPin = await this.createNote() const pendingPin = await createNote()
await this.mutator.changeItem(pendingPin, (mutator) => { await mutator.changeItem(pendingPin, (mutator) => {
mutator.pinned = true mutator.pinned = true
}) })
const criteria = { includePinned: true } const criteria = { includePinned: true }
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection)
criteria, .length,
this.itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection,
).length,
).to.equal(2) ).to.equal(2)
}) })
it('includeTrashed off', async function () { it('includeTrashed off', async function () {
await this.createNote() await createNote()
const pendingTrash = await this.createNote() const pendingTrash = await createNote()
await this.mutator.changeItem(pendingTrash, (mutator) => { await mutator.changeItem(pendingTrash, (mutator) => {
mutator.trashed = true mutator.trashed = true
}) })
const criteria = { includeTrashed: false } const criteria = { includeTrashed: false }
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection)
criteria, .length,
this.itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection,
).length,
).to.equal(1) ).to.equal(1)
}) })
it('includeTrashed on', async function () { it('includeTrashed on', async function () {
await this.createNote() await createNote()
const pendingTrash = await this.createNote() const pendingTrash = await createNote()
await this.mutator.changeItem(pendingTrash, (mutator) => { await mutator.changeItem(pendingTrash, (mutator) => {
mutator.trashed = true mutator.trashed = true
}) })
const criteria = { includeTrashed: true } const criteria = { includeTrashed: true }
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection)
criteria, .length,
this.itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection,
).length,
).to.equal(2) ).to.equal(2)
}) })
it('includeArchived off', async function () { it('includeArchived off', async function () {
await this.createNote() await createNote()
const pendingArchive = await this.createNote() const pendingArchive = await createNote()
await this.mutator.changeItem(pendingArchive, (mutator) => { await mutator.changeItem(pendingArchive, (mutator) => {
mutator.archived = true mutator.archived = true
}) })
const criteria = { includeArchived: false } const criteria = { includeArchived: false }
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection)
criteria, .length,
this.itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection,
).length,
).to.equal(1) ).to.equal(1)
}) })
it('includeArchived on', async function () { it('includeArchived on', async function () {
await this.createNote() await createNote()
const pendingArchive = await this.createNote() const pendingArchive = await createNote()
await this.mutator.changeItem(pendingArchive, (mutator) => { await mutator.changeItem(pendingArchive, (mutator) => {
mutator.archived = true mutator.archived = true
}) })
const criteria = { const criteria = {
includeArchived: true, includeArchived: true,
} }
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection)
criteria, .length,
this.itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection,
).length,
).to.equal(2) ).to.equal(2)
}) })
it('includeProtected off', async function () { it('includeProtected off', async function () {
await this.createNote() await createNote()
const pendingProtected = await this.createNote() const pendingProtected = await createNote()
await this.mutator.changeItem(pendingProtected, (mutator) => { await mutator.changeItem(pendingProtected, (mutator) => {
mutator.protected = true mutator.protected = true
}) })
const criteria = { includeProtected: false } const criteria = { includeProtected: false }
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection)
criteria, .length,
this.itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection,
).length,
).to.equal(1) ).to.equal(1)
}) })
it('includeProtected on', async function () { it('includeProtected on', async function () {
await this.createNote() await createNote()
const pendingProtected = await this.createNote() const pendingProtected = await createNote()
await this.mutator.changeItem(pendingProtected, (mutator) => { await mutator.changeItem(pendingProtected, (mutator) => {
mutator.protected = true mutator.protected = true
}) })
const criteria = { const criteria = {
includeProtected: true, includeProtected: true,
} }
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection)
criteria, .length,
this.itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection,
).length,
).to.equal(2) ).to.equal(2)
}) })
it('protectedSearchEnabled false', async function () { it('protectedSearchEnabled false', async function () {
const normal = await this.createNote('hello', 'world') const normal = await createNote('hello', 'world')
await this.mutator.changeItem(normal, (mutator) => { await mutator.changeItem(normal, (mutator) => {
mutator.protected = true mutator.protected = true
}) })
const criteria = { const criteria = {
searchQuery: { query: 'world', includeProtectedNoteText: false }, searchQuery: { query: 'world', includeProtectedNoteText: false },
} }
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection)
criteria, .length,
this.itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection,
).length,
).to.equal(0) ).to.equal(0)
}) })
it('protectedSearchEnabled true', async function () { it('protectedSearchEnabled true', async function () {
const normal = await this.createNote() const normal = await createNote()
await this.mutator.changeItem(normal, (mutator) => { await mutator.changeItem(normal, (mutator) => {
mutator.protected = true mutator.protected = true
}) })
const criteria = { const criteria = {
searchQuery: { query: 'world', includeProtectedNoteText: true }, searchQuery: { query: 'world', includeProtectedNoteText: true },
} }
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(criteria, itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection)
criteria, .length,
this.itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection,
).length,
).to.equal(1) ).to.equal(1)
}) })
it('tags', async function () { it('tags', async function () {
const note = await this.createNote() const note = await createNote()
const tag = await this.createTag([note]) const tag = await createTag([note])
const looseTag = await this.createTag([], 'loose') const looseTag = await createTag([], 'loose')
const matchingCriteria = { const matchingCriteria = {
tags: [tag], tags: [tag],
@@ -210,8 +186,8 @@ describe('note display criteria', function () {
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
matchingCriteria, matchingCriteria,
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
@@ -221,125 +197,125 @@ describe('note display criteria', function () {
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
nonmatchingCriteria, nonmatchingCriteria,
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
}) })
describe('smart views', function () { describe('smart views', function () {
it('normal note', async function () { it('normal note', async function () {
await this.createNote() await createNote()
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.archivedSmartView], views: [itemManager.archivedSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
}) })
it('trashed note', async function () { 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 mutator.trashed = true
}) })
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeTrashed: false, includeTrashed: false,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.archivedSmartView], views: [itemManager.archivedSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
}) })
it('archived note', async function () { it('archived 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 = false mutator.trashed = false
mutator.archived = true mutator.archived = true
}) })
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeArchived: false, includeArchived: false,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.archivedSmartView], views: [itemManager.archivedSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
}) })
it('archived + trashed note', async function () { 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.trashed = true
mutator.archived = true mutator.archived = true
}) })
@@ -347,30 +323,30 @@ describe('note display criteria', function () {
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.archivedSmartView], views: [itemManager.archivedSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
}) })
@@ -378,87 +354,87 @@ describe('note display criteria', function () {
describe('includeTrash', function () { describe('includeTrash', function () {
it('normal note', async function () { it('normal note', async function () {
await this.createNote() await createNote()
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeTrashed: true, includeTrashed: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
includeTrashed: true, includeTrashed: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
}) })
it('trashed note', async function () { 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 mutator.trashed = true
}) })
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeTrashed: false, includeTrashed: false,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeTrashed: true, includeTrashed: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
includeTrashed: true, includeTrashed: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.archivedSmartView], views: [itemManager.archivedSmartView],
includeTrashed: true, includeTrashed: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
}) })
it('archived + trashed note', async function () { 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.trashed = true
mutator.archived = true mutator.archived = true
}) })
@@ -466,30 +442,30 @@ describe('note display criteria', function () {
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.archivedSmartView], views: [itemManager.archivedSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
}) })
@@ -497,85 +473,85 @@ describe('note display criteria', function () {
describe('includeArchived', function () { describe('includeArchived', function () {
it('normal note', async function () { it('normal note', async function () {
await this.createNote() await createNote()
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
}) })
it('archived note', async function () { it('archived note', async function () {
const normal = await this.createNote() const normal = await createNote()
await this.mutator.changeItem(normal, (mutator) => { await mutator.changeItem(normal, (mutator) => {
mutator.archived = true mutator.archived = true
}) })
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeArchived: false, includeArchived: false,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.archivedSmartView], views: [itemManager.archivedSmartView],
includeArchived: false, includeArchived: false,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
}) })
it('archived + trashed note', async function () { 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.trashed = true
mutator.archived = true mutator.archived = true
}) })
@@ -583,33 +559,33 @@ describe('note display criteria', function () {
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.archivedSmartView], views: [itemManager.archivedSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
}) })
@@ -617,7 +593,7 @@ describe('note display criteria', function () {
describe.skip('multiple tags', function () { describe.skip('multiple tags', function () {
it('normal note', async function () { it('normal note', async function () {
await this.createNote() await createNote()
/** /**
* This test presently fails because the compound predicate created * This test presently fails because the compound predicate created
* when using multiple views is an AND predicate instead of OR * when using multiple views is an AND predicate instead of OR
@@ -625,82 +601,78 @@ describe('note display criteria', function () {
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [ views: [itemManager.allNotesSmartView, itemManager.archivedSmartView, itemManager.trashSmartView],
this.itemManager.allNotesSmartView,
this.itemManager.archivedSmartView,
this.itemManager.trashSmartView,
],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
}) })
it('archived note', async function () { it('archived note', async function () {
const normal = await this.createNote() const normal = await createNote()
await this.mutator.changeItem(normal, (mutator) => { await mutator.changeItem(normal, (mutator) => {
mutator.archived = true mutator.archived = true
}) })
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeArchived: false, includeArchived: false,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.archivedSmartView], views: [itemManager.archivedSmartView],
includeArchived: false, includeArchived: false,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
}) })
it('archived + trashed note', async function () { 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.trashed = true
mutator.archived = true mutator.archived = true
}) })
@@ -708,33 +680,33 @@ describe('note display criteria', function () {
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.allNotesSmartView], views: [itemManager.allNotesSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.trashSmartView], views: [itemManager.trashSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(1) ).to.equal(1)
expect( expect(
notesAndFilesMatchingOptions( notesAndFilesMatchingOptions(
{ {
views: [this.itemManager.archivedSmartView], views: [itemManager.archivedSmartView],
includeArchived: true, includeArchived: true,
}, },
this.itemManager.collection.all(ContentType.TYPES.Note), itemManager.collection.all(ContentType.TYPES.Note),
this.itemManager.collection, itemManager.collection,
).length, ).length,
).to.equal(0) ).to.equal(0)
}) })

View File

@@ -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' import * as Factory from './lib/factory.js'
describe('payload', () => { chai.use(chaiAsPromised)
beforeEach(async function () { const expect = chai.expect
this.createBarePayload = () => {
return new DecryptedPayload({
uuid: '123',
content_type: ContentType.TYPES.Note,
content: {
title: 'hello',
},
})
}
this.createEncryptedPayload = () => { describe('payload', () => {
return new EncryptedPayload({ const createBarePayload = () => {
uuid: '123', return new DecryptedPayload({
content_type: ContentType.TYPES.Note, uuid: '123',
content: '004:foo:bar', 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 () { it('constructor should set expected fields', function () {
const payload = this.createBarePayload() const payload = createBarePayload()
expect(payload.uuid).to.be.ok expect(payload.uuid).to.be.ok
expect(payload.content_type).to.be.ok expect(payload.content_type).to.be.ok
@@ -46,25 +43,25 @@ describe('payload', () => {
}) })
it('created at should default to present', function () { 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) expect(payload.created_at - new Date()).to.be.below(1)
}) })
it('updated at should default to epoch', function () { it('updated at should default to epoch', function () {
const payload = this.createBarePayload() const payload = createBarePayload()
expect(payload.updated_at.getTime()).to.equal(0) expect(payload.updated_at.getTime()).to.equal(0)
}) })
it('payload format bare', function () { it('payload format bare', function () {
const payload = this.createBarePayload() const payload = createBarePayload()
expect(isDecryptedPayload(payload)).to.equal(true) expect(isDecryptedPayload(payload)).to.equal(true)
}) })
it('payload format encrypted string', function () { it('payload format encrypted string', function () {
const payload = this.createEncryptedPayload() const payload = createEncryptedPayload()
expect(isEncryptedPayload(payload)).to.equal(true) expect(isEncryptedPayload(payload)).to.equal(true)
}) })
@@ -92,13 +89,13 @@ describe('payload', () => {
}) })
it('payload version 004', function () { it('payload version 004', function () {
const payload = this.createEncryptedPayload() const payload = createEncryptedPayload()
expect(payload.version).to.equal('004') expect(payload.version).to.equal('004')
}) })
it('merged with absent content', function () { it('merged with absent content', function () {
const payload = this.createBarePayload() const payload = createBarePayload()
const merged = payload.copy({ const merged = payload.copy({
uuid: '123', uuid: '123',
content_type: ContentType.TYPES.Note, content_type: ContentType.TYPES.Note,
@@ -125,7 +122,7 @@ describe('payload', () => {
}) })
it('should be immutable', async function () { it('should be immutable', async function () {
const payload = this.createBarePayload() const payload = createBarePayload()
await Factory.sleep(0.1) await Factory.sleep(0.1)

View File

@@ -1,31 +1,33 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
import { createRelatedNoteTagPairPayload } from './lib/Items.js' import { createRelatedNoteTagPairPayload } from './lib/Items.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('payload encryption', function () { describe('payload encryption', function () {
let application
beforeEach(async function () { beforeEach(async function () {
this.timeout(Factory.TenSecondTimeout) this.timeout(Factory.TenSecondTimeout)
localStorage.clear() localStorage.clear()
this.application = await Factory.createInitAppWithFakeCrypto() application = await Factory.createInitAppWithFakeCrypto()
}) })
afterEach(async function () { afterEach(async function () {
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
localStorage.clear() localStorage.clear()
application = undefined
}) })
it('creating payload from item should create copy not by reference', async function () { 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()) const payload = new DecryptedPayload(item.payload.ejected())
expect(item.content === payload.content).to.equal(false) expect(item.content === payload.content).to.equal(false)
expect(item.content.references === payload.content.references).to.equal(false) expect(item.content.references === payload.content.references).to.equal(false)
}) })
it('creating payload from item should preserve appData', async function () { 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()) const payload = new DecryptedPayload(item.payload.ejected())
expect(item.content.appData).to.be.ok expect(item.content.appData).to.be.ok
expect(JSON.stringify(item.content)).to.equal(JSON.stringify(payload.content)) expect(JSON.stringify(item.content)).to.equal(JSON.stringify(payload.content))
@@ -40,7 +42,7 @@ describe('payload encryption', function () {
lastSyncBegan: new Date(), lastSyncBegan: new Date(),
}) })
const encryptedPayload = await this.application.encryption.encryptSplitSingle({ const encryptedPayload = await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [notePayload], items: [notePayload],
}, },
@@ -85,7 +87,7 @@ describe('payload encryption', function () {
}) })
it('copying payload with override content should override completely', async 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 payload = new DecryptedPayload(item.payload.ejected())
const mutated = new DecryptedPayload({ const mutated = new DecryptedPayload({
...payload, ...payload,
@@ -114,7 +116,7 @@ describe('payload encryption', function () {
it('returns valid encrypted params for syncing', async function () { it('returns valid encrypted params for syncing', async function () {
const payload = Factory.createNotePayload() const payload = Factory.createNotePayload()
const encryptedPayload = CreateEncryptedServerSyncPushPayload( const encryptedPayload = CreateEncryptedServerSyncPushPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
@@ -126,7 +128,7 @@ describe('payload encryption', function () {
expect(encryptedPayload.content_type).to.be.ok expect(encryptedPayload.content_type).to.be.ok
expect(encryptedPayload.created_at).to.be.ok expect(encryptedPayload.created_at).to.be.ok
expect(encryptedPayload.content).to.satisfy((string) => { expect(encryptedPayload.content).to.satisfy((string) => {
return string.startsWith(this.application.encryption.getLatestVersion()) return string.startsWith(application.encryption.getLatestVersion())
}) })
}).timeout(5000) }).timeout(5000)
@@ -134,7 +136,7 @@ describe('payload encryption', function () {
const payload = Factory.createNotePayload() const payload = Factory.createNotePayload()
const encryptedPayload = CreateEncryptedLocalStorageContextPayload( const encryptedPayload = CreateEncryptedLocalStorageContextPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
@@ -150,14 +152,14 @@ describe('payload encryption', function () {
expect(encryptedPayload.deleted).to.not.be.ok expect(encryptedPayload.deleted).to.not.be.ok
expect(encryptedPayload.errorDecrypting).to.not.be.ok expect(encryptedPayload.errorDecrypting).to.not.be.ok
expect(encryptedPayload.content).to.satisfy((string) => { 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 () { it('omits deleted for export file', async function () {
const payload = Factory.createNotePayload() const payload = Factory.createNotePayload()
const encryptedPayload = CreateEncryptedBackupFileContextPayload( const encryptedPayload = CreateEncryptedBackupFileContextPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
@@ -170,7 +172,7 @@ describe('payload encryption', function () {
expect(encryptedPayload.created_at).to.be.ok expect(encryptedPayload.created_at).to.be.ok
expect(encryptedPayload.deleted).to.not.be.ok expect(encryptedPayload.deleted).to.not.be.ok
expect(encryptedPayload.content).to.satisfy((string) => { expect(encryptedPayload.content).to.satisfy((string) => {
return string.startsWith(this.application.encryption.getLatestVersion()) return string.startsWith(application.encryption.getLatestVersion())
}) })
}) })

View File

@@ -1,14 +1,17 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('payload manager', () => { describe('payload manager', () => {
let payloadManager
let createNotePayload
beforeEach(async function () { beforeEach(async function () {
const logger = new Logger('test') const logger = new Logger('test')
this.payloadManager = new PayloadManager(logger) payloadManager = new PayloadManager(logger)
this.createNotePayload = async () => {
createNotePayload = async () => {
return new DecryptedPayload({ return new DecryptedPayload({
uuid: Factory.generateUuidish(), uuid: Factory.generateUuidish(),
content_type: ContentType.TYPES.Note, content_type: ContentType.TYPES.Note,
@@ -21,15 +24,15 @@ describe('payload manager', () => {
}) })
it('emit payload should create local record', async function () { it('emit payload should create local record', async function () {
const payload = await this.createNotePayload() const payload = await createNotePayload()
await this.payloadManager.emitPayload(payload) 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 () { it('merge payloads onto master', async function () {
const payload = await this.createNotePayload() const payload = await createNotePayload()
await this.payloadManager.emitPayload(payload) await payloadManager.emitPayload(payload)
const newTitle = `${Math.random()}` const newTitle = `${Math.random()}`
const changedPayload = payload.copy({ const changedPayload = payload.copy({
@@ -38,19 +41,19 @@ describe('payload manager', () => {
title: newTitle, title: newTitle,
}, },
}) })
const { changed, inserted } = await this.payloadManager.applyPayloads([changedPayload]) const { changed, inserted } = await payloadManager.applyPayloads([changedPayload])
expect(changed.length).to.equal(1) expect(changed.length).to.equal(1)
expect(inserted.length).to.equal(0) 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 () { it('insertion observer', async function () {
const observations = [] const observations = []
this.payloadManager.addObserver(ContentType.TYPES.Any, ({ inserted }) => { payloadManager.addObserver(ContentType.TYPES.Any, ({ inserted }) => {
observations.push({ inserted }) observations.push({ inserted })
}) })
const payload = await this.createNotePayload() const payload = await createNotePayload()
await this.payloadManager.emitPayload(payload) await payloadManager.emitPayload(payload)
expect(observations.length).equal(1) expect(observations.length).equal(1)
expect(observations[0].inserted[0]).equal(payload) expect(observations[0].inserted[0]).equal(payload)
@@ -58,14 +61,14 @@ describe('payload manager', () => {
it('change observer', async function () { it('change observer', async function () {
const observations = [] const observations = []
this.payloadManager.addObserver(ContentType.TYPES.Any, ({ changed }) => { payloadManager.addObserver(ContentType.TYPES.Any, ({ changed }) => {
if (changed.length > 0) { if (changed.length > 0) {
observations.push({ changed }) observations.push({ changed })
} }
}) })
const payload = await this.createNotePayload() const payload = await createNotePayload()
await this.payloadManager.emitPayload(payload) await payloadManager.emitPayload(payload)
await this.payloadManager.emitPayload( await payloadManager.emitPayload(
payload.copy({ payload.copy({
content: { content: {
...payload.content, ...payload.content,
@@ -79,12 +82,12 @@ describe('payload manager', () => {
}) })
it('reset state', async function () { it('reset state', async function () {
this.payloadManager.addObserver(ContentType.TYPES.Any, ({}) => {}) payloadManager.addObserver(ContentType.TYPES.Any, ({}) => {})
const payload = await this.createNotePayload() const payload = await createNotePayload()
await this.payloadManager.emitPayload(payload) await payloadManager.emitPayload(payload)
await this.payloadManager.resetState() await payloadManager.resetState()
expect(this.payloadManager.collection.all().length).to.equal(0) expect(payloadManager.collection.all().length).to.equal(0)
expect(this.payloadManager.changeObservers.length).equal(1) expect(payloadManager.changeObservers.length).equal(1)
}) })
}) })

View File

@@ -1,111 +1,116 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('preferences', function () { describe('preferences', function () {
let application
let email
let password
let context
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
this.context = await Factory.createAppContext() context = await Factory.createAppContext()
await this.context.launch() await context.launch()
this.application = this.context.application application = context.application
this.email = UuidGenerator.GenerateUuid() email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid() password = UuidGenerator.GenerateUuid()
}) })
afterEach(async function () { afterEach(async function () {
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
localStorage.clear() localStorage.clear()
context = undefined
}) })
function register() { function register() {
return Factory.registerUserToApplication({ return Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
} }
it('sets preference', async function () { it('sets preference', async function () {
await this.application.setPreference('editorLeft', 300) await application.setPreference('editorLeft', 300)
expect(this.application.getPreference('editorLeft')).to.equal(300) expect(application.getPreference('editorLeft')).to.equal(300)
}) })
it('saves preference', async function () { it('saves preference', async function () {
await register.call(this) await register.call(this)
await this.application.setPreference('editorLeft', 300) await application.setPreference('editorLeft', 300)
await this.application.sync.sync() await application.sync.sync()
this.application = await Factory.signOutAndBackIn(this.application, this.email, this.password) application = await Factory.signOutAndBackIn(application, email, password)
const editorLeft = this.application.getPreference('editorLeft') const editorLeft = application.getPreference('editorLeft')
expect(editorLeft).to.equal(300) expect(editorLeft).to.equal(300)
}).timeout(10000) }).timeout(10000)
it('clears preferences on signout', async function () { it('clears preferences on signout', async function () {
await register.call(this) await register.call(this)
await this.application.setPreference('editorLeft', 300) await application.setPreference('editorLeft', 300)
await this.application.sync.sync() await application.sync.sync()
this.application = await Factory.signOutApplicationAndReturnNew(this.application) application = await Factory.signOutApplicationAndReturnNew(application)
expect(this.application.getPreference('editorLeft')).to.equal(undefined) expect(application.getPreference('editorLeft')).to.equal(undefined)
}) })
it('returns default value for non-existent preference', async function () { it('returns default value for non-existent preference', async function () {
await register.call(this) await register.call(this)
const editorLeft = this.application.getPreference('editorLeft', 100) const editorLeft = application.getPreference('editorLeft', 100)
expect(editorLeft).to.equal(100) expect(editorLeft).to.equal(100)
}) })
it('emits an event when preferences change', async function () { it('emits an event when preferences change', async function () {
const promise = new Promise((resolve) => { const promise = new Promise((resolve) => {
this.application.addEventObserver(() => { application.addEventObserver(() => {
resolve() resolve()
}, ApplicationEvent.PreferencesChanged) }, ApplicationEvent.PreferencesChanged)
}) })
await this.application.setPreference('editorLeft', 300) await application.setPreference('editorLeft', 300)
await promise await promise
expect(promise).to.be.fulfilled expect(promise).to.be.fulfilled
}) })
it('discards existing preferences when signing in', async function () { it('discards existing preferences when signing in', async function () {
await register.call(this) await register.call(this)
await this.application.setPreference('editorLeft', 300) await application.setPreference('editorLeft', 300)
await this.application.sync.sync() await application.sync.sync()
this.application = await this.context.signout() application = await context.signout()
await this.application.setPreference('editorLeft', 200) await application.setPreference('editorLeft', 200)
await this.application.signIn(this.email, this.password) await application.signIn(email, password)
const promise = this.context.awaitUserPrefsSingletonResolution() const promise = context.awaitUserPrefsSingletonResolution()
await this.application.sync.sync({ awaitAll: true }) await application.sync.sync({ awaitAll: true })
await promise await promise
const editorLeft = this.application.getPreference('editorLeft') const editorLeft = application.getPreference('editorLeft')
expect(editorLeft).to.equal(300) expect(editorLeft).to.equal(300)
}) })
it('reads stored preferences on start without waiting for syncing to complete', async function () { it('reads stored preferences on start without waiting for syncing to complete', async function () {
const prefKey = 'editorLeft' const prefKey = 'editorLeft'
const prefValue = 300 const prefValue = 300
const identifier = this.application.identifier const identifier = application.identifier
await register.call(this) await register.call(this)
await this.application.setPreference(prefKey, prefValue) await application.setPreference(prefKey, prefValue)
await this.application.sync.sync() 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) => { 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 await willSyncPromise
expect(this.application.preferences.preferences).to.exist expect(application.preferences.preferences).to.exist
expect(this.application.getPreference(prefKey)).to.equal(prefValue) expect(application.getPreference(prefKey)).to.equal(prefValue)
}) })
}) })

View File

@@ -1,6 +1,6 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect

View File

@@ -1,61 +1,60 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('protocol', function () { describe('protocol', function () {
let application
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
this.application = await Factory.createInitAppWithFakeCrypto() application = await Factory.createInitAppWithFakeCrypto()
this.email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid()
}) })
afterEach(async function () { afterEach(async function () {
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
this.application = null application = null
localStorage.clear() localStorage.clear()
}) })
it('checks version to make sure its 004', function () { 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 () { 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 () { it('platform derivation support', function () {
expect( expect(
this.application.encryption.platformSupportsKeyDerivation({ application.encryption.platformSupportsKeyDerivation({
version: '001', version: '001',
}), }),
).to.equal(true) ).to.equal(true)
expect( expect(
this.application.encryption.platformSupportsKeyDerivation({ application.encryption.platformSupportsKeyDerivation({
version: '002', version: '002',
}), }),
).to.equal(true) ).to.equal(true)
expect( expect(
this.application.encryption.platformSupportsKeyDerivation({ application.encryption.platformSupportsKeyDerivation({
version: '003', version: '003',
}), }),
).to.equal(true) ).to.equal(true)
expect( expect(
this.application.encryption.platformSupportsKeyDerivation({ application.encryption.platformSupportsKeyDerivation({
version: '004', version: '004',
}), }),
).to.equal(true) ).to.equal(true)
expect( expect(
this.application.encryption.platformSupportsKeyDerivation({ application.encryption.platformSupportsKeyDerivation({
version: '005', version: '005',
}), }),
).to.equal(true) ).to.equal(true)
}) })
it('key params versions <= 002 should include pw_cost in portable value', function () { 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', version: '002',
pw_cost: 5000, pw_cost: 5000,
}) })
@@ -63,15 +62,15 @@ describe('protocol', function () {
}) })
it('version comparison of 002 should be older than library version', 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 () { 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 () { 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) expect(isProtocolVersionExpired(currentVersion)).to.equal(false)
}) })
@@ -91,7 +90,7 @@ describe('protocol', function () {
const payload = Factory.createNotePayload() const payload = Factory.createNotePayload()
let error let error
try { try {
await this.application.encryption.decryptSplitSingle({ await application.encryption.decryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
@@ -103,10 +102,10 @@ describe('protocol', function () {
}) })
it('ejected payload should not have meta fields', async 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 payload = Factory.createNotePayload()
const result = CreateEncryptedServerSyncPushPayload( const result = CreateEncryptedServerSyncPushPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
@@ -121,7 +120,7 @@ describe('protocol', function () {
it('encrypted payload for server should include duplicate_of field', async function () { it('encrypted payload for server should include duplicate_of field', async function () {
const payload = Factory.createNotePayload('Test') const payload = Factory.createNotePayload('Test')
const encryptedPayload = CreateEncryptedServerSyncPushPayload( const encryptedPayload = CreateEncryptedServerSyncPushPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
@@ -134,7 +133,7 @@ describe('protocol', function () {
it('ejected payload for server should include duplicate_of field', async function () { it('ejected payload for server should include duplicate_of field', async function () {
const payload = Factory.createNotePayload('Test') const payload = Factory.createNotePayload('Test')
const encryptedPayload = CreateEncryptedServerSyncPushPayload( const encryptedPayload = CreateEncryptedServerSyncPushPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
@@ -147,7 +146,7 @@ describe('protocol', function () {
it('encrypted payload for storage should include duplicate_of field', async function () { it('encrypted payload for storage should include duplicate_of field', async function () {
const payload = Factory.createNotePayload('Test') const payload = Factory.createNotePayload('Test')
const encryptedPayload = CreateEncryptedLocalStorageContextPayload( const encryptedPayload = CreateEncryptedLocalStorageContextPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
@@ -160,7 +159,7 @@ describe('protocol', function () {
it('ejected payload for storage should include duplicate_of field', async function () { it('ejected payload for storage should include duplicate_of field', async function () {
const payload = Factory.createNotePayload('Test') const payload = Factory.createNotePayload('Test')
const encryptedPayload = CreateEncryptedLocalStorageContextPayload( const encryptedPayload = CreateEncryptedLocalStorageContextPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
@@ -173,7 +172,7 @@ describe('protocol', function () {
it('encrypted payload for file should include duplicate_of field', async function () { it('encrypted payload for file should include duplicate_of field', async function () {
const payload = Factory.createNotePayload('Test') const payload = Factory.createNotePayload('Test')
const encryptedPayload = CreateEncryptedBackupFileContextPayload( const encryptedPayload = CreateEncryptedBackupFileContextPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },
@@ -186,7 +185,7 @@ describe('protocol', function () {
it('ejected payload for file should include duplicate_of field', async function () { it('ejected payload for file should include duplicate_of field', async function () {
const payload = Factory.createNotePayload('Test') const payload = Factory.createNotePayload('Test')
const encryptedPayload = CreateEncryptedBackupFileContextPayload( const encryptedPayload = CreateEncryptedBackupFileContextPayload(
await this.application.encryption.encryptSplitSingle({ await application.encryption.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {
items: [payload], items: [payload],
}, },

View File

@@ -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 * as Factory from './lib/factory.js'
import WebDeviceInterface from './lib/web_device_interface.js' import WebDeviceInterface from './lib/web_device_interface.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('server session', function () { describe('server session', function () {
this.timeout(Factory.TenSecondTimeout) this.timeout(Factory.TenSecondTimeout)
let application
let email
let password
let newPassword
const syncOptions = { const syncOptions = {
checkIntegrity: true, checkIntegrity: true,
awaitAll: true, awaitAll: true,
@@ -16,16 +19,15 @@ describe('server session', function () {
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
this.expectedItemCount = BaseItemCounts.DefaultItems application = await Factory.createInitAppWithFakeCrypto()
this.application = await Factory.createInitAppWithFakeCrypto() email = UuidGenerator.GenerateUuid()
this.email = UuidGenerator.GenerateUuid() password = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid() newPassword = Factory.randomString()
this.newPassword = Factory.randomString()
}) })
afterEach(async function () { afterEach(async function () {
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
this.application = null application = null
localStorage.clear() 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 () { it('should succeed when a sync request is perfomed with an expired access token', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, 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) expect(response.status).to.equal(200)
}).timeout(Factory.TwentySecondTimeout) }).timeout(Factory.TwentySecondTimeout)
it('should return the new session in the response when refreshed', async function () { it('should return the new session in the response when refreshed', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
const response = await this.application.legacyApi.refreshSession() const response = await application.legacyApi.refreshSession()
expect(response.status).to.equal(200) expect(response.status).to.equal(200)
expect(response.data.session.access_token).to.be.a('string') 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 () { it('should be refreshed on any api call if access token is expired', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
// Saving the current session information for later... // 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. // 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. // 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. // 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.accessToken.value).to.not.equal(sessionAfterSync.accessToken.value)
expect(sessionBeforeSync.refreshToken.value).to.not.equal(sessionAfterSync.refreshToken.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 () { it('should not deadlock while renewing session', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, 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 // 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. // where runHttp does not await a pending refreshSession promise if the request being made is itself a refreshSession request.
this.application.http.__latencySimulatorMs = 1000 application.http.__latencySimulatorMs = 1000
await this.application.sync.sync(syncOptions) 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()) expect(sessionAfterSync.accessToken.expiresAt).to.be.greaterThan(Date.now())
}).timeout(Factory.TwentySecondTimeout) }).timeout(Factory.TwentySecondTimeout)
it('should succeed when a sync request is perfomed after signing into an ephemeral session', async function () { it('should succeed when a sync request is perfomed after signing into an ephemeral session', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, 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) expect(response.status).to.equal(200)
}) })
it('should succeed when a sync request is perfomed after registering into an ephemeral session', async function () { it('should succeed when a sync request is perfomed after registering into an ephemeral session', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
ephemeral: true, ephemeral: true,
}) })
const response = await this.application.legacyApi.sync([]) const response = await application.legacyApi.sync([])
expect(response.status).to.equal(200) expect(response.status).to.equal(200)
}) })
it('should be consistent between storage and apiService', async function () { it('should be consistent between storage and apiService', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
const sessionFromStorage = await getSessionFromStorage(this.application) const sessionFromStorage = await getSessionFromStorage(application)
const sessionFromApiService = this.application.legacyApi.getSession() const sessionFromApiService = application.legacyApi.getSession()
expect(sessionFromStorage.accessToken).to.equal(sessionFromApiService.accessToken.value) expect(sessionFromStorage.accessToken).to.equal(sessionFromApiService.accessToken.value)
expect(sessionFromStorage.refreshToken).to.equal(sessionFromApiService.refreshToken.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.refreshExpiration).to.equal(sessionFromApiService.refreshToken.expiresAt)
expect(sessionFromStorage.readonlyAccess).to.equal(sessionFromApiService.isReadOnly()) expect(sessionFromStorage.readonlyAccess).to.equal(sessionFromApiService.isReadOnly())
await this.application.legacyApi.refreshSession() await application.legacyApi.refreshSession()
const updatedSessionFromStorage = await getSessionFromStorage(this.application) const updatedSessionFromStorage = await getSessionFromStorage(application)
const updatedSessionFromApiService = this.application.legacyApi.getSession() const updatedSessionFromApiService = application.legacyApi.getSession()
expect(updatedSessionFromStorage.accessToken).to.equal(updatedSessionFromApiService.accessToken.value) expect(updatedSessionFromStorage.accessToken).to.equal(updatedSessionFromApiService.accessToken.value)
expect(updatedSessionFromStorage.refreshToken).to.equal(updatedSessionFromApiService.refreshToken.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 () { it('should be performed successfully and terminate session with a valid access token', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
const signOutResponse = await this.application.legacyApi.signOut() const signOutResponse = await application.legacyApi.signOut()
expect(signOutResponse.status).to.equal(204) expect(signOutResponse.status).to.equal(204)
Factory.ignoreChallenges(this.application) Factory.ignoreChallenges(application)
const syncResponse = await this.application.legacyApi.sync([]) const syncResponse = await application.legacyApi.sync([])
expect(syncResponse.status).to.equal(401) expect(syncResponse.status).to.equal(401)
expect(syncResponse.data.error.tag).to.equal('invalid-auth') expect(syncResponse.data.error.tag).to.equal('invalid-auth')
expect(syncResponse.data.error.message).to.equal('Invalid login credentials.') 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 () { it('sign out request should be performed successfully and terminate session with expired access token', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
// Waiting enough time for the access token to expire, before performing a sign out request. // 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) expect(signOutResponse.status).to.equal(204)
Factory.ignoreChallenges(this.application) Factory.ignoreChallenges(application)
const syncResponse = await this.application.legacyApi.sync([]) const syncResponse = await application.legacyApi.sync([])
expect(syncResponse.status).to.equal(401) expect(syncResponse.status).to.equal(401)
expect(syncResponse.data.error.tag).to.equal('invalid-auth') expect(syncResponse.data.error.tag).to.equal('invalid-auth')
expect(syncResponse.data.error.message).to.equal('Invalid login credentials.') 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 () { it('change password request should be successful with a valid access token', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, 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 expect(changePasswordResponse.error).to.not.be.ok
this.application = await Factory.signOutApplicationAndReturnNew(this.application) application = await Factory.signOutApplicationAndReturnNew(application)
const loginResponse = await Factory.loginToApplication({ const loginResponse = await Factory.loginToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.newPassword, password: newPassword,
}) })
expect(loginResponse).to.be.ok 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 () { it('change password request should be successful after the expired access token is refreshed', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
// Waiting enough time for the access token to expire. // 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 expect(changePasswordResponse.error).to.not.be.ok
this.application = await Factory.signOutApplicationAndReturnNew(this.application) application = await Factory.signOutApplicationAndReturnNew(application)
const loginResponse = await Factory.loginToApplication({ const loginResponse = await Factory.loginToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.newPassword, password: newPassword,
}) })
expect(loginResponse).to.be.ok 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 () { it('change password request should fail with an invalid access token', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
this.application.storage.setValue(StorageKey.Session, { application.storage.setValue(StorageKey.Session, {
accessToken: 'this-is-a-fake-token-1234', accessToken: 'this-is-a-fake-token-1234',
refreshToken: 'this-is-a-fake-token-1234', refreshToken: 'this-is-a-fake-token-1234',
accessExpiration: 999999999999999, accessExpiration: 999999999999999,
refreshExpiration: 99999999999999, refreshExpiration: 99999999999999,
readonlyAccess: false, readonlyAccess: false,
}) })
this.application.sessions.initializeFromDisk() application.sessions.initializeFromDisk()
Factory.ignoreChallenges(this.application) Factory.ignoreChallenges(application)
const changePasswordResponse = await this.application.changePassword(this.password, this.newPassword) const changePasswordResponse = await application.changePassword(password, newPassword)
expect(changePasswordResponse.error.message).to.equal('Invalid login credentials.') expect(changePasswordResponse.error.message).to.equal('Invalid login credentials.')
}) })
it('change password request should fail with an expired refresh token', async function () { it('change password request should fail with an expired refresh token', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
this.application.sync.lockSyncing() application.sync.lockSyncing()
/** Waiting for the refresh token to expire. */ /** Waiting for the refresh token to expire. */
await sleepUntilSessionExpires(this.application, false) await sleepUntilSessionExpires(application, false)
Factory.ignoreChallenges(this.application) Factory.ignoreChallenges(application)
const changePasswordResponse = await this.application.changePassword(this.password, this.newPassword) const changePasswordResponse = await application.changePassword(password, newPassword)
expect(changePasswordResponse).to.be.ok expect(changePasswordResponse).to.be.ok
expect(changePasswordResponse.error.message).to.equal('Invalid login credentials.') 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 () { it('should sign in successfully after signing out', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
await this.application.legacyApi.signOut() await application.legacyApi.signOut()
this.application.legacyApi.session = undefined 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).to.be.ok
expect(currentSession.accessToken).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 () { it('should fail when renewing a session with an expired refresh token', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, 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.status).to.equal(400)
expect(refreshSessionResponse.data.error.tag).to.equal('expired-refresh-token') 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. The access token and refresh token should be expired up to this point.
Here we make sure that any subsequent requests will fail. Here we make sure that any subsequent requests will fail.
*/ */
Factory.ignoreChallenges(this.application) Factory.ignoreChallenges(application)
const syncResponse = await this.application.legacyApi.sync([]) const syncResponse = await application.legacyApi.sync([])
expect(syncResponse.status).to.equal(401) expect(syncResponse.status).to.equal(401)
expect(syncResponse.data.error.tag).to.equal('invalid-auth') expect(syncResponse.data.error.tag).to.equal('invalid-auth')
expect(syncResponse.data.error.message).to.equal('Invalid login credentials.') 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 () { it('should fail when renewing a session with an invalid refresh token', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, 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, accessToken: originalSession.accessToken.value,
refreshToken: 'this-is-a-fake-token-1234', refreshToken: 'this-is-a-fake-token-1234',
accessExpiration: originalSession.accessToken.expiresAt, accessExpiration: originalSession.accessToken.expiresAt,
refreshExpiration: originalSession.refreshToken.expiresAt, refreshExpiration: originalSession.refreshToken.expiresAt,
readonlyAccess: false, 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.status).to.equal(400)
expect(refreshSessionResponse.data.error.tag).to.equal('invalid-refresh-token') expect(refreshSessionResponse.data.error.tag).to.equal('invalid-refresh-token')
expect(refreshSessionResponse.data.error.message).to.equal('The refresh token is not valid.') expect(refreshSessionResponse.data.error.message).to.equal('The refresh token is not valid.')
// Access token should remain 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) expect(syncResponse.status).to.equal(200)
}) })
it('should fail if syncing while a session refresh is in progress', async function () { it('should fail if syncing while a session refresh is in progress', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
const refreshPromise = this.application.legacyApi.refreshSession() const refreshPromise = application.legacyApi.refreshSession()
const syncResponse = await this.application.legacyApi.sync([]) const syncResponse = await application.legacyApi.sync([])
expect(syncResponse.data.error).to.be.ok 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 () { it('notes should be synced as expected after refreshing a session', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
const notesBeforeSync = await Factory.createManyMappedNotes(this.application, 5) const notesBeforeSync = await Factory.createManyMappedNotes(application, 5)
await sleepUntilSessionExpires(this.application) await sleepUntilSessionExpires(application)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
expect(this.application.sync.isOutOfSync()).to.equal(false) expect(application.sync.isOutOfSync()).to.equal(false)
this.application = await Factory.signOutApplicationAndReturnNew(this.application) application = await Factory.signOutApplicationAndReturnNew(application)
await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) await application.signIn(email, password, undefined, undefined, undefined, true)
const expectedNotesUuids = notesBeforeSync.map((n) => n.uuid) 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) expect(notesResults.length).to.equal(notesBeforeSync.length)
for (const aNoteBeforeSync of notesBeforeSync) { 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) expect(aNoteBeforeSync.isItemContentEqualWith(noteResult)).to.equal(true)
} }
}).timeout(Factory.TwentySecondTimeout) }).timeout(Factory.TwentySecondTimeout)
it('should prompt user for account password and sign back in on invalid session', async function () { it('should prompt user for account password and sign back in on invalid session', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
const email = `${Math.random()}` email = `${Math.random()}`
const password = `${Math.random()}` password = `${Math.random()}`
let didPromptForSignIn = false let didPromptForSignIn = false
const receiveChallenge = async (challenge) => { const receiveChallenge = async (challenge) => {
didPromptForSignIn = true didPromptForSignIn = true
@@ -541,55 +543,55 @@ describe('server session', function () {
it('should return current session in list of sessions', async function () { it('should return current session in list of sessions', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
const response = await this.application.legacyApi.getSessionsList() const response = await application.legacyApi.getSessionsList()
expect(response.data[0].current).to.equal(true) expect(response.data[0].current).to.equal(true)
}) })
it('signing out should delete session from all list', async function () { it('signing out should delete session from all list', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
/** Create new session aside from existing one */ /** Create new session aside from existing one */
const app2 = await Factory.createAndInitializeApplication('app2') 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) expect(response.data.length).to.equal(2)
await app2.user.signOut() await app2.user.signOut()
const response2 = await this.application.legacyApi.getSessionsList() const response2 = await application.legacyApi.getSessionsList()
expect(response2.data.length).to.equal(1) expect(response2.data.length).to.equal(1)
}) })
it('revoking a session should destroy local data', async function () { it('revoking a session should destroy local data', async function () {
Factory.handlePasswordChallenges(this.application, this.password) Factory.handlePasswordChallenges(application, password)
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
const app2identifier = 'app2' const app2identifier = 'app2'
const app2 = await Factory.createAndInitializeApplication(app2identifier) const app2 = await Factory.createAndInitializeApplication(app2identifier)
await app2.signIn(this.email, this.password) await app2.signIn(email, password)
const app2Deinit = new Promise((resolve) => { const app2Deinit = new Promise((resolve) => {
app2.setOnDeinit(() => { app2.setOnDeinit(() => {
resolve() resolve()
}) })
}) })
const { data: sessions } = await this.application.getSessions() const { data: sessions } = await application.getSessions()
const app2session = sessions.find((session) => !session.current) const app2session = sessions.find((session) => !session.current)
await this.application.revokeSession(app2session.uuid) await application.revokeSession(app2session.uuid)
void app2.sync.sync() void app2.sync.sync()
await app2Deinit await app2Deinit
@@ -599,23 +601,23 @@ describe('server session', function () {
}).timeout(Factory.TwentySecondTimeout) }).timeout(Factory.TwentySecondTimeout)
it('revoking other sessions should destroy their local data', async function () { it('revoking other sessions should destroy their local data', async function () {
Factory.handlePasswordChallenges(this.application, this.password) Factory.handlePasswordChallenges(application, password)
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
const app2identifier = 'app2' const app2identifier = 'app2'
const app2 = await Factory.createAndInitializeApplication(app2identifier) const app2 = await Factory.createAndInitializeApplication(app2identifier)
await app2.signIn(this.email, this.password) await app2.signIn(email, password)
const app2Deinit = new Promise((resolve) => { const app2Deinit = new Promise((resolve) => {
app2.setOnDeinit(() => { app2.setOnDeinit(() => {
resolve() resolve()
}) })
}) })
await this.application.revokeAllOtherSessions() await application.revokeAllOtherSessions()
void app2.sync.sync() void app2.sync.sync()
await app2Deinit await app2Deinit
@@ -626,24 +628,24 @@ describe('server session', function () {
it('signing out with invalid session token should still delete local data', async function () { it('signing out with invalid session token should still delete local data', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
this.application.storage.setValue(StorageKey.Session, { application.storage.setValue(StorageKey.Session, {
accessToken: undefined, accessToken: undefined,
refreshToken: undefined, refreshToken: undefined,
accessExpiration: 999999999999999, accessExpiration: 999999999999999,
refreshExpiration: 999999999999999, refreshExpiration: 999999999999999,
readonlyAccess: false, 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 expect(localStorage.getItem(storageKey)).to.be.ok
await this.application.user.signOut() await application.user.signOut()
expect(localStorage.getItem(storageKey)).to.not.be.ok expect(localStorage.getItem(storageKey)).to.not.be.ok
}) })
}) })

View File

@@ -30,6 +30,11 @@ describe('settings service', function () {
}) })
}) })
afterEach(async function () {
await Factory.safeDeinit(application)
localStorage.clear()
})
const reInitializeApplicationWithRealCrypto = async () => { const reInitializeApplicationWithRealCrypto = async () => {
await Factory.safeDeinit(application) 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 () { it('creates and reads a setting', async function () {
await application.settings.updateSetting(validSetting, fakePayload) await application.settings.updateSetting(validSetting, fakePayload)
const responseCreate = await application.settings.getSetting(validSetting) const responseCreate = await application.settings.getSetting(validSetting)

View File

@@ -1,8 +1,6 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import { BaseItemCounts } from './lib/BaseItemCounts.js' import { BaseItemCounts } from './lib/BaseItemCounts.js'
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
import WebDeviceInterface from './lib/web_device_interface.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
@@ -28,48 +26,60 @@ describe('singletons', function () {
return context.singletons.findOrCreateContentTypeSingleton(ContentType.TYPES.UserPrefs, FillItemContent({})) 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 () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
this.expectedItemCount = BaseItemCounts.DefaultItems expectedItemCount = BaseItemCounts.DefaultItems
this.context = await Factory.createAppContext() context = await Factory.createAppContext()
await this.context.launch() await context.launch()
this.application = this.context.application application = context.application
this.email = UuidGenerator.GenerateUuid() email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid() password = UuidGenerator.GenerateUuid()
this.registerUser = async () => { registerUser = async () => {
this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount expectedItemCount = BaseItemCounts.DefaultItemsWithAccount
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
} }
this.signOut = async () => { signOut = async () => {
this.application = await this.context.signout() application = await context.signout()
} }
this.signIn = async () => { signIn = async () => {
await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) 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('content_type', '=', ContentType.TYPES.Component),
new Predicate('package_info.identifier', '=', this.extManagerId), new Predicate('package_info.identifier', '=', extManagerId),
]) ])
this.createExtMgr = () => { createExtMgr = () => {
return this.application.mutator.createItem( return application.mutator.createItem(
ContentType.TYPES.Component, ContentType.TYPES.Component,
{ {
package_info: { package_info: {
name: 'Extensions', name: 'Extensions',
identifier: this.extManagerId, identifier: extManagerId,
}, },
}, },
true, true,
@@ -78,13 +88,13 @@ describe('singletons', function () {
}) })
afterEach(async function () { afterEach(async function () {
expect(this.application.sync.isOutOfSync()).to.equal(false) expect(application.sync.isOutOfSync()).to.equal(false)
expect(this.application.items.items.length).to.equal(this.expectedItemCount) expect(application.items.items.length).to.equal(expectedItemCount)
const rawPayloads = await this.application.storage.getAllRawPayloads() const rawPayloads = await application.storage.getAllRawPayloads()
expect(rawPayloads.length).to.equal(this.expectedItemCount) expect(rawPayloads.length).to.equal(expectedItemCount)
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
localStorage.clear() localStorage.clear()
}) })
@@ -95,99 +105,99 @@ describe('singletons', function () {
const prefs2 = createPrefsPayload() const prefs2 = createPrefsPayload()
const prefs3 = createPrefsPayload() const prefs3 = createPrefsPayload()
const items = await this.application.mutator.emitItemsFromPayloads( const items = await application.mutator.emitItemsFromPayloads(
[prefs1, prefs2, prefs3], [prefs1, prefs2, prefs3],
PayloadEmitSource.LocalChanged, PayloadEmitSource.LocalChanged,
) )
await this.application.mutator.setItemsDirty(items) await application.mutator.setItemsDirty(items)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
expect(this.application.items.items.length).to.equal(this.expectedItemCount) expect(application.items.items.length).to.equal(expectedItemCount)
}) })
it('duplicate components should auto-resolve to 1', async function () { it('duplicate components should auto-resolve to 1', async function () {
const extManager = await this.createExtMgr() const extManager = await createExtMgr()
this.expectedItemCount += 1 expectedItemCount += 1
/** Call needlessly */ /** Call needlessly */
await this.createExtMgr() await createExtMgr()
await this.createExtMgr() await createExtMgr()
await this.createExtMgr() await createExtMgr()
expect(extManager).to.be.ok 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 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 () { it('resolves via find or create', async function () {
/* Set to never synced as singleton manager will attempt to sync before resolving */ /* Set to never synced as singleton manager will attempt to sync before resolving */
this.application.sync.ut_clearLastSyncDate() application.sync.ut_clearLastSyncDate()
this.application.sync.ut_setDatabaseLoaded(false) application.sync.ut_setDatabaseLoaded(false)
const contentType = ContentType.TYPES.UserPrefs const contentType = ContentType.TYPES.UserPrefs
const predicate = new Predicate('content_type', '=', contentType) const predicate = new Predicate('content_type', '=', contentType)
/* Start a sync right after we await singleton resolve below */ /* Start a sync right after we await singleton resolve below */
setTimeout(() => { setTimeout(() => {
this.application.sync.ut_setDatabaseLoaded(true) application.sync.ut_setDatabaseLoaded(true)
this.application.sync.sync({ application.sync.sync({
/* Simulate the first sync occuring as that is handled specially by sync service */ /* Simulate the first sync occuring as that is handled specially by sync service */
mode: SyncMode.DownloadFirst, mode: SyncMode.DownloadFirst,
}) })
}) })
const userPreferences = await this.context.singletons.findOrCreateContentTypeSingleton(contentType, {}) const userPreferences = await context.singletons.findOrCreateContentTypeSingleton(contentType, {})
expect(userPreferences).to.be.ok 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 expect(refreshedUserPrefs).to.be.ok
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
expect(this.application.items.itemsMatchingPredicate(contentType, predicate).length).to.equal(1) expect(application.items.itemsMatchingPredicate(contentType, predicate).length).to.equal(1)
}) })
it('resolves registered predicate with signing in/out', async function () { it('resolves registered predicate with signing in/out', async function () {
await this.registerUser() await registerUser()
await this.signOut() await signOut()
this.email = UuidGenerator.GenerateUuid() email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid() password = UuidGenerator.GenerateUuid()
await this.createExtMgr() await createExtMgr()
this.expectedItemCount += 1 expectedItemCount += 1
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, 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 await extraSync
}).timeout(15000) }).timeout(15000)
it('singletons that are deleted after download first sync should not sync to server', async function () { it('singletons that are deleted after download first sync should not sync to server', async function () {
await this.registerUser() await registerUser()
await this.createExtMgr() await createExtMgr()
await this.createExtMgr() await createExtMgr()
await this.createExtMgr() await createExtMgr()
this.expectedItemCount++ expectedItemCount++
let didCompleteRelevantSync = false let didCompleteRelevantSync = false
let beginCheckingResponse = false let beginCheckingResponse = false
this.application.sync.addEventObserver(async (eventName, data) => { application.sync.addEventObserver(async (eventName, data) => {
if (eventName === SyncEvent.DownloadFirstSyncCompleted) { if (eventName === SyncEvent.DownloadFirstSyncCompleted) {
beginCheckingResponse = true beginCheckingResponse = true
} }
@@ -202,59 +212,59 @@ describe('singletons', function () {
expect(matching).to.not.be.ok 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) expect(didCompleteRelevantSync).to.equal(true)
}).timeout(10000) }).timeout(10000)
it('signing into account and retrieving singleton shouldnt put us in deadlock', async function () { it('signing into account and retrieving singleton shouldnt put us in deadlock', async function () {
await this.registerUser() await registerUser()
/** Create prefs */ /** Create prefs */
const ogPrefs = await findOrCreatePrefsSingleton(this.context) const ogPrefs = await findOrCreatePrefsSingleton(context)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
this.application = await this.context.signout() application = await context.signout()
/** Create another instance while signed out */ /** Create another instance while signed out */
await findOrCreatePrefsSingleton(this.context) await findOrCreatePrefsSingleton(context)
await Factory.loginToApplication({ await Factory.loginToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
/** After signing in, the instance retrieved from the server should be the one kept */ /** 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) 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) expect(allPrefs.length).to.equal(1)
}) })
it('resolving singleton before first sync, then signing in, should result in correct number of instances', async function () { 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 */ /** Create prefs and associate them with account */
const ogPrefs = await findOrCreatePrefsSingleton(this.context) const ogPrefs = await findOrCreatePrefsSingleton(context)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
this.application = await this.context.signout() application = await context.signout()
/** Create another instance while signed out */ /** Create another instance while signed out */
await findOrCreatePrefsSingleton(this.context) await findOrCreatePrefsSingleton(context)
await Factory.loginToApplication({ await Factory.loginToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
/** After signing in, the instance retrieved from the server should be the one kept */ /** 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) 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) expect(allPrefs.length).to.equal(1)
}) })
it('if only result is errorDecrypting, create new item', async function () { 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({ const erroredPayload = new EncryptedPayload({
...item.payload.ejected(), ...item.payload.ejected(),
@@ -262,13 +272,13 @@ describe('singletons', function () {
errorDecrypting: true, 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.uuid).to.not.equal(item.uuid)
expect(resolvedItem.errorDecrypting).to.not.be.ok expect(resolvedItem.errorDecrypting).to.not.be.ok
}) })
@@ -284,13 +294,13 @@ describe('singletons', function () {
const sharedContent = { const sharedContent = {
package_info: { package_info: {
name: 'Extensions', name: 'Extensions',
identifier: this.extManagerId, identifier: extManagerId,
}, },
} }
const errorDecryptingFalse = false const errorDecryptingFalse = false
await Factory.insertItemWithOverride( await Factory.insertItemWithOverride(
this.application, application,
ContentType.TYPES.Component, ContentType.TYPES.Component,
sharedContent, sharedContent,
true, true,
@@ -299,16 +309,16 @@ describe('singletons', function () {
const errorDecryptingTrue = true const errorDecryptingTrue = true
const errored = await Factory.insertItemWithOverride( const errored = await Factory.insertItemWithOverride(
this.application, application,
ContentType.TYPES.Component, ContentType.TYPES.Component,
sharedContent, sharedContent,
true, true,
errorDecryptingTrue, 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 */ /** Now mark errored as not errorDecrypting and sync */
const notErrored = new DecryptedPayload({ const notErrored = new DecryptedPayload({
@@ -317,34 +327,31 @@ describe('singletons', function () {
errorDecrypting: false, 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 */ /** Item will get decrypted on current tick, so wait one before syncing */
await Factory.sleep(0) 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 () { it('alternating the uuid of a singleton should return correct result', async function () {
const payload = createPrefsPayload() const payload = createPrefsPayload()
const item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) const item = await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
const predicate = new Predicate('content_type', '=', item.content_type) const predicate = new Predicate('content_type', '=', item.content_type)
let resolvedItem = await this.context.singletons.findOrCreateContentTypeSingleton( let resolvedItem = await context.singletons.findOrCreateContentTypeSingleton(payload.content_type, payload.content)
payload.content_type,
payload.content,
)
const originalUuid = resolvedItem.uuid const originalUuid = resolvedItem.uuid
await Factory.alternateUuidForItem(this.application, resolvedItem.uuid) await Factory.alternateUuidForItem(application, resolvedItem.uuid)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
const resolvedItem2 = await this.context.singletons.findOrCreateContentTypeSingleton( const resolvedItem2 = await context.singletons.findOrCreateContentTypeSingleton(
payload.content_type, payload.content_type,
payload.content, payload.content,
) )
resolvedItem = this.application.items.findItem(resolvedItem.uuid) resolvedItem = application.items.findItem(resolvedItem.uuid)
expect(resolvedItem).to.not.be.ok expect(resolvedItem).to.not.be.ok
expect(resolvedItem2.uuid).to.not.equal(originalUuid) 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)
}) })
}) })

View File

@@ -1,7 +1,6 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import { BaseItemCounts } from './lib/BaseItemCounts.js' import { BaseItemCounts } from './lib/BaseItemCounts.js'
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
@@ -13,128 +12,137 @@ describe('storage manager', function () {
*/ */
const BASE_KEY_COUNT = ['storage', 'snjs_version', 'keychain'].length const BASE_KEY_COUNT = ['storage', 'snjs_version', 'keychain'].length
let application
let email
let password
let expectedKeyCount
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
this.expectedKeyCount = BASE_KEY_COUNT expectedKeyCount = BASE_KEY_COUNT
this.context = await Factory.createAppContext() context = await Factory.createAppContext()
await this.context.launch() await context.launch()
this.application = this.context.application application = context.application
this.email = UuidGenerator.GenerateUuid() email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid() password = UuidGenerator.GenerateUuid()
}) })
afterEach(async function () { afterEach(async function () {
await Factory.safeDeinit(this.application) await context.deinit()
application = undefined
context = undefined
localStorage.clear() localStorage.clear()
}) })
it('should set and retrieve values', async function () { it('should set and retrieve values', async function () {
const key = 'foo' const key = 'foo'
const value = 'bar' const value = 'bar'
await this.application.storage.setValue(key, value) await application.storage.setValue(key, value)
expect(await this.application.storage.getValue(key)).to.eql(value) expect(await application.storage.getValue(key)).to.eql(value)
}) })
it('should set and retrieve items', async function () { it('should set and retrieve items', async function () {
const payload = Factory.createNotePayload() const payload = Factory.createNotePayload()
await this.application.storage.savePayload(payload) await application.storage.savePayload(payload)
const payloads = await this.application.storage.getAllRawPayloads() const payloads = await application.storage.getAllRawPayloads()
expect(payloads.length).to.equal(BaseItemCounts.DefaultItems + 1) expect(payloads.length).to.equal(BaseItemCounts.DefaultItems + 1)
}) })
it('should clear values', async function () { it('should clear values', async function () {
const key = 'foo' const key = 'foo'
const value = 'bar' const value = 'bar'
await this.application.storage.setValue(key, value) await application.storage.setValue(key, value)
await this.application.storage.clearAllData() await application.storage.clearAllData()
expect(await this.application.storage.getValue(key)).to.not.be.ok expect(await application.storage.getValue(key)).to.not.be.ok
}) })
it('serverPassword should not be saved to keychain', async function () { it('serverPassword should not be saved to keychain', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
ephemeral: false, 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.masterKey).to.be.ok
expect(keychainValue.serverPassword).to.not.be.ok expect(keychainValue.serverPassword).to.not.be.ok
}) })
it('regular session should persist data', async function () { it('regular session should persist data', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
ephemeral: false, ephemeral: false,
}) })
const key = 'foo' const key = 'foo'
const value = 'bar' 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) expect(Object.keys(localStorage).length).to.equal(expectedKeyCount + BaseItemCounts.DefaultItemsWithAccount)
const retrievedValue = await this.application.storage.getValue(key) const retrievedValue = await application.storage.getValue(key)
expect(retrievedValue).to.equal(value) expect(retrievedValue).to.equal(value)
}) })
it('ephemeral session should not persist data', async function () { it('ephemeral session should not persist data', async function () {
this.retries(2) this.retries(2)
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
ephemeral: true, ephemeral: true,
}) })
const key = 'foo' const key = 'foo'
const value = 'bar' const value = 'bar'
await this.application.storage.setValueAndAwaitPersist(key, value) await application.storage.setValueAndAwaitPersist(key, value)
const expectedKeys = ['keychain'] const expectedKeys = ['keychain']
expect(Object.keys(localStorage).length).to.equal(expectedKeys.length) 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) expect(retrievedValue).to.equal(value)
}) })
it('ephemeral session should not persist to database', async function () { it('ephemeral session should not persist to database', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
ephemeral: true, ephemeral: true,
}) })
await Factory.createSyncedNote(this.application) await Factory.createSyncedNote(application)
const rawPayloads = await this.application.storage.getAllRawPayloads() const rawPayloads = await application.storage.getAllRawPayloads()
expect(rawPayloads.length).to.equal(0) expect(rawPayloads.length).to.equal(0)
}) })
it('storage with no account and no passcode should not be encrypted', async function () { it('storage with no account and no passcode should not be encrypted', async function () {
await this.application.storage.setValueAndAwaitPersist('foo', 'bar') await application.storage.setValueAndAwaitPersist('foo', 'bar')
const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] const wrappedValue = application.storage.values[ValueModesKeys.Wrapped]
const payload = new DecryptedPayload(wrappedValue) const payload = new DecryptedPayload(wrappedValue)
expect(payload.content).to.be.an.instanceof(Object) expect(payload.content).to.be.an.instanceof(Object)
}) })
it('storage aftering adding passcode should be encrypted', async function () { it('storage aftering adding passcode should be encrypted', async function () {
await this.application.storage.setValueAndAwaitPersist('foo', 'bar') await application.storage.setValueAndAwaitPersist('foo', 'bar')
await this.application.addPasscode('123') await application.addPasscode('123')
const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] const wrappedValue = application.storage.values[ValueModesKeys.Wrapped]
const payload = new EncryptedPayload(wrappedValue) const payload = new EncryptedPayload(wrappedValue)
expect(payload.content).to.be.a('string') expect(payload.content).to.be.a('string')
}) })
it('storage after adding passcode then removing passcode should not be encrypted', async function () { it('storage after adding passcode then removing passcode should not be encrypted', async function () {
const passcode = '123' const passcode = '123'
Factory.handlePasswordChallenges(this.application, passcode) Factory.handlePasswordChallenges(application, passcode)
await this.application.storage.setValueAndAwaitPersist('foo', 'bar') await application.storage.setValueAndAwaitPersist('foo', 'bar')
await this.application.addPasscode(passcode) await application.addPasscode(passcode)
await this.application.storage.setValueAndAwaitPersist('bar', 'foo') await application.storage.setValueAndAwaitPersist('bar', 'foo')
await this.application.removePasscode() await application.removePasscode()
const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] const wrappedValue = application.storage.values[ValueModesKeys.Wrapped]
const payload = new DecryptedPayload(wrappedValue) const payload = new DecryptedPayload(wrappedValue)
expect(payload.content).to.be.an.instanceof(Object) 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. * the account keys to be moved to the keychain.
* */ * */
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
}) })
expect(await this.application.device.getNamespacedKeychainValue(this.application.identifier)).to.be.ok expect(await application.device.getNamespacedKeychainValue(application.identifier)).to.be.ok
await this.application.storage.setValueAndAwaitPersist('foo', 'bar') await application.storage.setValueAndAwaitPersist('foo', 'bar')
Factory.handlePasswordChallenges(this.application, this.password) Factory.handlePasswordChallenges(application, password)
await this.application.addPasscode(passcode) await application.addPasscode(passcode)
expect(await this.application.device.getNamespacedKeychainValue(this.application.identifier)).to.not.be.ok expect(await application.device.getNamespacedKeychainValue(application.identifier)).to.not.be.ok
await this.application.storage.setValueAndAwaitPersist('bar', 'foo') await application.storage.setValueAndAwaitPersist('bar', 'foo')
Factory.handlePasswordChallenges(this.application, passcode) Factory.handlePasswordChallenges(application, passcode)
await this.application.removePasscode() await application.removePasscode()
expect(await this.application.device.getNamespacedKeychainValue(this.application.identifier)).to.be.ok 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) const payload = new EncryptedPayload(wrappedValue)
expect(payload.content).to.be.a('string') expect(payload.content).to.be.a('string')
}) })
it('adding account should encrypt storage with account keys', async function () { 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({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
ephemeral: true, ephemeral: true,
}) })
const accountKey = await this.application.encryption.getRootKey() const accountKey = await application.encryption.getRootKey()
expect(await this.application.storage.canDecryptWithKey(accountKey)).to.equal(true) expect(await application.storage.canDecryptWithKey(accountKey)).to.equal(true)
}) })
it('signing out of account should decrypt storage', async function () { 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({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
ephemeral: true, ephemeral: true,
}) })
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
await this.application.storage.setValueAndAwaitPersist('bar', 'foo') application = await Factory.signOutApplicationAndReturnNew(application)
const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped]
await application.storage.setValueAndAwaitPersist('bar', 'foo')
const wrappedValue = application.storage.values[ValueModesKeys.Wrapped]
const payload = new DecryptedPayload(wrappedValue) const payload = new DecryptedPayload(wrappedValue)
expect(payload.content).to.be.an.instanceof(Object) 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 () { 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 */ /** 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({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
ephemeral: true, ephemeral: true,
}) })
/** Should not be wrapped root key yet */ /** 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' const passcode = '123'
Factory.handlePasswordChallenges(this.application, this.password) Factory.handlePasswordChallenges(application, password)
await this.application.addPasscode(passcode) await application.addPasscode(passcode)
await this.application.storage.setValueAndAwaitPersist('bar', 'foo') await application.storage.setValueAndAwaitPersist('bar', 'foo')
/** Root key should now be wrapped */ /** 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() const accountKey = await application.encryption.getRootKey()
expect(await this.application.storage.canDecryptWithKey(accountKey)).to.equal(true) expect(await application.storage.canDecryptWithKey(accountKey)).to.equal(true)
const passcodeKey = await this.application.encryption.computeWrappingKey(passcode) const passcodeKey = await application.encryption.computeWrappingKey(passcode)
const wrappedRootKey = await this.application.encryption.rootKeyManager.getWrappedRootKey() const wrappedRootKey = await application.encryption.rootKeyManager.getWrappedRootKey()
/** Expect that we can decrypt wrapped root key with passcode key */ /** Expect that we can decrypt wrapped root key with passcode key */
const payload = new EncryptedPayload(wrappedRootKey) const payload = new EncryptedPayload(wrappedRootKey)
const decrypted = await this.application.encryption.decryptSplitSingle({ const decrypted = await application.encryption.decryptSplitSingle({
usesRootKey: { usesRootKey: {
items: [payload], items: [payload],
key: passcodeKey, key: passcodeKey,
@@ -230,9 +243,9 @@ describe('storage manager', function () {
}) })
it('stored payloads should not contain metadata fields', async function () { it('stored payloads should not contain metadata fields', async function () {
await this.application.addPasscode('123') await application.addPasscode('123')
await Factory.createSyncedNote(this.application) await Factory.createSyncedNote(application)
const payloads = await this.application.storage.getAllRawPayloads() const payloads = await application.storage.getAllRawPayloads()
const payload = payloads[0] const payload = payloads[0]
expect(payload.fields).to.not.be.ok expect(payload.fields).to.not.be.ok
expect(payload.source).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 () { it('storing an offline synced payload should not include dirty flag', async function () {
await this.application.addPasscode('123') await application.addPasscode('123')
await Factory.createSyncedNote(this.application) await Factory.createSyncedNote(application)
const payloads = await this.application.storage.getAllRawPayloads() const payloads = await application.storage.getAllRawPayloads()
const payload = payloads[0] const payload = payloads[0]
expect(payload.dirty).to.not.be.ok 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 () { it('storing an online synced payload should not include dirty flag', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
ephemeral: false, ephemeral: false,
}) })
await Factory.createSyncedNote(this.application) await Factory.createSyncedNote(application)
const payloads = await this.application.storage.getAllRawPayloads() const payloads = await application.storage.getAllRawPayloads()
const payload = payloads[0] const payload = payloads[0]
expect(payload.dirty).to.not.be.ok 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 () { it('signing out should clear unwrapped value store', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
ephemeral: false, ephemeral: false,
}) })
this.application = await Factory.signOutApplicationAndReturnNew(this.application) application = await Factory.signOutApplicationAndReturnNew(application)
const values = this.application.storage.values[ValueModesKeys.Unwrapped]
const values = application.storage.values[ValueModesKeys.Unwrapped]
expect(Object.keys(values).length).to.equal(0) expect(Object.keys(values).length).to.equal(0)
await Factory.safeDeinit(application)
}) })
it('signing out should clear payloads', async function () { it('signing out should clear payloads', async function () {
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
ephemeral: false, ephemeral: false,
}) })
await Factory.createSyncedNote(this.application) await Factory.createSyncedNote(application)
expect(await Factory.storagePayloadCount(this.application)).to.equal(BaseItemCounts.DefaultItemsWithAccount + 1) expect(await Factory.storagePayloadCount(application)).to.equal(BaseItemCounts.DefaultItemsWithAccount + 1)
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
application = await Factory.signOutApplicationAndReturnNew(application)
await Factory.sleep(0.1, 'Allow all untrackable singleton syncs to complete') 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)
}) })
}) })

View File

@@ -10,11 +10,6 @@ describe('subscriptions', function () {
let context let context
let subscriptionManager let subscriptionManager
afterEach(async function () {
await Factory.safeDeinit(application)
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -34,6 +29,11 @@ describe('subscriptions', function () {
await context.activatePaidSubscriptionForUser() await context.activatePaidSubscriptionForUser()
}) })
afterEach(async function () {
await Factory.safeDeinit(application)
localStorage.clear()
})
it('should invite a user by email to a shared subscription', async () => { it('should invite a user by email to a shared subscription', async () => {
await subscriptionManager.inviteToSubscription('test@test.te') await subscriptionManager.inviteToSubscription('test@test.te')

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,37 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import { BaseItemCounts } from '../lib/BaseItemCounts.js' import { BaseItemCounts } from '../lib/BaseItemCounts.js'
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('sync integrity', () => { describe('sync integrity', () => {
before(function () { let application
localStorage.clear() let email
}) let password
let expectedItemCount
after(function () {
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount localStorage.clear()
this.application = await Factory.createInitAppWithFakeCrypto() expectedItemCount = BaseItemCounts.DefaultItemsWithAccount
this.email = UuidGenerator.GenerateUuid() application = await Factory.createInitAppWithFakeCrypto()
this.password = UuidGenerator.GenerateUuid() email = UuidGenerator.GenerateUuid()
password = UuidGenerator.GenerateUuid()
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, 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) => { const awaitSyncEventPromise = (application, targetEvent) => {
return new Promise((resolve) => { return new Promise((resolve) => {
application.sync.addEventObserver((event) => { 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 () { it('should detect when out of sync', async function () {
const item = await this.application.mutator.emitItemFromPayload( const item = await application.mutator.emitItemFromPayload(
Factory.createNotePayload(), Factory.createNotePayload(),
PayloadEmitSource.LocalChanged, PayloadEmitSource.LocalChanged,
) )
this.expectedItemCount++ expectedItemCount++
const didEnterOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.EnterOutOfSync) const didEnterOutOfSync = awaitSyncEventPromise(application, SyncEvent.EnterOutOfSync)
await this.application.sync.sync({ checkIntegrity: true }) await application.sync.sync({ checkIntegrity: true })
await this.application.items.removeItemFromMemory(item) await application.items.removeItemFromMemory(item)
await this.application.sync.sync({ checkIntegrity: true, awaitAll: true }) await application.sync.sync({ checkIntegrity: true, awaitAll: true })
await didEnterOutOfSync await didEnterOutOfSync
}) })
it('should self heal after out of sync', async function () { 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(), Factory.createNotePayload(),
PayloadEmitSource.LocalChanged, PayloadEmitSource.LocalChanged,
) )
this.expectedItemCount++ expectedItemCount++
const didEnterOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.EnterOutOfSync) const didEnterOutOfSync = awaitSyncEventPromise(application, SyncEvent.EnterOutOfSync)
const didExitOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.ExitOutOfSync) const didExitOutOfSync = awaitSyncEventPromise(application, SyncEvent.ExitOutOfSync)
await this.application.sync.sync({ checkIntegrity: true }) await application.sync.sync({ checkIntegrity: true })
await this.application.items.removeItemFromMemory(item) await application.items.removeItemFromMemory(item)
await this.application.sync.sync({ checkIntegrity: true, awaitAll: true }) await application.sync.sync({ checkIntegrity: true, awaitAll: true })
await Promise.all([didEnterOutOfSync, didExitOutOfSync]) await Promise.all([didEnterOutOfSync, didExitOutOfSync])
expect(this.application.sync.isOutOfSync()).to.equal(false) expect(application.sync.isOutOfSync()).to.equal(false)
}) })
}) })

View File

@@ -1,11 +1,12 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
import { createRelatedNoteTagPairPayload } from '../lib/Items.js' import { createRelatedNoteTagPairPayload } from '../lib/Items.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('notes + tags syncing', function () { describe('notes + tags syncing', function () {
let application
const syncOptions = { const syncOptions = {
checkIntegrity: true, checkIntegrity: true,
awaitAll: true, awaitAll: true,
@@ -16,30 +17,31 @@ describe('notes + tags syncing', function () {
}) })
beforeEach(async function () { beforeEach(async function () {
this.application = await Factory.createInitAppWithFakeCrypto() application = await Factory.createInitAppWithFakeCrypto()
Factory.disableIntegrityAutoHeal(this.application) Factory.disableIntegrityAutoHeal(application)
const email = UuidGenerator.GenerateUuid() const email = UuidGenerator.GenerateUuid()
const password = UuidGenerator.GenerateUuid() const password = UuidGenerator.GenerateUuid()
await Factory.registerUserToApplication({ await Factory.registerUserToApplication({
application: this.application, application: application,
email, email,
password, password,
}) })
}) })
afterEach(async function () { 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 () { it('syncing an item then downloading it should include items_key_id', async function () {
const note = await Factory.createMappedNote(this.application) const note = await Factory.createMappedNote(application)
await this.application.mutator.setItemDirty(note) await application.mutator.setItemDirty(note)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
await this.application.payloads.resetState() await application.payloads.resetState()
await this.application.items.resetState() await application.items.resetState()
await this.application.sync.clearSyncPositionTokens() await application.sync.clearSyncPositionTokens()
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
const downloadedNote = this.application.items.getDisplayableNotes()[0] const downloadedNote = application.items.getDisplayableNotes()[0]
expect(downloadedNote.items_key_id).to.not.be.ok expect(downloadedNote.items_key_id).to.not.be.ok
// Allow time for waitingForKey // Allow time for waitingForKey
await Factory.sleep(0.1) await Factory.sleep(0.1)
@@ -52,21 +54,21 @@ describe('notes + tags syncing', function () {
const notePayload = pair[0] const notePayload = pair[0]
const tagPayload = pair[1] const tagPayload = pair[1]
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
const note = this.application.items.getItems([ContentType.TYPES.Note])[0] const note = application.items.getItems([ContentType.TYPES.Note])[0]
const tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] const tag = application.items.getItems([ContentType.TYPES.Tag])[0]
expect(this.application.items.getDisplayableNotes().length).to.equal(1) expect(application.items.getDisplayableNotes().length).to.equal(1)
expect(this.application.items.getDisplayableTags().length).to.equal(1) expect(application.items.getDisplayableTags().length).to.equal(1)
for (let i = 0; i < 9; i++) { for (let i = 0; i < 9; i++) {
await this.application.mutator.setItemsDirty([note, tag]) await application.mutator.setItemsDirty([note, tag])
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
this.application.sync.clearSyncPositionTokens() application.sync.clearSyncPositionTokens()
expect(tag.content.references.length).to.equal(1) 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(tag.noteCount).to.equal(1)
expect(this.application.items.getDisplayableNotes().length).to.equal(1) expect(application.items.getDisplayableNotes().length).to.equal(1)
expect(this.application.items.getDisplayableTags().length).to.equal(1) expect(application.items.getDisplayableTags().length).to.equal(1)
console.warn('Waiting 0.1s...') console.warn('Waiting 0.1s...')
await Factory.sleep(0.1) await Factory.sleep(0.1)
} }
@@ -76,59 +78,59 @@ describe('notes + tags syncing', function () {
const pair = createRelatedNoteTagPairPayload() const pair = createRelatedNoteTagPairPayload()
const notePayload = pair[0] const notePayload = pair[0]
const tagPayload = pair[1] const tagPayload = pair[1]
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
const originalNote = this.application.items.getDisplayableNotes()[0] const originalNote = application.items.getDisplayableNotes()[0]
const originalTag = this.application.items.getDisplayableTags()[0] const originalTag = application.items.getDisplayableTags()[0]
await this.application.mutator.setItemsDirty([originalNote, originalTag]) 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.content.references.length).to.equal(1)
expect(originalTag.noteCount).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), // when signing in, all local items are cleared from storage (but kept in memory; to clear desktop logs),
// then resaved with alternated uuids. // then resaved with alternated uuids.
await this.application.storage.clearAllPayloads() await application.storage.clearAllPayloads()
await this.application.sync.markAllItemsAsNeedingSyncAndPersist() await application.sync.markAllItemsAsNeedingSyncAndPersist()
expect(this.application.items.getDisplayableNotes().length).to.equal(1) expect(application.items.getDisplayableNotes().length).to.equal(1)
expect(this.application.items.getDisplayableTags().length).to.equal(1) expect(application.items.getDisplayableTags().length).to.equal(1)
const note = this.application.items.getDisplayableNotes()[0] const note = application.items.getDisplayableNotes()[0]
const tag = this.application.items.getDisplayableTags()[0] const tag = application.items.getDisplayableTags()[0]
expect(tag.content.references.length).to.equal(1) expect(tag.content.references.length).to.equal(1)
expect(note.content.references.length).to.equal(0) expect(note.content.references.length).to.equal(0)
expect(tag.noteCount).to.equal(1) 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 () { it('duplicating a tag should maintian its relationships', async function () {
const pair = createRelatedNoteTagPairPayload() const pair = createRelatedNoteTagPairPayload()
const notePayload = pair[0] const notePayload = pair[0]
const tagPayload = pair[1] const tagPayload = pair[1]
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
let note = this.application.items.getDisplayableNotes()[0] let note = application.items.getDisplayableNotes()[0]
let tag = this.application.items.getDisplayableTags()[0] let tag = application.items.getDisplayableTags()[0]
expect(this.application.items.itemsReferencingItem(note).length).to.equal(1) expect(application.items.itemsReferencingItem(note).length).to.equal(1)
await this.application.mutator.setItemsDirty([note, tag]) await application.mutator.setItemsDirty([note, tag])
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
await this.application.sync.clearSyncPositionTokens() await application.sync.clearSyncPositionTokens()
note = this.application.items.findItem(note.uuid) note = application.items.findItem(note.uuid)
tag = this.application.items.findItem(tag.uuid) tag = application.items.findItem(tag.uuid)
expect(note.dirty).to.equal(false) expect(note.dirty).to.equal(false)
expect(tag.dirty).to.equal(false) expect(tag.dirty).to.equal(false)
expect(this.application.items.getDisplayableNotes().length).to.equal(1) expect(application.items.getDisplayableNotes().length).to.equal(1)
expect(this.application.items.getDisplayableTags().length).to.equal(1) expect(application.items.getDisplayableTags().length).to.equal(1)
await Factory.changePayloadTimeStampAndSync( await Factory.changePayloadTimeStampAndSync(
this.application, application,
tag.payload, tag.payload,
Factory.dateToMicroseconds(Factory.yesterday()), Factory.dateToMicroseconds(Factory.yesterday()),
{ {
@@ -137,13 +139,13 @@ describe('notes + tags syncing', function () {
syncOptions, syncOptions,
) )
tag = this.application.items.findItem(tag.uuid) tag = application.items.findItem(tag.uuid)
// tag should now be conflicted and a copy created // tag should now be conflicted and a copy created
expect(this.application.items.getDisplayableNotes().length).to.equal(1) expect(application.items.getDisplayableNotes().length).to.equal(1)
expect(this.application.items.getDisplayableTags().length).to.equal(2) expect(application.items.getDisplayableTags().length).to.equal(2)
const tags = this.application.items.getDisplayableTags() const tags = application.items.getDisplayableTags()
const conflictedTag = tags.find((tag) => { const conflictedTag = tags.find((tag) => {
return !!tag.content.conflict_of 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.content.conflict_of).to.equal(originalTag.uuid)
expect(conflictedTag.noteCount).to.equal(originalTag.noteCount) expect(conflictedTag.noteCount).to.equal(originalTag.noteCount)
expect(this.application.items.itemsReferencingItem(conflictedTag).length).to.equal(0) expect(application.items.itemsReferencingItem(conflictedTag).length).to.equal(0)
expect(this.application.items.itemsReferencingItem(originalTag).length).to.equal(0) expect(application.items.itemsReferencingItem(originalTag).length).to.equal(0)
// Two tags now link to this note // 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.length).to.equal(2)
expect(referencingItems[0]).to.not.equal(referencingItems[1]) expect(referencingItems[0]).to.not.equal(referencingItems[1])
}).timeout(10000) }).timeout(10000)

View File

@@ -1,71 +1,70 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import { BaseItemCounts } from '../lib/BaseItemCounts.js' import { BaseItemCounts } from '../lib/BaseItemCounts.js'
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('offline syncing', () => { describe('offline syncing', () => {
let context
let application
let expectedItemCount
const syncOptions = { const syncOptions = {
checkIntegrity: true, checkIntegrity: true,
awaitAll: true, awaitAll: true,
} }
beforeEach(async function () { beforeEach(async function () {
this.expectedItemCount = BaseItemCounts.DefaultItems localStorage.clear()
this.context = await Factory.createAppContext() expectedItemCount = BaseItemCounts.DefaultItems
await this.context.launch() context = await Factory.createAppContext()
this.application = this.context.application await context.launch()
application = context.application
}) })
afterEach(async function () { afterEach(async function () {
expect(this.application.sync.isOutOfSync()).to.equal(false) expect(application.sync.isOutOfSync()).to.equal(false)
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
})
before(async function () {
localStorage.clear()
})
after(async function () {
localStorage.clear() localStorage.clear()
application = undefined
context = undefined
}) })
it('uuid alternation should delete original payload', async function () { it('uuid alternation should delete original payload', async function () {
const note = await Factory.createMappedNote(this.application) const note = await Factory.createMappedNote(application)
this.expectedItemCount++ expectedItemCount++
await Factory.alternateUuidForItem(this.application, note.uuid) await Factory.alternateUuidForItem(application, note.uuid)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
const notes = this.application.items.getDisplayableNotes() const notes = application.items.getDisplayableNotes()
expect(notes.length).to.equal(1) expect(notes.length).to.equal(1)
expect(notes[0].uuid).to.not.equal(note.uuid) expect(notes[0].uuid).to.not.equal(note.uuid)
const items = this.application.items.allTrackedItems() const items = application.items.allTrackedItems()
expect(items.length).to.equal(this.expectedItemCount) expect(items.length).to.equal(expectedItemCount)
}) })
it('should sync item with no passcode', async function () { it('should sync item with no passcode', async function () {
let note = await Factory.createMappedNote(this.application) let note = await Factory.createMappedNote(application)
expect(Uuids(this.application.items.getDirtyItems()).includes(note.uuid)) 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. */ /** 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) 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() const rawPayloads2 = await application.storage.getAllRawPayloads()
expect(rawPayloads2.length).to.equal(this.expectedItemCount) expect(rawPayloads2.length).to.equal(expectedItemCount)
const itemsKeyRaw = (await Factory.getStoragePayloadsOfType(this.application, ContentType.TYPES.ItemsKey))[0] const itemsKeyRaw = (await Factory.getStoragePayloadsOfType(application, ContentType.TYPES.ItemsKey))[0]
const noteRaw = (await Factory.getStoragePayloadsOfType(this.application, ContentType.TYPES.Note))[0] const noteRaw = (await Factory.getStoragePayloadsOfType(application, ContentType.TYPES.Note))[0]
/** Encrypts with default items key */ /** Encrypts with default items key */
expect(typeof noteRaw.content).to.equal('string') expect(typeof noteRaw.content).to.equal('string')
@@ -75,30 +74,30 @@ describe('offline syncing', () => {
}) })
it('should sync item encrypted with passcode', async function () { it('should sync item encrypted with passcode', async function () {
await this.application.addPasscode('foobar') await application.addPasscode('foobar')
await Factory.createMappedNote(this.application) await Factory.createMappedNote(application)
expect(this.application.items.getDirtyItems().length).to.equal(1) expect(application.items.getDirtyItems().length).to.equal(1)
const rawPayloads1 = await this.application.storage.getAllRawPayloads() const rawPayloads1 = await application.storage.getAllRawPayloads()
expect(rawPayloads1.length).to.equal(this.expectedItemCount) expect(rawPayloads1.length).to.equal(expectedItemCount)
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
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() const rawPayloads2 = await application.storage.getAllRawPayloads()
expect(rawPayloads2.length).to.equal(this.expectedItemCount) expect(rawPayloads2.length).to.equal(expectedItemCount)
const payload = rawPayloads2[0] const payload = rawPayloads2[0]
expect(typeof payload.content).to.equal('string') 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 () { it('signing out while offline should succeed', async function () {
await Factory.createMappedNote(this.application) await Factory.createMappedNote(application)
this.expectedItemCount++ expectedItemCount++
await this.application.sync.sync(syncOptions) await application.sync.sync(syncOptions)
this.application = await Factory.signOutApplicationAndReturnNew(this.application) application = await Factory.signOutApplicationAndReturnNew(application)
expect(this.application.sessions.isSignedIn()).to.equal(false) expect(application.sessions.isSignedIn()).to.equal(false)
expect(this.application.sessions.getUser()).to.not.be.ok expect(application.sessions.getUser()).to.not.be.ok
}) })
}) })

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +1,49 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
import * as Factory from './lib/factory.js' import * as Factory from './lib/factory.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('upgrading', () => { describe('upgrading', () => {
let application
let context
let email
let password
let passcode
let receiveChallenge
let receiveChallengeWithApp
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
this.context = await Factory.createAppContext() context = await Factory.createAppContext()
await this.context.launch() await context.launch()
this.application = this.context.application application = context.application
this.email = UuidGenerator.GenerateUuid() email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid() password = UuidGenerator.GenerateUuid()
this.passcode = '1234' passcode = '1234'
const promptValueReply = (prompts) => { const promptValueReply = (prompts) => {
const values = [] const values = []
for (const prompt of prompts) { for (const prompt of prompts) {
if (prompt.validation === ChallengeValidation.LocalPasscode) { if (prompt.validation === ChallengeValidation.LocalPasscode) {
values.push(CreateChallengeValue(prompt, this.passcode)) values.push(CreateChallengeValue(prompt, passcode))
} else { } else {
values.push(CreateChallengeValue(prompt, this.password)) values.push(CreateChallengeValue(prompt, password))
} }
} }
return values 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, { application.addChallengeObserver(challenge, {
onInvalidValue: (value) => { onInvalidValue: (value) => {
const values = promptValueReply([value.prompt]) const values = promptValueReply([value.prompt])
application.submitValuesForChallenge(challenge, values) application.submitValuesForChallenge(challenge, values)
numPasscodeAttempts++
}, },
}) })
const initialValues = promptValueReply(challenge.prompts) const initialValues = promptValueReply(challenge.prompts)
@@ -44,7 +52,7 @@ describe('upgrading', () => {
}) })
afterEach(async function () { afterEach(async function () {
await Factory.safeDeinit(this.application) await Factory.safeDeinit(application)
localStorage.clear() localStorage.clear()
}) })
@@ -52,76 +60,72 @@ describe('upgrading', () => {
const oldVersion = ProtocolVersion.V003 const oldVersion = ProtocolVersion.V003
/** Register with 003 version */ /** Register with 003 version */
await Factory.registerOldUser({ await Factory.registerOldUser({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
version: oldVersion, 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 () { it('upgrade should be available when passcode only', async function () {
const oldVersion = ProtocolVersion.V003 const oldVersion = ProtocolVersion.V003
await Factory.setOldVersionPasscode({ await Factory.setOldVersionPasscode({
application: this.application, application: application,
passcode: this.passcode, passcode: passcode,
version: oldVersion, 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 () { it('upgrades application protocol from 003 to 004', async function () {
const oldVersion = ProtocolVersion.V003 const oldVersion = ProtocolVersion.V003
const newVersion = ProtocolVersion.V004 const newVersion = ProtocolVersion.V004
await Factory.createMappedNote(this.application) await Factory.createMappedNote(application)
/** Register with 003 version */ /** Register with 003 version */
await Factory.registerOldUser({ await Factory.registerOldUser({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
version: oldVersion, version: oldVersion,
}) })
await Factory.setOldVersionPasscode({ await Factory.setOldVersionPasscode({
application: this.application, application: application,
passcode: this.passcode, passcode: passcode,
version: oldVersion, version: oldVersion,
}) })
expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(oldVersion)
oldVersion, expect((await application.encryption.getRootKeyParams()).version).to.equal(oldVersion)
) expect((await application.encryption.getRootKey()).keyVersion).to.equal(oldVersion)
expect((await this.application.encryption.getRootKeyParams()).version).to.equal(oldVersion)
expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(oldVersion)
this.application.setLaunchCallback({ application.setLaunchCallback({
receiveChallenge: this.receiveChallenge, receiveChallenge: receiveChallenge,
}) })
const result = await this.application.upgradeProtocolVersion() const result = await application.upgradeProtocolVersion()
expect(result).to.deep.equal({ success: true }) 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) const payload = new EncryptedPayload(wrappedRootKey)
expect(payload.version).to.equal(newVersion) expect(payload.version).to.equal(newVersion)
expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(newVersion)
newVersion, expect((await application.encryption.getRootKeyParams()).version).to.equal(newVersion)
) expect((await application.encryption.getRootKey()).keyVersion).to.equal(newVersion)
expect((await this.application.encryption.getRootKeyParams()).version).to.equal(newVersion)
expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(newVersion)
/** /**
* Immediately logging out ensures we don't rely on subsequent * Immediately logging out ensures we don't rely on subsequent
* sync events to complete the upgrade * sync events to complete the upgrade
*/ */
this.application = await Factory.signOutApplicationAndReturnNew(this.application) application = await Factory.signOutApplicationAndReturnNew(application)
await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) await application.signIn(email, password, undefined, undefined, undefined, true)
expect(this.application.items.getDisplayableNotes().length).to.equal(1) expect(application.items.getDisplayableNotes().length).to.equal(1)
expect(this.application.payloads.invalidPayloads).to.be.empty expect(application.payloads.invalidPayloads).to.be.empty
}).timeout(15000) }).timeout(15000)
it('upgrading from 003 to 004 with passcode only then reiniting app should create valid state', async function () { 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 const oldVersion = ProtocolVersion.V003
await Factory.setOldVersionPasscode({ await Factory.setOldVersionPasscode({
application: this.application, application: application,
passcode: this.passcode, passcode: passcode,
version: oldVersion, version: oldVersion,
}) })
await Factory.createSyncedNote(this.application) await Factory.createSyncedNote(application)
this.application.setLaunchCallback({ application.setLaunchCallback({
receiveChallenge: this.receiveChallenge, receiveChallenge: receiveChallenge,
}) })
const identifier = this.application.identifier const identifier = application.identifier
/** Recreate the app once */ /** Recreate the app once */
const appFirst = Factory.createApplicationWithFakeCrypto(identifier) const appFirst = Factory.createApplicationWithFakeCrypto(identifier)
await appFirst.prepareForLaunch({ await appFirst.prepareForLaunch({
receiveChallenge: (challenge) => { receiveChallenge: (challenge) => {
this.receiveChallengeWithApp(appFirst, challenge) receiveChallengeWithApp(appFirst, challenge)
}, },
}) })
await appFirst.launch(true) await appFirst.launch(true)
@@ -162,7 +166,7 @@ describe('upgrading', () => {
const appSecond = Factory.createApplicationWithFakeCrypto(identifier) const appSecond = Factory.createApplicationWithFakeCrypto(identifier)
await appSecond.prepareForLaunch({ await appSecond.prepareForLaunch({
receiveChallenge: (challenge) => { receiveChallenge: (challenge) => {
this.receiveChallengeWithApp(appSecond, challenge) receiveChallengeWithApp(appSecond, challenge)
}, },
}) })
await appSecond.launch(true) await appSecond.launch(true)
@@ -172,46 +176,46 @@ describe('upgrading', () => {
it('protocol version should be upgraded on password change', async function () { it('protocol version should be upgraded on password change', async function () {
/** Delete default items key that is created on launch */ /** Delete default items key that is created on launch */
const itemsKey = await this.application.encryption.getSureDefaultItemsKey() const itemsKey = await application.encryption.getSureDefaultItemsKey()
await this.application.mutator.setItemToBeDeleted(itemsKey) await application.mutator.setItemToBeDeleted(itemsKey)
expect(Uuids(this.application.items.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false) expect(Uuids(application.items.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false)
Factory.createMappedNote(this.application) Factory.createMappedNote(application)
/** Register with 003 version */ /** Register with 003 version */
await Factory.registerOldUser({ await Factory.registerOldUser({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
version: ProtocolVersion.V003, 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 application.encryption.getRootKeyParams()).version).to.equal(ProtocolVersion.V003)
expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(ProtocolVersion.V003) expect((await application.encryption.getRootKey()).keyVersion).to.equal(ProtocolVersion.V003)
/** Ensure note is encrypted with 003 */ /** 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.length).to.equal(1)
expect(notePayloads[0].version).to.equal(ProtocolVersion.V003) 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 expect(error).to.not.exist
const latestVersion = this.application.encryption.getLatestVersion() const latestVersion = application.encryption.getLatestVersion()
expect((await this.application.encryption.getRootKeyParams()).version).to.equal(latestVersion) expect((await application.encryption.getRootKeyParams()).version).to.equal(latestVersion)
expect((await this.application.encryption.getRootKey()).keyVersion).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) expect(defaultItemsKey.keyVersion).to.equal(latestVersion)
/** After change, note should now be encrypted with latest protocol version */ /** After change, note should now be encrypted with latest protocol version */
const note = this.application.items.getDisplayableNotes()[0] const note = application.items.getDisplayableNotes()[0]
await Factory.markDirtyAndSyncItem(this.application, note) 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] const refreshedNotePayload = refreshedNotePayloads[0]
expect(refreshedNotePayload.version).to.equal(latestVersion) expect(refreshedNotePayload.version).to.equal(latestVersion)
}).timeout(5000) }).timeout(5000)
@@ -221,19 +225,19 @@ describe('upgrading', () => {
const oldVersion = ProtocolVersion.V003 const oldVersion = ProtocolVersion.V003
beforeEach(async function () { beforeEach(async function () {
await Factory.createMappedNote(this.application) await Factory.createMappedNote(application)
/** Register with 003 version */ /** Register with 003 version */
await Factory.registerOldUser({ await Factory.registerOldUser({
application: this.application, application: application,
email: this.email, email: email,
password: this.password, password: password,
version: oldVersion, version: oldVersion,
}) })
await Factory.setOldVersionPasscode({ await Factory.setOldVersionPasscode({
application: this.application, application: application,
passcode: this.passcode, passcode: passcode,
version: oldVersion, version: oldVersion,
}) })
}) })
@@ -243,44 +247,36 @@ describe('upgrading', () => {
}) })
it('rolls back the local protocol upgrade if syncing fails', async function () { it('rolls back the local protocol upgrade if syncing fails', async function () {
sinon.replace(this.application.sync, 'sync', sinon.fake()) sinon.replace(application.sync, 'sync', sinon.fake())
this.application.setLaunchCallback({ application.setLaunchCallback({
receiveChallenge: this.receiveChallenge, receiveChallenge: receiveChallenge,
}) })
expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(oldVersion)
oldVersion, const errors = await application.upgradeProtocolVersion()
)
const errors = await this.application.upgradeProtocolVersion()
expect(errors).to.not.be.empty expect(errors).to.not.be.empty
/** Ensure we're still on 003 */ /** Ensure we're still on 003 */
expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(oldVersion)
oldVersion, expect((await application.encryption.getRootKeyParams()).version).to.equal(oldVersion)
) expect((await application.encryption.getRootKey()).keyVersion).to.equal(oldVersion)
expect((await this.application.encryption.getRootKeyParams()).version).to.equal(oldVersion) expect((await application.encryption.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion)
expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(oldVersion)
expect((await this.application.encryption.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion)
}) })
it('rolls back the local protocol upgrade if the server responds with an error', async function () { 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({ application.setLaunchCallback({
receiveChallenge: this.receiveChallenge, receiveChallenge: receiveChallenge,
}) })
expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(oldVersion)
oldVersion, const errors = await application.upgradeProtocolVersion()
)
const errors = await this.application.upgradeProtocolVersion()
expect(errors).to.not.be.empty expect(errors).to.not.be.empty
/** Ensure we're still on 003 */ /** Ensure we're still on 003 */
expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( expect((await application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal(oldVersion)
oldVersion, expect((await application.encryption.getRootKeyParams()).version).to.equal(oldVersion)
) expect((await application.encryption.getRootKey()).keyVersion).to.equal(oldVersion)
expect((await this.application.encryption.getRootKeyParams()).version).to.equal(oldVersion) expect((await application.encryption.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion)
expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(oldVersion)
expect((await this.application.encryption.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion)
}) })
}) })
}) })

View File

@@ -1,5 +1,4 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable no-undef */
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect

View File

@@ -9,11 +9,6 @@ describe('asymmetric messages', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -23,6 +18,12 @@ describe('asymmetric messages', function () {
await context.register() 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 () => { it('should not trust message if the trusted payload data recipientUuid does not match the message user uuid', async () => {
const { sharedVault, contactContext, deinitContactContext } = const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context) await Collaboration.createSharedVaultWithAcceptedInvite(context)

View File

@@ -9,11 +9,6 @@ describe('shared vault conflicts', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -23,6 +18,12 @@ describe('shared vault conflicts', function () {
await context.register() 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 () => { 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 } = const { sharedVault, note, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) 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 () => { it('attempting to modify note as read user should result in SharedVaultInsufficientPermissionsError', async () => {
const { note, contactContext, deinitContactContext } = const { note, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context, SharedVaultUserPermission.PERMISSIONS.Read) await Collaboration.createSharedVaultWithAcceptedInviteAndNote(
context,
SharedVaultUserPermission.PERMISSIONS.Read,
)
const promise = contactContext.resolveWithConflicts() const promise = contactContext.resolveWithConflicts()
await contactContext.changeNoteTitleAndSync(note, 'new title') await contactContext.changeNoteTitleAndSync(note, 'new title')

View File

@@ -9,11 +9,6 @@ describe('contacts', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -23,6 +18,12 @@ describe('contacts', function () {
await context.register() await context.register()
}) })
afterEach(async function () {
await context.deinit()
localStorage.clear()
sinon.restore()
})
it('should create contact', async () => { it('should create contact', async () => {
const contact = await context.contacts.createOrEditTrustedContact({ const contact = await context.contacts.createOrEditTrustedContact({
name: 'John Doe', name: 'John Doe',

View File

@@ -9,11 +9,6 @@ describe('shared vault crypto', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -23,6 +18,12 @@ describe('shared vault crypto', function () {
await context.register() await context.register()
}) })
afterEach(async function () {
await context.deinit()
localStorage.clear()
sinon.restore()
})
describe('root key', () => { describe('root key', () => {
it('root key loaded from disk should have keypairs', async () => { it('root key loaded from disk should have keypairs', async () => {
const appIdentifier = context.identifier const appIdentifier = context.identifier

View File

@@ -10,11 +10,6 @@ describe('shared vault deletion', function () {
let context let context
let sharedVaults let sharedVaults
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -26,6 +21,12 @@ describe('shared vault deletion', function () {
sharedVaults = context.sharedVaults 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 () => { it('should remove item from all user devices when item is deleted permanently', async () => {
const { note, contactContext, deinitContactContext } = const { note, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
@@ -102,7 +103,10 @@ describe('shared vault deletion', function () {
it('leaving a shared vault should remove its items locally', async () => { it('leaving a shared vault should remove its items locally', async () => {
const { sharedVault, note, contactContext, deinitContactContext } = 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) const originalNote = contactContext.items.findItem(note.uuid)
expect(originalNote).to.not.be.undefined expect(originalNote).to.not.be.undefined

View File

@@ -11,11 +11,6 @@ describe('shared vault files', function () {
let context let context
let vaults let vaults
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -28,6 +23,12 @@ describe('shared vault files', function () {
await context.activatePaidSubscriptionForUser() await context.activatePaidSubscriptionForUser()
}) })
afterEach(async function () {
await context.deinit()
localStorage.clear()
sinon.restore()
})
describe('private vaults', () => { describe('private vaults', () => {
it('should be able to upload and download file to private vault as owner', async () => { it('should be able to upload and download file to private vault as owner', async () => {
const vault = await Collaboration.createPrivateVault(context) const vault = await Collaboration.createPrivateVault(context)

View File

@@ -9,11 +9,6 @@ describe.skip('vault importing', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -23,6 +18,12 @@ describe.skip('vault importing', function () {
await context.register() await context.register()
}) })
afterEach(async function () {
await context.deinit()
localStorage.clear()
sinon.restore()
})
it('should import vaulted items with synced root key', async () => { it('should import vaulted items with synced root key', async () => {
console.error('TODO: implement') console.error('TODO: implement')
}) })

View File

@@ -9,11 +9,6 @@ describe('shared vault invites', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -22,6 +17,12 @@ describe('shared vault invites', function () {
await context.register() await context.register()
}) })
afterEach(async function () {
await context.deinit()
localStorage.clear()
sinon.restore()
})
it('should invite contact to vault', async () => { it('should invite contact to vault', async () => {
const sharedVault = await Collaboration.createSharedVault(context) const sharedVault = await Collaboration.createSharedVault(context)
const { contactContext, deinitContactContext } = await Collaboration.createContactContext() const { contactContext, deinitContactContext } = await Collaboration.createContactContext()

View File

@@ -9,11 +9,6 @@ describe('shared vault items', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -23,6 +18,12 @@ describe('shared vault items', function () {
await context.register() 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 () => { it('should add item to shared vault with no other members', async () => {
const note = await context.createSyncedNote('foo', 'bar') const note = await context.createSyncedNote('foo', 'bar')

View File

@@ -8,11 +8,6 @@ describe('vault key management', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -21,6 +16,12 @@ describe('vault key management', function () {
await context.launch() await context.launch()
}) })
afterEach(async function () {
await context.deinit()
localStorage.clear()
sinon.restore()
})
describe('locking', () => { describe('locking', () => {
it('should throw if attempting to add item to locked vault', async () => { it('should throw if attempting to add item to locked vault', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({ const vault = await context.vaults.createUserInputtedPasswordVault({

View File

@@ -9,11 +9,6 @@ describe('vault key rotation', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -23,6 +18,12 @@ describe('vault key rotation', function () {
await context.register() await context.register()
}) })
afterEach(async function () {
await context.deinit()
localStorage.clear()
sinon.restore()
})
it('should reencrypt all items keys belonging to key system', async () => { it('should reencrypt all items keys belonging to key system', async () => {
const { sharedVault, contactContext, deinitContactContext } = const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context) await Collaboration.createSharedVaultWithAcceptedInvite(context)

View File

@@ -9,11 +9,6 @@ describe('vault key sharing', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -23,6 +18,12 @@ describe('vault key sharing', function () {
await context.register() 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 () => { 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({ const privateVault = await context.vaults.createUserInputtedPasswordVault({
name: 'My Private Vault', name: 'My Private Vault',

View File

@@ -9,11 +9,6 @@ describe('keypair change', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -23,6 +18,12 @@ describe('keypair change', function () {
await context.register() 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 () => { it('contacts should be able to handle receiving multiple keypair changed messages and trust them in order', async () => {
const { note, contactContext, deinitContactContext } = const { note, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)

View File

@@ -9,11 +9,6 @@ describe('shared vault permissions', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -23,6 +18,12 @@ describe('shared vault permissions', function () {
await context.register() 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 () => { it('non-admin user should not be able to invite user', async () => {
context.anticipateConsoleError('Could not create invite') context.anticipateConsoleError('Could not create invite')
@@ -42,6 +43,8 @@ describe('shared vault permissions', function () {
expect(result.isFailed()).to.be.true expect(result.isFailed()).to.be.true
await thirdParty.deinitContactContext()
await deinitContactContext() await deinitContactContext()
}) })

View File

@@ -7,24 +7,23 @@ describe('public key cryptography', function () {
this.timeout(Factory.TwentySecondTimeout) this.timeout(Factory.TwentySecondTimeout)
let context let context
let sessions
let encryption
afterEach(async function () { beforeEach(async () => {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () {
localStorage.clear() localStorage.clear()
context = await Factory.createVaultsContextWithRealCrypto() context = await Factory.createVaultsContextWithRealCrypto()
await context.launch() await context.launch()
await context.register() await context.register()
})
sessions = context.application.sessions afterEach(async () => {
encryption = context.encryption await context.deinit()
localStorage.clear()
sinon.restore()
context = undefined
}) })
it('should create keypair during registration', () => { it('should create keypair during registration', () => {
@@ -38,7 +37,7 @@ describe('public key cryptography', function () {
it('should populate keypair during sign in', async () => { it('should populate keypair during sign in', async () => {
const email = context.email const email = context.email
const password = context.password const password = context.password
await context.signout() await context.deinit()
const recreatedContext = await Factory.createVaultsContextWithRealCrypto() const recreatedContext = await Factory.createVaultsContextWithRealCrypto()
await recreatedContext.launch() await recreatedContext.launch()

View File

@@ -24,6 +24,8 @@ describe('shared vaults', function () {
afterEach(async function () { afterEach(async function () {
await context.deinit() await context.deinit()
localStorage.clear() localStorage.clear()
sinon.restore()
context = undefined
}) })
it('should update vault name and description', async () => { it('should update vault name and description', async () => {

View File

@@ -9,11 +9,6 @@ describe('signatures', function () {
let context let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
@@ -23,6 +18,13 @@ describe('signatures', function () {
await context.register() await context.register()
}) })
afterEach(async function () {
await context.deinit()
localStorage.clear()
sinon.restore()
context = undefined
})
describe('item decryption signature verification', () => { describe('item decryption signature verification', () => {
it('should have failing signature if contact public key does not match', async () => { it('should have failing signature if contact public key does not match', async () => {
const { note, contactContext, deinitContactContext } = const { note, contactContext, deinitContactContext } =

View File

@@ -9,21 +9,24 @@ describe('vaults', function () {
let context let context
let vaults let vaults
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () { beforeEach(async function () {
localStorage.clear() localStorage.clear()
context = await Factory.createVaultsContextWithRealCrypto() context = await Factory.createVaultsContextWithFakeCrypto()
await context.launch() await context.launch()
vaults = context.vaults vaults = context.vaults
}) })
afterEach(async function () {
await context.deinit()
localStorage.clear()
sinon.restore()
context = undefined
vaults = undefined
})
describe('offline', function () { describe('offline', function () {
it('should be able to create an offline vault', async () => { it('should be able to create an offline vault', async () => {
const vault = await vaults.createRandomizedVault({ const vault = await vaults.createRandomizedVault({
@@ -77,7 +80,7 @@ describe('vaults', function () {
await vaults.moveItemToVault(vault, note) await vaults.moveItemToVault(vault, note)
await context.deinit() await context.deinit()
const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier) const recreatedContext = await Factory.createVaultsContextWithFakeCrypto(appIdentifier)
await recreatedContext.launch() await recreatedContext.launch()
const updatedNote = recreatedContext.items.findItem(note.uuid) const updatedNote = recreatedContext.items.findItem(note.uuid)
@@ -101,7 +104,7 @@ describe('vaults', function () {
await context.deinit() await context.deinit()
const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier) const recreatedContext = await Factory.createVaultsContextWithFakeCrypto(appIdentifier)
await recreatedContext.launch() await recreatedContext.launch()
const notes = recreatedContext.notes const notes = recreatedContext.notes
@@ -128,7 +131,7 @@ describe('vaults', function () {
await context.deinit() await context.deinit()
const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier) const recreatedContext = await Factory.createVaultsContextWithFakeCrypto(appIdentifier)
await recreatedContext.launch() await recreatedContext.launch()
const updatedNote = recreatedContext.items.findItem(note.uuid) const updatedNote = recreatedContext.items.findItem(note.uuid)
@@ -181,7 +184,7 @@ describe('vaults', function () {
await vaults.moveItemToVault(vault, note) await vaults.moveItemToVault(vault, note)
await context.deinit() await context.deinit()
const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier) const recreatedContext = await Factory.createVaultsContextWithFakeCrypto(appIdentifier)
await recreatedContext.launch() await recreatedContext.launch()
const updatedNote = recreatedContext.items.findItem(note.uuid) const updatedNote = recreatedContext.items.findItem(note.uuid)