internal: incomplete vault systems behind feature flag (#2340)
This commit is contained in:
@@ -22,7 +22,7 @@ describe('000 legacy protocol operations', () => {
|
||||
|
||||
let error
|
||||
try {
|
||||
protocol004.generateDecryptedParametersSync({
|
||||
protocol004.generateDecryptedParameters({
|
||||
uuid: 'foo',
|
||||
content: string,
|
||||
content_type: 'foo',
|
||||
|
||||
@@ -7,16 +7,16 @@ const expect = chai.expect
|
||||
describe('004 protocol operations', function () {
|
||||
const _identifier = 'hello@test.com'
|
||||
const _password = 'password'
|
||||
let _keyParams
|
||||
let _key
|
||||
let rootKeyParams
|
||||
let rootKey
|
||||
|
||||
const application = Factory.createApplicationWithRealCrypto()
|
||||
const protocol004 = new SNProtocolOperator004(new SNWebCrypto())
|
||||
|
||||
before(async function () {
|
||||
await Factory.initializeApplication(application)
|
||||
_key = await protocol004.createRootKey(_identifier, _password, KeyParamsOrigination.Registration)
|
||||
_keyParams = _key.keyParams
|
||||
rootKey = await protocol004.createRootKey(_identifier, _password, KeyParamsOrigination.Registration)
|
||||
rootKeyParams = rootKey.keyParams
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
@@ -69,43 +69,58 @@ describe('004 protocol operations', function () {
|
||||
})
|
||||
|
||||
it('properly encrypts and decrypts', async function () {
|
||||
const text = 'hello world'
|
||||
const rawKey = _key.masterKey
|
||||
const nonce = await application.protocolService.crypto.generateRandomKey(192)
|
||||
const payload = new DecryptedPayload({
|
||||
uuid: '123',
|
||||
content_type: ContentType.ItemsKey,
|
||||
content: FillItemContent({
|
||||
title: 'foo',
|
||||
text: 'bar',
|
||||
}),
|
||||
})
|
||||
|
||||
const operator = application.protocolService.operatorManager.operatorForVersion(ProtocolVersion.V004)
|
||||
const authenticatedData = { foo: 'bar' }
|
||||
const encString = await operator.encryptString004(text, rawKey, nonce, authenticatedData)
|
||||
const decString = await operator.decryptString004(
|
||||
encString,
|
||||
rawKey,
|
||||
nonce,
|
||||
await operator.authenticatedDataToString(authenticatedData),
|
||||
)
|
||||
expect(decString).to.equal(text)
|
||||
|
||||
const encrypted = await operator.generateEncryptedParameters(payload, rootKey)
|
||||
const decrypted = await operator.generateDecryptedParameters(encrypted, rootKey)
|
||||
|
||||
expect(decrypted.content.title).to.equal('foo')
|
||||
expect(decrypted.content.text).to.equal('bar')
|
||||
})
|
||||
|
||||
it('fails to decrypt non-matching aad', async function () {
|
||||
const text = 'hello world'
|
||||
const rawKey = _key.masterKey
|
||||
const nonce = await application.protocolService.crypto.generateRandomKey(192)
|
||||
const payload = new DecryptedPayload({
|
||||
uuid: '123',
|
||||
content_type: ContentType.ItemsKey,
|
||||
content: FillItemContent({
|
||||
title: 'foo',
|
||||
text: 'bar',
|
||||
}),
|
||||
})
|
||||
|
||||
const operator = application.protocolService.operatorManager.operatorForVersion(ProtocolVersion.V004)
|
||||
const aad = { foo: 'bar' }
|
||||
const nonmatchingAad = { foo: 'rab' }
|
||||
const encString = await operator.encryptString004(text, rawKey, nonce, aad)
|
||||
const decString = await operator.decryptString004(encString, rawKey, nonce, nonmatchingAad)
|
||||
expect(decString).to.not.be.ok
|
||||
|
||||
const encrypted = await operator.generateEncryptedParameters(payload, rootKey)
|
||||
const decrypted = await operator.generateDecryptedParameters(
|
||||
{
|
||||
...encrypted,
|
||||
uuid: 'nonmatching',
|
||||
},
|
||||
rootKey,
|
||||
)
|
||||
|
||||
expect(decrypted.errorDecrypting).to.equal(true)
|
||||
})
|
||||
|
||||
it('generates existing keys for key params', async function () {
|
||||
const key = await application.protocolService.computeRootKey(_password, _keyParams)
|
||||
expect(key.compare(_key)).to.be.true
|
||||
const key = await application.protocolService.computeRootKey(_password, rootKeyParams)
|
||||
expect(key.compare(rootKey)).to.be.true
|
||||
})
|
||||
|
||||
it('can decrypt encrypted params', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
const key = await protocol004.createItemsKey()
|
||||
const params = await protocol004.generateEncryptedParametersSync(payload, key)
|
||||
const decrypted = await protocol004.generateDecryptedParametersSync(params, key)
|
||||
const params = await protocol004.generateEncryptedParameters(payload, key)
|
||||
const decrypted = await protocol004.generateDecryptedParameters(params, key)
|
||||
expect(decrypted.errorDecrypting).to.not.be.ok
|
||||
expect(decrypted.content).to.eql(payload.content)
|
||||
})
|
||||
@@ -113,9 +128,9 @@ describe('004 protocol operations', function () {
|
||||
it('modifying the uuid of the payload should fail to decrypt', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
const key = await protocol004.createItemsKey()
|
||||
const params = await protocol004.generateEncryptedParametersSync(payload, key)
|
||||
const params = await protocol004.generateEncryptedParameters(payload, key)
|
||||
params.uuid = 'foo'
|
||||
const result = await protocol004.generateDecryptedParametersSync(params, key)
|
||||
const result = await protocol004.generateDecryptedParameters(params, key)
|
||||
expect(result.errorDecrypting).to.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
58
packages/snjs/mocha/TestRegistry/BaseTests.js
Normal file
58
packages/snjs/mocha/TestRegistry/BaseTests.js
Normal file
@@ -0,0 +1,58 @@
|
||||
export const BaseTests = [
|
||||
'memory.test.js',
|
||||
'protocol.test.js',
|
||||
'utils.test.js',
|
||||
'000.test.js',
|
||||
'001.test.js',
|
||||
'002.test.js',
|
||||
'003.test.js',
|
||||
'004.test.js',
|
||||
'username.test.js',
|
||||
'app-group.test.js',
|
||||
'application.test.js',
|
||||
'payload.test.js',
|
||||
'payload_encryption.test.js',
|
||||
'item.test.js',
|
||||
'item_manager.test.js',
|
||||
'features.test.js',
|
||||
'settings.test.js',
|
||||
'mfa_service.test.js',
|
||||
'mutator.test.js',
|
||||
'mutator_service.test.js',
|
||||
'payload_manager.test.js',
|
||||
'collections.test.js',
|
||||
'note_display_criteria.test.js',
|
||||
'keys.test.js',
|
||||
'key_params.test.js',
|
||||
'key_recovery_service.test.js',
|
||||
'backups.test.js',
|
||||
'upgrading.test.js',
|
||||
'model_tests/importing.test.js',
|
||||
'model_tests/appmodels.test.js',
|
||||
'model_tests/items.test.js',
|
||||
'model_tests/mapping.test.js',
|
||||
'model_tests/notes_smart_tags.test.js',
|
||||
'model_tests/notes_tags.test.js',
|
||||
'model_tests/notes_tags_folders.test.js',
|
||||
'model_tests/performance.test.js',
|
||||
'sync_tests/offline.test.js',
|
||||
'sync_tests/notes_tags.test.js',
|
||||
'sync_tests/online.test.js',
|
||||
'sync_tests/conflicting.test.js',
|
||||
'sync_tests/integrity.test.js',
|
||||
'auth-fringe-cases.test.js',
|
||||
'auth.test.js',
|
||||
'device_auth.test.js',
|
||||
'storage.test.js',
|
||||
'protection.test.js',
|
||||
'singletons.test.js',
|
||||
'migrations/migration.test.js',
|
||||
'migrations/tags-to-folders.test.js',
|
||||
'history.test.js',
|
||||
'actions.test.js',
|
||||
'preferences.test.js',
|
||||
'files.test.js',
|
||||
'session.test.js',
|
||||
'subscriptions.test.js',
|
||||
'recovery.test.js',
|
||||
];
|
||||
7
packages/snjs/mocha/TestRegistry/MainRegistry.js
Normal file
7
packages/snjs/mocha/TestRegistry/MainRegistry.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { BaseTests } from './BaseTests.js'
|
||||
import { VaultTests } from './VaultTests.js'
|
||||
|
||||
export default {
|
||||
BaseTests,
|
||||
VaultTests,
|
||||
}
|
||||
16
packages/snjs/mocha/TestRegistry/VaultTests.js
Normal file
16
packages/snjs/mocha/TestRegistry/VaultTests.js
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
export const VaultTests = [
|
||||
'vaults/vaults.test.js',
|
||||
'vaults/pkc.test.js',
|
||||
'vaults/contacts.test.js',
|
||||
'vaults/crypto.test.js',
|
||||
'vaults/asymmetric-messages.test.js',
|
||||
'vaults/shared_vaults.test.js',
|
||||
'vaults/invites.test.js',
|
||||
'vaults/items.test.js',
|
||||
'vaults/conflicts.test.js',
|
||||
'vaults/deletion.test.js',
|
||||
'vaults/permissions.test.js',
|
||||
'vaults/key_rotation.test.js',
|
||||
'vaults/files.test.js',
|
||||
];
|
||||
@@ -170,10 +170,7 @@ describe('actions service', () => {
|
||||
})
|
||||
|
||||
// Extension item
|
||||
const extensionItem = await this.application.itemManager.createItem(
|
||||
ContentType.ActionsExtension,
|
||||
this.actionsExtension,
|
||||
)
|
||||
const extensionItem = await this.application.mutator.createItem(ContentType.ActionsExtension, this.actionsExtension)
|
||||
this.extensionItemUuid = extensionItem.uuid
|
||||
})
|
||||
|
||||
@@ -185,7 +182,7 @@ describe('actions service', () => {
|
||||
})
|
||||
|
||||
it('should get extension items', async function () {
|
||||
await this.itemManager.createItem(ContentType.Note, {
|
||||
await this.application.mutator.createItem(ContentType.Note, {
|
||||
title: 'A simple note',
|
||||
text: 'Standard Notes rocks! lml.',
|
||||
})
|
||||
@@ -194,7 +191,7 @@ describe('actions service', () => {
|
||||
})
|
||||
|
||||
it('should get extensions in context of item', async function () {
|
||||
const noteItem = await this.itemManager.createItem(ContentType.Note, {
|
||||
const noteItem = await this.application.mutator.createItem(ContentType.Note, {
|
||||
title: 'Another note',
|
||||
text: 'Whiskey In The Jar',
|
||||
})
|
||||
@@ -205,7 +202,7 @@ describe('actions service', () => {
|
||||
})
|
||||
|
||||
it('should get actions based on item context', async function () {
|
||||
const tagItem = await this.itemManager.createItem(ContentType.Tag, {
|
||||
const tagItem = await this.application.mutator.createItem(ContentType.Tag, {
|
||||
title: 'Music',
|
||||
})
|
||||
|
||||
@@ -217,7 +214,7 @@ describe('actions service', () => {
|
||||
})
|
||||
|
||||
it('should load extension in context of item', async function () {
|
||||
const noteItem = await this.itemManager.createItem(ContentType.Note, {
|
||||
const noteItem = await this.application.mutator.createItem(ContentType.Note, {
|
||||
title: 'Yet another note',
|
||||
text: 'And all things will end ♫',
|
||||
})
|
||||
@@ -249,7 +246,7 @@ describe('actions service', () => {
|
||||
const sandbox = sinon.createSandbox()
|
||||
|
||||
before(async function () {
|
||||
this.noteItem = await this.itemManager.createItem(ContentType.Note, {
|
||||
this.noteItem = await this.application.mutator.createItem(ContentType.Note, {
|
||||
title: 'Hey',
|
||||
text: 'Welcome To Paradise',
|
||||
})
|
||||
@@ -331,7 +328,7 @@ describe('actions service', () => {
|
||||
const sandbox = sinon.createSandbox()
|
||||
|
||||
before(async function () {
|
||||
this.noteItem = await this.itemManager.createItem(ContentType.Note, {
|
||||
this.noteItem = await this.application.mutator.createItem(ContentType.Note, {
|
||||
title: 'Excuse Me',
|
||||
text: 'Time To Be King 8)',
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/Applications.js'
|
||||
import { BaseItemCounts } from './lib/BaseItemCounts.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -75,7 +75,7 @@ describe('application instances', () => {
|
||||
/** Recreate app with different host */
|
||||
const recreatedContext = await Factory.createAppContext({
|
||||
identifier: 'app',
|
||||
host: 'http://nonsense.host'
|
||||
host: 'http://nonsense.host',
|
||||
})
|
||||
await recreatedContext.launch()
|
||||
|
||||
@@ -134,7 +134,7 @@ describe('application instances', () => {
|
||||
})
|
||||
|
||||
it('shows confirmation dialog when there are unsaved changes', async () => {
|
||||
await testSNApp.itemManager.setItemDirty(testNote1)
|
||||
await testSNApp.mutator.setItemDirty(testNote1)
|
||||
await testSNApp.user.signOut()
|
||||
|
||||
const expectedConfirmMessage = signOutConfirmMessage(1)
|
||||
@@ -154,7 +154,7 @@ describe('application instances', () => {
|
||||
})
|
||||
|
||||
it('does not show confirmation dialog when there are unsaved changes and the "force" option is set to true', async () => {
|
||||
await testSNApp.itemManager.setItemDirty(testNote1)
|
||||
await testSNApp.mutator.setItemDirty(testNote1)
|
||||
await testSNApp.user.signOut(true)
|
||||
|
||||
expect(confirmAlert.callCount).to.equal(0)
|
||||
@@ -166,7 +166,7 @@ describe('application instances', () => {
|
||||
confirmAlert.restore()
|
||||
confirmAlert = sinon.stub(testSNApp.alertService, 'confirm').callsFake((_message) => false)
|
||||
|
||||
await testSNApp.itemManager.setItemDirty(testNote1)
|
||||
await testSNApp.mutator.setItemDirty(testNote1)
|
||||
await testSNApp.user.signOut()
|
||||
|
||||
const expectedConfirmMessage = signOutConfirmMessage(1)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/Applications.js'
|
||||
import { BaseItemCounts } from './lib/BaseItemCounts.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -85,14 +85,14 @@ describe('auth fringe cases', () => {
|
||||
|
||||
const serverText = 'server text'
|
||||
|
||||
await context.application.mutator.changeAndSaveItem(firstVersionOfNote, (mutator) => {
|
||||
await context.application.changeAndSaveItem(firstVersionOfNote, (mutator) => {
|
||||
mutator.text = serverText
|
||||
})
|
||||
|
||||
const newApplication = await Factory.signOutApplicationAndReturnNew(context.application)
|
||||
|
||||
/** Create same note but now offline */
|
||||
await newApplication.itemManager.emitItemFromPayload(firstVersionOfNote.payload)
|
||||
await newApplication.mutator.emitItemFromPayload(firstVersionOfNote.payload)
|
||||
|
||||
/** Sign in and merge local data */
|
||||
await newApplication.signIn(context.email, context.password, undefined, undefined, true, true)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/Applications.js'
|
||||
import { BaseItemCounts } from './lib/BaseItemCounts.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -15,7 +15,7 @@ describe('basic auth', function () {
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
@@ -262,7 +262,7 @@ describe('basic auth', function () {
|
||||
if (!didCompleteDownloadFirstSync) {
|
||||
return
|
||||
}
|
||||
if (!didCompletePostDownloadFirstSync && eventName === SyncEvent.SingleRoundTripSyncCompleted) {
|
||||
if (!didCompletePostDownloadFirstSync && eventName === SyncEvent.PaginatedSyncRequestCompleted) {
|
||||
didCompletePostDownloadFirstSync = true
|
||||
/** Should be in sync */
|
||||
outOfSync = this.application.syncService.isOutOfSync()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/Applications.js'
|
||||
import { BaseItemCounts } from './lib/BaseItemCounts.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -25,9 +25,6 @@ describe('backups', function () {
|
||||
this.application = null
|
||||
})
|
||||
|
||||
const BASE_ITEM_COUNT_ENCRYPTED = BaseItemCounts.DefaultItems
|
||||
const BASE_ITEM_COUNT_DECRYPTED = ['UserPreferences', 'DarkTheme'].length
|
||||
|
||||
it('backup file should have a version number', async function () {
|
||||
let data = await this.application.createDecryptedBackupFile()
|
||||
expect(data.version).to.equal(this.application.protocolService.getLatestVersion())
|
||||
@@ -39,7 +36,9 @@ describe('backups', 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)])
|
||||
const data = await this.application.createDecryptedBackupFile()
|
||||
expect(data.items.length).to.equal(BASE_ITEM_COUNT_DECRYPTED + 2)
|
||||
const offsetForNewItems = 2
|
||||
const offsetForNoItemsKey = -1
|
||||
expect(data.items.length).to.equal(BaseItemCounts.DefaultItems + offsetForNewItems + offsetForNoItemsKey)
|
||||
})
|
||||
|
||||
it('passcode + no account backup file should have correct number of items', async function () {
|
||||
@@ -49,12 +48,12 @@ describe('backups', function () {
|
||||
|
||||
// Encrypted backup without authorization
|
||||
const encryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
|
||||
expect(encryptedData.items.length).to.equal(BASE_ITEM_COUNT_ENCRYPTED + 2)
|
||||
expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItems + 2)
|
||||
|
||||
// Encrypted backup with authorization
|
||||
Factory.handlePasswordChallenges(this.application, passcode)
|
||||
const authorizedEncryptedData = await this.application.createEncryptedBackupFile()
|
||||
expect(authorizedEncryptedData.items.length).to.equal(BASE_ITEM_COUNT_ENCRYPTED + 2)
|
||||
expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItems + 2)
|
||||
})
|
||||
|
||||
it('no passcode + account backup file should have correct number of items', async function () {
|
||||
@@ -68,17 +67,17 @@ describe('backups', function () {
|
||||
|
||||
// Encrypted backup without authorization
|
||||
const encryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
|
||||
expect(encryptedData.items.length).to.equal(BASE_ITEM_COUNT_ENCRYPTED + 2)
|
||||
expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2)
|
||||
|
||||
Factory.handlePasswordChallenges(this.application, this.password)
|
||||
|
||||
// Decrypted backup
|
||||
const decryptedData = await this.application.createDecryptedBackupFile()
|
||||
expect(decryptedData.items.length).to.equal(BASE_ITEM_COUNT_DECRYPTED + 2)
|
||||
expect(decryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccountWithoutItemsKey + 2)
|
||||
|
||||
// Encrypted backup with authorization
|
||||
const authorizedEncryptedData = await this.application.createEncryptedBackupFile()
|
||||
expect(authorizedEncryptedData.items.length).to.equal(BASE_ITEM_COUNT_ENCRYPTED + 2)
|
||||
expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2)
|
||||
})
|
||||
|
||||
it('passcode + account backup file should have correct number of items', async function () {
|
||||
@@ -91,17 +90,17 @@ describe('backups', function () {
|
||||
|
||||
// Encrypted backup without authorization
|
||||
const encryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
|
||||
expect(encryptedData.items.length).to.equal(BASE_ITEM_COUNT_ENCRYPTED + 2)
|
||||
expect(encryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2)
|
||||
|
||||
Factory.handlePasswordChallenges(this.application, passcode)
|
||||
|
||||
// Decrypted backup
|
||||
const decryptedData = await this.application.createDecryptedBackupFile()
|
||||
expect(decryptedData.items.length).to.equal(BASE_ITEM_COUNT_DECRYPTED + 2)
|
||||
expect(decryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccountWithoutItemsKey + 2)
|
||||
|
||||
// Encrypted backup with authorization
|
||||
const authorizedEncryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
|
||||
expect(authorizedEncryptedData.items.length).to.equal(BASE_ITEM_COUNT_ENCRYPTED + 2)
|
||||
expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2)
|
||||
})
|
||||
|
||||
it('backup file item should have correct fields', async function () {
|
||||
@@ -154,7 +153,7 @@ describe('backups', function () {
|
||||
errorDecrypting: true,
|
||||
})
|
||||
|
||||
await this.application.itemManager.emitItemFromPayload(errored)
|
||||
await this.application.payloadManager.emitPayload(errored)
|
||||
|
||||
const erroredItem = this.application.itemManager.findAnyItem(errored.uuid)
|
||||
|
||||
@@ -162,7 +161,7 @@ describe('backups', function () {
|
||||
|
||||
const backupData = await this.application.createDecryptedBackupFile()
|
||||
|
||||
expect(backupData.items.length).to.equal(BASE_ITEM_COUNT_DECRYPTED + 2)
|
||||
expect(backupData.items.length).to.equal(BaseItemCounts.DefaultItemsNoAccounNoItemsKey + 2)
|
||||
})
|
||||
|
||||
it('decrypted backup file should not have keyParams', async function () {
|
||||
|
||||
@@ -31,9 +31,9 @@ describe('features', () => {
|
||||
expires_at: tomorrow,
|
||||
}
|
||||
|
||||
sinon.spy(application.itemManager, 'createItem')
|
||||
sinon.spy(application.itemManager, 'changeComponent')
|
||||
sinon.spy(application.itemManager, 'setItemsToBeDeleted')
|
||||
sinon.spy(application.mutator, 'createItem')
|
||||
sinon.spy(application.mutator, 'changeComponent')
|
||||
sinon.spy(application.mutator, 'setItemsToBeDeleted')
|
||||
|
||||
getUserFeatures = sinon.stub(application.apiService, 'getUserFeatures').callsFake(() => {
|
||||
return Promise.resolve({
|
||||
@@ -82,7 +82,7 @@ describe('features', () => {
|
||||
|
||||
it('should fetch user features and create items for features with content type', async () => {
|
||||
expect(application.apiService.getUserFeatures.callCount).to.equal(1)
|
||||
expect(application.itemManager.createItem.callCount).to.equal(2)
|
||||
expect(application.mutator.createItem.callCount).to.equal(2)
|
||||
|
||||
const themeItems = application.items.getItems(ContentType.Theme)
|
||||
const systemThemeCount = 1
|
||||
@@ -117,7 +117,7 @@ describe('features', () => {
|
||||
// Wipe roles from initial sync
|
||||
await application.featuresService.setOnlineRoles([])
|
||||
// Create pre-existing item for theme without all the info
|
||||
await application.itemManager.createItem(
|
||||
await application.mutator.createItem(
|
||||
ContentType.Theme,
|
||||
FillItemContent({
|
||||
package_info: {
|
||||
@@ -129,7 +129,7 @@ describe('features', () => {
|
||||
await application.sync.sync()
|
||||
// Timeout since we don't await for features update
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
expect(application.itemManager.changeComponent.callCount).to.equal(1)
|
||||
expect(application.mutator.changeComponent.callCount).to.equal(1)
|
||||
const themeItems = application.items.getItems(ContentType.Theme)
|
||||
expect(themeItems).to.have.lengthOf(1)
|
||||
expect(themeItems[0].content).to.containSubset(
|
||||
@@ -172,7 +172,7 @@ describe('features', () => {
|
||||
|
||||
// Timeout since we don't await for features update
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
expect(application.itemManager.setItemsToBeDeleted.calledWith([sinon.match({ uuid: themeItem.uuid })])).to.equal(
|
||||
expect(application.mutator.setItemsToBeDeleted.calledWith([sinon.match({ uuid: themeItem.uuid })])).to.equal(
|
||||
true,
|
||||
)
|
||||
|
||||
@@ -202,7 +202,7 @@ describe('features', () => {
|
||||
sinon.stub(application.featuresService, 'migrateFeatureRepoToUserSetting').callsFake(resolve)
|
||||
})
|
||||
|
||||
await application.itemManager.createItem(
|
||||
await application.mutator.createItem(
|
||||
ContentType.ExtensionRepo,
|
||||
FillItemContent({
|
||||
url: `https://extensions.standardnotes.org/${extensionKey}`,
|
||||
@@ -224,7 +224,7 @@ describe('features', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
.callsFake(() => {})
|
||||
const extensionKey = UuidGenerator.GenerateUuid().split('-').join('')
|
||||
await application.itemManager.createItem(
|
||||
await application.mutator.createItem(
|
||||
ContentType.ExtensionRepo,
|
||||
FillItemContent({
|
||||
url: `https://extensions.standardnotes.org/${extensionKey}`,
|
||||
@@ -255,7 +255,7 @@ describe('features', () => {
|
||||
return false
|
||||
})
|
||||
const extensionKey = UuidGenerator.GenerateUuid().split('-').join('')
|
||||
await application.itemManager.createItem(
|
||||
await application.mutator.createItem(
|
||||
ContentType.ExtensionRepo,
|
||||
FillItemContent({
|
||||
url: `https://extensions.standardnotes.org/${extensionKey}`,
|
||||
@@ -290,7 +290,7 @@ describe('features', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
await application.itemManager.createItem(
|
||||
await application.mutator.createItem(
|
||||
ContentType.ExtensionRepo,
|
||||
FillItemContent({
|
||||
url: `https://extensions.standardnotes.org/${extensionKey}`,
|
||||
@@ -304,7 +304,7 @@ describe('features', () => {
|
||||
it('previous extension repo should be migrated to offline feature repo', async () => {
|
||||
application = await Factory.signOutApplicationAndReturnNew(application)
|
||||
const extensionKey = UuidGenerator.GenerateUuid().split('-').join('')
|
||||
await application.itemManager.createItem(
|
||||
await application.mutator.createItem(
|
||||
ContentType.ExtensionRepo,
|
||||
FillItemContent({
|
||||
url: `https://extensions.standardnotes.org/${extensionKey}`,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as Factory from './lib/factory.js'
|
||||
import * as Events from './lib/Events.js'
|
||||
import * as Utils from './lib/Utils.js'
|
||||
import * as Files from './lib/Files.js'
|
||||
|
||||
@@ -38,22 +39,7 @@ describe('files', function () {
|
||||
})
|
||||
|
||||
if (subscription) {
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
||||
timestamp: Date.now(),
|
||||
offline: false,
|
||||
discountCode: null,
|
||||
limitedDiscountPurchased: false,
|
||||
newSubscriber: true,
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
})
|
||||
await Factory.sleep(2)
|
||||
await context.publicMockSubscriptionPurchaseEvent()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +52,7 @@ describe('files', function () {
|
||||
await setup({ fakeCrypto: true, subscription: true })
|
||||
|
||||
const remoteIdentifier = Utils.generateUuid()
|
||||
const token = await application.apiService.createFileValetToken(remoteIdentifier, 'write')
|
||||
const token = await application.apiService.createUserFileValetToken(remoteIdentifier, 'write')
|
||||
|
||||
expect(token.length).to.be.above(0)
|
||||
})
|
||||
@@ -75,15 +61,15 @@ describe('files', function () {
|
||||
await setup({ fakeCrypto: true, subscription: false })
|
||||
|
||||
const remoteIdentifier = Utils.generateUuid()
|
||||
const tokenOrError = await application.apiService.createFileValetToken(remoteIdentifier, 'write')
|
||||
const tokenOrError = await application.apiService.createUserFileValetToken(remoteIdentifier, 'write')
|
||||
|
||||
expect(tokenOrError.tag).to.equal('no-subscription')
|
||||
expect(isClientDisplayableError(tokenOrError)).to.equal(true)
|
||||
})
|
||||
|
||||
it('should not create valet token from server when user has an expired subscription - @paidfeature', async function () {
|
||||
await setup({ fakeCrypto: true, subscription: false })
|
||||
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PLUS_PLAN',
|
||||
@@ -96,27 +82,27 @@ describe('files', function () {
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
payAmount: 59.0,
|
||||
})
|
||||
|
||||
await Factory.sleep(2)
|
||||
|
||||
const remoteIdentifier = Utils.generateUuid()
|
||||
const tokenOrError = await application.apiService.createFileValetToken(remoteIdentifier, 'write')
|
||||
const tokenOrError = await application.apiService.createUserFileValetToken(remoteIdentifier, 'write')
|
||||
|
||||
expect(tokenOrError.tag).to.equal('expired-subscription')
|
||||
expect(isClientDisplayableError(tokenOrError)).to.equal(true)
|
||||
})
|
||||
|
||||
it('creating two upload sessions successively should succeed - @paidfeature', async function () {
|
||||
await setup({ fakeCrypto: true, subscription: true })
|
||||
|
||||
const firstToken = await application.apiService.createFileValetToken(Utils.generateUuid(), 'write')
|
||||
const firstSession = await application.apiService.startUploadSession(firstToken)
|
||||
const firstToken = await application.apiService.createUserFileValetToken(Utils.generateUuid(), 'write')
|
||||
const firstSession = await application.apiService.startUploadSession(firstToken, 'user')
|
||||
|
||||
expect(firstSession.uploadId).to.be.ok
|
||||
|
||||
const secondToken = await application.apiService.createFileValetToken(Utils.generateUuid(), 'write')
|
||||
const secondSession = await application.apiService.startUploadSession(secondToken)
|
||||
const secondToken = await application.apiService.createUserFileValetToken(Utils.generateUuid(), 'write')
|
||||
const secondSession = await application.apiService.startUploadSession(secondToken, 'user')
|
||||
|
||||
expect(secondSession.uploadId).to.be.ok
|
||||
})
|
||||
@@ -129,7 +115,7 @@ describe('files', function () {
|
||||
|
||||
const file = await Files.uploadFile(fileService, buffer, 'my-file', 'md', 1000)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(fileService, itemManager, file.remoteIdentifier)
|
||||
const downloadedBytes = await Files.downloadFile(fileService, file)
|
||||
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
})
|
||||
@@ -142,7 +128,7 @@ describe('files', function () {
|
||||
|
||||
const file = await Files.uploadFile(fileService, buffer, 'my-file', 'md', 100000)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(fileService, itemManager, file.remoteIdentifier)
|
||||
const downloadedBytes = await Files.downloadFile(fileService, file)
|
||||
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('history manager', () => {
|
||||
})
|
||||
|
||||
function setTextAndSync(application, item, text) {
|
||||
return application.mutator.changeAndSaveItem(
|
||||
return application.changeAndSaveItem(
|
||||
item,
|
||||
(mutator) => {
|
||||
mutator.text = text
|
||||
@@ -59,7 +59,7 @@ describe('history manager', () => {
|
||||
expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(0)
|
||||
|
||||
/** Sync with different contents, should create new entry */
|
||||
await this.application.mutator.changeAndSaveItem(
|
||||
await this.application.changeAndSaveItem(
|
||||
item,
|
||||
(mutator) => {
|
||||
mutator.title = Math.random()
|
||||
@@ -79,7 +79,7 @@ describe('history manager', () => {
|
||||
const context = await Factory.createAppContext({ identifier })
|
||||
await context.launch()
|
||||
expect(context.application.historyManager.sessionHistoryForItem(item).length).to.equal(0)
|
||||
await context.application.mutator.changeAndSaveItem(
|
||||
await context.application.changeAndSaveItem(
|
||||
item,
|
||||
(mutator) => {
|
||||
mutator.title = Math.random()
|
||||
@@ -97,13 +97,13 @@ describe('history manager', () => {
|
||||
it('creating new item and making 1 change should create 0 revisions', async function () {
|
||||
const context = await Factory.createAppContext()
|
||||
await context.launch()
|
||||
const item = await context.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
const item = await context.application.items.createTemplateItem(ContentType.Note, {
|
||||
references: [],
|
||||
})
|
||||
await context.application.mutator.insertItem(item)
|
||||
expect(context.application.historyManager.sessionHistoryForItem(item).length).to.equal(0)
|
||||
|
||||
await context.application.mutator.changeAndSaveItem(
|
||||
await context.application.changeAndSaveItem(
|
||||
item,
|
||||
(mutator) => {
|
||||
mutator.title = Math.random()
|
||||
@@ -172,8 +172,8 @@ describe('history manager', () => {
|
||||
text: Factory.randomString(100),
|
||||
}),
|
||||
)
|
||||
let item = await this.application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
await this.application.itemManager.setItemDirty(item)
|
||||
let item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.setItemDirty(item)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
/** It should keep the first and last by default */
|
||||
item = await setTextAndSync(this.application, item, item.content.text)
|
||||
@@ -202,9 +202,9 @@ describe('history manager', () => {
|
||||
}),
|
||||
)
|
||||
|
||||
let item = await this.application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
let item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
|
||||
await this.application.itemManager.setItemDirty(item)
|
||||
await this.application.mutator.setItemDirty(item)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
item = await setTextAndSync(this.application, item, item.content.text + Factory.randomString(1))
|
||||
@@ -241,9 +241,9 @@ describe('history manager', () => {
|
||||
|
||||
it('unsynced entries should use payload created_at for preview titles', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
const item = this.application.items.findItem(payload.uuid)
|
||||
await this.application.mutator.changeAndSaveItem(
|
||||
await this.application.changeAndSaveItem(
|
||||
item,
|
||||
(mutator) => {
|
||||
mutator.title = Math.random()
|
||||
@@ -306,7 +306,7 @@ describe('history manager', () => {
|
||||
expect(itemHistory.length).to.equal(1)
|
||||
|
||||
/** Sync with different contents, should not create a new entry */
|
||||
await this.application.mutator.changeAndSaveItem(
|
||||
await this.application.changeAndSaveItem(
|
||||
item,
|
||||
(mutator) => {
|
||||
mutator.title = Math.random()
|
||||
@@ -327,7 +327,7 @@ describe('history manager', () => {
|
||||
await Factory.sleep(Factory.ServerRevisionFrequency)
|
||||
/** Sync with different contents, should create new entry */
|
||||
const newTitleAfterFirstChange = `The title should be: ${Math.random()}`
|
||||
await this.application.mutator.changeAndSaveItem(
|
||||
await this.application.changeAndSaveItem(
|
||||
item,
|
||||
(mutator) => {
|
||||
mutator.title = newTitleAfterFirstChange
|
||||
@@ -343,7 +343,10 @@ describe('history manager', () => {
|
||||
expect(itemHistory.length).to.equal(2)
|
||||
|
||||
const oldestEntry = lastElement(itemHistory)
|
||||
let revisionFromServerOrError = await this.application.getRevision.execute({ itemUuid: item.uuid, revisionUuid: oldestEntry.uuid })
|
||||
let revisionFromServerOrError = await this.application.getRevision.execute({
|
||||
itemUuid: item.uuid,
|
||||
revisionUuid: oldestEntry.uuid,
|
||||
})
|
||||
const revisionFromServer = revisionFromServerOrError.getValue()
|
||||
expect(revisionFromServer).to.be.ok
|
||||
|
||||
@@ -359,7 +362,7 @@ describe('history manager', () => {
|
||||
it('duplicate revisions should not have the originals uuid', async function () {
|
||||
const note = await Factory.createSyncedNote(this.application)
|
||||
await Factory.markDirtyAndSyncItem(this.application, note)
|
||||
const dupe = await this.application.itemManager.duplicateItem(note, true)
|
||||
const dupe = await this.application.mutator.duplicateItem(note, true)
|
||||
await Factory.markDirtyAndSyncItem(this.application, dupe)
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
@@ -367,7 +370,10 @@ describe('history manager', () => {
|
||||
const dupeHistoryOrError = await this.application.listRevisions.execute({ itemUuid: dupe.uuid })
|
||||
const dupeHistory = dupeHistoryOrError.getValue()
|
||||
|
||||
const dupeRevisionOrError = await this.application.getRevision.execute({ itemUuid: dupe.uuid, revisionUuid: dupeHistory[0].uuid })
|
||||
const dupeRevisionOrError = await this.application.getRevision.execute({
|
||||
itemUuid: dupe.uuid,
|
||||
revisionUuid: dupeHistory[0].uuid,
|
||||
})
|
||||
const dupeRevision = dupeRevisionOrError.getValue()
|
||||
expect(dupeRevision.payload.uuid).to.equal(dupe.uuid)
|
||||
})
|
||||
@@ -384,7 +390,7 @@ describe('history manager', () => {
|
||||
await Factory.sleep(Factory.ServerRevisionFrequency)
|
||||
await Factory.markDirtyAndSyncItem(this.application, note)
|
||||
|
||||
const dupe = await this.application.itemManager.duplicateItem(note, true)
|
||||
const dupe = await this.application.mutator.duplicateItem(note, true)
|
||||
await Factory.markDirtyAndSyncItem(this.application, dupe)
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
@@ -405,12 +411,12 @@ describe('history manager', () => {
|
||||
await Factory.sleep(Factory.ServerRevisionFrequency)
|
||||
|
||||
const changedText = `${Math.random()}`
|
||||
await this.application.mutator.changeAndSaveItem(note, (mutator) => {
|
||||
await this.application.changeAndSaveItem(note, (mutator) => {
|
||||
mutator.title = changedText
|
||||
})
|
||||
await Factory.markDirtyAndSyncItem(this.application, note)
|
||||
|
||||
const dupe = await this.application.itemManager.duplicateItem(note, true)
|
||||
const dupe = await this.application.mutator.duplicateItem(note, true)
|
||||
await Factory.markDirtyAndSyncItem(this.application, dupe)
|
||||
|
||||
await Factory.sleep(Factory.ServerRevisionCreationDelay)
|
||||
@@ -420,7 +426,10 @@ describe('history manager', () => {
|
||||
expect(itemHistory.length).to.be.above(1)
|
||||
const newestRevision = itemHistory[0]
|
||||
|
||||
const fetchedOrError = await this.application.getRevision.execute({ itemUuid: dupe.uuid, revisionUuid: newestRevision.uuid })
|
||||
const fetchedOrError = await this.application.getRevision.execute({
|
||||
itemUuid: dupe.uuid,
|
||||
revisionUuid: newestRevision.uuid,
|
||||
})
|
||||
const fetched = fetchedOrError.getValue()
|
||||
expect(fetched.payload.errorDecrypting).to.not.be.ok
|
||||
expect(fetched.payload.content.title).to.equal(changedText)
|
||||
|
||||
@@ -1,167 +1,120 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import * as Factory from './lib/factory.js'
|
||||
import { BaseItemCounts } from './lib/BaseItemCounts.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('item manager', function () {
|
||||
let context
|
||||
let application
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
this.payloadManager = new PayloadManager()
|
||||
this.itemManager = new ItemManager(this.payloadManager)
|
||||
this.createNote = async () => {
|
||||
return this.itemManager.createItem(ContentType.Note, {
|
||||
title: 'hello',
|
||||
text: 'world',
|
||||
})
|
||||
}
|
||||
localStorage.clear()
|
||||
|
||||
this.createTag = async (notes = []) => {
|
||||
const references = notes.map((note) => {
|
||||
return {
|
||||
uuid: note.uuid,
|
||||
content_type: note.content_type,
|
||||
}
|
||||
})
|
||||
return this.itemManager.createItem(ContentType.Tag, {
|
||||
title: 'thoughts',
|
||||
references: references,
|
||||
})
|
||||
}
|
||||
context = await Factory.createAppContextWithFakeCrypto()
|
||||
application = context.application
|
||||
|
||||
await context.launch()
|
||||
})
|
||||
|
||||
it('create item', async function () {
|
||||
const item = await this.createNote()
|
||||
const createNote = async () => {
|
||||
return application.mutator.createItem(ContentType.Note, {
|
||||
title: 'hello',
|
||||
text: 'world',
|
||||
})
|
||||
}
|
||||
|
||||
expect(item).to.be.ok
|
||||
expect(item.title).to.equal('hello')
|
||||
})
|
||||
|
||||
it('emitting item through payload and marking dirty should have userModifiedDate', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
const item = await this.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
const result = await this.itemManager.setItemDirty(item)
|
||||
const appData = result.payload.content.appData
|
||||
expect(appData[DecryptedItem.DefaultAppDomain()][AppDataField.UserModifiedDate]).to.be.ok
|
||||
})
|
||||
const createTag = async (notes = []) => {
|
||||
const references = notes.map((note) => {
|
||||
return {
|
||||
uuid: note.uuid,
|
||||
content_type: note.content_type,
|
||||
}
|
||||
})
|
||||
return application.mutator.createItem(ContentType.Tag, {
|
||||
title: 'thoughts',
|
||||
references: references,
|
||||
})
|
||||
}
|
||||
|
||||
it('find items with valid uuid', async function () {
|
||||
const item = await this.createNote()
|
||||
const item = await createNote()
|
||||
|
||||
const results = await this.itemManager.findItems([item.uuid])
|
||||
const results = await application.items.findItems([item.uuid])
|
||||
expect(results.length).to.equal(1)
|
||||
expect(results[0]).to.equal(item)
|
||||
})
|
||||
|
||||
it('find items with invalid uuid no blanks', async function () {
|
||||
const results = await this.itemManager.findItems([Factory.generateUuidish()])
|
||||
const results = await application.items.findItems([Factory.generateUuidish()])
|
||||
expect(results.length).to.equal(0)
|
||||
})
|
||||
|
||||
it('find items with invalid uuid include blanks', async function () {
|
||||
const includeBlanks = true
|
||||
const results = await this.itemManager.findItemsIncludingBlanks([Factory.generateUuidish()])
|
||||
const results = await application.items.findItemsIncludingBlanks([Factory.generateUuidish()])
|
||||
expect(results.length).to.equal(1)
|
||||
expect(results[0]).to.not.be.ok
|
||||
})
|
||||
|
||||
it('item state', async function () {
|
||||
await this.createNote()
|
||||
await createNote()
|
||||
|
||||
expect(this.itemManager.items.length).to.equal(1)
|
||||
expect(this.itemManager.getDisplayableNotes().length).to.equal(1)
|
||||
expect(application.items.items.length).to.equal(1 + BaseItemCounts.DefaultItems)
|
||||
expect(application.items.getDisplayableNotes().length).to.equal(1)
|
||||
})
|
||||
|
||||
it('find item', async function () {
|
||||
const item = await this.createNote()
|
||||
const item = await createNote()
|
||||
|
||||
const foundItem = this.itemManager.findItem(item.uuid)
|
||||
const foundItem = application.items.findItem(item.uuid)
|
||||
expect(foundItem).to.be.ok
|
||||
})
|
||||
|
||||
it('reference map', async function () {
|
||||
const note = await this.createNote()
|
||||
const tag = await this.createTag([note])
|
||||
const note = await createNote()
|
||||
const tag = await createTag([note])
|
||||
|
||||
expect(this.itemManager.collection.referenceMap.directMap.get(tag.uuid)).to.eql([note.uuid])
|
||||
expect(application.items.collection.referenceMap.directMap.get(tag.uuid)).to.eql([note.uuid])
|
||||
})
|
||||
|
||||
it('inverse reference map', async function () {
|
||||
const note = await this.createNote()
|
||||
const tag = await this.createTag([note])
|
||||
const note = await createNote()
|
||||
const tag = await createTag([note])
|
||||
|
||||
expect(this.itemManager.collection.referenceMap.inverseMap.get(note.uuid)).to.eql([tag.uuid])
|
||||
expect(application.items.collection.referenceMap.inverseMap.get(note.uuid)).to.eql([tag.uuid])
|
||||
})
|
||||
|
||||
it('inverse reference map should not have duplicates', async function () {
|
||||
const note = await this.createNote()
|
||||
const tag = await this.createTag([note])
|
||||
await this.itemManager.changeItem(tag)
|
||||
const note = await createNote()
|
||||
const tag = await createTag([note])
|
||||
await application.mutator.changeItem(tag)
|
||||
|
||||
expect(this.itemManager.collection.referenceMap.inverseMap.get(note.uuid)).to.eql([tag.uuid])
|
||||
})
|
||||
|
||||
it('deleting from reference map', async function () {
|
||||
const note = await this.createNote()
|
||||
const tag = await this.createTag([note])
|
||||
await this.itemManager.setItemToBeDeleted(note)
|
||||
|
||||
expect(this.itemManager.collection.referenceMap.directMap.get(tag.uuid)).to.eql([])
|
||||
expect(this.itemManager.collection.referenceMap.inverseMap.get(note.uuid).length).to.equal(0)
|
||||
})
|
||||
|
||||
it('deleting referenced item should update referencing item references', async function () {
|
||||
const note = await this.createNote()
|
||||
let tag = await this.createTag([note])
|
||||
await this.itemManager.setItemToBeDeleted(note)
|
||||
|
||||
tag = this.itemManager.findItem(tag.uuid)
|
||||
expect(tag.content.references.length).to.equal(0)
|
||||
})
|
||||
|
||||
it('removing relationship should update reference map', async function () {
|
||||
const note = await this.createNote()
|
||||
const tag = await this.createTag([note])
|
||||
await this.itemManager.changeItem(tag, (mutator) => {
|
||||
mutator.removeItemAsRelationship(note)
|
||||
})
|
||||
|
||||
expect(this.itemManager.collection.referenceMap.directMap.get(tag.uuid)).to.eql([])
|
||||
expect(this.itemManager.collection.referenceMap.inverseMap.get(note.uuid)).to.eql([])
|
||||
})
|
||||
|
||||
it('emitting discardable payload should remove it from our collection', async function () {
|
||||
const note = await this.createNote()
|
||||
|
||||
const payload = new DeletedPayload({
|
||||
...note.payload.ejected(),
|
||||
content: undefined,
|
||||
deleted: true,
|
||||
dirty: false,
|
||||
})
|
||||
|
||||
expect(payload.discardable).to.equal(true)
|
||||
|
||||
await this.itemManager.emitItemFromPayload(payload)
|
||||
|
||||
expect(this.itemManager.findItem(note.uuid)).to.not.be.ok
|
||||
expect(application.items.collection.referenceMap.inverseMap.get(note.uuid)).to.eql([tag.uuid])
|
||||
})
|
||||
|
||||
it('items that reference item', async function () {
|
||||
const note = await this.createNote()
|
||||
const tag = await this.createTag([note])
|
||||
const note = await createNote()
|
||||
const tag = await createTag([note])
|
||||
|
||||
const itemsThatReference = this.itemManager.itemsReferencingItem(note)
|
||||
const itemsThatReference = application.items.itemsReferencingItem(note)
|
||||
expect(itemsThatReference.length).to.equal(1)
|
||||
expect(itemsThatReference[0]).to.equal(tag)
|
||||
})
|
||||
|
||||
it('observer', async function () {
|
||||
const observed = []
|
||||
this.itemManager.addObserver(ContentType.Any, ({ changed, inserted, removed, source, sourceKey }) => {
|
||||
application.items.addObserver(ContentType.Any, ({ changed, inserted, removed, source, sourceKey }) => {
|
||||
observed.push({ changed, inserted, removed, source, sourceKey })
|
||||
})
|
||||
const note = await this.createNote()
|
||||
const tag = await this.createTag([note])
|
||||
const note = await createNote()
|
||||
const tag = await createTag([note])
|
||||
expect(observed.length).to.equal(2)
|
||||
|
||||
const firstObserved = observed[0]
|
||||
@@ -171,59 +124,23 @@ describe('item manager', function () {
|
||||
expect(secondObserved.inserted).to.eql([tag])
|
||||
})
|
||||
|
||||
it('change existing item', async function () {
|
||||
const note = await this.createNote()
|
||||
const newTitle = String(Math.random())
|
||||
await this.itemManager.changeItem(note, (mutator) => {
|
||||
mutator.title = newTitle
|
||||
})
|
||||
|
||||
const latestVersion = this.itemManager.findItem(note.uuid)
|
||||
expect(latestVersion.title).to.equal(newTitle)
|
||||
})
|
||||
|
||||
it('change non-existant item through uuid should fail', async function () {
|
||||
const note = await this.itemManager.createTemplateItem(ContentType.Note, {
|
||||
title: 'hello',
|
||||
text: 'world',
|
||||
})
|
||||
|
||||
const changeFn = async () => {
|
||||
const newTitle = String(Math.random())
|
||||
return this.itemManager.changeItem(note, (mutator) => {
|
||||
mutator.title = newTitle
|
||||
})
|
||||
}
|
||||
await Factory.expectThrowsAsync(() => changeFn(), 'Attempting to change non-existant item')
|
||||
})
|
||||
|
||||
it('set items dirty', async function () {
|
||||
const note = await this.createNote()
|
||||
await this.itemManager.setItemDirty(note)
|
||||
|
||||
const dirtyItems = this.itemManager.getDirtyItems()
|
||||
expect(dirtyItems.length).to.equal(1)
|
||||
expect(dirtyItems[0].uuid).to.equal(note.uuid)
|
||||
expect(dirtyItems[0].dirty).to.equal(true)
|
||||
})
|
||||
|
||||
it('dirty items should not include errored items', async function () {
|
||||
const note = await this.itemManager.setItemDirty(await this.createNote())
|
||||
const note = await application.mutator.setItemDirty(await createNote())
|
||||
const errorred = new EncryptedPayload({
|
||||
...note.payload,
|
||||
content: '004:...',
|
||||
errorDecrypting: true,
|
||||
})
|
||||
|
||||
await this.itemManager.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged)
|
||||
await application.mutator.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged)
|
||||
|
||||
const dirtyItems = this.itemManager.getDirtyItems()
|
||||
const dirtyItems = application.items.getDirtyItems()
|
||||
|
||||
expect(dirtyItems.length).to.equal(0)
|
||||
})
|
||||
|
||||
it('dirty items should include errored items if they are being deleted', async function () {
|
||||
const note = await this.itemManager.setItemDirty(await this.createNote())
|
||||
const note = await application.mutator.setItemDirty(await createNote())
|
||||
const errorred = new DeletedPayload({
|
||||
...note.payload,
|
||||
content: undefined,
|
||||
@@ -231,181 +148,63 @@ describe('item manager', function () {
|
||||
deleted: true,
|
||||
})
|
||||
|
||||
await this.itemManager.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged)
|
||||
await application.mutator.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged)
|
||||
|
||||
const dirtyItems = this.itemManager.getDirtyItems()
|
||||
const dirtyItems = application.items.getDirtyItems()
|
||||
|
||||
expect(dirtyItems.length).to.equal(1)
|
||||
})
|
||||
|
||||
describe('duplicateItem', async function () {
|
||||
const sandbox = sinon.createSandbox()
|
||||
|
||||
beforeEach(async function () {
|
||||
this.emitPayloads = sandbox.spy(this.itemManager.payloadManager, 'emitPayloads')
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
it('should duplicate the item and set the duplicate_of property', async function () {
|
||||
const note = await this.createNote()
|
||||
await this.itemManager.duplicateItem(note)
|
||||
sinon.assert.calledTwice(this.emitPayloads)
|
||||
|
||||
const originalNote = this.itemManager.getDisplayableNotes()[0]
|
||||
const duplicatedNote = this.itemManager.getDisplayableNotes()[1]
|
||||
|
||||
expect(this.itemManager.items.length).to.equal(2)
|
||||
expect(this.itemManager.getDisplayableNotes().length).to.equal(2)
|
||||
expect(originalNote.uuid).to.not.equal(duplicatedNote.uuid)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.duplicateOf)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.payload.duplicate_of)
|
||||
expect(duplicatedNote.conflictOf).to.be.undefined
|
||||
expect(duplicatedNote.payload.content.conflict_of).to.be.undefined
|
||||
})
|
||||
|
||||
it('should duplicate the item and set the duplicate_of and conflict_of properties', async function () {
|
||||
const note = await this.createNote()
|
||||
await this.itemManager.duplicateItem(note, true)
|
||||
sinon.assert.calledTwice(this.emitPayloads)
|
||||
|
||||
const originalNote = this.itemManager.getDisplayableNotes()[0]
|
||||
const duplicatedNote = this.itemManager.getDisplayableNotes()[1]
|
||||
|
||||
expect(this.itemManager.items.length).to.equal(2)
|
||||
expect(this.itemManager.getDisplayableNotes().length).to.equal(2)
|
||||
expect(originalNote.uuid).to.not.equal(duplicatedNote.uuid)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.duplicateOf)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.payload.duplicate_of)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.conflictOf)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.payload.content.conflict_of)
|
||||
})
|
||||
|
||||
it('duplicate item with relationships', async function () {
|
||||
const note = await this.createNote()
|
||||
const tag = await this.createTag([note])
|
||||
const duplicate = await this.itemManager.duplicateItem(tag)
|
||||
|
||||
expect(duplicate.content.references).to.have.length(1)
|
||||
expect(this.itemManager.items).to.have.length(3)
|
||||
expect(this.itemManager.getDisplayableTags()).to.have.length(2)
|
||||
})
|
||||
|
||||
it('adds duplicated item as a relationship to items referencing it', async function () {
|
||||
const note = await this.createNote()
|
||||
let tag = await this.createTag([note])
|
||||
const duplicateNote = await this.itemManager.duplicateItem(note)
|
||||
expect(tag.content.references).to.have.length(1)
|
||||
|
||||
tag = this.itemManager.findItem(tag.uuid)
|
||||
const references = tag.content.references.map((ref) => ref.uuid)
|
||||
expect(references).to.have.length(2)
|
||||
expect(references).to.include(note.uuid, duplicateNote.uuid)
|
||||
})
|
||||
|
||||
it('duplicates item with additional content', async function () {
|
||||
const note = await this.itemManager.createItem(ContentType.Note, {
|
||||
title: 'hello',
|
||||
text: 'world',
|
||||
})
|
||||
const duplicateNote = await this.itemManager.duplicateItem(note, false, {
|
||||
title: 'hello (copy)',
|
||||
})
|
||||
|
||||
expect(duplicateNote.title).to.equal('hello (copy)')
|
||||
expect(duplicateNote.text).to.equal('world')
|
||||
})
|
||||
})
|
||||
|
||||
it('set item deleted', async function () {
|
||||
const note = await this.createNote()
|
||||
await this.itemManager.setItemToBeDeleted(note)
|
||||
|
||||
/** Items should never be mutated directly */
|
||||
expect(note.deleted).to.not.be.ok
|
||||
|
||||
const latestVersion = this.payloadManager.findOne(note.uuid)
|
||||
expect(latestVersion.deleted).to.equal(true)
|
||||
expect(latestVersion.dirty).to.equal(true)
|
||||
expect(latestVersion.content).to.not.be.ok
|
||||
|
||||
/** Deleted items do not show up in item manager's public interface */
|
||||
expect(this.itemManager.items.length).to.equal(0)
|
||||
expect(this.itemManager.getDisplayableNotes().length).to.equal(0)
|
||||
})
|
||||
|
||||
it('system smart views', async function () {
|
||||
expect(this.itemManager.systemSmartViews.length).to.be.above(0)
|
||||
expect(application.items.systemSmartViews.length).to.be.above(0)
|
||||
})
|
||||
|
||||
it('find tag by title', async function () {
|
||||
const tag = await this.createTag()
|
||||
const tag = await createTag()
|
||||
|
||||
expect(this.itemManager.findTagByTitle(tag.title)).to.be.ok
|
||||
expect(application.items.findTagByTitle(tag.title)).to.be.ok
|
||||
})
|
||||
|
||||
it('find tag by title should be case insensitive', async function () {
|
||||
const tag = await this.createTag()
|
||||
const tag = await createTag()
|
||||
|
||||
expect(this.itemManager.findTagByTitle(tag.title.toUpperCase())).to.be.ok
|
||||
expect(application.items.findTagByTitle(tag.title.toUpperCase())).to.be.ok
|
||||
})
|
||||
|
||||
it('find or create tag by title', async function () {
|
||||
const title = 'foo'
|
||||
|
||||
expect(await this.itemManager.findOrCreateTagByTitle(title)).to.be.ok
|
||||
expect(await application.mutator.findOrCreateTagByTitle({ title: title })).to.be.ok
|
||||
})
|
||||
|
||||
it('note count', async function () {
|
||||
await this.createNote()
|
||||
expect(this.itemManager.noteCount).to.equal(1)
|
||||
})
|
||||
|
||||
it('trash', async function () {
|
||||
const note = await this.createNote()
|
||||
const versionTwo = await this.itemManager.changeItem(note, (mutator) => {
|
||||
mutator.trashed = true
|
||||
})
|
||||
|
||||
expect(this.itemManager.trashSmartView).to.be.ok
|
||||
expect(versionTwo.trashed).to.equal(true)
|
||||
expect(versionTwo.dirty).to.equal(true)
|
||||
expect(versionTwo.content).to.be.ok
|
||||
|
||||
expect(this.itemManager.items.length).to.equal(1)
|
||||
expect(this.itemManager.trashedItems.length).to.equal(1)
|
||||
|
||||
await this.itemManager.emptyTrash()
|
||||
const versionThree = this.payloadManager.findOne(note.uuid)
|
||||
expect(versionThree.deleted).to.equal(true)
|
||||
expect(this.itemManager.trashedItems.length).to.equal(0)
|
||||
await createNote()
|
||||
expect(application.items.noteCount).to.equal(1)
|
||||
})
|
||||
|
||||
it('remove all items from memory', async function () {
|
||||
const observed = []
|
||||
this.itemManager.addObserver(ContentType.Any, ({ changed, inserted, removed, ignored }) => {
|
||||
application.items.addObserver(ContentType.Any, ({ changed, inserted, removed, ignored }) => {
|
||||
observed.push({ changed, inserted, removed, ignored })
|
||||
})
|
||||
await this.createNote()
|
||||
await this.itemManager.removeAllItemsFromMemory()
|
||||
await createNote()
|
||||
await application.items.removeAllItemsFromMemory()
|
||||
|
||||
const deletionEvent = observed[1]
|
||||
expect(deletionEvent.removed[0].deleted).to.equal(true)
|
||||
expect(this.itemManager.items.length).to.equal(0)
|
||||
expect(application.items.items.length).to.equal(0)
|
||||
})
|
||||
|
||||
it('remove item locally', async function () {
|
||||
const observed = []
|
||||
this.itemManager.addObserver(ContentType.Any, ({ changed, inserted, removed, ignored }) => {
|
||||
application.items.addObserver(ContentType.Any, ({ changed, inserted, removed, ignored }) => {
|
||||
observed.push({ changed, inserted, removed, ignored })
|
||||
})
|
||||
const note = await this.createNote()
|
||||
await this.itemManager.removeItemLocally(note)
|
||||
const note = await createNote()
|
||||
await application.items.removeItemLocally(note)
|
||||
|
||||
expect(observed.length).to.equal(1)
|
||||
expect(this.itemManager.findItem(note.uuid)).to.not.be.ok
|
||||
expect(application.items.findItem(note.uuid)).to.not.be.ok
|
||||
})
|
||||
|
||||
it('emitting a payload from within observer should queue to end', async function () {
|
||||
@@ -421,7 +220,7 @@ describe('item manager', function () {
|
||||
const changedTitle = 'changed title'
|
||||
let didEmit = false
|
||||
let latestVersion
|
||||
this.itemManager.addObserver(ContentType.Note, ({ changed, inserted }) => {
|
||||
application.items.addObserver(ContentType.Note, ({ changed, inserted }) => {
|
||||
const all = changed.concat(inserted)
|
||||
if (!didEmit) {
|
||||
didEmit = true
|
||||
@@ -431,60 +230,60 @@ describe('item manager', function () {
|
||||
title: changedTitle,
|
||||
},
|
||||
})
|
||||
this.itemManager.emitItemFromPayload(changedPayload)
|
||||
application.mutator.emitItemFromPayload(changedPayload)
|
||||
}
|
||||
latestVersion = all[0]
|
||||
})
|
||||
await this.itemManager.emitItemFromPayload(payload)
|
||||
await application.mutator.emitItemFromPayload(payload)
|
||||
expect(latestVersion.title).to.equal(changedTitle)
|
||||
})
|
||||
|
||||
describe('searchTags', async function () {
|
||||
it('should return tag with query matching title', async function () {
|
||||
const tag = await this.itemManager.findOrCreateTagByTitle('tag')
|
||||
const tag = await application.mutator.findOrCreateTagByTitle({ title: 'tag' })
|
||||
|
||||
const results = this.itemManager.searchTags('tag')
|
||||
const results = application.items.searchTags('tag')
|
||||
expect(results).lengthOf(1)
|
||||
expect(results[0].title).to.equal(tag.title)
|
||||
})
|
||||
it('should return all tags with query partially matching title', async function () {
|
||||
const firstTag = await this.itemManager.findOrCreateTagByTitle('tag one')
|
||||
const secondTag = await this.itemManager.findOrCreateTagByTitle('tag two')
|
||||
const firstTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag one' })
|
||||
const secondTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag two' })
|
||||
|
||||
const results = this.itemManager.searchTags('tag')
|
||||
const results = application.items.searchTags('tag')
|
||||
expect(results).lengthOf(2)
|
||||
expect(results[0].title).to.equal(firstTag.title)
|
||||
expect(results[1].title).to.equal(secondTag.title)
|
||||
})
|
||||
it('should be case insensitive', async function () {
|
||||
const tag = await this.itemManager.findOrCreateTagByTitle('Tag')
|
||||
const tag = await application.mutator.findOrCreateTagByTitle({ title: 'Tag' })
|
||||
|
||||
const results = this.itemManager.searchTags('tag')
|
||||
const results = application.items.searchTags('tag')
|
||||
expect(results).lengthOf(1)
|
||||
expect(results[0].title).to.equal(tag.title)
|
||||
})
|
||||
it('should return tag with query matching delimiter separated component', async function () {
|
||||
const tag = await this.itemManager.findOrCreateTagByTitle('parent.child')
|
||||
const tag = await application.mutator.findOrCreateTagByTitle({ title: 'parent.child' })
|
||||
|
||||
const results = this.itemManager.searchTags('child')
|
||||
const results = application.items.searchTags('child')
|
||||
expect(results).lengthOf(1)
|
||||
expect(results[0].title).to.equal(tag.title)
|
||||
})
|
||||
it('should return tags with matching query including delimiter', async function () {
|
||||
const tag = await this.itemManager.findOrCreateTagByTitle('parent.child')
|
||||
const tag = await application.mutator.findOrCreateTagByTitle({ title: 'parent.child' })
|
||||
|
||||
const results = this.itemManager.searchTags('parent.chi')
|
||||
const results = application.items.searchTags('parent.chi')
|
||||
expect(results).lengthOf(1)
|
||||
expect(results[0].title).to.equal(tag.title)
|
||||
})
|
||||
|
||||
it('should return tags in natural order', async function () {
|
||||
const firstTag = await this.itemManager.findOrCreateTagByTitle('tag 100')
|
||||
const secondTag = await this.itemManager.findOrCreateTagByTitle('tag 2')
|
||||
const thirdTag = await this.itemManager.findOrCreateTagByTitle('tag b')
|
||||
const fourthTag = await this.itemManager.findOrCreateTagByTitle('tag a')
|
||||
const firstTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag 100' })
|
||||
const secondTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag 2' })
|
||||
const thirdTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag b' })
|
||||
const fourthTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag a' })
|
||||
|
||||
const results = this.itemManager.searchTags('tag')
|
||||
const results = application.items.searchTags('tag')
|
||||
expect(results).lengthOf(4)
|
||||
expect(results[0].title).to.equal(secondTag.title)
|
||||
expect(results[1].title).to.equal(firstTag.title)
|
||||
@@ -493,15 +292,15 @@ describe('item manager', function () {
|
||||
})
|
||||
|
||||
it('should not return tags associated with note', async function () {
|
||||
const firstTag = await this.itemManager.findOrCreateTagByTitle('tag one')
|
||||
const secondTag = await this.itemManager.findOrCreateTagByTitle('tag two')
|
||||
const firstTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag one' })
|
||||
const secondTag = await application.mutator.findOrCreateTagByTitle({ title: 'tag two' })
|
||||
|
||||
const note = await this.createNote()
|
||||
await this.itemManager.changeItem(firstTag, (mutator) => {
|
||||
const note = await createNote()
|
||||
await application.mutator.changeItem(firstTag, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(note)
|
||||
})
|
||||
|
||||
const results = this.itemManager.searchTags('tag', note)
|
||||
const results = application.items.searchTags('tag', note)
|
||||
expect(results).lengthOf(1)
|
||||
expect(results[0].title).to.equal(secondTag.title)
|
||||
})
|
||||
@@ -509,68 +308,68 @@ describe('item manager', function () {
|
||||
|
||||
describe('smart views', async function () {
|
||||
it('all view should not include archived notes by default', async function () {
|
||||
const normal = await this.createNote()
|
||||
const normal = await createNote()
|
||||
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await application.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.archived = true
|
||||
})
|
||||
|
||||
this.itemManager.setPrimaryItemDisplayOptions({
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
application.items.setPrimaryItemDisplayOptions({
|
||||
views: [application.items.allNotesSmartView],
|
||||
})
|
||||
|
||||
expect(this.itemManager.getDisplayableNotes().length).to.equal(0)
|
||||
expect(application.items.getDisplayableNotes().length).to.equal(0)
|
||||
})
|
||||
|
||||
it('archived view should not include trashed notes by default', async function () {
|
||||
const normal = await this.createNote()
|
||||
const normal = await createNote()
|
||||
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await application.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.archived = true
|
||||
mutator.trashed = true
|
||||
})
|
||||
|
||||
this.itemManager.setPrimaryItemDisplayOptions({
|
||||
views: [this.itemManager.archivedSmartView],
|
||||
application.items.setPrimaryItemDisplayOptions({
|
||||
views: [application.items.archivedSmartView],
|
||||
})
|
||||
|
||||
expect(this.itemManager.getDisplayableNotes().length).to.equal(0)
|
||||
expect(application.items.getDisplayableNotes().length).to.equal(0)
|
||||
})
|
||||
|
||||
it('trashed view should include archived notes by default', async function () {
|
||||
const normal = await this.createNote()
|
||||
const normal = await createNote()
|
||||
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await application.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.archived = true
|
||||
mutator.trashed = true
|
||||
})
|
||||
|
||||
this.itemManager.setPrimaryItemDisplayOptions({
|
||||
views: [this.itemManager.trashSmartView],
|
||||
application.items.setPrimaryItemDisplayOptions({
|
||||
views: [application.items.trashSmartView],
|
||||
})
|
||||
|
||||
expect(this.itemManager.getDisplayableNotes().length).to.equal(1)
|
||||
expect(application.items.getDisplayableNotes().length).to.equal(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSortedTagsForNote', async function () {
|
||||
it('should return tags associated with a note in natural order', async function () {
|
||||
const tags = [
|
||||
await this.itemManager.findOrCreateTagByTitle('tag 100'),
|
||||
await this.itemManager.findOrCreateTagByTitle('tag 2'),
|
||||
await this.itemManager.findOrCreateTagByTitle('tag b'),
|
||||
await this.itemManager.findOrCreateTagByTitle('tag a'),
|
||||
await application.mutator.findOrCreateTagByTitle({ title: 'tag 100' }),
|
||||
await application.mutator.findOrCreateTagByTitle({ title: 'tag 2' }),
|
||||
await application.mutator.findOrCreateTagByTitle({ title: 'tag b' }),
|
||||
await application.mutator.findOrCreateTagByTitle({ title: 'tag a' }),
|
||||
]
|
||||
|
||||
const note = await this.createNote()
|
||||
const note = await createNote()
|
||||
|
||||
tags.map(async (tag) => {
|
||||
await this.itemManager.changeItem(tag, (mutator) => {
|
||||
await application.mutator.changeItem(tag, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(note)
|
||||
})
|
||||
})
|
||||
|
||||
const results = this.itemManager.getSortedTagsForItem(note)
|
||||
const results = application.items.getSortedTagsForItem(note)
|
||||
|
||||
expect(results).lengthOf(tags.length)
|
||||
expect(results[0].title).to.equal(tags[1].title)
|
||||
@@ -583,16 +382,16 @@ describe('item manager', function () {
|
||||
describe('getTagParentChain', function () {
|
||||
it('should return parent tags for a tag', async function () {
|
||||
const [parent, child, grandchild, _other] = await Promise.all([
|
||||
this.itemManager.findOrCreateTagByTitle('parent'),
|
||||
this.itemManager.findOrCreateTagByTitle('parent.child'),
|
||||
this.itemManager.findOrCreateTagByTitle('parent.child.grandchild'),
|
||||
this.itemManager.findOrCreateTagByTitle('some other tag'),
|
||||
application.mutator.findOrCreateTagByTitle({ title: 'parent' }),
|
||||
application.mutator.findOrCreateTagByTitle({ title: 'parent.child' }),
|
||||
application.mutator.findOrCreateTagByTitle({ title: 'parent.child.grandchild' }),
|
||||
application.mutator.findOrCreateTagByTitle({ title: 'some other tag' }),
|
||||
])
|
||||
|
||||
await this.itemManager.setTagParent(parent, child)
|
||||
await this.itemManager.setTagParent(child, grandchild)
|
||||
await application.mutator.setTagParent(parent, child)
|
||||
await application.mutator.setTagParent(child, grandchild)
|
||||
|
||||
const results = this.itemManager.getTagParentChain(grandchild)
|
||||
const results = application.items.getTagParentChain(grandchild)
|
||||
|
||||
expect(results).lengthOf(2)
|
||||
expect(results[0].uuid).to.equal(parent.uuid)
|
||||
|
||||
@@ -200,7 +200,9 @@ describe('key recovery service', function () {
|
||||
const receiveChallenge = (challenge) => {
|
||||
totalPromptCount++
|
||||
/** Give unassociated password when prompted */
|
||||
application.submitValuesForChallenge(challenge, [CreateChallengeValue(challenge.prompts[0], unassociatedPassword)])
|
||||
application.submitValuesForChallenge(challenge, [
|
||||
CreateChallengeValue(challenge.prompts[0], unassociatedPassword),
|
||||
])
|
||||
}
|
||||
await application.prepareForLaunch({ receiveChallenge })
|
||||
await application.launch(true)
|
||||
@@ -272,7 +274,9 @@ describe('key recovery service', function () {
|
||||
expect(result.error).to.not.be.ok
|
||||
expect(contextB.application.items.getAnyItems(ContentType.ItemsKey).length).to.equal(2)
|
||||
|
||||
const newItemsKey = contextB.application.items.getDisplayableItemsKeys().find((k) => k.uuid !== originalItemsKey.uuid)
|
||||
const newItemsKey = contextB.application.items
|
||||
.getDisplayableItemsKeys()
|
||||
.find((k) => k.uuid !== originalItemsKey.uuid)
|
||||
|
||||
const note = await Factory.createSyncedNote(contextB.application)
|
||||
|
||||
@@ -432,6 +436,7 @@ describe('key recovery service', function () {
|
||||
expect(decryptedKey.content.itemsKey).to.equal(correctItemsKey.content.itemsKey)
|
||||
|
||||
expect(application.syncService.isOutOfSync()).to.equal(false)
|
||||
|
||||
await context.deinit()
|
||||
})
|
||||
|
||||
@@ -457,6 +462,8 @@ describe('key recovery service', function () {
|
||||
updated_at: newUpdated,
|
||||
})
|
||||
|
||||
context.disableKeyRecovery()
|
||||
|
||||
await context.receiveServerResponse({ retrievedItems: [errored.ejected()] })
|
||||
|
||||
/** Our current items key should not be overwritten */
|
||||
@@ -567,7 +574,9 @@ describe('key recovery service', function () {
|
||||
const application = context.application
|
||||
const receiveChallenge = (challenge) => {
|
||||
/** Give unassociated password when prompted */
|
||||
application.submitValuesForChallenge(challenge, [CreateChallengeValue(challenge.prompts[0], unassociatedPassword)])
|
||||
application.submitValuesForChallenge(challenge, [
|
||||
CreateChallengeValue(challenge.prompts[0], unassociatedPassword),
|
||||
])
|
||||
}
|
||||
await application.prepareForLaunch({ receiveChallenge })
|
||||
await application.launch(true)
|
||||
@@ -667,13 +676,15 @@ describe('key recovery service', function () {
|
||||
const stored = (await appA.deviceInterface.getAllDatabaseEntries(appA.identifier)).find(
|
||||
(payload) => payload.uuid === newDefaultKey.uuid,
|
||||
)
|
||||
const storedParams = await appA.protocolService.getKeyEmbeddedKeyParams(new EncryptedPayload(stored))
|
||||
const storedParams = await appA.protocolService.getKeyEmbeddedKeyParamsFromItemsKey(new EncryptedPayload(stored))
|
||||
|
||||
const correctStored = (await appB.deviceInterface.getAllDatabaseEntries(appB.identifier)).find(
|
||||
(payload) => payload.uuid === newDefaultKey.uuid,
|
||||
)
|
||||
|
||||
const correctParams = await appB.protocolService.getKeyEmbeddedKeyParams(new EncryptedPayload(correctStored))
|
||||
const correctParams = await appB.protocolService.getKeyEmbeddedKeyParamsFromItemsKey(
|
||||
new EncryptedPayload(correctStored),
|
||||
)
|
||||
|
||||
expect(storedParams).to.eql(correctParams)
|
||||
|
||||
|
||||
@@ -141,7 +141,8 @@ describe('keys', function () {
|
||||
})
|
||||
|
||||
it('should use items key for encryption of note', async function () {
|
||||
const keyToUse = await this.application.protocolService.itemsEncryption.keyToUseForItemEncryption()
|
||||
const notePayload = Factory.createNotePayload()
|
||||
const keyToUse = await this.application.protocolService.itemsEncryption.keyToUseForItemEncryption(notePayload)
|
||||
expect(keyToUse.content_type).to.equal(ContentType.ItemsKey)
|
||||
})
|
||||
|
||||
@@ -153,7 +154,7 @@ describe('keys', function () {
|
||||
},
|
||||
})
|
||||
|
||||
const itemsKey = this.application.protocolService.itemsKeyForPayload(encryptedPayload)
|
||||
const itemsKey = this.application.protocolService.itemsKeyForEncryptedPayload(encryptedPayload)
|
||||
expect(itemsKey).to.be.ok
|
||||
})
|
||||
|
||||
@@ -166,7 +167,7 @@ describe('keys', function () {
|
||||
},
|
||||
})
|
||||
|
||||
const itemsKey = this.application.protocolService.itemsKeyForPayload(encryptedPayload)
|
||||
const itemsKey = this.application.protocolService.itemsKeyForEncryptedPayload(encryptedPayload)
|
||||
expect(itemsKey).to.be.ok
|
||||
|
||||
const decryptedPayload = await this.application.protocolService.decryptSplitSingle({
|
||||
@@ -187,7 +188,7 @@ describe('keys', function () {
|
||||
},
|
||||
})
|
||||
|
||||
const itemsKey = this.application.protocolService.itemsKeyForPayload(encryptedPayload)
|
||||
const itemsKey = this.application.protocolService.itemsKeyForEncryptedPayload(encryptedPayload)
|
||||
|
||||
await this.application.itemManager.removeItemLocally(itemsKey)
|
||||
|
||||
@@ -197,14 +198,14 @@ describe('keys', function () {
|
||||
},
|
||||
})
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads([erroredPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([erroredPayload], PayloadEmitSource.LocalChanged)
|
||||
|
||||
const note = this.application.itemManager.findAnyItem(notePayload.uuid)
|
||||
expect(note.errorDecrypting).to.equal(true)
|
||||
expect(note.waitingForKey).to.equal(true)
|
||||
|
||||
const keyPayload = new DecryptedPayload(itemsKey.payload.ejected())
|
||||
await this.application.itemManager.emitItemsFromPayloads([keyPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([keyPayload], PayloadEmitSource.LocalChanged)
|
||||
|
||||
/**
|
||||
* Sleeping is required to trigger asyncronous protocolService.decryptItemsWaitingForKeys,
|
||||
@@ -238,7 +239,7 @@ describe('keys', function () {
|
||||
},
|
||||
})
|
||||
|
||||
await this.application.syncService.handleSuccessServerResponse({ payloadsSavedOrSaving: [] }, response)
|
||||
await this.application.syncService.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response)
|
||||
|
||||
const refreshedKey = this.application.payloadManager.findOne(itemsKey.uuid)
|
||||
|
||||
@@ -273,10 +274,8 @@ describe('keys', function () {
|
||||
const rawPayloads = await this.application.diskStorageService.getAllRawPayloads()
|
||||
const itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === itemsKey.uuid)
|
||||
const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload)
|
||||
const operator = this.application.protocolService.operatorManager.operatorForVersion(ProtocolVersion.V004)
|
||||
const comps = operator.deconstructEncryptedPayloadString(itemsKeyPayload.content)
|
||||
const rawAuthenticatedData = comps.authenticatedData
|
||||
const authenticatedData = await operator.stringToAuthenticatedData(rawAuthenticatedData)
|
||||
|
||||
const authenticatedData = this.context.encryption.getEmbeddedPayloadAuthenticatedData(itemsKeyPayload)
|
||||
const rootKeyParams = await this.application.protocolService.getRootKeyParams()
|
||||
|
||||
expect(authenticatedData.kp).to.be.ok
|
||||
@@ -649,7 +648,7 @@ describe('keys', function () {
|
||||
await contextB.deinit()
|
||||
})
|
||||
|
||||
describe('changing password on 003 client while signed into 004 client should', function () {
|
||||
describe('changing password on 003 client while signed into 004 client', function () {
|
||||
/**
|
||||
* When an 004 client signs into 003 account, it creates a root key based items key.
|
||||
* Then, if the 003 client changes its account password, and the 004 client
|
||||
@@ -658,7 +657,7 @@ describe('keys', function () {
|
||||
* items sync to the 004 client, it can't decrypt them with its existing items key
|
||||
* because its based on the old root key.
|
||||
*/
|
||||
it.skip('add new items key', async function () {
|
||||
it.skip('should add new items key', async function () {
|
||||
this.timeout(Factory.TwentySecondTimeout * 3)
|
||||
let oldClient = this.application
|
||||
|
||||
@@ -718,7 +717,13 @@ describe('keys', function () {
|
||||
await Factory.safeDeinit(oldClient)
|
||||
})
|
||||
|
||||
it('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')
|
||||
this.context.anticipateConsoleError(
|
||||
'Cannot find items key to use for encryption',
|
||||
'No items keys being created in this test',
|
||||
)
|
||||
|
||||
/** Register an 003 account */
|
||||
await Factory.registerOldUser({
|
||||
application: this.application,
|
||||
@@ -734,7 +739,15 @@ describe('keys', function () {
|
||||
await this.application.protocolService.getRootKeyParams(),
|
||||
)
|
||||
const operator = this.application.protocolService.operatorManager.operatorForVersion(ProtocolVersion.V003)
|
||||
const newRootKey = await operator.createRootKey(this.email, this.password)
|
||||
const newRootKeyTemplate = await operator.createRootKey(this.email, this.password)
|
||||
const newRootKey = CreateNewRootKey({
|
||||
...newRootKeyTemplate.content,
|
||||
...{
|
||||
encryptionKeyPair: {},
|
||||
signingKeyPair: {},
|
||||
},
|
||||
})
|
||||
|
||||
Object.defineProperty(this.application.apiService, 'apiVersion', {
|
||||
get: function () {
|
||||
return '20190520'
|
||||
@@ -748,7 +761,7 @@ describe('keys', function () {
|
||||
currentServerPassword: currentRootKey.serverPassword,
|
||||
newRootKey,
|
||||
})
|
||||
await this.application.protocolService.reencryptItemsKeys()
|
||||
await this.application.protocolService.reencryptApplicableItemsAfterUserRootKeyChange()
|
||||
/** Note: this may result in a deadlock if features_service syncs and results in an error */
|
||||
await this.application.sync.sync({ awaitAll: true })
|
||||
|
||||
@@ -776,11 +789,16 @@ describe('keys', function () {
|
||||
* The corrective action was to do a final check in protocolService.handleDownloadFirstSyncCompletion
|
||||
* to ensure there exists an items key corresponding to the user's account version.
|
||||
*/
|
||||
const promise = this.context.awaitNextSucessfulSync()
|
||||
await this.context.sync()
|
||||
await promise
|
||||
|
||||
await this.application.itemManager.removeAllItemsFromMemory()
|
||||
expect(this.application.protocolService.getSureDefaultItemsKey()).to.not.be.ok
|
||||
|
||||
const protocol003 = new SNProtocolOperator003(new SNWebCrypto())
|
||||
const key = await protocol003.createItemsKey()
|
||||
await this.application.itemManager.emitItemFromPayload(
|
||||
await this.application.mutator.emitItemFromPayload(
|
||||
key.payload.copy({
|
||||
content: {
|
||||
...key.payload.content,
|
||||
@@ -791,17 +809,21 @@ describe('keys', function () {
|
||||
updated_at: Date.now(),
|
||||
}),
|
||||
)
|
||||
|
||||
const defaultKey = this.application.protocolService.getSureDefaultItemsKey()
|
||||
expect(defaultKey.keyVersion).to.equal(ProtocolVersion.V003)
|
||||
expect(defaultKey.uuid).to.equal(key.uuid)
|
||||
|
||||
await Factory.registerUserToApplication({ application: this.application })
|
||||
expect(await this.application.protocolService.itemsEncryption.keyToUseForItemEncryption()).to.be.ok
|
||||
|
||||
const notePayload = Factory.createNotePayload()
|
||||
expect(await this.application.protocolService.itemsEncryption.keyToUseForItemEncryption(notePayload)).to.be.ok
|
||||
})
|
||||
|
||||
it('having unsynced items keys should resync them upon download first sync completion', async function () {
|
||||
await Factory.registerUserToApplication({ application: this.application })
|
||||
const itemsKey = this.application.itemManager.getDisplayableItemsKeys()[0]
|
||||
await this.application.itemManager.emitItemFromPayload(
|
||||
await this.application.mutator.emitItemFromPayload(
|
||||
itemsKey.payload.copy({
|
||||
dirty: false,
|
||||
updated_at: new Date(0),
|
||||
|
||||
@@ -2,6 +2,7 @@ import FakeWebCrypto from './fake_web_crypto.js'
|
||||
import * as Applications from './Applications.js'
|
||||
import * as Utils from './Utils.js'
|
||||
import * as Defaults from './Defaults.js'
|
||||
import * as Events from './Events.js'
|
||||
import { createNotePayload } from './Items.js'
|
||||
|
||||
UuidGenerator.SetGenerator(new FakeWebCrypto().generateUUID)
|
||||
@@ -11,6 +12,8 @@ const MaximumSyncOptions = {
|
||||
awaitAll: true,
|
||||
}
|
||||
|
||||
let GlobalSubscriptionIdCounter = 1001
|
||||
|
||||
export class AppContext {
|
||||
constructor({ identifier, crypto, email, password, passcode, host } = {}) {
|
||||
this.identifier = identifier || `${Math.random()}`
|
||||
@@ -46,6 +49,62 @@ export class AppContext {
|
||||
)
|
||||
}
|
||||
|
||||
get vaults() {
|
||||
return this.application.vaultService
|
||||
}
|
||||
|
||||
get sessions() {
|
||||
return this.application.sessions
|
||||
}
|
||||
|
||||
get items() {
|
||||
return this.application.items
|
||||
}
|
||||
|
||||
get mutator() {
|
||||
return this.application.mutator
|
||||
}
|
||||
|
||||
get payloads() {
|
||||
return this.application.payloadManager
|
||||
}
|
||||
|
||||
get encryption() {
|
||||
return this.application.protocolService
|
||||
}
|
||||
|
||||
get contacts() {
|
||||
return this.application.contactService
|
||||
}
|
||||
|
||||
get sharedVaults() {
|
||||
return this.application.sharedVaultService
|
||||
}
|
||||
|
||||
get files() {
|
||||
return this.application.fileService
|
||||
}
|
||||
|
||||
get keys() {
|
||||
return this.application.keySystemKeyManager
|
||||
}
|
||||
|
||||
get asymmetric() {
|
||||
return this.application.asymmetricMessageService
|
||||
}
|
||||
|
||||
get publicKey() {
|
||||
return this.sessions.getPublicKey()
|
||||
}
|
||||
|
||||
get signingPublicKey() {
|
||||
return this.sessions.getSigningPublicKey()
|
||||
}
|
||||
|
||||
get privateKey() {
|
||||
return this.encryption.getKeyPair().privateKey
|
||||
}
|
||||
|
||||
ignoreChallenges() {
|
||||
this.ignoringChallenges = true
|
||||
}
|
||||
@@ -118,7 +177,10 @@ export class AppContext {
|
||||
},
|
||||
})
|
||||
|
||||
return this.application.syncService.handleSuccessServerResponse({ payloadsSavedOrSaving: [] }, response)
|
||||
return this.application.syncService.handleSuccessServerResponse(
|
||||
{ payloadsSavedOrSaving: [], options: {} },
|
||||
response,
|
||||
)
|
||||
}
|
||||
|
||||
resolveWhenKeyRecovered(uuid) {
|
||||
@@ -131,6 +193,16 @@ export class AppContext {
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenSharedVaultUserKeysResolved() {
|
||||
return new Promise((resolve) => {
|
||||
this.application.vaultService.collaboration.addEventObserver((eventName) => {
|
||||
if (eventName === SharedVaultServiceEvent.SharedVaultStatusChanged) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async awaitSignInEvent() {
|
||||
return new Promise((resolve) => {
|
||||
this.application.userService.addEventObserver((eventName) => {
|
||||
@@ -182,6 +254,155 @@ export class AppContext {
|
||||
})
|
||||
}
|
||||
|
||||
awaitNextSyncSharedVaultFromScratchEvent() {
|
||||
return new Promise((resolve) => {
|
||||
const removeObserver = this.application.syncService.addEventObserver((event, data) => {
|
||||
if (event === SyncEvent.PaginatedSyncRequestCompleted && data?.options?.sharedVaultUuids) {
|
||||
removeObserver()
|
||||
resolve(data)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWithUploadedPayloads() {
|
||||
return new Promise((resolve) => {
|
||||
this.application.syncService.addEventObserver((event, data) => {
|
||||
if (event === SyncEvent.PaginatedSyncRequestCompleted) {
|
||||
resolve(data.uploadedPayloads)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWithConflicts() {
|
||||
return new Promise((resolve) => {
|
||||
this.application.syncService.addEventObserver((event, response) => {
|
||||
if (event === SyncEvent.PaginatedSyncRequestCompleted) {
|
||||
resolve(response.rawConflictObjects)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenSavedSyncPayloadsIncludesItemUuid(uuid) {
|
||||
return new Promise((resolve) => {
|
||||
this.application.syncService.addEventObserver((event, response) => {
|
||||
if (event === SyncEvent.PaginatedSyncRequestCompleted) {
|
||||
const savedPayload = response.savedPayloads.find((payload) => payload.uuid === uuid)
|
||||
if (savedPayload) {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenSavedSyncPayloadsIncludesItemThatIsDuplicatedOf(uuid) {
|
||||
return new Promise((resolve) => {
|
||||
this.application.syncService.addEventObserver((event, response) => {
|
||||
if (event === SyncEvent.PaginatedSyncRequestCompleted) {
|
||||
const savedPayload = response.savedPayloads.find((payload) => payload.duplicate_of === uuid)
|
||||
if (savedPayload) {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenItemCompletesAddingToVault(targetItem) {
|
||||
return new Promise((resolve) => {
|
||||
const objectToSpy = this.vaults
|
||||
sinon.stub(objectToSpy, 'moveItemToVault').callsFake(async (vault, item) => {
|
||||
objectToSpy.moveItemToVault.restore()
|
||||
const result = await objectToSpy.moveItemToVault(vault, item)
|
||||
if (!targetItem || item.uuid === targetItem.uuid) {
|
||||
resolve()
|
||||
}
|
||||
return result
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenItemCompletesRemovingFromVault(targetItem) {
|
||||
return new Promise((resolve) => {
|
||||
const objectToSpy = this.vaults
|
||||
sinon.stub(objectToSpy, 'removeItemFromVault').callsFake(async (item) => {
|
||||
objectToSpy.removeItemFromVault.restore()
|
||||
const result = await objectToSpy.removeItemFromVault(item)
|
||||
if (item.uuid === targetItem.uuid) {
|
||||
resolve()
|
||||
}
|
||||
return result
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenAsymmetricMessageProcessingCompletes() {
|
||||
return new Promise((resolve) => {
|
||||
const objectToSpy = this.asymmetric
|
||||
sinon.stub(objectToSpy, 'handleRemoteReceivedAsymmetricMessages').callsFake(async (messages) => {
|
||||
objectToSpy.handleRemoteReceivedAsymmetricMessages.restore()
|
||||
const result = await objectToSpy.handleRemoteReceivedAsymmetricMessages(messages)
|
||||
resolve()
|
||||
return result
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenUserMessagesProcessingCompletes() {
|
||||
return new Promise((resolve) => {
|
||||
const objectToSpy = this.application.userEventService
|
||||
sinon.stub(objectToSpy, 'handleReceivedUserEvents').callsFake(async (params) => {
|
||||
objectToSpy.handleReceivedUserEvents.restore()
|
||||
const result = await objectToSpy.handleReceivedUserEvents(params)
|
||||
resolve()
|
||||
return result
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenSharedVaultServiceSendsContactShareMessage() {
|
||||
return new Promise((resolve) => {
|
||||
const objectToSpy = this.sharedVaults
|
||||
sinon.stub(objectToSpy, 'shareContactWithUserAdministeredSharedVaults').callsFake(async (contact) => {
|
||||
objectToSpy.shareContactWithUserAdministeredSharedVaults.restore()
|
||||
const result = await objectToSpy.shareContactWithUserAdministeredSharedVaults(contact)
|
||||
resolve()
|
||||
return result
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenSharedVaultKeyRotationInvitesGetSent(targetVault) {
|
||||
return new Promise((resolve) => {
|
||||
const objectToSpy = this.sharedVaults
|
||||
sinon.stub(objectToSpy, 'handleVaultRootKeyRotatedEvent').callsFake(async (vault) => {
|
||||
objectToSpy.handleVaultRootKeyRotatedEvent.restore()
|
||||
const result = await objectToSpy.handleVaultRootKeyRotatedEvent(vault)
|
||||
if (vault.systemIdentifier === targetVault.systemIdentifier) {
|
||||
resolve()
|
||||
}
|
||||
return result
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenSharedVaultChangeInvitesAreSent(sharedVaultUuid) {
|
||||
return new Promise((resolve) => {
|
||||
const objectToSpy = this.sharedVaults
|
||||
sinon.stub(objectToSpy, 'handleVaultRootKeyRotatedEvent').callsFake(async (vault) => {
|
||||
objectToSpy.handleVaultRootKeyRotatedEvent.restore()
|
||||
const result = await objectToSpy.handleVaultRootKeyRotatedEvent(vault)
|
||||
if (vault.sharing.sharedVaultUuid === sharedVaultUuid) {
|
||||
resolve()
|
||||
}
|
||||
return result
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
awaitUserPrefsSingletonCreation() {
|
||||
const preferences = this.application.preferencesService.preferences
|
||||
if (preferences) {
|
||||
@@ -232,6 +453,10 @@ export class AppContext {
|
||||
await this.application.sync.sync(options || { awaitAll: true })
|
||||
}
|
||||
|
||||
async clearSyncPositionTokens() {
|
||||
await this.application.sync.clearSyncPositionTokens()
|
||||
}
|
||||
|
||||
async maximumSync() {
|
||||
await this.sync(MaximumSyncOptions)
|
||||
}
|
||||
@@ -290,22 +515,31 @@ export class AppContext {
|
||||
})
|
||||
}
|
||||
|
||||
async createSyncedNote(title, text) {
|
||||
async createSyncedNote(title = 'foo', text = 'bar') {
|
||||
const payload = createNotePayload(title, text)
|
||||
const item = await this.application.items.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
await this.application.items.setItemDirty(item)
|
||||
const item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.setItemDirty(item)
|
||||
await this.application.syncService.sync(MaximumSyncOptions)
|
||||
const note = this.application.items.findItem(payload.uuid)
|
||||
|
||||
return note
|
||||
}
|
||||
|
||||
lockSyncing() {
|
||||
this.application.syncService.lockSyncing()
|
||||
}
|
||||
|
||||
unlockSyncing() {
|
||||
this.application.syncService.unlockSyncing()
|
||||
}
|
||||
|
||||
async deleteItemAndSync(item) {
|
||||
await this.application.mutator.deleteItem(item)
|
||||
await this.sync()
|
||||
}
|
||||
|
||||
async changeNoteTitle(note, title) {
|
||||
return this.application.items.changeNote(note, (mutator) => {
|
||||
return this.application.mutator.changeNote(note, (mutator) => {
|
||||
mutator.title = title
|
||||
})
|
||||
}
|
||||
@@ -325,6 +559,10 @@ export class AppContext {
|
||||
return this.application.items.getDisplayableNotes().length
|
||||
}
|
||||
|
||||
get notes() {
|
||||
return this.application.items.getDisplayableNotes()
|
||||
}
|
||||
|
||||
async createConflictedNotes(otherContext) {
|
||||
const note = await this.createSyncedNote()
|
||||
|
||||
@@ -341,4 +579,41 @@ export class AppContext {
|
||||
conflict: this.findNoteByTitle('title-2'),
|
||||
}
|
||||
}
|
||||
|
||||
findDuplicateNote(duplicateOfUuid) {
|
||||
const items = this.items.getDisplayableNotes()
|
||||
return items.find((note) => note.duplicateOf === duplicateOfUuid)
|
||||
}
|
||||
|
||||
get userUuid() {
|
||||
return this.application.sessions.user.uuid
|
||||
}
|
||||
|
||||
sleep(seconds) {
|
||||
return Utils.sleep(seconds)
|
||||
}
|
||||
|
||||
anticipateConsoleError(message, _reason) {
|
||||
console.warn('Anticipating a console error with message:', message)
|
||||
}
|
||||
|
||||
async publicMockSubscriptionPurchaseEvent() {
|
||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: this.email,
|
||||
subscriptionId: GlobalSubscriptionIdCounter++,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
||||
timestamp: Date.now(),
|
||||
offline: false,
|
||||
discountCode: null,
|
||||
limitedDiscountPurchased: false,
|
||||
newSubscriber: true,
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.0,
|
||||
})
|
||||
|
||||
await Utils.sleep(2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,6 @@ import WebDeviceInterface from './web_device_interface.js'
|
||||
import FakeWebCrypto from './fake_web_crypto.js'
|
||||
import * as Defaults from './Defaults.js'
|
||||
|
||||
export const BaseItemCounts = {
|
||||
DefaultItems: ['ItemsKey', 'UserPreferences', 'DarkTheme'].length,
|
||||
}
|
||||
|
||||
export function createApplicationWithOptions({ identifier, environment, platform, host, crypto, device }) {
|
||||
if (!device) {
|
||||
device = new WebDeviceInterface()
|
||||
|
||||
35
packages/snjs/mocha/lib/BaseItemCounts.js
Normal file
35
packages/snjs/mocha/lib/BaseItemCounts.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const ExpectedItemCountsWithVaultFeatureEnabled = {
|
||||
Items: ['ItemsKey', 'UserPreferences', 'DarkTheme'].length,
|
||||
ItemsWithAccount: ['ItemsKey', 'UserPreferences', 'DarkTheme', 'TrustedSelfContact'].length,
|
||||
ItemsWithAccountWithoutItemsKey: ['UserPreferences', 'DarkTheme', 'TrustedSelfContact'].length,
|
||||
ItemsNoAccounNoItemsKey: ['UserPreferences', 'DarkTheme'].length,
|
||||
BackupFileRootKeyEncryptedItems: ['TrustedSelfContact'].length,
|
||||
}
|
||||
|
||||
const ExpectedItemCountsWithVaultFeatureDisabled = {
|
||||
Items: ['ItemsKey', 'UserPreferences', 'DarkTheme'].length,
|
||||
ItemsWithAccount: ['ItemsKey', 'UserPreferences', 'DarkTheme'].length,
|
||||
ItemsWithAccountWithoutItemsKey: ['UserPreferences', 'DarkTheme'].length,
|
||||
ItemsNoAccounNoItemsKey: ['UserPreferences', 'DarkTheme'].length,
|
||||
BackupFileRootKeyEncryptedItems: [].length,
|
||||
}
|
||||
|
||||
const isVaultsEnabled = InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)
|
||||
|
||||
export const BaseItemCounts = {
|
||||
DefaultItems: isVaultsEnabled
|
||||
? ExpectedItemCountsWithVaultFeatureEnabled.Items
|
||||
: ExpectedItemCountsWithVaultFeatureDisabled.Items,
|
||||
DefaultItemsWithAccount: isVaultsEnabled
|
||||
? ExpectedItemCountsWithVaultFeatureEnabled.ItemsWithAccount
|
||||
: ExpectedItemCountsWithVaultFeatureDisabled.ItemsWithAccount,
|
||||
DefaultItemsWithAccountWithoutItemsKey: isVaultsEnabled
|
||||
? ExpectedItemCountsWithVaultFeatureEnabled.ItemsWithAccountWithoutItemsKey
|
||||
: ExpectedItemCountsWithVaultFeatureDisabled.ItemsWithAccountWithoutItemsKey,
|
||||
DefaultItemsNoAccounNoItemsKey: isVaultsEnabled
|
||||
? ExpectedItemCountsWithVaultFeatureEnabled.ItemsNoAccounNoItemsKey
|
||||
: ExpectedItemCountsWithVaultFeatureDisabled.ItemsNoAccounNoItemsKey,
|
||||
BackupFileRootKeyEncryptedItems: isVaultsEnabled
|
||||
? ExpectedItemCountsWithVaultFeatureEnabled.BackupFileRootKeyEncryptedItems
|
||||
: ExpectedItemCountsWithVaultFeatureDisabled.BackupFileRootKeyEncryptedItems,
|
||||
}
|
||||
140
packages/snjs/mocha/lib/Collaboration.js
Normal file
140
packages/snjs/mocha/lib/Collaboration.js
Normal file
@@ -0,0 +1,140 @@
|
||||
import * as Factory from './factory.js'
|
||||
|
||||
export const createContactContext = async () => {
|
||||
const contactContext = await Factory.createAppContextWithRealCrypto()
|
||||
await contactContext.launch()
|
||||
await contactContext.register()
|
||||
|
||||
return {
|
||||
contactContext,
|
||||
deinitContactContext: contactContext.deinit.bind(contactContext),
|
||||
}
|
||||
}
|
||||
|
||||
export const createTrustedContactForUserOfContext = async (
|
||||
contextAddingNewContact,
|
||||
contextImportingContactInfoFrom,
|
||||
) => {
|
||||
const contact = await contextAddingNewContact.application.contactService.createOrEditTrustedContact({
|
||||
name: 'John Doe',
|
||||
publicKey: contextImportingContactInfoFrom.publicKey,
|
||||
signingPublicKey: contextImportingContactInfoFrom.signingPublicKey,
|
||||
contactUuid: contextImportingContactInfoFrom.userUuid,
|
||||
})
|
||||
|
||||
return contact
|
||||
}
|
||||
|
||||
export const acceptAllInvites = async (context) => {
|
||||
const inviteRecords = context.sharedVaults.getCachedPendingInviteRecords()
|
||||
for (const record of inviteRecords) {
|
||||
await context.sharedVaults.acceptPendingSharedVaultInvite(record)
|
||||
}
|
||||
}
|
||||
|
||||
export const createSharedVaultWithAcceptedInvite = async (context, permissions = SharedVaultPermission.Write) => {
|
||||
const { sharedVault, contact, contactContext, deinitContactContext } =
|
||||
await createSharedVaultWithUnacceptedButTrustedInvite(context, permissions)
|
||||
|
||||
const promise = contactContext.awaitNextSyncSharedVaultFromScratchEvent()
|
||||
|
||||
await acceptAllInvites(contactContext)
|
||||
|
||||
await promise
|
||||
|
||||
const contactVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
|
||||
|
||||
return { sharedVault, contact, contactVault, contactContext, deinitContactContext }
|
||||
}
|
||||
|
||||
export const createSharedVaultWithAcceptedInviteAndNote = async (
|
||||
context,
|
||||
permissions = SharedVaultPermission.Write,
|
||||
) => {
|
||||
const { sharedVault, contactContext, contact, deinitContactContext } = await createSharedVaultWithAcceptedInvite(
|
||||
context,
|
||||
permissions,
|
||||
)
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
const updatedNote = await moveItemToVault(context, sharedVault, note)
|
||||
await contactContext.sync()
|
||||
|
||||
return { sharedVault, note: updatedNote, contact, contactContext, deinitContactContext }
|
||||
}
|
||||
|
||||
export const createSharedVaultWithUnacceptedButTrustedInvite = async (
|
||||
context,
|
||||
permissions = SharedVaultPermission.Write,
|
||||
) => {
|
||||
const sharedVault = await createSharedVault(context)
|
||||
|
||||
const { contactContext, deinitContactContext } = await createContactContext()
|
||||
const contact = await createTrustedContactForUserOfContext(context, contactContext)
|
||||
await createTrustedContactForUserOfContext(contactContext, context)
|
||||
|
||||
const invite = await context.sharedVaults.inviteContactToSharedVault(sharedVault, contact, permissions)
|
||||
await contactContext.sync()
|
||||
|
||||
return { sharedVault, contact, contactContext, deinitContactContext, invite }
|
||||
}
|
||||
|
||||
export const createSharedVaultWithUnacceptedAndUntrustedInvite = async (
|
||||
context,
|
||||
permissions = SharedVaultPermission.Write,
|
||||
) => {
|
||||
const sharedVault = await createSharedVault(context)
|
||||
|
||||
const { contactContext, deinitContactContext } = await createContactContext()
|
||||
const contact = await createTrustedContactForUserOfContext(context, contactContext)
|
||||
|
||||
const invite = await context.sharedVaults.inviteContactToSharedVault(sharedVault, contact, permissions)
|
||||
await contactContext.sync()
|
||||
|
||||
return { sharedVault, contact, contactContext, deinitContactContext, invite }
|
||||
}
|
||||
|
||||
export const inviteNewPartyToSharedVault = async (context, sharedVault, permissions = SharedVaultPermission.Write) => {
|
||||
const { contactContext: thirdPartyContext, deinitContactContext: deinitThirdPartyContext } =
|
||||
await createContactContext()
|
||||
|
||||
const thirdPartyContact = await createTrustedContactForUserOfContext(context, thirdPartyContext)
|
||||
await createTrustedContactForUserOfContext(thirdPartyContext, context)
|
||||
await context.sharedVaults.inviteContactToSharedVault(sharedVault, thirdPartyContact, permissions)
|
||||
|
||||
await thirdPartyContext.sync()
|
||||
|
||||
return { thirdPartyContext, thirdPartyContact, deinitThirdPartyContext }
|
||||
}
|
||||
|
||||
export const createPrivateVault = async (context) => {
|
||||
const privateVault = await context.vaults.createRandomizedVault({
|
||||
name: 'My Private Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
|
||||
return privateVault
|
||||
}
|
||||
|
||||
export const createSharedVault = async (context) => {
|
||||
const sharedVault = await context.sharedVaults.createSharedVault({ name: 'My Shared Vault' })
|
||||
|
||||
if (isClientDisplayableError(sharedVault)) {
|
||||
throw new Error(sharedVault.text)
|
||||
}
|
||||
|
||||
return sharedVault
|
||||
}
|
||||
|
||||
export const createSharedVaultWithNote = async (context) => {
|
||||
const sharedVault = await createSharedVault(context)
|
||||
const note = await context.createSyncedNote()
|
||||
const updatedNote = await moveItemToVault(context, sharedVault, note)
|
||||
return { sharedVault, note: updatedNote }
|
||||
}
|
||||
|
||||
export const moveItemToVault = async (context, sharedVault, item) => {
|
||||
const promise = context.resolveWhenItemCompletesAddingToVault(item)
|
||||
const updatedItem = await context.vaults.moveItemToVault(sharedVault, item)
|
||||
await promise
|
||||
return updatedItem
|
||||
}
|
||||
19
packages/snjs/mocha/lib/Events.js
Normal file
19
packages/snjs/mocha/lib/Events.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as Defaults from './Defaults.js'
|
||||
|
||||
export async function publishMockedEvent(eventType, eventPayload) {
|
||||
const response = await fetch(`${Defaults.getDefaultMockedEventServiceUrl()}/events`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
eventType,
|
||||
eventPayload,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to publish mocked event: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export async function uploadFile(fileService, buffer, name, ext, chunkSize) {
|
||||
const operation = await fileService.beginNewFileUpload(buffer.byteLength)
|
||||
export async function uploadFile(fileService, buffer, name, ext, chunkSize, vault) {
|
||||
const operation = await fileService.beginNewFileUpload(buffer.byteLength, vault)
|
||||
|
||||
let chunkId = 1
|
||||
for (let i = 0; i < buffer.length; i += chunkSize) {
|
||||
@@ -18,14 +18,16 @@ export async function uploadFile(fileService, buffer, name, ext, chunkSize) {
|
||||
return file
|
||||
}
|
||||
|
||||
export async function downloadFile(fileService, itemManager, remoteIdentifier) {
|
||||
const file = itemManager.getItems(ContentType.File).find((file) => file.remoteIdentifier === remoteIdentifier)
|
||||
|
||||
export async function downloadFile(fileService, file) {
|
||||
let receivedBytes = new Uint8Array()
|
||||
|
||||
await fileService.downloadFile(file, (decryptedBytes) => {
|
||||
const error = await fileService.downloadFile(file, (decryptedBytes) => {
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...decryptedBytes])
|
||||
})
|
||||
|
||||
if (error) {
|
||||
throw new Error('Could not download file', error.text)
|
||||
}
|
||||
|
||||
return receivedBytes
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export function createRelatedNoteTagPairPayload({ noteTitle, noteText, tagTitle,
|
||||
|
||||
export async function createSyncedNoteWithTag(application) {
|
||||
const payloads = createRelatedNoteTagPairPayload()
|
||||
await application.itemManager.emitItemsFromPayloads(payloads)
|
||||
await application.mutator.emitItemsFromPayloads(payloads)
|
||||
return application.sync.sync(MaximumSyncOptions)
|
||||
}
|
||||
|
||||
|
||||
@@ -71,24 +71,6 @@ export function getDefaultHost() {
|
||||
return Defaults.getDefaultHost()
|
||||
}
|
||||
|
||||
export async function publishMockedEvent(eventType, eventPayload) {
|
||||
const response = await fetch(`${Defaults.getDefaultMockedEventServiceUrl()}/events`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
eventType,
|
||||
eventPayload,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to publish mocked event: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function createApplicationWithFakeCrypto(identifier, environment, platform, host) {
|
||||
return Applications.createApplicationWithFakeCrypto(identifier, environment, platform, host)
|
||||
}
|
||||
@@ -154,7 +136,7 @@ export async function registerOldUser({ application, email, password, version })
|
||||
keyParams: accountKey.keyParams,
|
||||
})
|
||||
/** Mark all existing items as dirty. */
|
||||
await application.itemManager.changeItems(application.itemManager.items, (m) => {
|
||||
await application.mutator.changeItems(application.itemManager.items, (m) => {
|
||||
m.dirty = true
|
||||
})
|
||||
await application.sessionManager.handleSuccessAuthResponse(response, accountKey)
|
||||
@@ -188,18 +170,18 @@ export function itemToStoragePayload(item) {
|
||||
|
||||
export function createMappedNote(application, title, text, dirty = true) {
|
||||
const payload = createNotePayload(title, text, dirty)
|
||||
return application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
return application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
}
|
||||
|
||||
export async function createMappedTag(application, tagParams = {}) {
|
||||
const payload = createStorageItemTagPayload(tagParams)
|
||||
return application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
return application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
}
|
||||
|
||||
export async function createSyncedNote(application, title, text) {
|
||||
const payload = createNotePayload(title, text)
|
||||
const item = await application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
await application.itemManager.setItemDirty(item)
|
||||
const item = await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
await application.mutator.setItemDirty(item)
|
||||
await application.syncService.sync(syncOptions)
|
||||
const note = application.items.findItem(payload.uuid)
|
||||
return note
|
||||
@@ -218,7 +200,7 @@ export async function createManyMappedNotes(application, count) {
|
||||
const createdNotes = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
const note = await createMappedNote(application)
|
||||
await application.itemManager.setItemDirty(note)
|
||||
await application.mutator.setItemDirty(note)
|
||||
createdNotes.push(note)
|
||||
}
|
||||
return createdNotes
|
||||
@@ -406,7 +388,7 @@ export function pinNote(application, note) {
|
||||
}
|
||||
|
||||
export async function insertItemWithOverride(application, contentType, content, needsSync = false, errorDecrypting) {
|
||||
const item = await application.itemManager.createItem(contentType, content, needsSync)
|
||||
const item = await application.mutator.createItem(contentType, content, needsSync)
|
||||
|
||||
if (errorDecrypting) {
|
||||
const encrypted = new EncryptedPayload({
|
||||
@@ -415,12 +397,12 @@ export async function insertItemWithOverride(application, contentType, content,
|
||||
errorDecrypting,
|
||||
})
|
||||
|
||||
await application.itemManager.emitItemFromPayload(encrypted)
|
||||
await application.payloadManager.emitPayload(encrypted)
|
||||
} else {
|
||||
const decrypted = new DecryptedPayload({
|
||||
...item.payload.ejected(),
|
||||
})
|
||||
await application.itemManager.emitItemFromPayload(decrypted)
|
||||
await application.payloadManager.emitPayload(decrypted)
|
||||
}
|
||||
|
||||
return application.itemManager.findAnyItem(item.uuid)
|
||||
@@ -441,7 +423,7 @@ export async function markDirtyAndSyncItem(application, itemToLookupUuidFor) {
|
||||
throw Error('Attempting to save non-inserted item')
|
||||
}
|
||||
if (!item.dirty) {
|
||||
await application.itemManager.changeItem(item, undefined, MutationType.NoUpdateUserTimestamps)
|
||||
await application.mutator.changeItem(item, undefined, MutationType.NoUpdateUserTimestamps)
|
||||
}
|
||||
await application.sync.sync()
|
||||
}
|
||||
@@ -467,23 +449,22 @@ export async function changePayloadTimeStamp(application, payload, timestamp, co
|
||||
updated_at_timestamp: timestamp,
|
||||
})
|
||||
|
||||
await application.itemManager.emitItemFromPayload(changedPayload)
|
||||
await application.mutator.emitItemFromPayload(changedPayload)
|
||||
|
||||
return application.itemManager.findAnyItem(payload.uuid)
|
||||
}
|
||||
|
||||
export async function changePayloadUpdatedAt(application, payload, updatedAt) {
|
||||
const latestPayload = application.payloadManager.collection.find(payload.uuid)
|
||||
|
||||
const changedPayload = new DecryptedPayload({
|
||||
...latestPayload,
|
||||
...latestPayload.ejected(),
|
||||
dirty: true,
|
||||
dirtyIndex: getIncrementedDirtyIndex(),
|
||||
updated_at: updatedAt,
|
||||
})
|
||||
|
||||
await application.itemManager.emitItemFromPayload(changedPayload)
|
||||
|
||||
return application.itemManager.findAnyItem(payload.uuid)
|
||||
return application.mutator.emitItemFromPayload(changedPayload)
|
||||
}
|
||||
|
||||
export async function changePayloadTimeStampDeleteAndSync(application, payload, timestamp, syncOptions) {
|
||||
@@ -497,6 +478,6 @@ export async function changePayloadTimeStampDeleteAndSync(application, payload,
|
||||
updated_at_timestamp: timestamp,
|
||||
})
|
||||
|
||||
await application.itemManager.emitItemFromPayload(changedPayload)
|
||||
await application.payloadManager.emitPayload(changedPayload)
|
||||
await application.sync.sync(syncOptions)
|
||||
}
|
||||
|
||||
@@ -158,8 +158,39 @@ export default class FakeWebCrypto {
|
||||
return data.message
|
||||
}
|
||||
|
||||
sodiumCryptoBoxGenerateKeypair() {
|
||||
return { publicKey: this.randomString(64), privateKey: this.randomString(64), keyType: 'x25519' }
|
||||
sodiumCryptoSign(message, secretKey) {
|
||||
const data = {
|
||||
message,
|
||||
secretKey,
|
||||
}
|
||||
return btoa(JSON.stringify(data))
|
||||
}
|
||||
|
||||
sodiumCryptoKdfDeriveFromKey(key, subkeyNumber, subkeyLength, context) {
|
||||
return btoa(key + subkeyNumber + subkeyLength + context)
|
||||
}
|
||||
|
||||
sodiumCryptoGenericHash(message, key) {
|
||||
return btoa(message + key)
|
||||
}
|
||||
|
||||
sodiumCryptoSignVerify(message, signature, publicKey) {
|
||||
return true
|
||||
}
|
||||
|
||||
sodiumCryptoBoxSeedKeypair(seed) {
|
||||
return {
|
||||
privateKey: seed,
|
||||
publicKey: seed,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sodiumCryptoSignSeedKeypair(seed) {
|
||||
return {
|
||||
privateKey: seed,
|
||||
publicKey: seed,
|
||||
}
|
||||
}
|
||||
|
||||
generateOtpSecret() {
|
||||
|
||||
@@ -37,6 +37,10 @@ export default class WebDeviceInterface {
|
||||
return {}
|
||||
}
|
||||
|
||||
clearAllDataFromDevice() {
|
||||
localStorage.clear()
|
||||
}
|
||||
|
||||
_getDatabaseKeyPrefix(identifier) {
|
||||
if (identifier) {
|
||||
return `${identifier}-item-`
|
||||
@@ -61,29 +65,45 @@ export default class WebDeviceInterface {
|
||||
|
||||
async getDatabaseLoadChunks(options, identifier) {
|
||||
const entries = await this.getAllDatabaseEntries(identifier)
|
||||
const sorted = GetSortedPayloadsByPriority(entries, options)
|
||||
const {
|
||||
itemsKeyPayloads,
|
||||
keySystemRootKeyPayloads,
|
||||
keySystemItemsKeyPayloads,
|
||||
contentTypePriorityPayloads,
|
||||
remainingPayloads,
|
||||
} = GetSortedPayloadsByPriority(entries, options)
|
||||
|
||||
const itemsKeysChunk = {
|
||||
entries: sorted.itemsKeyPayloads,
|
||||
entries: itemsKeyPayloads,
|
||||
}
|
||||
|
||||
const keySystemRootKeysChunk = {
|
||||
entries: keySystemRootKeyPayloads,
|
||||
}
|
||||
|
||||
const keySystemItemsKeysChunk = {
|
||||
entries: keySystemItemsKeyPayloads,
|
||||
}
|
||||
|
||||
const contentTypePriorityChunk = {
|
||||
entries: sorted.contentTypePriorityPayloads,
|
||||
entries: contentTypePriorityPayloads,
|
||||
}
|
||||
|
||||
const remainingPayloadsChunks = []
|
||||
for (let i = 0; i < sorted.remainingPayloads.length; i += options.batchSize) {
|
||||
for (let i = 0; i < remainingPayloads.length; i += options.batchSize) {
|
||||
remainingPayloadsChunks.push({
|
||||
entries: sorted.remainingPayloads.slice(i, i + options.batchSize),
|
||||
entries: remainingPayloads.slice(i, i + options.batchSize),
|
||||
})
|
||||
}
|
||||
|
||||
const result = {
|
||||
fullEntries: {
|
||||
itemsKeys: itemsKeysChunk,
|
||||
keySystemRootKeys: keySystemRootKeysChunk,
|
||||
keySystemItemsKeys: keySystemItemsKeysChunk,
|
||||
remainingChunks: [contentTypePriorityChunk, ...remainingPayloadsChunks],
|
||||
},
|
||||
remainingChunksItemCount: sorted.contentTypePriorityPayloads.length + sorted.remainingPayloads.length,
|
||||
remainingChunksItemCount: contentTypePriorityPayloads.length + remainingPayloads.length,
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('migrations', () => {
|
||||
}),
|
||||
}),
|
||||
)
|
||||
await application.mutator.insertItem(mfaItem)
|
||||
await application.mutator.insertItem(mfaItem, true)
|
||||
await application.sync.sync()
|
||||
|
||||
expect(application.items.getItems('SF|MFA').length).to.equal(1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable camelcase */
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from '../lib/Applications.js'
|
||||
import { BaseItemCounts } from '../lib/BaseItemCounts.js'
|
||||
import * as Factory from '../lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -70,8 +70,8 @@ describe('app models', () => {
|
||||
},
|
||||
})
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
|
||||
await this.application.itemManager.emitItemsFromPayloads([params2], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([params2], PayloadEmitSource.LocalChanged)
|
||||
|
||||
const item1 = this.application.itemManager.findItem(params1.uuid)
|
||||
const item2 = this.application.itemManager.findItem(params2.uuid)
|
||||
@@ -93,11 +93,11 @@ describe('app models', () => {
|
||||
},
|
||||
})
|
||||
|
||||
let items = await this.application.itemManager.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
|
||||
let items = await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
|
||||
let item = items[0]
|
||||
expect(item).to.be.ok
|
||||
|
||||
items = await this.application.itemManager.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
|
||||
items = await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
|
||||
item = items[0]
|
||||
|
||||
expect(item.content.foo).to.equal('bar')
|
||||
@@ -108,10 +108,10 @@ describe('app models', () => {
|
||||
const item1 = await Factory.createMappedNote(this.application)
|
||||
const item2 = await Factory.createMappedNote(this.application)
|
||||
|
||||
await this.application.itemManager.changeItem(item1, (mutator) => {
|
||||
await this.application.mutator.changeItem(item1, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
|
||||
})
|
||||
await this.application.itemManager.changeItem(item2, (mutator) => {
|
||||
await this.application.mutator.changeItem(item2, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item1)
|
||||
})
|
||||
|
||||
@@ -123,10 +123,10 @@ describe('app models', () => {
|
||||
var item1 = await Factory.createMappedNote(this.application)
|
||||
var item2 = await Factory.createMappedNote(this.application)
|
||||
|
||||
await this.application.itemManager.changeItem(item1, (mutator) => {
|
||||
await this.application.mutator.changeItem(item1, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
|
||||
})
|
||||
await this.application.itemManager.changeItem(item2, (mutator) => {
|
||||
await this.application.mutator.changeItem(item2, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item1)
|
||||
})
|
||||
|
||||
@@ -143,7 +143,7 @@ describe('app models', () => {
|
||||
references: [],
|
||||
},
|
||||
})
|
||||
await this.application.itemManager.emitItemsFromPayloads([damagedPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([damagedPayload], PayloadEmitSource.LocalChanged)
|
||||
|
||||
const refreshedItem1_2 = this.application.itemManager.findItem(item1.uuid)
|
||||
const refreshedItem2_2 = this.application.itemManager.findItem(item2.uuid)
|
||||
@@ -155,10 +155,10 @@ describe('app models', () => {
|
||||
it('creating and removing relationships between two items should have valid references', async function () {
|
||||
var item1 = await Factory.createMappedNote(this.application)
|
||||
var item2 = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.changeItem(item1, (mutator) => {
|
||||
await this.application.mutator.changeItem(item1, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
|
||||
})
|
||||
await this.application.itemManager.changeItem(item2, (mutator) => {
|
||||
await this.application.mutator.changeItem(item2, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item1)
|
||||
})
|
||||
|
||||
@@ -171,10 +171,10 @@ describe('app models', () => {
|
||||
expect(this.application.itemManager.itemsReferencingItem(item1)).to.include(refreshedItem2)
|
||||
expect(this.application.itemManager.itemsReferencingItem(item2)).to.include(refreshedItem1)
|
||||
|
||||
await this.application.itemManager.changeItem(item1, (mutator) => {
|
||||
await this.application.mutator.changeItem(item1, (mutator) => {
|
||||
mutator.removeItemAsRelationship(item2)
|
||||
})
|
||||
await this.application.itemManager.changeItem(item2, (mutator) => {
|
||||
await this.application.mutator.changeItem(item2, (mutator) => {
|
||||
mutator.removeItemAsRelationship(item1)
|
||||
})
|
||||
|
||||
@@ -190,7 +190,7 @@ describe('app models', () => {
|
||||
|
||||
it('properly duplicates item with no relationships', async function () {
|
||||
const item = await Factory.createMappedNote(this.application)
|
||||
const duplicate = await this.application.itemManager.duplicateItem(item)
|
||||
const duplicate = await this.application.mutator.duplicateItem(item)
|
||||
expect(duplicate.uuid).to.not.equal(item.uuid)
|
||||
expect(item.isItemContentEqualWith(duplicate)).to.equal(true)
|
||||
expect(item.created_at.toISOString()).to.equal(duplicate.created_at.toISOString())
|
||||
@@ -201,13 +201,13 @@ describe('app models', () => {
|
||||
const item1 = await Factory.createMappedNote(this.application)
|
||||
const item2 = await Factory.createMappedNote(this.application)
|
||||
|
||||
const refreshedItem1 = await this.application.itemManager.changeItem(item1, (mutator) => {
|
||||
const refreshedItem1 = await this.application.mutator.changeItem(item1, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
|
||||
})
|
||||
|
||||
expect(refreshedItem1.content.references.length).to.equal(1)
|
||||
|
||||
const duplicate = await this.application.itemManager.duplicateItem(item1)
|
||||
const duplicate = await this.application.mutator.duplicateItem(item1)
|
||||
expect(duplicate.uuid).to.not.equal(item1.uuid)
|
||||
expect(duplicate.content.references.length).to.equal(1)
|
||||
|
||||
@@ -223,11 +223,11 @@ describe('app models', () => {
|
||||
it('removing references should update cross-refs', async function () {
|
||||
const item1 = await Factory.createMappedNote(this.application)
|
||||
const item2 = await Factory.createMappedNote(this.application)
|
||||
const refreshedItem1 = await this.application.itemManager.changeItem(item1, (mutator) => {
|
||||
const refreshedItem1 = await this.application.mutator.changeItem(item1, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
|
||||
})
|
||||
|
||||
const refreshedItem1_2 = await this.application.itemManager.emitItemFromPayload(
|
||||
const refreshedItem1_2 = await this.application.mutator.emitItemFromPayload(
|
||||
refreshedItem1.payloadRepresentation({
|
||||
deleted: true,
|
||||
content: {
|
||||
@@ -247,7 +247,7 @@ describe('app models', () => {
|
||||
const item1 = await Factory.createMappedNote(this.application)
|
||||
const item2 = await Factory.createMappedNote(this.application)
|
||||
|
||||
const refreshedItem1 = await this.application.itemManager.changeItem(item1, (mutator) => {
|
||||
const refreshedItem1 = await this.application.mutator.changeItem(item1, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
|
||||
})
|
||||
|
||||
@@ -290,12 +290,12 @@ describe('app models', () => {
|
||||
waitingForKey: true,
|
||||
})
|
||||
|
||||
await this.application.itemManager.emitItemFromPayload(errored)
|
||||
await this.application.payloadManager.emitPayload(errored)
|
||||
|
||||
expect(this.application.payloadManager.findOne(item1.uuid).errorDecrypting).to.equal(true)
|
||||
expect(this.application.payloadManager.findOne(item1.uuid).items_key_id).to.equal(itemsKey.uuid)
|
||||
|
||||
sinon.stub(this.application.protocolService.itemsEncryption, 'decryptErroredPayloads').callsFake(() => {
|
||||
sinon.stub(this.application.protocolService.itemsEncryption, 'decryptErroredItemPayloads').callsFake(() => {
|
||||
// prevent auto decryption
|
||||
})
|
||||
|
||||
@@ -310,7 +310,7 @@ describe('app models', () => {
|
||||
const item2 = await Factory.createMappedNote(this.application)
|
||||
this.expectedItemCount += 2
|
||||
|
||||
await this.application.itemManager.changeItem(item1, (mutator) => {
|
||||
await this.application.mutator.changeItem(item1, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
|
||||
})
|
||||
|
||||
@@ -339,13 +339,13 @@ describe('app models', () => {
|
||||
it('maintains referencing relationships when duplicating', async function () {
|
||||
const tag = await Factory.createMappedTag(this.application)
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
const refreshedTag = await this.application.itemManager.changeItem(tag, (mutator) => {
|
||||
const refreshedTag = await this.application.mutator.changeItem(tag, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(note)
|
||||
})
|
||||
|
||||
expect(refreshedTag.content.references.length).to.equal(1)
|
||||
|
||||
const noteCopy = await this.application.itemManager.duplicateItem(note)
|
||||
const noteCopy = await this.application.mutator.duplicateItem(note)
|
||||
expect(note.uuid).to.not.equal(noteCopy.uuid)
|
||||
|
||||
expect(this.application.itemManager.getDisplayableNotes().length).to.equal(2)
|
||||
@@ -358,7 +358,7 @@ describe('app models', () => {
|
||||
})
|
||||
|
||||
it('maintains editor reference when duplicating note', async function () {
|
||||
const editor = await this.application.itemManager.createItem(
|
||||
const editor = await this.application.mutator.createItem(
|
||||
ContentType.Component,
|
||||
{ area: ComponentArea.Editor, package_info: { identifier: 'foo-editor' } },
|
||||
true,
|
||||
@@ -369,7 +369,7 @@ describe('app models', () => {
|
||||
|
||||
expect(this.application.componentManager.editorForNote(note).uuid).to.equal(editor.uuid)
|
||||
|
||||
const duplicate = await this.application.itemManager.duplicateItem(note, true)
|
||||
const duplicate = await this.application.mutator.duplicateItem(note, true)
|
||||
expect(this.application.componentManager.editorForNote(duplicate).uuid).to.equal(editor.uuid)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from '../lib/Applications.js'
|
||||
import { BaseItemCounts } from '../lib/BaseItemCounts.js'
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import { createRelatedNoteTagPairPayload } from '../lib/Items.js'
|
||||
chai.use(chaiAsPromised)
|
||||
@@ -43,7 +43,7 @@ describe('importing', function () {
|
||||
|
||||
it('should not import backups made from unsupported versions', async function () {
|
||||
await setup({ fakeCrypto: true })
|
||||
const result = await application.mutator.importData({
|
||||
const result = await application.importData({
|
||||
version: '-1',
|
||||
items: [],
|
||||
})
|
||||
@@ -58,7 +58,7 @@ describe('importing', function () {
|
||||
password,
|
||||
version: ProtocolVersion.V003,
|
||||
})
|
||||
const result = await application.mutator.importData({
|
||||
const result = await application.importData({
|
||||
version: ProtocolVersion.V004,
|
||||
items: [],
|
||||
})
|
||||
@@ -71,7 +71,7 @@ describe('importing', function () {
|
||||
const notePayload = pair[0]
|
||||
const tagPayload = pair[1]
|
||||
|
||||
await application.itemManager.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
expectedItemCount += 2
|
||||
const note = application.itemManager.getItems([ContentType.Note])[0]
|
||||
const tag = application.itemManager.getItems([ContentType.Tag])[0]
|
||||
@@ -82,7 +82,7 @@ describe('importing', function () {
|
||||
expect(note.content.references.length).to.equal(0)
|
||||
expect(application.itemManager.itemsReferencingItem(note).length).to.equal(1)
|
||||
|
||||
await application.mutator.importData(
|
||||
await application.importData(
|
||||
{
|
||||
items: [notePayload, tagPayload],
|
||||
},
|
||||
@@ -105,7 +105,7 @@ describe('importing', function () {
|
||||
*/
|
||||
await setup({ fakeCrypto: true })
|
||||
const notePayload = Factory.createNotePayload()
|
||||
await application.itemManager.emitItemFromPayload(notePayload, PayloadEmitSource.LocalChanged)
|
||||
await application.mutator.emitItemFromPayload(notePayload, PayloadEmitSource.LocalChanged)
|
||||
expectedItemCount++
|
||||
const mutatedNote = new DecryptedPayload({
|
||||
...notePayload,
|
||||
@@ -114,7 +114,7 @@ describe('importing', function () {
|
||||
title: `${Math.random()}`,
|
||||
},
|
||||
})
|
||||
await application.mutator.importData(
|
||||
await application.importData(
|
||||
{
|
||||
items: [mutatedNote, mutatedNote, mutatedNote],
|
||||
},
|
||||
@@ -130,7 +130,7 @@ describe('importing', function () {
|
||||
await setup({ fakeCrypto: true })
|
||||
const pair = createRelatedNoteTagPairPayload()
|
||||
const tagPayload = pair[1]
|
||||
await application.itemManager.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
|
||||
await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
|
||||
const mutatedTag = new DecryptedPayload({
|
||||
...tagPayload,
|
||||
content: {
|
||||
@@ -138,7 +138,7 @@ describe('importing', function () {
|
||||
references: [],
|
||||
},
|
||||
})
|
||||
await application.mutator.importData(
|
||||
await application.importData(
|
||||
{
|
||||
items: [mutatedTag],
|
||||
},
|
||||
@@ -153,7 +153,7 @@ describe('importing', function () {
|
||||
const pair = createRelatedNoteTagPairPayload()
|
||||
const notePayload = pair[0]
|
||||
const tagPayload = pair[1]
|
||||
await application.itemManager.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
|
||||
await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
|
||||
expectedItemCount += 2
|
||||
const note = application.itemManager.getDisplayableNotes()[0]
|
||||
const tag = application.itemManager.getDisplayableTags()[0]
|
||||
@@ -171,7 +171,7 @@ describe('importing', function () {
|
||||
title: `${Math.random()}`,
|
||||
},
|
||||
})
|
||||
await application.mutator.importData(
|
||||
await application.importData(
|
||||
{
|
||||
items: [mutatedNote, mutatedTag],
|
||||
},
|
||||
@@ -217,7 +217,7 @@ describe('importing', function () {
|
||||
const tag = await Factory.createMappedTag(application)
|
||||
expectedItemCount += 2
|
||||
|
||||
await application.itemManager.changeItem(tag, (mutator) => {
|
||||
await application.mutator.changeItem(tag, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(note)
|
||||
})
|
||||
|
||||
@@ -240,7 +240,7 @@ describe('importing', function () {
|
||||
},
|
||||
)
|
||||
|
||||
await application.mutator.importData(
|
||||
await application.importData(
|
||||
{
|
||||
items: [externalNote, externalTag],
|
||||
},
|
||||
@@ -272,12 +272,14 @@ describe('importing', function () {
|
||||
await application.sync.sync({ awaitAll: true })
|
||||
|
||||
await application.mutator.deleteItem(note)
|
||||
await application.sync.sync()
|
||||
expect(application.items.findItem(note.uuid)).to.not.exist
|
||||
|
||||
await application.mutator.deleteItem(tag)
|
||||
await application.sync.sync()
|
||||
expect(application.items.findItem(tag.uuid)).to.not.exist
|
||||
|
||||
await application.mutator.importData(
|
||||
await application.importData(
|
||||
{
|
||||
items: [note, tag],
|
||||
},
|
||||
@@ -311,7 +313,7 @@ describe('importing', function () {
|
||||
password: password,
|
||||
})
|
||||
|
||||
await application.mutator.importData(
|
||||
await application.importData(
|
||||
{
|
||||
items: [note.payload],
|
||||
},
|
||||
@@ -341,7 +343,7 @@ describe('importing', function () {
|
||||
password: password,
|
||||
})
|
||||
|
||||
await application.mutator.importData(
|
||||
await application.importData(
|
||||
{
|
||||
items: [note],
|
||||
},
|
||||
@@ -372,12 +374,14 @@ describe('importing', function () {
|
||||
await application.sync.sync({ awaitAll: true })
|
||||
|
||||
await application.mutator.deleteItem(note)
|
||||
await application.sync.sync()
|
||||
expect(application.items.findItem(note.uuid)).to.not.exist
|
||||
|
||||
await application.mutator.deleteItem(tag)
|
||||
await application.sync.sync()
|
||||
expect(application.items.findItem(tag.uuid)).to.not.exist
|
||||
|
||||
await application.mutator.importData(backupData, true)
|
||||
await application.importData(backupData, true)
|
||||
|
||||
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
|
||||
expect(application.items.findItem(note.uuid).deleted).to.not.be.ok
|
||||
@@ -402,7 +406,7 @@ describe('importing', function () {
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
Factory.handlePasswordChallenges(application, password)
|
||||
|
||||
await application.mutator.importData(backupData, true)
|
||||
await application.importData(backupData, true)
|
||||
|
||||
const importedNote = application.items.findItem(note.uuid)
|
||||
const importedTag = application.items.findItem(tag.uuid)
|
||||
@@ -427,7 +431,7 @@ describe('importing', function () {
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
Factory.handlePasswordChallenges(application, password)
|
||||
|
||||
await application.mutator.importData(backupData, true)
|
||||
await application.importData(backupData, true)
|
||||
|
||||
const importedNote = application.items.findItem(note.uuid)
|
||||
const importedTag = application.items.findItem(tag.uuid)
|
||||
@@ -445,7 +449,7 @@ describe('importing', function () {
|
||||
version: oldVersion,
|
||||
})
|
||||
|
||||
const noteItem = await application.itemManager.createItem(ContentType.Note, {
|
||||
const noteItem = await application.mutator.createItem(ContentType.Note, {
|
||||
title: 'Encrypted note',
|
||||
text: 'On protocol version 003.',
|
||||
})
|
||||
@@ -456,7 +460,7 @@ describe('importing', function () {
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
Factory.handlePasswordChallenges(application, password)
|
||||
|
||||
const result = await application.mutator.importData(backupData, true)
|
||||
const result = await application.importData(backupData, true)
|
||||
expect(result).to.not.be.undefined
|
||||
expect(result.affectedItems.length).to.be.eq(backupData.items.length)
|
||||
expect(result.errorCount).to.be.eq(0)
|
||||
@@ -512,7 +516,7 @@ describe('importing', function () {
|
||||
application = await Factory.createInitAppWithRealCrypto()
|
||||
Factory.handlePasswordChallenges(application, password)
|
||||
|
||||
const result = await application.mutator.importData(backupData, true)
|
||||
const result = await application.importData(backupData, true)
|
||||
expect(result).to.not.be.undefined
|
||||
expect(result.affectedItems.length).to.be.eq(backupData.items.length)
|
||||
expect(result.errorCount).to.be.eq(0)
|
||||
@@ -526,7 +530,7 @@ describe('importing', function () {
|
||||
password: password,
|
||||
})
|
||||
|
||||
const noteItem = await application.itemManager.createItem(ContentType.Note, {
|
||||
const noteItem = await application.mutator.createItem(ContentType.Note, {
|
||||
title: 'Encrypted note',
|
||||
text: 'On protocol version 004.',
|
||||
})
|
||||
@@ -537,7 +541,7 @@ describe('importing', function () {
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
Factory.handlePasswordChallenges(application, password)
|
||||
|
||||
const result = await application.mutator.importData(backupData, true)
|
||||
const result = await application.importData(backupData, true)
|
||||
expect(result).to.not.be.undefined
|
||||
expect(result.affectedItems.length).to.be.eq(backupData.items.length)
|
||||
expect(result.errorCount).to.be.eq(0)
|
||||
@@ -556,7 +560,7 @@ describe('importing', function () {
|
||||
password: password,
|
||||
})
|
||||
|
||||
const noteItem = await application.itemManager.createItem(ContentType.Note, {
|
||||
const noteItem = await application.mutator.createItem(ContentType.Note, {
|
||||
title: 'This is a valid, encrypted note',
|
||||
text: 'On protocol version 004.',
|
||||
})
|
||||
@@ -577,7 +581,7 @@ describe('importing', function () {
|
||||
|
||||
backupData.items = [...backupData.items, madeUpPayload]
|
||||
|
||||
const result = await application.mutator.importData(backupData, true)
|
||||
const result = await application.importData(backupData, true)
|
||||
expect(result).to.not.be.undefined
|
||||
expect(result.affectedItems.length).to.be.eq(backupData.items.length - 1)
|
||||
expect(result.errorCount).to.be.eq(1)
|
||||
@@ -594,7 +598,7 @@ describe('importing', function () {
|
||||
version: oldVersion,
|
||||
})
|
||||
|
||||
await application.itemManager.createItem(ContentType.Note, {
|
||||
await application.mutator.createItem(ContentType.Note, {
|
||||
title: 'Encrypted note',
|
||||
text: 'On protocol version 003.',
|
||||
})
|
||||
@@ -615,7 +619,7 @@ describe('importing', function () {
|
||||
},
|
||||
})
|
||||
|
||||
const result = await application.mutator.importData(backupData, true)
|
||||
const result = await application.importData(backupData, true)
|
||||
expect(result).to.not.be.undefined
|
||||
|
||||
expect(result.affectedItems.length).to.be.eq(0)
|
||||
@@ -631,7 +635,7 @@ describe('importing', function () {
|
||||
password: password,
|
||||
})
|
||||
|
||||
await application.itemManager.createItem(ContentType.Note, {
|
||||
await application.mutator.createItem(ContentType.Note, {
|
||||
title: 'This is a valid, encrypted note',
|
||||
text: 'On protocol version 004.',
|
||||
})
|
||||
@@ -647,7 +651,7 @@ describe('importing', function () {
|
||||
},
|
||||
})
|
||||
|
||||
const result = await application.mutator.importData(backupData, true)
|
||||
const result = await application.importData(backupData, true)
|
||||
expect(result).to.not.be.undefined
|
||||
expect(result.affectedItems.length).to.be.eq(0)
|
||||
expect(result.errorCount).to.be.eq(backupData.items.length)
|
||||
@@ -662,7 +666,7 @@ describe('importing', function () {
|
||||
password: password,
|
||||
})
|
||||
|
||||
await application.itemManager.createItem(ContentType.Note, {
|
||||
await application.mutator.createItem(ContentType.Note, {
|
||||
title: 'Encrypted note',
|
||||
text: 'On protocol version 004.',
|
||||
})
|
||||
@@ -673,7 +677,7 @@ describe('importing', function () {
|
||||
await Factory.safeDeinit(application)
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
|
||||
const result = await application.mutator.importData(backupData)
|
||||
const result = await application.importData(backupData)
|
||||
|
||||
expect(result.error).to.be.ok
|
||||
})
|
||||
@@ -687,7 +691,7 @@ describe('importing', function () {
|
||||
})
|
||||
Factory.handlePasswordChallenges(application, password)
|
||||
|
||||
await application.itemManager.createItem(ContentType.Note, {
|
||||
await application.mutator.createItem(ContentType.Note, {
|
||||
title: 'Encrypted note',
|
||||
text: 'On protocol version 004.',
|
||||
})
|
||||
@@ -699,11 +703,13 @@ describe('importing', function () {
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
Factory.handlePasswordChallenges(application, password)
|
||||
|
||||
const result = await application.mutator.importData(backupData, true)
|
||||
const result = await application.importData(backupData, true)
|
||||
|
||||
expect(result).to.not.be.undefined
|
||||
expect(result.affectedItems.length).to.be.eq(0)
|
||||
expect(result.errorCount).to.be.eq(backupData.items.length)
|
||||
|
||||
expect(result.affectedItems.length).to.equal(BaseItemCounts.BackupFileRootKeyEncryptedItems)
|
||||
|
||||
expect(result.errorCount).to.be.eq(backupData.items.length - BaseItemCounts.BackupFileRootKeyEncryptedItems)
|
||||
expect(application.itemManager.getDisplayableNotes().length).to.equal(0)
|
||||
})
|
||||
|
||||
@@ -784,7 +790,14 @@ describe('importing', function () {
|
||||
|
||||
await application.prepareForLaunch({
|
||||
receiveChallenge: (challenge) => {
|
||||
if (challenge.prompts.length === 2) {
|
||||
if (challenge.reason === ChallengeReason.Custom) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
challenge.reason === ChallengeReason.DecryptEncryptedFile ||
|
||||
challenge.reason === ChallengeReason.ImportFile
|
||||
) {
|
||||
application.submitValuesForChallenge(
|
||||
challenge,
|
||||
challenge.prompts.map((prompt) =>
|
||||
@@ -796,9 +809,6 @@ describe('importing', function () {
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
const prompt = challenge.prompts[0]
|
||||
application.submitValuesForChallenge(challenge, [CreateChallengeValue(prompt, password)])
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -827,7 +837,7 @@ describe('importing', function () {
|
||||
},
|
||||
}
|
||||
|
||||
const result = await application.mutator.importData(backupFile, false)
|
||||
const result = await application.importData(backupFile, false)
|
||||
expect(result.errorCount).to.equal(0)
|
||||
await Factory.safeDeinit(application)
|
||||
})
|
||||
@@ -846,7 +856,7 @@ describe('importing', function () {
|
||||
Factory.handlePasswordChallenges(application, password)
|
||||
|
||||
const pair = createRelatedNoteTagPairPayload()
|
||||
await application.itemManager.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
|
||||
await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
|
||||
|
||||
await application.sync.sync()
|
||||
|
||||
@@ -862,7 +872,7 @@ describe('importing', function () {
|
||||
password: password,
|
||||
})
|
||||
|
||||
await application.mutator.importData(backupData, true)
|
||||
await application.importData(backupData, true)
|
||||
|
||||
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
|
||||
expect(application.itemManager.getDisplayableTags().length).to.equal(1)
|
||||
@@ -872,4 +882,8 @@ describe('importing', function () {
|
||||
expect(application.itemManager.referencesForItem(importedTag).length).to.equal(1)
|
||||
expect(application.itemManager.itemsReferencingItem(importedNote).length).to.equal(1)
|
||||
})
|
||||
|
||||
it('should decrypt backup file which contains a vaulted note without a synced key system root key', async () => {
|
||||
console.error('TODO: Implement this test')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from '../lib/Applications.js'
|
||||
import { BaseItemCounts } from '../lib/BaseItemCounts.js'
|
||||
import * as Factory from '../lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -22,11 +22,11 @@ describe('items', () => {
|
||||
|
||||
it('setting an item as dirty should update its client updated at', async function () {
|
||||
const params = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged)
|
||||
const item = this.application.itemManager.items[0]
|
||||
const prevDate = item.userModifiedDate.getTime()
|
||||
await Factory.sleep(0.1)
|
||||
await this.application.itemManager.setItemDirty(item, true)
|
||||
await this.application.mutator.setItemDirty(item, true)
|
||||
const refreshedItem = this.application.itemManager.findItem(item.uuid)
|
||||
const newDate = refreshedItem.userModifiedDate.getTime()
|
||||
expect(prevDate).to.not.equal(newDate)
|
||||
@@ -34,23 +34,23 @@ describe('items', () => {
|
||||
|
||||
it('setting an item as dirty with option to skip client updated at', async function () {
|
||||
const params = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged)
|
||||
const item = this.application.itemManager.items[0]
|
||||
const prevDate = item.userModifiedDate.getTime()
|
||||
await Factory.sleep(0.1)
|
||||
await this.application.itemManager.setItemDirty(item)
|
||||
await this.application.mutator.setItemDirty(item)
|
||||
const newDate = item.userModifiedDate.getTime()
|
||||
expect(prevDate).to.equal(newDate)
|
||||
})
|
||||
|
||||
it('properly pins, archives, and locks', async function () {
|
||||
const params = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged)
|
||||
|
||||
const item = this.application.itemManager.items[0]
|
||||
expect(item.pinned).to.not.be.ok
|
||||
|
||||
const refreshedItem = await this.application.mutator.changeAndSaveItem(
|
||||
const refreshedItem = await this.application.changeAndSaveItem(
|
||||
item,
|
||||
(mutator) => {
|
||||
mutator.pinned = true
|
||||
@@ -69,7 +69,7 @@ describe('items', () => {
|
||||
it('properly compares item equality', async function () {
|
||||
const params1 = Factory.createNotePayload()
|
||||
const params2 = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads([params1, params2], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([params1, params2], PayloadEmitSource.LocalChanged)
|
||||
|
||||
let item1 = this.application.itemManager.getDisplayableNotes()[0]
|
||||
let item2 = this.application.itemManager.getDisplayableNotes()[1]
|
||||
@@ -77,7 +77,7 @@ describe('items', () => {
|
||||
expect(item1.isItemContentEqualWith(item2)).to.equal(true)
|
||||
|
||||
// items should ignore this field when checking for equality
|
||||
item1 = await this.application.mutator.changeAndSaveItem(
|
||||
item1 = await this.application.changeAndSaveItem(
|
||||
item1,
|
||||
(mutator) => {
|
||||
mutator.userModifiedDate = new Date()
|
||||
@@ -86,7 +86,7 @@ describe('items', () => {
|
||||
undefined,
|
||||
syncOptions,
|
||||
)
|
||||
item2 = await this.application.mutator.changeAndSaveItem(
|
||||
item2 = await this.application.changeAndSaveItem(
|
||||
item2,
|
||||
(mutator) => {
|
||||
mutator.userModifiedDate = undefined
|
||||
@@ -98,7 +98,7 @@ describe('items', () => {
|
||||
|
||||
expect(item1.isItemContentEqualWith(item2)).to.equal(true)
|
||||
|
||||
item1 = await this.application.mutator.changeAndSaveItem(
|
||||
item1 = await this.application.changeAndSaveItem(
|
||||
item1,
|
||||
(mutator) => {
|
||||
mutator.mutableContent.foo = 'bar'
|
||||
@@ -110,7 +110,7 @@ describe('items', () => {
|
||||
|
||||
expect(item1.isItemContentEqualWith(item2)).to.equal(false)
|
||||
|
||||
item2 = await this.application.mutator.changeAndSaveItem(
|
||||
item2 = await this.application.changeAndSaveItem(
|
||||
item2,
|
||||
(mutator) => {
|
||||
mutator.mutableContent.foo = 'bar'
|
||||
@@ -123,7 +123,7 @@ describe('items', () => {
|
||||
expect(item1.isItemContentEqualWith(item2)).to.equal(true)
|
||||
expect(item2.isItemContentEqualWith(item1)).to.equal(true)
|
||||
|
||||
item1 = await this.application.mutator.changeAndSaveItem(
|
||||
item1 = await this.application.changeAndSaveItem(
|
||||
item1,
|
||||
(mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item2)
|
||||
@@ -132,7 +132,7 @@ describe('items', () => {
|
||||
undefined,
|
||||
syncOptions,
|
||||
)
|
||||
item2 = await this.application.mutator.changeAndSaveItem(
|
||||
item2 = await this.application.changeAndSaveItem(
|
||||
item2,
|
||||
(mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(item1)
|
||||
@@ -147,7 +147,7 @@ describe('items', () => {
|
||||
|
||||
expect(item1.isItemContentEqualWith(item2)).to.equal(false)
|
||||
|
||||
item1 = await this.application.mutator.changeAndSaveItem(
|
||||
item1 = await this.application.changeAndSaveItem(
|
||||
item1,
|
||||
(mutator) => {
|
||||
mutator.removeItemAsRelationship(item2)
|
||||
@@ -156,7 +156,7 @@ describe('items', () => {
|
||||
undefined,
|
||||
syncOptions,
|
||||
)
|
||||
item2 = await this.application.mutator.changeAndSaveItem(
|
||||
item2 = await this.application.changeAndSaveItem(
|
||||
item2,
|
||||
(mutator) => {
|
||||
mutator.removeItemAsRelationship(item1)
|
||||
@@ -174,12 +174,12 @@ describe('items', () => {
|
||||
it('content equality should not have side effects', async function () {
|
||||
const params1 = Factory.createNotePayload()
|
||||
const params2 = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads([params1, params2], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([params1, params2], PayloadEmitSource.LocalChanged)
|
||||
|
||||
let item1 = this.application.itemManager.getDisplayableNotes()[0]
|
||||
const item2 = this.application.itemManager.getDisplayableNotes()[1]
|
||||
|
||||
item1 = await this.application.mutator.changeAndSaveItem(
|
||||
item1 = await this.application.changeAndSaveItem(
|
||||
item1,
|
||||
(mutator) => {
|
||||
mutator.mutableContent.foo = 'bar'
|
||||
@@ -203,7 +203,7 @@ describe('items', () => {
|
||||
// There was an issue where calling that function would modify values directly to omit keys
|
||||
// in contentKeysToIgnoreWhenCheckingEquality.
|
||||
|
||||
await this.application.itemManager.setItemsDirty([item1, item2])
|
||||
await this.application.mutator.setItemsDirty([item1, item2])
|
||||
|
||||
expect(item1.userModifiedDate).to.be.ok
|
||||
expect(item2.userModifiedDate).to.be.ok
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from '../lib/Applications.js'
|
||||
import { BaseItemCounts } from '../lib/BaseItemCounts.js'
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import { createNoteParams } from '../lib/Items.js'
|
||||
chai.use(chaiAsPromised)
|
||||
@@ -20,7 +20,7 @@ describe('model manager mapping', () => {
|
||||
|
||||
it('mapping nonexistent item creates it', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
this.expectedItemCount++
|
||||
expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount)
|
||||
})
|
||||
@@ -31,13 +31,13 @@ describe('model manager mapping', () => {
|
||||
dirty: false,
|
||||
deleted: true,
|
||||
})
|
||||
await this.application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
await this.application.payloadManager.emitPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount)
|
||||
})
|
||||
|
||||
it('mapping and deleting nonexistent item creates and deletes it', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
|
||||
this.expectedItemCount++
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('model manager mapping', () => {
|
||||
|
||||
this.expectedItemCount--
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads([changedParams], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([changedParams], PayloadEmitSource.LocalChanged)
|
||||
|
||||
expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount)
|
||||
})
|
||||
@@ -59,22 +59,22 @@ describe('model manager mapping', () => {
|
||||
it('mapping deleted but dirty item should not delete it', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
|
||||
const [item] = await this.application.itemManager.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
const [item] = await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
|
||||
this.expectedItemCount++
|
||||
|
||||
await this.application.itemManager.emitItemFromPayload(new DeleteItemMutator(item).getDeletedResult())
|
||||
await this.application.payloadManager.emitPayload(new DeleteItemMutator(item).getDeletedResult())
|
||||
|
||||
const payload2 = new DeletedPayload(this.application.payloadManager.findOne(payload.uuid).ejected())
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads([payload2], PayloadEmitSource.LocalChanged)
|
||||
await this.application.payloadManager.emitPayloads([payload2], PayloadEmitSource.LocalChanged)
|
||||
|
||||
expect(this.application.payloadManager.collection.all().length).to.equal(this.expectedItemCount)
|
||||
})
|
||||
|
||||
it('mapping existing item updates its properties', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
|
||||
const newTitle = 'updated title'
|
||||
const mutated = new DecryptedPayload({
|
||||
@@ -84,7 +84,7 @@ describe('model manager mapping', () => {
|
||||
title: newTitle,
|
||||
},
|
||||
})
|
||||
await this.application.itemManager.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged)
|
||||
const item = this.application.itemManager.getDisplayableNotes()[0]
|
||||
|
||||
expect(item.content.title).to.equal(newTitle)
|
||||
@@ -92,9 +92,9 @@ describe('model manager mapping', () => {
|
||||
|
||||
it('setting an item dirty should retrieve it in dirty items', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
const note = this.application.itemManager.getDisplayableNotes()[0]
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
const dirtyItems = this.application.itemManager.getDirtyItems()
|
||||
expect(Uuids(dirtyItems).includes(note.uuid))
|
||||
})
|
||||
@@ -106,7 +106,7 @@ describe('model manager mapping', () => {
|
||||
for (let i = 0; i < count; i++) {
|
||||
payloads.push(Factory.createNotePayload())
|
||||
}
|
||||
await this.application.itemManager.emitItemsFromPayloads(payloads, PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads(payloads, PayloadEmitSource.LocalChanged)
|
||||
await this.application.syncService.markAllItemsAsNeedingSyncAndPersist()
|
||||
|
||||
const dirtyItems = this.application.itemManager.getDirtyItems()
|
||||
@@ -115,14 +115,14 @@ describe('model manager mapping', () => {
|
||||
|
||||
it('sync observers should be notified of changes', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
const item = this.application.itemManager.items[0]
|
||||
return new Promise((resolve) => {
|
||||
this.application.itemManager.addObserver(ContentType.Any, ({ changed }) => {
|
||||
expect(changed[0].uuid === item.uuid)
|
||||
resolve()
|
||||
})
|
||||
this.application.itemManager.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Utils from '../lib/Utils.js'
|
||||
import { createRelatedNoteTagPairPayload } from '../lib/Items.js'
|
||||
import { BaseItemCounts } from '../lib/Applications.js'
|
||||
import { BaseItemCounts } from '../lib/BaseItemCounts.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('notes and tags', () => {
|
||||
|
||||
it('uses proper class for note', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
const note = this.application.itemManager.getItems([ContentType.Note])[0]
|
||||
expect(note.constructor === SNNote).to.equal(true)
|
||||
})
|
||||
@@ -33,7 +33,7 @@ describe('notes and tags', () => {
|
||||
it('properly constructs syncing params', async function () {
|
||||
const title = 'Foo'
|
||||
const text = 'Bar'
|
||||
const note = await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
const note = await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title,
|
||||
text,
|
||||
})
|
||||
@@ -41,7 +41,7 @@ describe('notes and tags', () => {
|
||||
expect(note.content.title).to.equal(title)
|
||||
expect(note.content.text).to.equal(text)
|
||||
|
||||
const tag = await this.application.mutator.createTemplateItem(ContentType.Tag, {
|
||||
const tag = await this.application.items.createTemplateItem(ContentType.Tag, {
|
||||
title,
|
||||
})
|
||||
|
||||
@@ -73,7 +73,7 @@ describe('notes and tags', () => {
|
||||
},
|
||||
})
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads([mutatedNote, mutatedTag], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([mutatedNote, mutatedTag], PayloadEmitSource.LocalChanged)
|
||||
const note = this.application.itemManager.getItems([ContentType.Note])[0]
|
||||
const tag = this.application.itemManager.getItems([ContentType.Tag])[0]
|
||||
|
||||
@@ -89,7 +89,7 @@ describe('notes and tags', () => {
|
||||
expect(notePayload.content.references.length).to.equal(0)
|
||||
expect(tagPayload.content.references.length).to.equal(1)
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
let note = this.application.itemManager.getDisplayableNotes()[0]
|
||||
let tag = this.application.itemManager.getDisplayableTags()[0]
|
||||
|
||||
@@ -106,7 +106,7 @@ describe('notes and tags', () => {
|
||||
expect(note.payload.references.length).to.equal(0)
|
||||
expect(tag.noteCount).to.equal(1)
|
||||
|
||||
await this.application.itemManager.setItemToBeDeleted(note)
|
||||
await this.application.mutator.setItemToBeDeleted(note)
|
||||
|
||||
tag = this.application.itemManager.getDisplayableTags()[0]
|
||||
|
||||
@@ -130,7 +130,7 @@ describe('notes and tags', () => {
|
||||
const notePayload = pair[0]
|
||||
const tagPayload = pair[1]
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
|
||||
let note = this.application.itemManager.getItems([ContentType.Note])[0]
|
||||
let tag = this.application.itemManager.getItems([ContentType.Tag])[0]
|
||||
|
||||
@@ -147,7 +147,7 @@ describe('notes and tags', () => {
|
||||
references: [],
|
||||
},
|
||||
})
|
||||
await this.application.itemManager.emitItemsFromPayloads([mutatedTag], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([mutatedTag], PayloadEmitSource.LocalChanged)
|
||||
|
||||
note = this.application.itemManager.findItem(note.uuid)
|
||||
tag = this.application.itemManager.findItem(tag.uuid)
|
||||
@@ -177,14 +177,14 @@ describe('notes and tags', () => {
|
||||
const notePayload = pair[0]
|
||||
const tagPayload = pair[1]
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
const note = this.application.itemManager.getItems([ContentType.Note])[0]
|
||||
let tag = this.application.itemManager.getItems([ContentType.Tag])[0]
|
||||
|
||||
expect(note.content.references.length).to.equal(0)
|
||||
expect(tag.content.references.length).to.equal(1)
|
||||
|
||||
tag = await this.application.mutator.changeAndSaveItem(
|
||||
tag = await this.application.changeAndSaveItem(
|
||||
tag,
|
||||
(mutator) => {
|
||||
mutator.removeItemAsRelationship(note)
|
||||
@@ -200,11 +200,11 @@ describe('notes and tags', () => {
|
||||
|
||||
it('properly handles tag duplication', async function () {
|
||||
const pair = createRelatedNoteTagPairPayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged)
|
||||
let note = this.application.itemManager.getDisplayableNotes()[0]
|
||||
let tag = this.application.itemManager.getDisplayableTags()[0]
|
||||
|
||||
const duplicateTag = await this.application.itemManager.duplicateItem(tag, true)
|
||||
const duplicateTag = await this.application.mutator.duplicateItem(tag, true)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
note = this.application.itemManager.findItem(note.uuid)
|
||||
@@ -232,9 +232,9 @@ describe('notes and tags', () => {
|
||||
const pair = createRelatedNoteTagPairPayload()
|
||||
const notePayload = pair[0]
|
||||
const tagPayload = pair[1]
|
||||
await this.application.itemManager.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
const note = this.application.itemManager.getItems([ContentType.Note])[0]
|
||||
const duplicateNote = await this.application.itemManager.duplicateItem(note, true)
|
||||
const duplicateNote = await this.application.mutator.duplicateItem(note, true)
|
||||
expect(note.uuid).to.not.equal(duplicateNote.uuid)
|
||||
|
||||
expect(this.application.itemManager.itemsReferencingItem(duplicateNote).length).to.equal(
|
||||
@@ -246,7 +246,7 @@ describe('notes and tags', () => {
|
||||
const pair = createRelatedNoteTagPairPayload()
|
||||
const notePayload = pair[0]
|
||||
const tagPayload = pair[1]
|
||||
await this.application.itemManager.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
const note = this.application.itemManager.getItems([ContentType.Note])[0]
|
||||
let tag = this.application.itemManager.getItems([ContentType.Tag])[0]
|
||||
|
||||
@@ -256,16 +256,16 @@ describe('notes and tags', () => {
|
||||
expect(note.content.references.length).to.equal(0)
|
||||
expect(this.application.itemManager.itemsReferencingItem(note).length).to.equal(1)
|
||||
|
||||
await this.application.itemManager.setItemToBeDeleted(tag)
|
||||
await this.application.mutator.setItemToBeDeleted(tag)
|
||||
tag = this.application.itemManager.findItem(tag.uuid)
|
||||
expect(tag).to.not.be.ok
|
||||
})
|
||||
|
||||
it('modifying item content should not modify payload content', async function () {
|
||||
const notePayload = Factory.createNotePayload()
|
||||
await this.application.itemManager.emitItemsFromPayloads([notePayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([notePayload], PayloadEmitSource.LocalChanged)
|
||||
let note = this.application.itemManager.getItems([ContentType.Note])[0]
|
||||
note = await this.application.mutator.changeAndSaveItem(
|
||||
note = await this.application.changeAndSaveItem(
|
||||
note,
|
||||
(mutator) => {
|
||||
mutator.mutableContent.title = Math.random()
|
||||
@@ -285,12 +285,12 @@ describe('notes and tags', () => {
|
||||
const notePayload = pair[0]
|
||||
const tagPayload = pair[1]
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
let note = this.application.itemManager.getItems([ContentType.Note])[0]
|
||||
let tag = this.application.itemManager.getItems([ContentType.Tag])[0]
|
||||
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
await this.application.itemManager.setItemToBeDeleted(tag)
|
||||
await this.application.mutator.setItemToBeDeleted(tag)
|
||||
|
||||
note = this.application.itemManager.findItem(note.uuid)
|
||||
this.application.itemManager.findItem(tag.uuid)
|
||||
@@ -302,7 +302,7 @@ describe('notes and tags', () => {
|
||||
await Promise.all(
|
||||
['Y', 'Z', 'A', 'B'].map(async (title) => {
|
||||
return this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, { title }),
|
||||
await this.application.items.createTemplateItem(ContentType.Note, { title }),
|
||||
)
|
||||
}),
|
||||
)
|
||||
@@ -316,7 +316,7 @@ describe('notes and tags', () => {
|
||||
})
|
||||
|
||||
it('setting a note dirty should collapse its properties into content', async function () {
|
||||
let note = await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
let note = await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'Foo',
|
||||
})
|
||||
await this.application.mutator.insertItem(note)
|
||||
@@ -339,7 +339,7 @@ describe('notes and tags', () => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote)
|
||||
})
|
||||
await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
}),
|
||||
)
|
||||
@@ -379,7 +379,7 @@ describe('notes and tags', () => {
|
||||
await Promise.all(
|
||||
['Y', 'Z', 'A', 'B'].map(async (title) => {
|
||||
return this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title,
|
||||
}),
|
||||
)
|
||||
@@ -413,17 +413,17 @@ describe('notes and tags', () => {
|
||||
describe('Smart views', function () {
|
||||
it('"title", "startsWith", "Foo"', async function () {
|
||||
const note = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'Foo 🎲',
|
||||
}),
|
||||
)
|
||||
await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'Not Foo 🎲',
|
||||
}),
|
||||
)
|
||||
const view = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.SmartView, {
|
||||
await this.application.items.createTemplateItem(ContentType.SmartView, {
|
||||
title: 'Foo Notes',
|
||||
predicate: {
|
||||
keypath: 'title',
|
||||
@@ -447,7 +447,7 @@ describe('notes and tags', () => {
|
||||
|
||||
it('"pinned", "=", true', async function () {
|
||||
const note = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
}),
|
||||
)
|
||||
@@ -455,13 +455,13 @@ describe('notes and tags', () => {
|
||||
mutator.pinned = true
|
||||
})
|
||||
await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'B',
|
||||
pinned: false,
|
||||
}),
|
||||
)
|
||||
const view = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.SmartView, {
|
||||
await this.application.items.createTemplateItem(ContentType.SmartView, {
|
||||
title: 'Pinned',
|
||||
predicate: {
|
||||
keypath: 'pinned',
|
||||
@@ -485,7 +485,7 @@ describe('notes and tags', () => {
|
||||
|
||||
it('"pinned", "=", false', async function () {
|
||||
const pinnedNote = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
}),
|
||||
)
|
||||
@@ -493,12 +493,12 @@ describe('notes and tags', () => {
|
||||
mutator.pinned = true
|
||||
})
|
||||
const unpinnedNote = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'B',
|
||||
}),
|
||||
)
|
||||
const view = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.SmartView, {
|
||||
await this.application.items.createTemplateItem(ContentType.SmartView, {
|
||||
title: 'Not pinned',
|
||||
predicate: {
|
||||
keypath: 'pinned',
|
||||
@@ -522,19 +522,19 @@ describe('notes and tags', () => {
|
||||
|
||||
it('"text.length", ">", 500', async function () {
|
||||
const longNote = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
text: Array(501).fill(0).join(''),
|
||||
}),
|
||||
)
|
||||
await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'B',
|
||||
text: 'b',
|
||||
}),
|
||||
)
|
||||
const view = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.SmartView, {
|
||||
await this.application.items.createTemplateItem(ContentType.SmartView, {
|
||||
title: 'Long',
|
||||
predicate: {
|
||||
keypath: 'text.length',
|
||||
@@ -563,18 +563,20 @@ describe('notes and tags', () => {
|
||||
})
|
||||
|
||||
const recentNote = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
}),
|
||||
true,
|
||||
)
|
||||
|
||||
await this.application.sync.sync()
|
||||
|
||||
const olderNote = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'B',
|
||||
text: 'b',
|
||||
}),
|
||||
true,
|
||||
)
|
||||
|
||||
const threeDays = 3 * 24 * 60 * 60 * 1000
|
||||
@@ -582,13 +584,13 @@ describe('notes and tags', () => {
|
||||
|
||||
/** Create an unsynced note which shouldn't get an updated_at */
|
||||
await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'B',
|
||||
text: 'b',
|
||||
}),
|
||||
)
|
||||
const view = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.SmartView, {
|
||||
await this.application.items.createTemplateItem(ContentType.SmartView, {
|
||||
title: 'One day ago',
|
||||
predicate: {
|
||||
keypath: 'serverUpdatedAt',
|
||||
@@ -598,6 +600,9 @@ describe('notes and tags', () => {
|
||||
}),
|
||||
)
|
||||
const matches = this.application.items.notesMatchingSmartView(view)
|
||||
expect(matches.length).to.equal(1)
|
||||
expect(matches[0].uuid).to.equal(recentNote.uuid)
|
||||
|
||||
this.application.items.setPrimaryItemDisplayOptions({
|
||||
sortBy: 'title',
|
||||
sortDirection: 'asc',
|
||||
@@ -605,13 +610,11 @@ describe('notes and tags', () => {
|
||||
})
|
||||
const displayedNotes = this.application.items.getDisplayableNotes()
|
||||
expect(displayedNotes).to.deep.equal(matches)
|
||||
expect(matches.length).to.equal(1)
|
||||
expect(matches[0].uuid).to.equal(recentNote.uuid)
|
||||
})
|
||||
|
||||
it('"tags.length", "=", 0', async function () {
|
||||
const untaggedNote = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
}),
|
||||
)
|
||||
@@ -622,7 +625,7 @@ describe('notes and tags', () => {
|
||||
})
|
||||
|
||||
const view = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.SmartView, {
|
||||
await this.application.items.createTemplateItem(ContentType.SmartView, {
|
||||
title: 'Untagged',
|
||||
predicate: {
|
||||
keypath: 'tags.length',
|
||||
@@ -650,13 +653,13 @@ describe('notes and tags', () => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(taggedNote)
|
||||
})
|
||||
await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
}),
|
||||
)
|
||||
|
||||
const view = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.SmartView, {
|
||||
await this.application.items.createTemplateItem(ContentType.SmartView, {
|
||||
title: 'B-tags',
|
||||
predicate: {
|
||||
keypath: 'tags',
|
||||
@@ -685,7 +688,7 @@ describe('notes and tags', () => {
|
||||
})
|
||||
|
||||
const pinnedNote = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
}),
|
||||
)
|
||||
@@ -694,7 +697,7 @@ describe('notes and tags', () => {
|
||||
})
|
||||
|
||||
const lockedNote = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
}),
|
||||
)
|
||||
@@ -703,7 +706,7 @@ describe('notes and tags', () => {
|
||||
})
|
||||
|
||||
const view = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.SmartView, {
|
||||
await this.application.items.createTemplateItem(ContentType.SmartView, {
|
||||
title: 'Pinned & Locked',
|
||||
predicate: {
|
||||
operator: 'and',
|
||||
@@ -733,7 +736,7 @@ describe('notes and tags', () => {
|
||||
})
|
||||
|
||||
const pinnedNote = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
}),
|
||||
)
|
||||
@@ -742,7 +745,7 @@ describe('notes and tags', () => {
|
||||
})
|
||||
|
||||
const pinnedAndProtectedNote = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
}),
|
||||
)
|
||||
@@ -752,13 +755,13 @@ describe('notes and tags', () => {
|
||||
})
|
||||
|
||||
await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.Note, {
|
||||
await this.application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'A',
|
||||
}),
|
||||
)
|
||||
|
||||
const view = await this.application.mutator.insertItem(
|
||||
await this.application.mutator.createTemplateItem(ContentType.SmartView, {
|
||||
await this.application.items.createTemplateItem(ContentType.SmartView, {
|
||||
title: 'Protected or Pinned',
|
||||
predicate: {
|
||||
operator: 'or',
|
||||
@@ -794,7 +797,7 @@ describe('notes and tags', () => {
|
||||
const notePayload3 = Factory.createNotePayload('Bar')
|
||||
const notePayload4 = Factory.createNotePayload('Testing')
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads(
|
||||
await this.application.mutator.emitItemsFromPayloads(
|
||||
[notePayload1, notePayload2, notePayload3, notePayload4, tagPayload1],
|
||||
PayloadEmitSource.LocalChanged,
|
||||
)
|
||||
@@ -824,7 +827,7 @@ describe('notes and tags', () => {
|
||||
const notePayload3 = Factory.createNotePayload('Testing FOO (Bar)')
|
||||
const notePayload4 = Factory.createNotePayload('This should not match')
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads(
|
||||
await this.application.mutator.emitItemsFromPayloads(
|
||||
[notePayload1, notePayload2, notePayload3, notePayload4, tagPayload1],
|
||||
PayloadEmitSource.LocalChanged,
|
||||
)
|
||||
|
||||
@@ -75,8 +75,8 @@ describe('tags as folders', () => {
|
||||
const note2 = await Factory.createMappedNote(this.application, 'my second note')
|
||||
|
||||
// ## The user add a note to the child tag
|
||||
await this.application.items.addTagToNote(note1, tags.child, true)
|
||||
await this.application.items.addTagToNote(note2, tags.another, true)
|
||||
await this.application.mutator.addTagToNote(note1, tags.child, true)
|
||||
await this.application.mutator.addTagToNote(note2, tags.another, true)
|
||||
|
||||
// ## The note has been added to other tags
|
||||
const note1Tags = await this.application.items.getSortedTagsForItem(note1)
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('mapping performance', () => {
|
||||
const batchSize = 100
|
||||
for (let i = 0; i < payloads.length; i += batchSize) {
|
||||
const subArray = payloads.slice(currentIndex, currentIndex + batchSize)
|
||||
await application.itemManager.emitItemsFromPayloads(subArray, PayloadEmitSource.LocalChanged)
|
||||
await application.mutator.emitItemsFromPayloads(subArray, PayloadEmitSource.LocalChanged)
|
||||
currentIndex += batchSize
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ describe('mapping performance', () => {
|
||||
const batchSize = 100
|
||||
for (let i = 0; i < payloads.length; i += batchSize) {
|
||||
var subArray = payloads.slice(currentIndex, currentIndex + batchSize)
|
||||
await application.itemManager.emitItemsFromPayloads(subArray, PayloadEmitSource.LocalChanged)
|
||||
await application.mutator.emitItemsFromPayloads(subArray, PayloadEmitSource.LocalChanged)
|
||||
currentIndex += batchSize
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as Factory from './lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('mutator', () => {
|
||||
describe('item mutator', () => {
|
||||
beforeEach(async function () {
|
||||
this.createBarePayload = () => {
|
||||
return new DecryptedPayload({
|
||||
|
||||
271
packages/snjs/mocha/mutator_service.test.js
Normal file
271
packages/snjs/mocha/mutator_service.test.js
Normal file
@@ -0,0 +1,271 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import * as Factory from './lib/factory.js'
|
||||
import { BaseItemCounts } from './lib/BaseItemCounts.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('mutator service', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let application
|
||||
let mutator
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithFakeCrypto()
|
||||
application = context.application
|
||||
mutator = application.mutator
|
||||
|
||||
await context.launch()
|
||||
})
|
||||
|
||||
const createNote = async () => {
|
||||
return mutator.createItem(ContentType.Note, {
|
||||
title: 'hello',
|
||||
text: 'world',
|
||||
})
|
||||
}
|
||||
|
||||
const createTag = async (notes = []) => {
|
||||
const references = notes.map((note) => {
|
||||
return {
|
||||
uuid: note.uuid,
|
||||
content_type: note.content_type,
|
||||
}
|
||||
})
|
||||
return mutator.createItem(ContentType.Tag, {
|
||||
title: 'thoughts',
|
||||
references: references,
|
||||
})
|
||||
}
|
||||
|
||||
it('create item', async function () {
|
||||
const item = await createNote()
|
||||
|
||||
expect(item).to.be.ok
|
||||
expect(item.title).to.equal('hello')
|
||||
})
|
||||
|
||||
it('emitting item through payload and marking dirty should have userModifiedDate', async function () {
|
||||
const payload = Factory.createNotePayload()
|
||||
const item = await mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
const result = await mutator.setItemDirty(item)
|
||||
const appData = result.payload.content.appData
|
||||
expect(appData[DecryptedItem.DefaultAppDomain()][AppDataField.UserModifiedDate]).to.be.ok
|
||||
})
|
||||
|
||||
it('deleting an item should make it immediately unfindable', async () => {
|
||||
const note = await context.createSyncedNote()
|
||||
await mutator.setItemToBeDeleted(note)
|
||||
const foundNote = application.items.findItem(note.uuid)
|
||||
expect(foundNote).to.not.be.ok
|
||||
})
|
||||
|
||||
it('deleting from reference map', async function () {
|
||||
const note = await createNote()
|
||||
const tag = await createTag([note])
|
||||
await mutator.setItemToBeDeleted(note)
|
||||
|
||||
expect(application.items.collection.referenceMap.directMap.get(tag.uuid)).to.eql([])
|
||||
expect(application.items.collection.referenceMap.inverseMap.get(note.uuid).length).to.equal(0)
|
||||
})
|
||||
|
||||
it('deleting referenced item should update referencing item references', async function () {
|
||||
const note = await createNote()
|
||||
let tag = await createTag([note])
|
||||
await mutator.setItemToBeDeleted(note)
|
||||
|
||||
tag = application.items.findItem(tag.uuid)
|
||||
expect(tag.content.references.length).to.equal(0)
|
||||
})
|
||||
|
||||
it('removing relationship should update reference map', async function () {
|
||||
const note = await createNote()
|
||||
const tag = await createTag([note])
|
||||
await mutator.changeItem(tag, (mutator) => {
|
||||
mutator.removeItemAsRelationship(note)
|
||||
})
|
||||
|
||||
expect(application.items.collection.referenceMap.directMap.get(tag.uuid)).to.eql([])
|
||||
expect(application.items.collection.referenceMap.inverseMap.get(note.uuid)).to.eql([])
|
||||
})
|
||||
|
||||
it('emitting discardable payload should remove it from our collection', async function () {
|
||||
const note = await createNote()
|
||||
|
||||
const payload = new DeletedPayload({
|
||||
...note.payload.ejected(),
|
||||
content: undefined,
|
||||
deleted: true,
|
||||
dirty: false,
|
||||
})
|
||||
|
||||
expect(payload.discardable).to.equal(true)
|
||||
|
||||
await context.payloads.emitPayload(payload)
|
||||
|
||||
expect(application.items.findItem(note.uuid)).to.not.be.ok
|
||||
})
|
||||
|
||||
it('change existing item', async function () {
|
||||
const note = await createNote()
|
||||
const newTitle = String(Math.random())
|
||||
await mutator.changeItem(note, (mutator) => {
|
||||
mutator.title = newTitle
|
||||
})
|
||||
|
||||
const latestVersion = application.items.findItem(note.uuid)
|
||||
expect(latestVersion.title).to.equal(newTitle)
|
||||
})
|
||||
|
||||
it('change non-existant item through uuid should fail', async function () {
|
||||
const note = await application.items.createTemplateItem(ContentType.Note, {
|
||||
title: 'hello',
|
||||
text: 'world',
|
||||
})
|
||||
|
||||
const changeFn = async () => {
|
||||
const newTitle = String(Math.random())
|
||||
return mutator.changeItem(note, (mutator) => {
|
||||
mutator.title = newTitle
|
||||
})
|
||||
}
|
||||
await Factory.expectThrowsAsync(() => changeFn(), 'Attempting to change non-existant item')
|
||||
})
|
||||
|
||||
it('set items dirty', async function () {
|
||||
const note = await createNote()
|
||||
await mutator.setItemDirty(note)
|
||||
|
||||
const dirtyItems = application.items.getDirtyItems()
|
||||
expect(dirtyItems.length).to.equal(1)
|
||||
expect(dirtyItems[0].uuid).to.equal(note.uuid)
|
||||
expect(dirtyItems[0].dirty).to.equal(true)
|
||||
})
|
||||
|
||||
describe('duplicateItem', async function () {
|
||||
const sandbox = sinon.createSandbox()
|
||||
|
||||
beforeEach(async function () {
|
||||
this.emitPayloads = sandbox.spy(application.items.payloadManager, 'emitPayloads')
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
it('should duplicate the item and set the duplicate_of property', async function () {
|
||||
const note = await createNote()
|
||||
await mutator.duplicateItem(note)
|
||||
sinon.assert.calledTwice(this.emitPayloads)
|
||||
|
||||
const originalNote = application.items.getDisplayableNotes()[0]
|
||||
const duplicatedNote = application.items.getDisplayableNotes()[1]
|
||||
|
||||
expect(application.items.items.length).to.equal(2 + BaseItemCounts.DefaultItems)
|
||||
expect(application.items.getDisplayableNotes().length).to.equal(2)
|
||||
expect(originalNote.uuid).to.not.equal(duplicatedNote.uuid)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.duplicateOf)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.payload.duplicate_of)
|
||||
expect(duplicatedNote.conflictOf).to.be.undefined
|
||||
expect(duplicatedNote.payload.content.conflict_of).to.be.undefined
|
||||
})
|
||||
|
||||
it('should duplicate the item and set the duplicate_of and conflict_of properties', async function () {
|
||||
const note = await createNote()
|
||||
await mutator.duplicateItem(note, true)
|
||||
sinon.assert.calledTwice(this.emitPayloads)
|
||||
|
||||
const originalNote = application.items.getDisplayableNotes()[0]
|
||||
const duplicatedNote = application.items.getDisplayableNotes()[1]
|
||||
|
||||
expect(application.items.items.length).to.equal(2 + BaseItemCounts.DefaultItems)
|
||||
expect(application.items.getDisplayableNotes().length).to.equal(2)
|
||||
expect(originalNote.uuid).to.not.equal(duplicatedNote.uuid)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.duplicateOf)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.payload.duplicate_of)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.conflictOf)
|
||||
expect(originalNote.uuid).to.equal(duplicatedNote.payload.content.conflict_of)
|
||||
})
|
||||
|
||||
it('duplicate item with relationships', async function () {
|
||||
const note = await createNote()
|
||||
const tag = await createTag([note])
|
||||
const duplicate = await mutator.duplicateItem(tag)
|
||||
|
||||
expect(duplicate.content.references).to.have.length(1)
|
||||
expect(application.items.items).to.have.length(3 + BaseItemCounts.DefaultItems)
|
||||
expect(application.items.getDisplayableTags()).to.have.length(2)
|
||||
})
|
||||
|
||||
it('adds duplicated item as a relationship to items referencing it', async function () {
|
||||
const note = await createNote()
|
||||
let tag = await createTag([note])
|
||||
const duplicateNote = await mutator.duplicateItem(note)
|
||||
expect(tag.content.references).to.have.length(1)
|
||||
|
||||
tag = application.items.findItem(tag.uuid)
|
||||
const references = tag.content.references.map((ref) => ref.uuid)
|
||||
expect(references).to.have.length(2)
|
||||
expect(references).to.include(note.uuid, duplicateNote.uuid)
|
||||
})
|
||||
|
||||
it('duplicates item with additional content', async function () {
|
||||
const note = await mutator.createItem(ContentType.Note, {
|
||||
title: 'hello',
|
||||
text: 'world',
|
||||
})
|
||||
const duplicateNote = await mutator.duplicateItem(note, false, {
|
||||
title: 'hello (copy)',
|
||||
})
|
||||
|
||||
expect(duplicateNote.title).to.equal('hello (copy)')
|
||||
expect(duplicateNote.text).to.equal('world')
|
||||
})
|
||||
})
|
||||
|
||||
it('set item deleted', async function () {
|
||||
const note = await createNote()
|
||||
await mutator.setItemToBeDeleted(note)
|
||||
|
||||
/** Items should never be mutated directly */
|
||||
expect(note.deleted).to.not.be.ok
|
||||
|
||||
const latestVersion = context.payloads.findOne(note.uuid)
|
||||
expect(latestVersion.deleted).to.equal(true)
|
||||
expect(latestVersion.dirty).to.equal(true)
|
||||
expect(latestVersion.content).to.not.be.ok
|
||||
|
||||
/** Deleted items do not show up in item manager's public interface */
|
||||
expect(application.items.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
expect(application.items.getDisplayableNotes().length).to.equal(0)
|
||||
})
|
||||
|
||||
it('should empty trash', async function () {
|
||||
const note = await createNote()
|
||||
const versionTwo = await mutator.changeItem(note, (mutator) => {
|
||||
mutator.trashed = true
|
||||
})
|
||||
|
||||
expect(application.items.trashSmartView).to.be.ok
|
||||
expect(versionTwo.trashed).to.equal(true)
|
||||
expect(versionTwo.dirty).to.equal(true)
|
||||
expect(versionTwo.content).to.be.ok
|
||||
|
||||
expect(application.items.items.length).to.equal(1 + BaseItemCounts.DefaultItems)
|
||||
expect(application.items.trashedItems.length).to.equal(1)
|
||||
|
||||
await application.mutator.emptyTrash()
|
||||
const versionThree = context.payloads.findOne(note.uuid)
|
||||
expect(versionThree.deleted).to.equal(true)
|
||||
expect(application.items.trashedItems.length).to.equal(0)
|
||||
})
|
||||
})
|
||||
@@ -6,9 +6,10 @@ describe('note display criteria', function () {
|
||||
beforeEach(async function () {
|
||||
this.payloadManager = new PayloadManager()
|
||||
this.itemManager = new ItemManager(this.payloadManager)
|
||||
this.mutator = new MutatorService(this.itemManager, this.payloadManager)
|
||||
|
||||
this.createNote = async (title = 'hello', text = 'world') => {
|
||||
return this.itemManager.createItem(ContentType.Note, {
|
||||
return this.mutator.createItem(ContentType.Note, {
|
||||
title: title,
|
||||
text: text,
|
||||
})
|
||||
@@ -21,7 +22,7 @@ describe('note display criteria', function () {
|
||||
content_type: note.content_type,
|
||||
}
|
||||
})
|
||||
return this.itemManager.createItem(ContentType.Tag, {
|
||||
return this.mutator.createItem(ContentType.Tag, {
|
||||
title: title,
|
||||
references: references,
|
||||
})
|
||||
@@ -31,138 +32,168 @@ describe('note display criteria', function () {
|
||||
it('includePinned off', async function () {
|
||||
await this.createNote()
|
||||
const pendingPin = await this.createNote()
|
||||
await this.itemManager.changeItem(pendingPin, (mutator) => {
|
||||
await this.mutator.changeItem(pendingPin, (mutator) => {
|
||||
mutator.pinned = true
|
||||
})
|
||||
const criteria = {
|
||||
includePinned: false,
|
||||
}
|
||||
expect(
|
||||
itemsMatchingOptions(criteria, this.itemManager.collection.all(ContentType.Note), this.itemManager.collection)
|
||||
.length,
|
||||
notesAndFilesMatchingOptions(
|
||||
criteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
).length,
|
||||
).to.equal(1)
|
||||
})
|
||||
|
||||
it('includePinned on', async function () {
|
||||
await this.createNote()
|
||||
const pendingPin = await this.createNote()
|
||||
await this.itemManager.changeItem(pendingPin, (mutator) => {
|
||||
await this.mutator.changeItem(pendingPin, (mutator) => {
|
||||
mutator.pinned = true
|
||||
})
|
||||
const criteria = { includePinned: true }
|
||||
expect(
|
||||
itemsMatchingOptions(criteria, this.itemManager.collection.all(ContentType.Note), this.itemManager.collection)
|
||||
.length,
|
||||
notesAndFilesMatchingOptions(
|
||||
criteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
).length,
|
||||
).to.equal(2)
|
||||
})
|
||||
|
||||
it('includeTrashed off', async function () {
|
||||
await this.createNote()
|
||||
const pendingTrash = await this.createNote()
|
||||
await this.itemManager.changeItem(pendingTrash, (mutator) => {
|
||||
await this.mutator.changeItem(pendingTrash, (mutator) => {
|
||||
mutator.trashed = true
|
||||
})
|
||||
const criteria = { includeTrashed: false }
|
||||
expect(
|
||||
itemsMatchingOptions(criteria, this.itemManager.collection.all(ContentType.Note), this.itemManager.collection)
|
||||
.length,
|
||||
notesAndFilesMatchingOptions(
|
||||
criteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
).length,
|
||||
).to.equal(1)
|
||||
})
|
||||
|
||||
it('includeTrashed on', async function () {
|
||||
await this.createNote()
|
||||
const pendingTrash = await this.createNote()
|
||||
await this.itemManager.changeItem(pendingTrash, (mutator) => {
|
||||
await this.mutator.changeItem(pendingTrash, (mutator) => {
|
||||
mutator.trashed = true
|
||||
})
|
||||
const criteria = { includeTrashed: true }
|
||||
expect(
|
||||
itemsMatchingOptions(criteria, this.itemManager.collection.all(ContentType.Note), this.itemManager.collection)
|
||||
.length,
|
||||
notesAndFilesMatchingOptions(
|
||||
criteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
).length,
|
||||
).to.equal(2)
|
||||
})
|
||||
|
||||
it('includeArchived off', async function () {
|
||||
await this.createNote()
|
||||
const pendingArchive = await this.createNote()
|
||||
await this.itemManager.changeItem(pendingArchive, (mutator) => {
|
||||
await this.mutator.changeItem(pendingArchive, (mutator) => {
|
||||
mutator.archived = true
|
||||
})
|
||||
const criteria = { includeArchived: false }
|
||||
expect(
|
||||
itemsMatchingOptions(criteria, this.itemManager.collection.all(ContentType.Note), this.itemManager.collection)
|
||||
.length,
|
||||
notesAndFilesMatchingOptions(
|
||||
criteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
).length,
|
||||
).to.equal(1)
|
||||
})
|
||||
|
||||
it('includeArchived on', async function () {
|
||||
await this.createNote()
|
||||
const pendingArchive = await this.createNote()
|
||||
await this.itemManager.changeItem(pendingArchive, (mutator) => {
|
||||
await this.mutator.changeItem(pendingArchive, (mutator) => {
|
||||
mutator.archived = true
|
||||
})
|
||||
const criteria = {
|
||||
includeArchived: true,
|
||||
}
|
||||
expect(
|
||||
itemsMatchingOptions(criteria, this.itemManager.collection.all(ContentType.Note), this.itemManager.collection)
|
||||
.length,
|
||||
notesAndFilesMatchingOptions(
|
||||
criteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
).length,
|
||||
).to.equal(2)
|
||||
})
|
||||
|
||||
it('includeProtected off', async function () {
|
||||
await this.createNote()
|
||||
const pendingProtected = await this.createNote()
|
||||
await this.itemManager.changeItem(pendingProtected, (mutator) => {
|
||||
await this.mutator.changeItem(pendingProtected, (mutator) => {
|
||||
mutator.protected = true
|
||||
})
|
||||
const criteria = { includeProtected: false }
|
||||
expect(
|
||||
itemsMatchingOptions(criteria, this.itemManager.collection.all(ContentType.Note), this.itemManager.collection)
|
||||
.length,
|
||||
notesAndFilesMatchingOptions(
|
||||
criteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
).length,
|
||||
).to.equal(1)
|
||||
})
|
||||
|
||||
it('includeProtected on', async function () {
|
||||
await this.createNote()
|
||||
const pendingProtected = await this.createNote()
|
||||
await this.itemManager.changeItem(pendingProtected, (mutator) => {
|
||||
await this.mutator.changeItem(pendingProtected, (mutator) => {
|
||||
mutator.protected = true
|
||||
})
|
||||
const criteria = {
|
||||
includeProtected: true,
|
||||
}
|
||||
expect(
|
||||
itemsMatchingOptions(criteria, this.itemManager.collection.all(ContentType.Note), this.itemManager.collection)
|
||||
.length,
|
||||
notesAndFilesMatchingOptions(
|
||||
criteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
).length,
|
||||
).to.equal(2)
|
||||
})
|
||||
|
||||
it('protectedSearchEnabled false', async function () {
|
||||
const normal = await this.createNote('hello', 'world')
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await this.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.protected = true
|
||||
})
|
||||
const criteria = {
|
||||
searchQuery: { query: 'world', includeProtectedNoteText: false },
|
||||
}
|
||||
expect(
|
||||
itemsMatchingOptions(criteria, this.itemManager.collection.all(ContentType.Note), this.itemManager.collection)
|
||||
.length,
|
||||
notesAndFilesMatchingOptions(
|
||||
criteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
).length,
|
||||
).to.equal(0)
|
||||
})
|
||||
|
||||
it('protectedSearchEnabled true', async function () {
|
||||
const normal = await this.createNote()
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await this.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.protected = true
|
||||
})
|
||||
const criteria = {
|
||||
searchQuery: { query: 'world', includeProtectedNoteText: true },
|
||||
}
|
||||
expect(
|
||||
itemsMatchingOptions(criteria, this.itemManager.collection.all(ContentType.Note), this.itemManager.collection)
|
||||
.length,
|
||||
notesAndFilesMatchingOptions(
|
||||
criteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
).length,
|
||||
).to.equal(1)
|
||||
})
|
||||
|
||||
@@ -175,7 +206,7 @@ describe('note display criteria', function () {
|
||||
tags: [tag],
|
||||
}
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
matchingCriteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
@@ -186,7 +217,7 @@ describe('note display criteria', function () {
|
||||
tags: [looseTag],
|
||||
}
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
nonmatchingCriteria,
|
||||
this.itemManager.collection.all(ContentType.Note),
|
||||
this.itemManager.collection,
|
||||
@@ -198,7 +229,7 @@ describe('note display criteria', function () {
|
||||
it('normal note', async function () {
|
||||
await this.createNote()
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
},
|
||||
@@ -208,7 +239,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
},
|
||||
@@ -218,7 +249,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(0)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.archivedSmartView],
|
||||
},
|
||||
@@ -230,12 +261,12 @@ describe('note display criteria', function () {
|
||||
|
||||
it('trashed note', async function () {
|
||||
const normal = await this.createNote()
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await this.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.trashed = true
|
||||
})
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeTrashed: false,
|
||||
@@ -246,7 +277,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(0)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
},
|
||||
@@ -256,7 +287,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.archivedSmartView],
|
||||
},
|
||||
@@ -268,12 +299,12 @@ describe('note display criteria', function () {
|
||||
|
||||
it('archived note', async function () {
|
||||
const normal = await this.createNote()
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await this.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.trashed = false
|
||||
mutator.archived = true
|
||||
})
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeArchived: false,
|
||||
@@ -284,7 +315,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(0)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
},
|
||||
@@ -294,7 +325,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(0)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.archivedSmartView],
|
||||
},
|
||||
@@ -306,13 +337,13 @@ describe('note display criteria', function () {
|
||||
|
||||
it('archived + trashed note', async function () {
|
||||
const normal = await this.createNote()
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await this.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.trashed = true
|
||||
mutator.archived = true
|
||||
})
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
},
|
||||
@@ -322,7 +353,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
},
|
||||
@@ -332,7 +363,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.archivedSmartView],
|
||||
},
|
||||
@@ -348,7 +379,7 @@ describe('note display criteria', function () {
|
||||
await this.createNote()
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeTrashed: true,
|
||||
@@ -359,7 +390,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
includeTrashed: true,
|
||||
@@ -373,12 +404,12 @@ describe('note display criteria', function () {
|
||||
it('trashed note', async function () {
|
||||
const normal = await this.createNote()
|
||||
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await this.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.trashed = true
|
||||
})
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeTrashed: false,
|
||||
@@ -389,7 +420,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(0)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeTrashed: true,
|
||||
@@ -400,7 +431,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
includeTrashed: true,
|
||||
@@ -411,7 +442,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.archivedSmartView],
|
||||
includeTrashed: true,
|
||||
@@ -425,13 +456,13 @@ describe('note display criteria', function () {
|
||||
it('archived + trashed note', async function () {
|
||||
const normal = await this.createNote()
|
||||
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await this.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.trashed = true
|
||||
mutator.archived = true
|
||||
})
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
},
|
||||
@@ -441,7 +472,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
},
|
||||
@@ -451,7 +482,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.archivedSmartView],
|
||||
},
|
||||
@@ -467,7 +498,7 @@ describe('note display criteria', function () {
|
||||
await this.createNote()
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeArchived: true,
|
||||
@@ -478,7 +509,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
includeArchived: true,
|
||||
@@ -491,12 +522,12 @@ describe('note display criteria', function () {
|
||||
|
||||
it('archived note', async function () {
|
||||
const normal = await this.createNote()
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await this.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.archived = true
|
||||
})
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeArchived: false,
|
||||
@@ -507,7 +538,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(0)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeArchived: true,
|
||||
@@ -518,7 +549,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
includeArchived: true,
|
||||
@@ -529,7 +560,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(0)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.archivedSmartView],
|
||||
includeArchived: false,
|
||||
@@ -542,13 +573,13 @@ describe('note display criteria', function () {
|
||||
|
||||
it('archived + trashed note', async function () {
|
||||
const normal = await this.createNote()
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await this.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.trashed = true
|
||||
mutator.archived = true
|
||||
})
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeArchived: true,
|
||||
@@ -559,7 +590,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
includeArchived: true,
|
||||
@@ -570,7 +601,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.archivedSmartView],
|
||||
includeArchived: true,
|
||||
@@ -587,7 +618,7 @@ describe('note display criteria', function () {
|
||||
await this.createNote()
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [
|
||||
this.itemManager.allNotesSmartView,
|
||||
@@ -601,7 +632,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
},
|
||||
@@ -613,12 +644,12 @@ describe('note display criteria', function () {
|
||||
|
||||
it('archived note', async function () {
|
||||
const normal = await this.createNote()
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await this.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.archived = true
|
||||
})
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeArchived: false,
|
||||
@@ -629,7 +660,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(0)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeArchived: true,
|
||||
@@ -640,7 +671,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
includeArchived: true,
|
||||
@@ -651,7 +682,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(0)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.archivedSmartView],
|
||||
includeArchived: false,
|
||||
@@ -664,13 +695,13 @@ describe('note display criteria', function () {
|
||||
|
||||
it('archived + trashed note', async function () {
|
||||
const normal = await this.createNote()
|
||||
await this.itemManager.changeItem(normal, (mutator) => {
|
||||
await this.mutator.changeItem(normal, (mutator) => {
|
||||
mutator.trashed = true
|
||||
mutator.archived = true
|
||||
})
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.allNotesSmartView],
|
||||
includeArchived: true,
|
||||
@@ -681,7 +712,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(0)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.trashSmartView],
|
||||
includeArchived: true,
|
||||
@@ -692,7 +723,7 @@ describe('note display criteria', function () {
|
||||
).to.equal(1)
|
||||
|
||||
expect(
|
||||
itemsMatchingOptions(
|
||||
notesAndFilesMatchingOptions(
|
||||
{
|
||||
views: [this.itemManager.archivedSmartView],
|
||||
includeArchived: true,
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('protections', function () {
|
||||
})
|
||||
|
||||
let note = await Factory.createMappedNote(application)
|
||||
note = await application.mutator.protectNote(note)
|
||||
note = await application.protections.protectNote(note)
|
||||
|
||||
expect(await application.authorizeNoteAccess(note)).to.be.true
|
||||
expect(challengePrompts).to.equal(1)
|
||||
@@ -57,7 +57,7 @@ describe('protections', function () {
|
||||
it('sets `note.protected` to true', async function () {
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
let note = await Factory.createMappedNote(application)
|
||||
note = await application.mutator.protectNote(note)
|
||||
note = await application.protections.protectNote(note)
|
||||
expect(note.protected).to.be.true
|
||||
})
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('protections', function () {
|
||||
|
||||
await application.addPasscode(passcode)
|
||||
let note = await Factory.createMappedNote(application)
|
||||
note = await application.mutator.protectNote(note)
|
||||
note = await application.protections.protectNote(note)
|
||||
|
||||
expect(await application.authorizeNoteAccess(note)).to.be.true
|
||||
expect(challengePrompts).to.equal(1)
|
||||
@@ -120,8 +120,8 @@ describe('protections', function () {
|
||||
await application.addPasscode(passcode)
|
||||
let note = await Factory.createMappedNote(application)
|
||||
const uuid = note.uuid
|
||||
note = await application.mutator.protectNote(note)
|
||||
note = await application.mutator.unprotectNote(note)
|
||||
note = await application.protections.protectNote(note)
|
||||
note = await application.protections.unprotectNote(note)
|
||||
expect(note.uuid).to.equal(uuid)
|
||||
expect(note.protected).to.equal(false)
|
||||
expect(challengePrompts).to.equal(1)
|
||||
@@ -142,8 +142,8 @@ describe('protections', function () {
|
||||
|
||||
await application.addPasscode(passcode)
|
||||
let note = await Factory.createMappedNote(application)
|
||||
note = await application.mutator.protectNote(note)
|
||||
const result = await application.mutator.unprotectNote(note)
|
||||
note = await application.protections.protectNote(note)
|
||||
const result = await application.protections.unprotectNote(note)
|
||||
expect(result).to.be.undefined
|
||||
expect(challengePrompts).to.equal(1)
|
||||
})
|
||||
@@ -174,7 +174,7 @@ describe('protections', function () {
|
||||
|
||||
await application.addPasscode(passcode)
|
||||
let note = await Factory.createMappedNote(application)
|
||||
note = await application.mutator.protectNote(note)
|
||||
note = await application.protections.protectNote(note)
|
||||
|
||||
expect(await application.authorizeNoteAccess(note)).to.be.true
|
||||
expect(await application.authorizeNoteAccess(note)).to.be.true
|
||||
@@ -226,7 +226,7 @@ describe('protections', function () {
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
|
||||
let note = await Factory.createMappedNote(application)
|
||||
note = await application.mutator.protectNote(note)
|
||||
note = await application.protections.protectNote(note)
|
||||
|
||||
expect(await application.authorizeNoteAccess(note)).to.be.true
|
||||
})
|
||||
@@ -431,8 +431,8 @@ describe('protections', function () {
|
||||
const NOTE_COUNT = 3
|
||||
let notes = await Factory.createManyMappedNotes(application, NOTE_COUNT)
|
||||
|
||||
notes[0] = await application.mutator.protectNote(notes[0])
|
||||
notes[1] = await application.mutator.protectNote(notes[1])
|
||||
notes[0] = await application.protections.protectNote(notes[0])
|
||||
notes[1] = await application.protections.protectNote(notes[1])
|
||||
|
||||
expect(await application.authorizeProtectedActionForNotes(notes, ChallengeReason.SelectProtectedNote)).lengthOf(
|
||||
NOTE_COUNT,
|
||||
@@ -468,8 +468,8 @@ describe('protections', function () {
|
||||
|
||||
const NOTE_COUNT = 3
|
||||
let notes = await Factory.createManyMappedNotes(application, NOTE_COUNT)
|
||||
notes[0] = await application.mutator.protectNote(notes[0])
|
||||
notes[1] = await application.mutator.protectNote(notes[1])
|
||||
notes[0] = await application.protections.protectNote(notes[0])
|
||||
notes[1] = await application.protections.protectNote(notes[1])
|
||||
|
||||
expect(await application.authorizeProtectedActionForNotes(notes, ChallengeReason.SelectProtectedNote)).lengthOf(
|
||||
NOTE_COUNT,
|
||||
@@ -493,8 +493,8 @@ describe('protections', function () {
|
||||
|
||||
const NOTE_COUNT = 3
|
||||
let notes = await Factory.createManyMappedNotes(application, NOTE_COUNT)
|
||||
notes[0] = await application.mutator.protectNote(notes[0])
|
||||
notes[1] = await application.mutator.protectNote(notes[1])
|
||||
notes[0] = await application.protections.protectNote(notes[0])
|
||||
notes[1] = await application.protections.protectNote(notes[1])
|
||||
|
||||
expect(await application.authorizeProtectedActionForNotes(notes, ChallengeReason.SelectProtectedNote)).lengthOf(1)
|
||||
expect(challengePrompts).to.equal(1)
|
||||
@@ -513,7 +513,7 @@ describe('protections', function () {
|
||||
|
||||
const NOTE_COUNT = 3
|
||||
let notes = await Factory.createManyMappedNotes(application, NOTE_COUNT)
|
||||
notes = await application.mutator.protectNotes(notes)
|
||||
notes = await application.protections.protectNotes(notes)
|
||||
|
||||
for (const note of notes) {
|
||||
expect(note.protected).to.be.true
|
||||
@@ -550,8 +550,8 @@ describe('protections', function () {
|
||||
|
||||
const NOTE_COUNT = 3
|
||||
let notes = await Factory.createManyMappedNotes(application, NOTE_COUNT)
|
||||
notes = await application.mutator.protectNotes(notes)
|
||||
notes = await application.mutator.unprotectNotes(notes)
|
||||
notes = await application.protections.protectNotes(notes)
|
||||
notes = await application.protections.unprotectNotes(notes)
|
||||
|
||||
for (const note of notes) {
|
||||
expect(note.protected).to.be.false
|
||||
@@ -587,8 +587,8 @@ describe('protections', function () {
|
||||
|
||||
const NOTE_COUNT = 3
|
||||
let notes = await Factory.createManyMappedNotes(application, NOTE_COUNT)
|
||||
notes = await application.mutator.protectNotes(notes)
|
||||
notes = await application.mutator.unprotectNotes(notes)
|
||||
notes = await application.protections.protectNotes(notes)
|
||||
notes = await application.protections.unprotectNotes(notes)
|
||||
|
||||
for (const note of notes) {
|
||||
expect(note.protected).to.be.false
|
||||
@@ -612,8 +612,8 @@ describe('protections', function () {
|
||||
|
||||
const NOTE_COUNT = 3
|
||||
let notes = await Factory.createManyMappedNotes(application, NOTE_COUNT)
|
||||
notes = await application.mutator.protectNotes(notes)
|
||||
notes = await application.mutator.unprotectNotes(notes)
|
||||
notes = await application.protections.protectNotes(notes)
|
||||
notes = await application.protections.unprotectNotes(notes)
|
||||
|
||||
for (const note of notes) {
|
||||
expect(note.protected).to.be(true)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/Applications.js'
|
||||
import { BaseItemCounts } from './lib/BaseItemCounts.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
import WebDeviceInterface from './lib/web_device_interface.js'
|
||||
chai.use(chaiAsPromised)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as Factory from './lib/factory.js'
|
||||
import * as Files from './lib/Files.js'
|
||||
import * as Events from './lib/Events.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -98,26 +99,43 @@ describe('settings service', function () {
|
||||
})
|
||||
|
||||
it('reads a nonexistent sensitive setting', async () => {
|
||||
const setting = await application.settings.getDoesSensitiveSettingExist(SettingName.create(SettingName.NAMES.MfaSecret).getValue())
|
||||
const setting = await application.settings.getDoesSensitiveSettingExist(
|
||||
SettingName.create(SettingName.NAMES.MfaSecret).getValue(),
|
||||
)
|
||||
expect(setting).to.equal(false)
|
||||
})
|
||||
|
||||
it('creates and reads a sensitive setting', async () => {
|
||||
await application.settings.updateSetting(SettingName.create(SettingName.NAMES.MfaSecret).getValue(), 'fake_secret', true)
|
||||
const setting = await application.settings.getDoesSensitiveSettingExist(SettingName.create(SettingName.NAMES.MfaSecret).getValue())
|
||||
await application.settings.updateSetting(
|
||||
SettingName.create(SettingName.NAMES.MfaSecret).getValue(),
|
||||
'fake_secret',
|
||||
true,
|
||||
)
|
||||
const setting = await application.settings.getDoesSensitiveSettingExist(
|
||||
SettingName.create(SettingName.NAMES.MfaSecret).getValue(),
|
||||
)
|
||||
expect(setting).to.equal(true)
|
||||
})
|
||||
|
||||
it('creates and lists a sensitive setting', async () => {
|
||||
await application.settings.updateSetting(SettingName.create(SettingName.NAMES.MfaSecret).getValue(), 'fake_secret', true)
|
||||
await application.settings.updateSetting(SettingName.create(SettingName.NAMES.MuteFailedBackupsEmails).getValue(), MuteFailedBackupsEmailsOption.Muted)
|
||||
await application.settings.updateSetting(
|
||||
SettingName.create(SettingName.NAMES.MfaSecret).getValue(),
|
||||
'fake_secret',
|
||||
true,
|
||||
)
|
||||
await application.settings.updateSetting(
|
||||
SettingName.create(SettingName.NAMES.MuteFailedBackupsEmails).getValue(),
|
||||
MuteFailedBackupsEmailsOption.Muted,
|
||||
)
|
||||
const settings = await application.settings.listSettings()
|
||||
expect(settings.getSettingValue(SettingName.create(SettingName.NAMES.MuteFailedBackupsEmails).getValue())).to.eql(MuteFailedBackupsEmailsOption.Muted)
|
||||
expect(settings.getSettingValue(SettingName.create(SettingName.NAMES.MuteFailedBackupsEmails).getValue())).to.eql(
|
||||
MuteFailedBackupsEmailsOption.Muted,
|
||||
)
|
||||
expect(settings.getSettingValue(SettingName.create(SettingName.NAMES.MfaSecret).getValue())).to.not.be.ok
|
||||
})
|
||||
|
||||
it('reads a subscription setting - @paidfeature', async () => {
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
@@ -130,19 +148,21 @@ describe('settings service', function () {
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
payAmount: 59.0,
|
||||
})
|
||||
|
||||
await Factory.sleep(2)
|
||||
|
||||
const setting = await application.settings.getSubscriptionSetting(SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue())
|
||||
const setting = await application.settings.getSubscriptionSetting(
|
||||
SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue(),
|
||||
)
|
||||
expect(setting).to.be.a('string')
|
||||
})
|
||||
|
||||
it('persist irreplaceable subscription settings between subsequent subscriptions - @paidfeature', async () => {
|
||||
await reInitializeApplicationWithRealCrypto()
|
||||
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: subscriptionId,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
@@ -155,7 +175,7 @@ describe('settings service', function () {
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
payAmount: 59.0,
|
||||
})
|
||||
await Factory.sleep(1)
|
||||
|
||||
@@ -166,13 +186,17 @@ describe('settings service', function () {
|
||||
|
||||
await Factory.sleep(1)
|
||||
|
||||
const limitSettingBefore = await application.settings.getSubscriptionSetting(SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue())
|
||||
const limitSettingBefore = await application.settings.getSubscriptionSetting(
|
||||
SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue(),
|
||||
)
|
||||
expect(limitSettingBefore).to.equal('107374182400')
|
||||
|
||||
const usedSettingBefore = await application.settings.getSubscriptionSetting(SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue())
|
||||
const usedSettingBefore = await application.settings.getSubscriptionSetting(
|
||||
SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(),
|
||||
)
|
||||
expect(usedSettingBefore).to.equal('196')
|
||||
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_EXPIRED', {
|
||||
await Events.publishMockedEvent('SUBSCRIPTION_EXPIRED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
@@ -181,11 +205,11 @@ describe('settings service', function () {
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userExistingSubscriptionsCount: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
payAmount: 59.0,
|
||||
})
|
||||
await Factory.sleep(1)
|
||||
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
@@ -198,14 +222,18 @@ describe('settings service', function () {
|
||||
totalActiveSubscriptionsCount: 2,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
payAmount: 59.0,
|
||||
})
|
||||
await Factory.sleep(1)
|
||||
|
||||
const limitSettingAfter = await application.settings.getSubscriptionSetting(SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue())
|
||||
const limitSettingAfter = await application.settings.getSubscriptionSetting(
|
||||
SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue(),
|
||||
)
|
||||
expect(limitSettingAfter).to.equal(limitSettingBefore)
|
||||
|
||||
const usedSettingAfter = await application.settings.getSubscriptionSetting(SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue())
|
||||
const usedSettingAfter = await application.settings.getSubscriptionSetting(
|
||||
SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(),
|
||||
)
|
||||
expect(usedSettingAfter).to.equal(usedSettingBefore)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/Applications.js'
|
||||
import { BaseItemCounts } from './lib/BaseItemCounts.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
import WebDeviceInterface from './lib/web_device_interface.js'
|
||||
chai.use(chaiAsPromised)
|
||||
@@ -38,7 +38,9 @@ describe('singletons', function () {
|
||||
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
|
||||
this.registerUser = async () => {
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount
|
||||
await Factory.registerUserToApplication({
|
||||
application: this.application,
|
||||
email: this.email,
|
||||
@@ -62,7 +64,7 @@ describe('singletons', function () {
|
||||
])
|
||||
|
||||
this.createExtMgr = () => {
|
||||
return this.application.itemManager.createItem(
|
||||
return this.application.mutator.createItem(
|
||||
ContentType.Component,
|
||||
{
|
||||
package_info: {
|
||||
@@ -93,11 +95,11 @@ describe('singletons', function () {
|
||||
const prefs2 = createPrefsPayload()
|
||||
const prefs3 = createPrefsPayload()
|
||||
|
||||
const items = await this.application.itemManager.emitItemsFromPayloads(
|
||||
const items = await this.application.mutator.emitItemsFromPayloads(
|
||||
[prefs1, prefs2, prefs3],
|
||||
PayloadEmitSource.LocalChanged,
|
||||
)
|
||||
await this.application.itemManager.setItemsDirty(items)
|
||||
await this.application.mutator.setItemsDirty(items)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount)
|
||||
})
|
||||
@@ -192,7 +194,7 @@ describe('singletons', function () {
|
||||
if (!beginCheckingResponse) {
|
||||
return
|
||||
}
|
||||
if (!didCompleteRelevantSync && eventName === SyncEvent.SingleRoundTripSyncCompleted) {
|
||||
if (!didCompleteRelevantSync && eventName === SyncEvent.PaginatedSyncRequestCompleted) {
|
||||
didCompleteRelevantSync = true
|
||||
const saved = data.savedPayloads
|
||||
expect(saved.length).to.equal(1)
|
||||
@@ -327,7 +329,7 @@ describe('singletons', function () {
|
||||
|
||||
it('alternating the uuid of a singleton should return correct result', async function () {
|
||||
const payload = createPrefsPayload()
|
||||
const item = await this.application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
const item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
const predicate = new Predicate('content_type', '=', item.content_type)
|
||||
let resolvedItem = await this.application.singletonManager.findOrCreateContentTypeSingleton(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/Applications.js'
|
||||
import { BaseItemCounts } from './lib/BaseItemCounts.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -279,7 +279,7 @@ describe('storage manager', function () {
|
||||
})
|
||||
|
||||
await Factory.createSyncedNote(this.application)
|
||||
expect(await Factory.storagePayloadCount(this.application)).to.equal(BaseItemCounts.DefaultItems + 1)
|
||||
expect(await Factory.storagePayloadCount(this.application)).to.equal(BaseItemCounts.DefaultItemsWithAccount + 1)
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
await Factory.sleep(0.1, 'Allow all untrackable singleton syncs to complete')
|
||||
expect(await Factory.storagePayloadCount(this.application)).to.equal(BaseItemCounts.DefaultItems)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as Factory from './lib/factory.js'
|
||||
import * as Events from './lib/Events.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
@@ -31,7 +32,7 @@ describe('subscriptions', function () {
|
||||
password: context.password,
|
||||
})
|
||||
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from '../lib/Applications.js'
|
||||
import { BaseItemCounts } from '../lib/BaseItemCounts.js'
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import { createSyncedNoteWithTag } from '../lib/Items.js'
|
||||
import * as Utils from '../lib/Utils.js'
|
||||
@@ -16,7 +16,7 @@ describe('online conflict handling', function () {
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount
|
||||
|
||||
this.context = await Factory.createAppContextWithFakeCrypto('AppA')
|
||||
await this.context.launch()
|
||||
@@ -64,7 +64,7 @@ describe('online conflict handling', function () {
|
||||
it('components should not be duplicated under any circumstances', async function () {
|
||||
const payload = createDirtyPayload(ContentType.Component)
|
||||
|
||||
const item = await this.application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
const item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
|
||||
this.expectedItemCount++
|
||||
|
||||
@@ -91,7 +91,7 @@ describe('online conflict handling', function () {
|
||||
|
||||
it('items keys should not be duplicated under any circumstances', async function () {
|
||||
const payload = createDirtyPayload(ContentType.ItemsKey)
|
||||
const item = await this.application.itemManager.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
const item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
|
||||
this.expectedItemCount++
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
/** First modify the item without saving so that
|
||||
@@ -118,7 +118,7 @@ describe('online conflict handling', function () {
|
||||
// create an item and sync it
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
this.expectedItemCount++
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
const rawPayloads = await this.application.diskStorageService.getAllRawPayloads()
|
||||
@@ -128,11 +128,11 @@ describe('online conflict handling', function () {
|
||||
const dirtyValue = `${Math.random()}`
|
||||
|
||||
/** Modify nonsense first to get around strategyWhenConflictingWithItem with previousRevision check */
|
||||
await this.application.itemManager.changeNote(note, (mutator) => {
|
||||
await this.application.mutator.changeNote(note, (mutator) => {
|
||||
mutator.title = 'any'
|
||||
})
|
||||
|
||||
await this.application.itemManager.changeNote(note, (mutator) => {
|
||||
await this.application.mutator.changeNote(note, (mutator) => {
|
||||
// modify this item locally to have differing contents from server
|
||||
mutator.title = dirtyValue
|
||||
// Intentionally don't change updated_at. We want to simulate a chaotic case where
|
||||
@@ -238,7 +238,7 @@ describe('online conflict handling', function () {
|
||||
it('should duplicate item if saving a modified item and clearing our sync token', async function () {
|
||||
let note = await Factory.createMappedNote(this.application)
|
||||
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
this.expectedItemCount++
|
||||
@@ -279,11 +279,11 @@ describe('online conflict handling', function () {
|
||||
it('should handle sync conflicts by not duplicating same data', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
this.expectedItemCount++
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
// keep item as is and set dirty
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
|
||||
// clear sync token so that all items are retrieved on next sync
|
||||
this.application.syncService.clearSyncPositionTokens()
|
||||
@@ -295,10 +295,10 @@ describe('online conflict handling', function () {
|
||||
|
||||
it('clearing conflict_of on two clients simultaneously should keep us in sync', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
this.expectedItemCount++
|
||||
|
||||
await this.application.mutator.changeAndSaveItem(
|
||||
await this.application.changeAndSaveItem(
|
||||
note,
|
||||
(mutator) => {
|
||||
// client A
|
||||
@@ -311,7 +311,7 @@ describe('online conflict handling', function () {
|
||||
|
||||
// client B
|
||||
await this.application.syncService.clearSyncPositionTokens()
|
||||
await this.application.itemManager.changeItem(
|
||||
await this.application.mutator.changeItem(
|
||||
note,
|
||||
(mutator) => {
|
||||
mutator.mutableContent.conflict_of = 'bar'
|
||||
@@ -329,10 +329,10 @@ describe('online conflict handling', function () {
|
||||
|
||||
it('setting property on two clients simultaneously should create conflict', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
this.expectedItemCount++
|
||||
|
||||
await this.application.mutator.changeAndSaveItem(
|
||||
await this.application.changeAndSaveItem(
|
||||
note,
|
||||
(mutator) => {
|
||||
// client A
|
||||
@@ -369,12 +369,12 @@ describe('online conflict handling', function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
const originalPayload = note.payloadRepresentation()
|
||||
this.expectedItemCount++
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount)
|
||||
|
||||
// client A
|
||||
await this.application.itemManager.setItemToBeDeleted(note)
|
||||
await this.application.mutator.setItemToBeDeleted(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
this.expectedItemCount--
|
||||
expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount)
|
||||
@@ -387,10 +387,10 @@ describe('online conflict handling', function () {
|
||||
deleted: false,
|
||||
updated_at: Factory.yesterday(),
|
||||
})
|
||||
await this.application.itemManager.emitItemsFromPayloads([mutatedPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([mutatedPayload], PayloadEmitSource.LocalChanged)
|
||||
const resultNote = this.application.itemManager.findItem(note.uuid)
|
||||
expect(resultNote.uuid).to.equal(note.uuid)
|
||||
await this.application.itemManager.setItemDirty(resultNote)
|
||||
await this.application.mutator.setItemDirty(resultNote)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
// We expect that this item is now gone for good, and a duplicate has not been created.
|
||||
@@ -400,7 +400,7 @@ describe('online conflict handling', function () {
|
||||
|
||||
it('if server says not deleted but client says deleted, keep server state', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
this.expectedItemCount++
|
||||
|
||||
// client A
|
||||
@@ -426,7 +426,7 @@ describe('online conflict handling', function () {
|
||||
|
||||
it('should create conflict if syncing an item that is stale', async function () {
|
||||
let note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
note = this.application.items.findItem(note.uuid)
|
||||
expect(note.dirty).to.equal(false)
|
||||
@@ -462,7 +462,7 @@ describe('online conflict handling', function () {
|
||||
|
||||
it('creating conflict with exactly equal content should keep us in sync', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
this.expectedItemCount++
|
||||
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
@@ -505,7 +505,7 @@ describe('online conflict handling', function () {
|
||||
for (const note of this.application.itemManager.getDisplayableNotes()) {
|
||||
/** First modify the item without saving so that
|
||||
* our local contents digress from the server's */
|
||||
await this.application.itemManager.changeItem(note, (mutator) => {
|
||||
await this.application.mutator.changeItem(note, (mutator) => {
|
||||
mutator.text = '1'
|
||||
})
|
||||
|
||||
@@ -530,18 +530,18 @@ describe('online conflict handling', function () {
|
||||
const payload1 = Factory.createStorageItemPayload(ContentType.Tag)
|
||||
const payload2 = Factory.createStorageItemPayload(ContentType.UserPrefs)
|
||||
this.expectedItemCount -= 1 /** auto-created user preferences */
|
||||
await this.application.itemManager.emitItemsFromPayloads([payload1, payload2], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([payload1, payload2], PayloadEmitSource.LocalChanged)
|
||||
this.expectedItemCount += 2
|
||||
let tag = this.application.itemManager.getItems(ContentType.Tag)[0]
|
||||
let userPrefs = this.application.itemManager.getItems(ContentType.UserPrefs)[0]
|
||||
expect(tag).to.be.ok
|
||||
expect(userPrefs).to.be.ok
|
||||
|
||||
tag = await this.application.itemManager.changeItem(tag, (mutator) => {
|
||||
tag = await this.application.mutator.changeItem(tag, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(userPrefs)
|
||||
})
|
||||
|
||||
await this.application.itemManager.setItemDirty(userPrefs)
|
||||
await this.application.mutator.setItemDirty(userPrefs)
|
||||
userPrefs = this.application.items.findItem(userPrefs.uuid)
|
||||
|
||||
expect(this.application.itemManager.itemsReferencingItem(userPrefs).length).to.equal(1)
|
||||
@@ -599,7 +599,7 @@ describe('online conflict handling', function () {
|
||||
*/
|
||||
let tag = await Factory.createMappedTag(this.application)
|
||||
let note = await Factory.createMappedNote(this.application)
|
||||
tag = await this.application.mutator.changeAndSaveItem(
|
||||
tag = await this.application.changeAndSaveItem(
|
||||
tag,
|
||||
(mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(note)
|
||||
@@ -608,7 +608,7 @@ describe('online conflict handling', function () {
|
||||
undefined,
|
||||
syncOptions,
|
||||
)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
this.expectedItemCount += 2
|
||||
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
@@ -663,18 +663,18 @@ describe('online conflict handling', function () {
|
||||
|
||||
const baseTitle = 'base title'
|
||||
/** Change the note */
|
||||
const noteAfterChange = await this.application.itemManager.changeItem(note, (mutator) => {
|
||||
const noteAfterChange = await this.application.mutator.changeItem(note, (mutator) => {
|
||||
mutator.title = baseTitle
|
||||
})
|
||||
await this.application.sync.sync()
|
||||
|
||||
/** Simulate a dropped response by reverting the note back its post-change, pre-sync state */
|
||||
const retroNote = await this.application.itemManager.emitItemFromPayload(noteAfterChange.payload)
|
||||
const retroNote = await this.application.mutator.emitItemFromPayload(noteAfterChange.payload)
|
||||
expect(retroNote.serverUpdatedAt.getTime()).to.equal(noteAfterChange.serverUpdatedAt.getTime())
|
||||
|
||||
/** Change the item to its final title and sync */
|
||||
const finalTitle = 'final title'
|
||||
await this.application.itemManager.changeItem(note, (mutator) => {
|
||||
await this.application.mutator.changeItem(note, (mutator) => {
|
||||
mutator.title = finalTitle
|
||||
})
|
||||
await this.application.sync.sync()
|
||||
@@ -708,7 +708,7 @@ describe('online conflict handling', function () {
|
||||
errorDecrypting: true,
|
||||
dirty: true,
|
||||
})
|
||||
await this.application.itemManager.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged)
|
||||
|
||||
/**
|
||||
* Retrieve this note from the server by clearing sync token
|
||||
@@ -758,7 +758,7 @@ describe('online conflict handling', function () {
|
||||
email: Utils.generateUuid(),
|
||||
password: Utils.generateUuid(),
|
||||
})
|
||||
await newApp.itemManager.emitItemsFromPayloads(priorData.map((i) => i.payload))
|
||||
await newApp.mutator.emitItemsFromPayloads(priorData.map((i) => i.payload))
|
||||
await newApp.syncService.markAllItemsAsNeedingSyncAndPersist()
|
||||
await newApp.syncService.sync(syncOptions)
|
||||
expect(newApp.payloadManager.invalidPayloads.length).to.equal(0)
|
||||
@@ -786,7 +786,7 @@ describe('online conflict handling', function () {
|
||||
password: password,
|
||||
})
|
||||
Factory.handlePasswordChallenges(newApp, password)
|
||||
await newApp.mutator.importData(backupFile, true)
|
||||
await newApp.importData(backupFile, true)
|
||||
expect(newApp.itemManager.getDisplayableTags().length).to.equal(1)
|
||||
expect(newApp.itemManager.getDisplayableNotes().length).to.equal(1)
|
||||
await Factory.safeDeinit(newApp)
|
||||
@@ -801,7 +801,7 @@ describe('online conflict handling', function () {
|
||||
await createSyncedNoteWithTag(this.application)
|
||||
const tag = this.application.itemManager.getDisplayableTags()[0]
|
||||
const note2 = await Factory.createMappedNote(this.application)
|
||||
await this.application.mutator.changeAndSaveItem(tag, (mutator) => {
|
||||
await this.application.changeAndSaveItem(tag, (mutator) => {
|
||||
mutator.e2ePendingRefactor_addItemAsRelationship(note2)
|
||||
})
|
||||
let backupFile = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
|
||||
@@ -821,7 +821,7 @@ describe('online conflict handling', function () {
|
||||
password: password,
|
||||
})
|
||||
Factory.handlePasswordChallenges(newApp, password)
|
||||
await newApp.mutator.importData(backupFile, true)
|
||||
await newApp.importData(backupFile, true)
|
||||
const newTag = newApp.itemManager.getDisplayableTags()[0]
|
||||
const notes = newApp.items.referencesForItem(newTag)
|
||||
expect(notes.length).to.equal(2)
|
||||
@@ -855,7 +855,7 @@ describe('online conflict handling', function () {
|
||||
},
|
||||
dirty: true,
|
||||
})
|
||||
await this.application.itemManager.emitItemFromPayload(modified)
|
||||
await this.application.mutator.emitItemFromPayload(modified)
|
||||
await this.application.sync.sync()
|
||||
expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1)
|
||||
await this.sharedFinalAssertions()
|
||||
@@ -879,7 +879,7 @@ describe('online conflict handling', function () {
|
||||
dirty: true,
|
||||
})
|
||||
this.expectedItemCount++
|
||||
await this.application.itemManager.emitItemFromPayload(modified)
|
||||
await this.application.mutator.emitItemFromPayload(modified)
|
||||
await this.application.sync.sync()
|
||||
expect(this.application.itemManager.getDisplayableNotes().length).to.equal(2)
|
||||
await this.sharedFinalAssertions()
|
||||
@@ -911,7 +911,7 @@ describe('online conflict handling', function () {
|
||||
dirty: true,
|
||||
})
|
||||
this.expectedItemCount++
|
||||
await this.application.itemManager.emitItemFromPayload(modified)
|
||||
await this.application.mutator.emitItemFromPayload(modified)
|
||||
await this.application.sync.sync()
|
||||
expect(this.application.itemManager.getDisplayableNotes().length).to.equal(2)
|
||||
await this.sharedFinalAssertions()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from '../lib/Applications.js'
|
||||
import { BaseItemCounts } from '../lib/BaseItemCounts.js'
|
||||
import * as Factory from '../lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -15,7 +15,7 @@ describe('sync integrity', () => {
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
@@ -44,7 +44,7 @@ describe('sync integrity', () => {
|
||||
})
|
||||
|
||||
it('should detect when out of sync', async function () {
|
||||
const item = await this.application.itemManager.emitItemFromPayload(
|
||||
const item = await this.application.mutator.emitItemFromPayload(
|
||||
Factory.createNotePayload(),
|
||||
PayloadEmitSource.LocalChanged,
|
||||
)
|
||||
@@ -60,7 +60,7 @@ describe('sync integrity', () => {
|
||||
})
|
||||
|
||||
it('should self heal after out of sync', async function () {
|
||||
const item = await this.application.itemManager.emitItemFromPayload(
|
||||
const item = await this.application.mutator.emitItemFromPayload(
|
||||
Factory.createNotePayload(),
|
||||
PayloadEmitSource.LocalChanged,
|
||||
)
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('notes + tags syncing', function () {
|
||||
|
||||
it('syncing an item then downloading it should include items_key_id', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
await this.application.payloadManager.resetState()
|
||||
await this.application.itemManager.resetState()
|
||||
@@ -52,14 +52,14 @@ describe('notes + tags syncing', function () {
|
||||
const notePayload = pair[0]
|
||||
const tagPayload = pair[1]
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
const note = this.application.itemManager.getItems([ContentType.Note])[0]
|
||||
const tag = this.application.itemManager.getItems([ContentType.Tag])[0]
|
||||
expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1)
|
||||
expect(this.application.itemManager.getDisplayableTags().length).to.equal(1)
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
await this.application.itemManager.setItemsDirty([note, tag])
|
||||
await this.application.mutator.setItemsDirty([note, tag])
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
this.application.syncService.clearSyncPositionTokens()
|
||||
expect(tag.content.references.length).to.equal(1)
|
||||
@@ -76,10 +76,10 @@ describe('notes + tags syncing', function () {
|
||||
const pair = createRelatedNoteTagPairPayload()
|
||||
const notePayload = pair[0]
|
||||
const tagPayload = pair[1]
|
||||
await this.application.itemManager.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
const originalNote = this.application.itemManager.getDisplayableNotes()[0]
|
||||
const originalTag = this.application.itemManager.getDisplayableTags()[0]
|
||||
await this.application.itemManager.setItemsDirty([originalNote, originalTag])
|
||||
await this.application.mutator.setItemsDirty([originalNote, originalTag])
|
||||
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
@@ -109,12 +109,12 @@ describe('notes + tags syncing', function () {
|
||||
const pair = createRelatedNoteTagPairPayload()
|
||||
const notePayload = pair[0]
|
||||
const tagPayload = pair[1]
|
||||
await this.application.itemManager.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged)
|
||||
let note = this.application.itemManager.getDisplayableNotes()[0]
|
||||
let tag = this.application.itemManager.getDisplayableTags()[0]
|
||||
expect(this.application.itemManager.itemsReferencingItem(note).length).to.equal(1)
|
||||
|
||||
await this.application.itemManager.setItemsDirty([note, tag])
|
||||
await this.application.mutator.setItemsDirty([note, tag])
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
await this.application.syncService.clearSyncPositionTokens()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from '../lib/Applications.js'
|
||||
import { BaseItemCounts } from '../lib/BaseItemCounts.js'
|
||||
import * as Factory from '../lib/factory.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
@@ -31,6 +31,21 @@ describe('offline syncing', () => {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
it('uuid alternation should delete original payload', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
this.expectedItemCount++
|
||||
|
||||
await Factory.alternateUuidForItem(this.application, note.uuid)
|
||||
await this.application.sync.sync(syncOptions)
|
||||
|
||||
const notes = this.application.itemManager.getDisplayableNotes()
|
||||
expect(notes.length).to.equal(1)
|
||||
expect(notes[0].uuid).to.not.equal(note.uuid)
|
||||
|
||||
const items = this.application.itemManager.allTrackedItems()
|
||||
expect(items.length).to.equal(this.expectedItemCount)
|
||||
})
|
||||
|
||||
it('should sync item with no passcode', async function () {
|
||||
let note = await Factory.createMappedNote(this.application)
|
||||
expect(Uuids(this.application.itemManager.getDirtyItems()).includes(note.uuid))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from '../lib/Applications.js'
|
||||
import { BaseItemCounts } from '../lib/BaseItemCounts.js'
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Utils from '../lib/Utils.js'
|
||||
chai.use(chaiAsPromised)
|
||||
@@ -15,7 +15,7 @@ describe('online syncing', function () {
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItemsWithAccount
|
||||
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
@@ -43,8 +43,10 @@ describe('online syncing', function () {
|
||||
|
||||
afterEach(async function () {
|
||||
expect(this.application.syncService.isOutOfSync()).to.equal(false)
|
||||
|
||||
const items = this.application.itemManager.allTrackedItems()
|
||||
expect(items.length).to.equal(this.expectedItemCount)
|
||||
|
||||
const rawPayloads = await this.application.diskStorageService.getAllRawPayloads()
|
||||
expect(rawPayloads.length).to.equal(this.expectedItemCount)
|
||||
await Factory.safeDeinit(this.application)
|
||||
@@ -119,18 +121,6 @@ describe('online syncing', function () {
|
||||
await Factory.sleep(0.5)
|
||||
}).timeout(20000)
|
||||
|
||||
it('uuid alternation should delete original payload', async function () {
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
this.expectedItemCount++
|
||||
await Factory.alternateUuidForItem(this.application, note.uuid)
|
||||
await this.application.sync.sync(syncOptions)
|
||||
|
||||
const notes = this.application.itemManager.getDisplayableNotes()
|
||||
expect(notes.length).to.equal(1)
|
||||
expect(notes[0].uuid).to.not.equal(note.uuid)
|
||||
})
|
||||
|
||||
it('having offline data then signing in should not alternate uuid and merge with account', async function () {
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
@@ -222,7 +212,7 @@ describe('online syncing', function () {
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
const promise = new Promise((resolve) => {
|
||||
this.application.syncService.addEventObserver(async (event) => {
|
||||
if (event === SyncEvent.SingleRoundTripSyncCompleted) {
|
||||
if (event === SyncEvent.PaginatedSyncRequestCompleted) {
|
||||
const note = this.application.items.findItem(originalNote.uuid)
|
||||
if (note) {
|
||||
expect(note.dirty).to.not.be.ok
|
||||
@@ -241,7 +231,7 @@ describe('online syncing', function () {
|
||||
expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(1)
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
this.expectedItemCount++
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
const rawPayloads = await this.application.diskStorageService.getAllRawPayloads()
|
||||
const notePayload = noteObjectsFromObjects(rawPayloads)
|
||||
@@ -283,7 +273,7 @@ describe('online syncing', function () {
|
||||
|
||||
const originalTitle = note.content.title
|
||||
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
const encrypted = CreateEncryptedServerSyncPushPayload(
|
||||
@@ -299,7 +289,7 @@ describe('online syncing', function () {
|
||||
errorDecrypting: true,
|
||||
})
|
||||
|
||||
const items = await this.application.itemManager.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged)
|
||||
const items = await this.application.mutator.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged)
|
||||
|
||||
const mappedItem = this.application.itemManager.findAnyItem(errorred.uuid)
|
||||
|
||||
@@ -311,7 +301,7 @@ describe('online syncing', function () {
|
||||
},
|
||||
})
|
||||
|
||||
const mappedItems2 = await this.application.itemManager.emitItemsFromPayloads(
|
||||
const mappedItems2 = await this.application.mutator.emitItemsFromPayloads(
|
||||
[decryptedPayload],
|
||||
PayloadEmitSource.LocalChanged,
|
||||
)
|
||||
@@ -336,14 +326,14 @@ describe('online syncing', function () {
|
||||
let note = await Factory.createMappedNote(this.application)
|
||||
this.expectedItemCount++
|
||||
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
note = this.application.items.findItem(note.uuid)
|
||||
expect(note.dirty).to.equal(false)
|
||||
expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount)
|
||||
|
||||
await this.application.itemManager.setItemToBeDeleted(note)
|
||||
await this.application.mutator.setItemToBeDeleted(note)
|
||||
note = this.application.items.findAnyItem(note.uuid)
|
||||
expect(note.dirty).to.equal(true)
|
||||
this.expectedItemCount--
|
||||
@@ -361,7 +351,7 @@ describe('online syncing', function () {
|
||||
|
||||
it('retrieving item with no content should correctly map local state', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
const syncToken = await this.application.syncService.getLastSyncToken()
|
||||
@@ -370,7 +360,7 @@ describe('online syncing', function () {
|
||||
expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount)
|
||||
|
||||
// client A
|
||||
await this.application.itemManager.setItemToBeDeleted(note)
|
||||
await this.application.mutator.setItemToBeDeleted(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
// Subtract 1
|
||||
@@ -399,7 +389,7 @@ describe('online syncing', function () {
|
||||
|
||||
await Factory.sleep(0.1)
|
||||
|
||||
await this.application.itemManager.changeItem(note, (mutator) => {
|
||||
await this.application.mutator.changeItem(note, (mutator) => {
|
||||
mutator.title = 'latest title'
|
||||
})
|
||||
|
||||
@@ -427,7 +417,7 @@ describe('online syncing', function () {
|
||||
|
||||
await Factory.sleep(0.1)
|
||||
|
||||
await this.application.itemManager.setItemToBeDeleted(note)
|
||||
await this.application.mutator.setItemToBeDeleted(note)
|
||||
|
||||
this.expectedItemCount--
|
||||
|
||||
@@ -444,8 +434,8 @@ describe('online syncing', function () {
|
||||
|
||||
it('items that are never synced and deleted should not be uploaded to server', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.itemManager.setItemToBeDeleted(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.mutator.setItemToBeDeleted(note)
|
||||
|
||||
let success = true
|
||||
let didCompleteRelevantSync = false
|
||||
@@ -457,7 +447,7 @@ describe('online syncing', function () {
|
||||
if (!beginCheckingResponse) {
|
||||
return
|
||||
}
|
||||
if (!didCompleteRelevantSync && eventName === SyncEvent.SingleRoundTripSyncCompleted) {
|
||||
if (!didCompleteRelevantSync && eventName === SyncEvent.PaginatedSyncRequestCompleted) {
|
||||
didCompleteRelevantSync = true
|
||||
const response = data
|
||||
const matching = response.savedPayloads.find((p) => p.uuid === note.uuid)
|
||||
@@ -474,20 +464,20 @@ describe('online syncing', function () {
|
||||
it('items that are deleted after download first sync complete should not be uploaded to server', async function () {
|
||||
/** The singleton manager may delete items are download first. We dont want those uploaded to server. */
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
|
||||
let success = true
|
||||
let didCompleteRelevantSync = false
|
||||
let beginCheckingResponse = false
|
||||
this.application.syncService.addEventObserver(async (eventName, data) => {
|
||||
if (eventName === SyncEvent.DownloadFirstSyncCompleted) {
|
||||
await this.application.itemManager.setItemToBeDeleted(note)
|
||||
await this.application.mutator.setItemToBeDeleted(note)
|
||||
beginCheckingResponse = true
|
||||
}
|
||||
if (!beginCheckingResponse) {
|
||||
return
|
||||
}
|
||||
if (!didCompleteRelevantSync && eventName === SyncEvent.SingleRoundTripSyncCompleted) {
|
||||
if (!didCompleteRelevantSync && eventName === SyncEvent.PaginatedSyncRequestCompleted) {
|
||||
didCompleteRelevantSync = true
|
||||
const response = data
|
||||
const matching = response.savedPayloads.find((p) => p.uuid === note.uuid)
|
||||
@@ -527,7 +517,7 @@ describe('online syncing', function () {
|
||||
|
||||
const decryptionResults = await this.application.protocolService.decryptSplit(keyedSplit)
|
||||
|
||||
await this.application.itemManager.emitItemsFromPayloads(decryptionResults, PayloadEmitSource.LocalChanged)
|
||||
await this.application.mutator.emitItemsFromPayloads(decryptionResults, PayloadEmitSource.LocalChanged)
|
||||
|
||||
expect(this.application.itemManager.allTrackedItems().length).to.equal(this.expectedItemCount)
|
||||
|
||||
@@ -543,7 +533,7 @@ describe('online syncing', function () {
|
||||
const largeItemCount = SyncUpDownLimit + 10
|
||||
for (let i = 0; i < largeItemCount; i++) {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
}
|
||||
|
||||
this.expectedItemCount += largeItemCount
|
||||
@@ -558,7 +548,7 @@ describe('online syncing', function () {
|
||||
const largeItemCount = SyncUpDownLimit + 10
|
||||
for (let i = 0; i < largeItemCount; i++) {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
}
|
||||
/** Upload */
|
||||
this.application.syncService.sync({ awaitAll: true, checkIntegrity: false })
|
||||
@@ -583,7 +573,7 @@ describe('online syncing', function () {
|
||||
|
||||
it('syncing an item should storage it encrypted', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
this.expectedItemCount++
|
||||
const rawPayloads = await this.application.diskStorageService.getAllRawPayloads()
|
||||
@@ -593,7 +583,7 @@ describe('online syncing', function () {
|
||||
|
||||
it('syncing an item before data load should storage it encrypted', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
this.expectedItemCount++
|
||||
|
||||
/** Simulate database not loaded */
|
||||
@@ -610,7 +600,7 @@ describe('online syncing', function () {
|
||||
it('saving an item after sync should persist it with content property', async function () {
|
||||
const note = await Factory.createMappedNote(this.application)
|
||||
const text = Factory.randomString(10000)
|
||||
await this.application.mutator.changeAndSaveItem(
|
||||
await this.application.changeAndSaveItem(
|
||||
note,
|
||||
(mutator) => {
|
||||
mutator.text = text
|
||||
@@ -634,7 +624,7 @@ describe('online syncing', function () {
|
||||
expect(this.application.itemManager.getDirtyItems().length).to.equal(0)
|
||||
|
||||
let note = await Factory.createMappedNote(this.application)
|
||||
note = await this.application.itemManager.changeItem(note, (mutator) => {
|
||||
note = await this.application.mutator.changeItem(note, (mutator) => {
|
||||
mutator.text = `${Math.random()}`
|
||||
})
|
||||
/** This sync request should exit prematurely as we called ut_setDatabaseNotLoaded */
|
||||
@@ -705,13 +695,13 @@ describe('online syncing', function () {
|
||||
|
||||
it('valid sync date tracking', async function () {
|
||||
let note = await Factory.createMappedNote(this.application)
|
||||
note = await this.application.itemManager.setItemDirty(note)
|
||||
note = await this.application.mutator.setItemDirty(note)
|
||||
this.expectedItemCount++
|
||||
|
||||
expect(note.dirty).to.equal(true)
|
||||
expect(note.payload.dirtyIndex).to.be.at.most(getCurrentDirtyIndex())
|
||||
|
||||
note = await this.application.itemManager.changeItem(note, (mutator) => {
|
||||
note = await this.application.mutator.changeItem(note, (mutator) => {
|
||||
mutator.text = `${Math.random()}`
|
||||
})
|
||||
const sync = this.application.sync.sync(syncOptions)
|
||||
@@ -748,7 +738,7 @@ describe('online syncing', function () {
|
||||
* It will do based on comparing whether item.dirtyIndex > item.globalDirtyIndexAtLastSync
|
||||
*/
|
||||
let note = await Factory.createMappedNote(this.application)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
this.expectedItemCount++
|
||||
|
||||
// client A. Don't await, we want to do other stuff.
|
||||
@@ -759,12 +749,12 @@ describe('online syncing', function () {
|
||||
|
||||
// While that sync is going on, we want to modify this item many times.
|
||||
const text = `${Math.random()}`
|
||||
note = await this.application.itemManager.changeItem(note, (mutator) => {
|
||||
note = await this.application.mutator.changeItem(note, (mutator) => {
|
||||
mutator.text = text
|
||||
})
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.itemManager.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
await this.application.mutator.setItemDirty(note)
|
||||
expect(note.payload.dirtyIndex).to.be.above(note.payload.globalDirtyIndexAtLastSync)
|
||||
|
||||
// Now do a regular sync with no latency.
|
||||
@@ -817,7 +807,7 @@ describe('online syncing', function () {
|
||||
|
||||
setTimeout(
|
||||
async function () {
|
||||
await this.application.itemManager.changeItem(note, (mutator) => {
|
||||
await this.application.mutator.changeItem(note, (mutator) => {
|
||||
mutator.text = newText
|
||||
})
|
||||
}.bind(this),
|
||||
@@ -862,9 +852,9 @@ describe('online syncing', function () {
|
||||
const newText = `${Math.random()}`
|
||||
|
||||
this.application.syncService.addEventObserver(async (eventName) => {
|
||||
if (eventName === SyncEvent.SyncWillBegin && !didPerformMutatation) {
|
||||
if (eventName === SyncEvent.SyncDidBeginProcessing && !didPerformMutatation) {
|
||||
didPerformMutatation = true
|
||||
await this.application.itemManager.changeItem(note, (mutator) => {
|
||||
await this.application.mutator.changeItem(note, (mutator) => {
|
||||
mutator.text = newText
|
||||
})
|
||||
}
|
||||
@@ -898,7 +888,7 @@ describe('online syncing', function () {
|
||||
dirtyIndex: changed[0].payload.globalDirtyIndexAtLastSync + 1,
|
||||
})
|
||||
|
||||
await this.application.itemManager.emitItemFromPayload(mutated)
|
||||
await this.application.mutator.emitItemFromPayload(mutated)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -916,6 +906,7 @@ describe('online syncing', function () {
|
||||
const note = await Factory.createSyncedNote(this.application)
|
||||
const preDeleteSyncToken = await this.application.syncService.getLastSyncToken()
|
||||
await this.application.mutator.deleteItem(note)
|
||||
await this.application.sync.sync()
|
||||
await this.application.syncService.setLastSyncToken(preDeleteSyncToken)
|
||||
await this.application.sync.sync(syncOptions)
|
||||
expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount)
|
||||
@@ -938,7 +929,7 @@ describe('online syncing', function () {
|
||||
dirty: true,
|
||||
})
|
||||
|
||||
await this.application.itemManager.emitItemFromPayload(errored)
|
||||
await this.application.payloadManager.emitPayload(errored)
|
||||
await this.application.sync.sync(syncOptions)
|
||||
|
||||
const updatedNote = this.application.items.findAnyItem(note.uuid)
|
||||
@@ -966,7 +957,7 @@ describe('online syncing', function () {
|
||||
},
|
||||
})
|
||||
|
||||
await this.application.syncService.handleSuccessServerResponse({ payloadsSavedOrSaving: [] }, response)
|
||||
await this.application.syncService.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response)
|
||||
|
||||
expect(this.application.payloadManager.findOne(invalidPayload.uuid)).to.not.be.ok
|
||||
expect(this.application.payloadManager.findOne(validPayload.uuid)).to.be.ok
|
||||
@@ -995,7 +986,7 @@ describe('online syncing', function () {
|
||||
content: {},
|
||||
})
|
||||
this.expectedItemCount++
|
||||
await this.application.itemManager.emitItemsFromPayloads([payload])
|
||||
await this.application.mutator.emitItemsFromPayloads([payload])
|
||||
await this.application.sync.sync(syncOptions)
|
||||
|
||||
/** Item should no longer be dirty, otherwise it would keep syncing */
|
||||
@@ -1006,7 +997,7 @@ describe('online syncing', function () {
|
||||
it('should call onPresyncSave before sync begins', async function () {
|
||||
const events = []
|
||||
this.application.syncService.addEventObserver((event) => {
|
||||
if (event === SyncEvent.SyncWillBegin) {
|
||||
if (event === SyncEvent.SyncDidBeginProcessing) {
|
||||
events.push('sync-will-begin')
|
||||
}
|
||||
})
|
||||
@@ -1032,6 +1023,7 @@ describe('online syncing', function () {
|
||||
|
||||
const note = await Factory.createSyncedNote(this.application)
|
||||
await this.application.mutator.deleteItem(note)
|
||||
await this.application.sync.sync()
|
||||
|
||||
expect(conditionMet).to.equal(true)
|
||||
})
|
||||
|
||||
@@ -12,14 +12,9 @@
|
||||
<script src="https://unpkg.com/sinon@13.0.2/pkg/sinon.js"></script>
|
||||
<script src="./vendor/sncrypto-web.js"></script>
|
||||
<script src="../dist/snjs.js"></script>
|
||||
<script>
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const syncServerHostName = urlParams.get('sync_server_host_name') ?? 'syncing-server-proxy';
|
||||
const bail = urlParams.get('bail') === 'false' ? false : true;
|
||||
const skipPaidFeatures = urlParams.get('skip_paid_features') === 'true' ? true : false;
|
||||
|
||||
<script type="module">
|
||||
Object.assign(window, SNCrypto);
|
||||
|
||||
Object.assign(window, SNLibrary);
|
||||
|
||||
SNLog.onLog = (message) => {
|
||||
@@ -30,6 +25,10 @@
|
||||
console.error(error);
|
||||
};
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const bail = urlParams.get('bail') === 'false' ? false : true;
|
||||
const skipPaidFeatures = urlParams.get('skip_paid_features') === 'true' ? true : false;
|
||||
|
||||
mocha.setup({
|
||||
ui: 'bdd',
|
||||
timeout: 5000,
|
||||
@@ -39,63 +38,42 @@
|
||||
mocha.grep('@paidfeature').invert();
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="memory.test.js"></script>
|
||||
<script type="module" src="protocol.test.js"></script>
|
||||
<script type="module" src="utils.test.js"></script>
|
||||
<script type="module" src="000.test.js"></script>
|
||||
<script type="module" src="001.test.js"></script>
|
||||
<script type="module" src="002.test.js"></script>
|
||||
<script type="module" src="003.test.js"></script>
|
||||
<script type="module" src="004.test.js"></script>
|
||||
<script type="module" src="username.test.js"></script>
|
||||
<script type="module" src="app-group.test.js"></script>
|
||||
<script type="module" src="application.test.js"></script>
|
||||
<script type="module" src="payload.test.js"></script>
|
||||
<script type="module" src="payload_encryption.test.js"></script>
|
||||
<script type="module" src="item.test.js"></script>
|
||||
<script type="module" src="item_manager.test.js"></script>
|
||||
<script type="module" src="features.test.js"></script>
|
||||
<script type="module" src="settings.test.js"></script>
|
||||
<script type="module" src="mfa_service.test.js"></script>
|
||||
<script type="module" src="mutator.test.js"></script>
|
||||
<script type="module" src="payload_manager.test.js"></script>
|
||||
<script type="module" src="collections.test.js"></script>
|
||||
<script type="module" src="note_display_criteria.test.js"></script>
|
||||
<script type="module" src="keys.test.js"></script>
|
||||
<script type="module" src="key_params.test.js"></script>
|
||||
<script type="module" src="key_recovery_service.test.js"></script>
|
||||
<script type="module" src="backups.test.js"></script>
|
||||
<script type="module" src="upgrading.test.js"></script>
|
||||
<script type="module" src="model_tests/importing.test.js"></script>
|
||||
<script type="module" src="model_tests/appmodels.test.js"></script>
|
||||
<script type="module" src="model_tests/items.test.js"></script>
|
||||
<script type="module" src="model_tests/mapping.test.js"></script>
|
||||
<script type="module" src="model_tests/notes_smart_tags.test.js"></script>
|
||||
<script type="module" src="model_tests/notes_tags.test.js"></script>
|
||||
<script type="module" src="model_tests/notes_tags_folders.test.js"></script>
|
||||
<script type="module" src="model_tests/performance.test.js"></script>
|
||||
<script type="module" src="sync_tests/offline.test.js"></script>
|
||||
<script type="module" src="sync_tests/notes_tags.test.js"></script>
|
||||
<script type="module" src="sync_tests/online.test.js"></script>
|
||||
<script type="module" src="sync_tests/conflicting.test.js"></script>
|
||||
<script type="module" src="sync_tests/integrity.test.js"></script>
|
||||
<script type="module" src="auth-fringe-cases.test.js"></script>
|
||||
<script type="module" src="auth.test.js"></script>
|
||||
<script type="module" src="device_auth.test.js"></script>
|
||||
<script type="module" src="storage.test.js"></script>
|
||||
<script type="module" src="protection.test.js"></script>
|
||||
<script type="module" src="singletons.test.js"></script>
|
||||
<script type="module" src="migrations/migration.test.js"></script>
|
||||
<script type="module" src="migrations/tags-to-folders.test.js"></script>
|
||||
<script type="module" src="history.test.js"></script>
|
||||
<script type="module" src="actions.test.js"></script>
|
||||
<script type="module" src="preferences.test.js"></script>
|
||||
<script type="module" src="files.test.js"></script>
|
||||
<script type="module" src="session.test.js"></script>
|
||||
<script type="module" src="subscriptions.test.js"></script>
|
||||
<script type="module" src="recovery.test.js"></script>
|
||||
|
||||
<script type="module">
|
||||
mocha.run();
|
||||
import MainRegistry from './TestRegistry/MainRegistry.js'
|
||||
|
||||
const InternalFeatureStatus = {
|
||||
[InternalFeature.Vaults]: { enabled: false, exclusive: false },
|
||||
}
|
||||
|
||||
const loadTest = (fileName) => {
|
||||
return new Promise((resolve) => {
|
||||
const script = document.createElement('script');
|
||||
script.type = 'module';
|
||||
script.src = fileName;
|
||||
script.async = false;
|
||||
script.defer = false;
|
||||
script.addEventListener('load', resolve);
|
||||
document.head.append(script);
|
||||
})
|
||||
}
|
||||
|
||||
const loadTests = async (fileNames) => {
|
||||
for (const fileName of fileNames) {
|
||||
await loadTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
if (InternalFeatureStatus[InternalFeature.Vaults].enabled) {
|
||||
InternalFeatureService.get().enableFeature(InternalFeature.Vaults);
|
||||
await loadTests(MainRegistry.VaultTests);
|
||||
}
|
||||
|
||||
if (!InternalFeatureStatus[InternalFeature.Vaults].exclusive) {
|
||||
await loadTests(MainRegistry.BaseTests);
|
||||
}
|
||||
|
||||
mocha.run()
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@@ -103,4 +81,4 @@
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -173,7 +173,7 @@ describe('upgrading', () => {
|
||||
it('protocol version should be upgraded on password change', async function () {
|
||||
/** Delete default items key that is created on launch */
|
||||
const itemsKey = await this.application.protocolService.getSureDefaultItemsKey()
|
||||
await this.application.itemManager.setItemToBeDeleted(itemsKey)
|
||||
await this.application.mutator.setItemToBeDeleted(itemsKey)
|
||||
expect(Uuids(this.application.itemManager.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false)
|
||||
|
||||
Factory.createMappedNote(this.application)
|
||||
|
||||
277
packages/snjs/mocha/vaults/asymmetric-messages.test.js
Normal file
277
packages/snjs/mocha/vaults/asymmetric-messages.test.js
Normal file
@@ -0,0 +1,277 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Collaboration from '../lib/Collaboration.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('asymmetric messages', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let service
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
service = context.asymmetric
|
||||
})
|
||||
|
||||
it('should not trust message if the trusted payload data recipientUuid does not match the message user uuid', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('should delete message after processing it', async () => {
|
||||
const { contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const eventData = {
|
||||
oldKeyPair: context.encryption.getKeyPair(),
|
||||
oldSigningKeyPair: context.encryption.getSigningKeyPair(),
|
||||
newKeyPair: context.encryption.getKeyPair(),
|
||||
newSigningKeyPair: context.encryption.getSigningKeyPair(),
|
||||
}
|
||||
|
||||
await service.sendOwnContactChangeEventToAllContacts(eventData)
|
||||
|
||||
const deleteFunction = sinon.spy(contactContext.asymmetric, 'deleteMessageAfterProcessing')
|
||||
|
||||
const promise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
|
||||
|
||||
await contactContext.sync()
|
||||
|
||||
await promise
|
||||
|
||||
expect(deleteFunction.callCount).to.equal(1)
|
||||
|
||||
const messages = await contactContext.asymmetric.getInboundMessages()
|
||||
expect(messages.length).to.equal(0)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should send contact share message after trusted contact belonging to group changes', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const { thirdPartyContext, deinitThirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
|
||||
context,
|
||||
sharedVault,
|
||||
)
|
||||
|
||||
await Collaboration.acceptAllInvites(thirdPartyContext)
|
||||
|
||||
const sendContactSharePromise = context.resolveWhenSharedVaultServiceSendsContactShareMessage()
|
||||
|
||||
await context.contacts.createOrEditTrustedContact({
|
||||
contactUuid: thirdPartyContext.userUuid,
|
||||
publicKey: thirdPartyContext.publicKey,
|
||||
signingPublicKey: thirdPartyContext.signingPublicKey,
|
||||
name: 'Changed 3rd Party Name',
|
||||
})
|
||||
|
||||
await sendContactSharePromise
|
||||
|
||||
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
|
||||
|
||||
await contactContext.sync()
|
||||
await completedProcessingMessagesPromise
|
||||
|
||||
const updatedContact = contactContext.contacts.findTrustedContact(thirdPartyContext.userUuid)
|
||||
expect(updatedContact.name).to.equal('Changed 3rd Party Name')
|
||||
|
||||
await deinitContactContext()
|
||||
await deinitThirdPartyContext()
|
||||
})
|
||||
|
||||
it('should not send contact share message to self or to contact who is changed', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const { thirdPartyContext, deinitThirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
|
||||
context,
|
||||
sharedVault,
|
||||
)
|
||||
const handleInitialContactShareMessage = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
|
||||
|
||||
await Collaboration.acceptAllInvites(thirdPartyContext)
|
||||
|
||||
await contactContext.sync()
|
||||
await handleInitialContactShareMessage
|
||||
|
||||
const sendContactSharePromise = context.resolveWhenSharedVaultServiceSendsContactShareMessage()
|
||||
|
||||
await context.contacts.createOrEditTrustedContact({
|
||||
contactUuid: thirdPartyContext.userUuid,
|
||||
publicKey: thirdPartyContext.publicKey,
|
||||
signingPublicKey: thirdPartyContext.signingPublicKey,
|
||||
name: 'Changed 3rd Party Name',
|
||||
})
|
||||
|
||||
await sendContactSharePromise
|
||||
|
||||
const firstPartySpy = sinon.spy(context.asymmetric, 'handleTrustedContactShareMessage')
|
||||
const secondPartySpy = sinon.spy(contactContext.asymmetric, 'handleTrustedContactShareMessage')
|
||||
const thirdPartySpy = sinon.spy(thirdPartyContext.asymmetric, 'handleTrustedContactShareMessage')
|
||||
|
||||
await context.sync()
|
||||
await contactContext.sync()
|
||||
await thirdPartyContext.sync()
|
||||
|
||||
expect(firstPartySpy.callCount).to.equal(0)
|
||||
expect(secondPartySpy.callCount).to.equal(1)
|
||||
expect(thirdPartySpy.callCount).to.equal(0)
|
||||
|
||||
await deinitThirdPartyContext()
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should send shared vault root key change message after root key change', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
await context.vaults.rotateVaultRootKey(sharedVault)
|
||||
|
||||
const firstPartySpy = sinon.spy(context.asymmetric, 'handleTrustedSharedVaultRootKeyChangedMessage')
|
||||
const secondPartySpy = sinon.spy(contactContext.asymmetric, 'handleTrustedSharedVaultRootKeyChangedMessage')
|
||||
|
||||
await context.sync()
|
||||
await contactContext.sync()
|
||||
|
||||
expect(firstPartySpy.callCount).to.equal(0)
|
||||
expect(secondPartySpy.callCount).to.equal(1)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should send shared vault metadata change message after shared vault name change', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
await context.vaults.changeVaultNameAndDescription(sharedVault, {
|
||||
name: 'New Name',
|
||||
description: 'New Description',
|
||||
})
|
||||
|
||||
const firstPartySpy = sinon.spy(context.asymmetric, 'handleVaultMetadataChangedMessage')
|
||||
const secondPartySpy = sinon.spy(contactContext.asymmetric, 'handleVaultMetadataChangedMessage')
|
||||
|
||||
await context.sync()
|
||||
await contactContext.sync()
|
||||
|
||||
expect(firstPartySpy.callCount).to.equal(0)
|
||||
expect(secondPartySpy.callCount).to.equal(1)
|
||||
|
||||
const updatedVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
|
||||
expect(updatedVault.name).to.equal('New Name')
|
||||
expect(updatedVault.description).to.equal('New Description')
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should send sender keypair changed message to trusted contacts', async () => {
|
||||
const { contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
await context.changePassword('new password')
|
||||
|
||||
const firstPartySpy = sinon.spy(context.asymmetric, 'handleTrustedSenderKeypairChangedMessage')
|
||||
const secondPartySpy = sinon.spy(contactContext.asymmetric, 'handleTrustedSenderKeypairChangedMessage')
|
||||
|
||||
await context.sync()
|
||||
|
||||
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
|
||||
await contactContext.sync()
|
||||
await completedProcessingMessagesPromise
|
||||
|
||||
expect(firstPartySpy.callCount).to.equal(0)
|
||||
expect(secondPartySpy.callCount).to.equal(1)
|
||||
|
||||
const contact = contactContext.contacts.findTrustedContact(context.userUuid)
|
||||
expect(contact.publicKeySet.encryption).to.equal(context.publicKey)
|
||||
expect(contact.publicKeySet.signing).to.equal(context.signingPublicKey)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should process sender keypair changed message', async () => {
|
||||
const { contactContext, deinitContactContext } = await Collaboration.createContactContext()
|
||||
await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
|
||||
await Collaboration.createTrustedContactForUserOfContext(contactContext, context)
|
||||
const originalContact = contactContext.contacts.findTrustedContact(context.userUuid)
|
||||
|
||||
await context.changePassword('new_password')
|
||||
|
||||
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
|
||||
await contactContext.sync()
|
||||
await completedProcessingMessagesPromise
|
||||
|
||||
const updatedContact = contactContext.contacts.findTrustedContact(context.userUuid)
|
||||
|
||||
expect(updatedContact.publicKeySet.encryption).to.not.equal(originalContact.publicKeySet.encryption)
|
||||
expect(updatedContact.publicKeySet.signing).to.not.equal(originalContact.publicKeySet.signing)
|
||||
|
||||
expect(updatedContact.publicKeySet.encryption).to.equal(context.publicKey)
|
||||
expect(updatedContact.publicKeySet.signing).to.equal(context.signingPublicKey)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('sender keypair changed message should be signed using old key pair', async () => {
|
||||
const { contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const oldKeyPair = context.encryption.getKeyPair()
|
||||
const oldSigningKeyPair = context.encryption.getSigningKeyPair()
|
||||
|
||||
await context.changePassword('new password')
|
||||
|
||||
const secondPartySpy = sinon.spy(contactContext.asymmetric, 'handleTrustedSenderKeypairChangedMessage')
|
||||
|
||||
await context.sync()
|
||||
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
|
||||
await contactContext.sync()
|
||||
await completedProcessingMessagesPromise
|
||||
|
||||
const message = secondPartySpy.args[0][0]
|
||||
const encryptedMessage = message.encrypted_message
|
||||
|
||||
const publicKeySet =
|
||||
contactContext.encryption.getSenderPublicKeySetFromAsymmetricallyEncryptedString(encryptedMessage)
|
||||
|
||||
expect(publicKeySet.encryption).to.equal(oldKeyPair.publicKey)
|
||||
expect(publicKeySet.signing).to.equal(oldSigningKeyPair.publicKey)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('sender keypair changed message should contain new keypair and be trusted', async () => {
|
||||
const { contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
await context.changePassword('new password')
|
||||
|
||||
const newKeyPair = context.encryption.getKeyPair()
|
||||
const newSigningKeyPair = context.encryption.getSigningKeyPair()
|
||||
|
||||
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
|
||||
await contactContext.sync()
|
||||
await completedProcessingMessagesPromise
|
||||
|
||||
const updatedContact = contactContext.contacts.findTrustedContact(context.userUuid)
|
||||
expect(updatedContact.publicKeySet.encryption).to.equal(newKeyPair.publicKey)
|
||||
expect(updatedContact.publicKeySet.signing).to.equal(newSigningKeyPair.publicKey)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should delete all inbound messages after changing user password', async () => {
|
||||
/** Messages to user are encrypted with old keypair and are no longer decryptable */
|
||||
console.error('TODO: implement test')
|
||||
})
|
||||
})
|
||||
186
packages/snjs/mocha/vaults/conflicts.test.js
Normal file
186
packages/snjs/mocha/vaults/conflicts.test.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Collaboration from '../lib/Collaboration.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('shared vault conflicts', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
})
|
||||
|
||||
it('after being removed from shared vault, attempting to sync previous vault item should result in SharedVaultNotMemberError. The item should be duplicated then removed.', async () => {
|
||||
const { sharedVault, note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
contactContext.lockSyncing()
|
||||
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
const promise = contactContext.resolveWithConflicts()
|
||||
contactContext.unlockSyncing()
|
||||
await contactContext.changeNoteTitleAndSync(note, 'new title')
|
||||
const conflicts = await promise
|
||||
await contactContext.sync()
|
||||
|
||||
expect(conflicts.length).to.equal(1)
|
||||
expect(conflicts[0].type).to.equal(ConflictType.SharedVaultNotMemberError)
|
||||
expect(conflicts[0].unsaved_item.content_type).to.equal(ContentType.Note)
|
||||
|
||||
const collaboratorNotes = contactContext.items.getDisplayableNotes()
|
||||
expect(collaboratorNotes.length).to.equal(1)
|
||||
expect(collaboratorNotes[0].duplicate_of).to.not.be.undefined
|
||||
expect(collaboratorNotes[0].title).to.equal('new title')
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('conflicts created should be associated with the vault', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
await context.changeNoteTitle(note, 'new title first client')
|
||||
await contactContext.changeNoteTitle(note, 'new title second client')
|
||||
|
||||
const doneAddingConflictToSharedVault = contactContext.resolveWhenSavedSyncPayloadsIncludesItemThatIsDuplicatedOf(
|
||||
note.uuid,
|
||||
)
|
||||
|
||||
await context.sync({ desc: 'First client sync' })
|
||||
await contactContext.sync({
|
||||
desc: 'Second client sync with conflicts to be created',
|
||||
})
|
||||
await doneAddingConflictToSharedVault
|
||||
await context.sync({ desc: 'First client sync with conflicts to be pulled in' })
|
||||
|
||||
expect(context.items.invalidItems.length).to.equal(0)
|
||||
expect(contactContext.items.invalidItems.length).to.equal(0)
|
||||
|
||||
const collaboratorNotes = contactContext.items.getDisplayableNotes()
|
||||
expect(collaboratorNotes.length).to.equal(2)
|
||||
expect(collaboratorNotes.find((note) => !!note.duplicate_of)).to.not.be.undefined
|
||||
|
||||
const originatorNotes = context.items.getDisplayableNotes()
|
||||
expect(originatorNotes.length).to.equal(2)
|
||||
expect(originatorNotes.find((note) => !!note.duplicate_of)).to.not.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('attempting to modify note as read user should result in SharedVaultInsufficientPermissionsError', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context, SharedVaultPermission.Read)
|
||||
|
||||
const promise = contactContext.resolveWithConflicts()
|
||||
await contactContext.changeNoteTitleAndSync(note, 'new title')
|
||||
const conflicts = await promise
|
||||
|
||||
expect(conflicts.length).to.equal(1)
|
||||
expect(conflicts[0].type).to.equal(ConflictType.SharedVaultInsufficientPermissionsError)
|
||||
expect(conflicts[0].unsaved_item.content_type).to.equal(ContentType.Note)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should handle SharedVaultNotMemberError by duplicating item to user non-vault', async () => {
|
||||
const { sharedVault, note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
await contactContext.changeNoteTitleAndSync(note, 'new title')
|
||||
const notes = contactContext.notes
|
||||
|
||||
expect(notes.length).to.equal(1)
|
||||
expect(notes[0].title).to.equal('new title')
|
||||
expect(notes[0].key_system_identifier).to.not.be.ok
|
||||
expect(notes[0].duplicate_of).to.equal(note.uuid)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('attempting to save note to non-existent vault should result in SharedVaultNotMemberError conflict', async () => {
|
||||
context.anticipateConsoleError(
|
||||
'Error decrypting contentKey from parameters',
|
||||
'An invalid shared vault uuid is being assigned to an item',
|
||||
)
|
||||
const { note } = await Collaboration.createSharedVaultWithNote(context)
|
||||
|
||||
const promise = context.resolveWithConflicts()
|
||||
|
||||
const objectToSpy = context.application.sync
|
||||
sinon.stub(objectToSpy, 'payloadsByPreparingForServer').callsFake(async (params) => {
|
||||
objectToSpy.payloadsByPreparingForServer.restore()
|
||||
const payloads = await objectToSpy.payloadsByPreparingForServer(params)
|
||||
for (const payload of payloads) {
|
||||
payload.shared_vault_uuid = 'non-existent-vault-uuid-123'
|
||||
}
|
||||
|
||||
return payloads
|
||||
})
|
||||
|
||||
await context.changeNoteTitleAndSync(note, 'new-title')
|
||||
const conflicts = await promise
|
||||
|
||||
expect(conflicts.length).to.equal(1)
|
||||
expect(conflicts[0].type).to.equal(ConflictType.SharedVaultNotMemberError)
|
||||
expect(conflicts[0].unsaved_item.content_type).to.equal(ContentType.Note)
|
||||
})
|
||||
|
||||
it('should create a non-vaulted copy if attempting to move item from vault to user and item belongs to someone else', async () => {
|
||||
const { note, sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
const promise = contactContext.resolveWithConflicts()
|
||||
await contactContext.vaults.removeItemFromVault(note)
|
||||
const conflicts = await promise
|
||||
|
||||
expect(conflicts.length).to.equal(1)
|
||||
expect(conflicts[0].unsaved_item.content_type).to.equal(ContentType.Note)
|
||||
|
||||
const duplicateNote = contactContext.findDuplicateNote(note.uuid)
|
||||
expect(duplicateNote).to.not.be.undefined
|
||||
expect(duplicateNote.key_system_identifier).to.not.be.ok
|
||||
|
||||
const existingNote = contactContext.items.findItem(note.uuid)
|
||||
expect(existingNote.key_system_identifier).to.equal(sharedVault.systemIdentifier)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should created a non-vaulted copy if admin attempts to move item from vault to user if the item belongs to someone else', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const note = await contactContext.createSyncedNote('foo', 'bar')
|
||||
await Collaboration.moveItemToVault(contactContext, sharedVault, note)
|
||||
await context.sync()
|
||||
|
||||
const promise = context.resolveWithConflicts()
|
||||
await context.vaults.removeItemFromVault(note)
|
||||
const conflicts = await promise
|
||||
|
||||
expect(conflicts.length).to.equal(1)
|
||||
expect(conflicts[0].unsaved_item.content_type).to.equal(ContentType.Note)
|
||||
|
||||
const duplicateNote = context.findDuplicateNote(note.uuid)
|
||||
expect(duplicateNote).to.not.be.undefined
|
||||
expect(duplicateNote.key_system_identifier).to.not.be.ok
|
||||
|
||||
const existingNote = context.items.findItem(note.uuid)
|
||||
expect(existingNote.key_system_identifier).to.equal(sharedVault.systemIdentifier)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
})
|
||||
83
packages/snjs/mocha/vaults/contacts.test.js
Normal file
83
packages/snjs/mocha/vaults/contacts.test.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('contacts', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
})
|
||||
|
||||
it('should create contact', async () => {
|
||||
const contact = await context.contacts.createOrEditTrustedContact({
|
||||
name: 'John Doe',
|
||||
publicKey: 'my_public_key',
|
||||
signingPublicKey: 'my_signing_public_key',
|
||||
contactUuid: '123',
|
||||
})
|
||||
|
||||
expect(contact).to.not.be.undefined
|
||||
expect(contact.name).to.equal('John Doe')
|
||||
expect(contact.publicKeySet.encryption).to.equal('my_public_key')
|
||||
expect(contact.publicKeySet.signing).to.equal('my_signing_public_key')
|
||||
expect(contact.contactUuid).to.equal('123')
|
||||
})
|
||||
|
||||
it('should create self contact on registration', async () => {
|
||||
const selfContact = context.contacts.getSelfContact()
|
||||
|
||||
expect(selfContact).to.not.be.undefined
|
||||
|
||||
expect(selfContact.publicKeySet.encryption).to.equal(context.publicKey)
|
||||
expect(selfContact.publicKeySet.signing).to.equal(context.signingPublicKey)
|
||||
})
|
||||
|
||||
it('should create self contact on sign in if it does not exist', async () => {
|
||||
let selfContact = context.contacts.getSelfContact()
|
||||
await context.mutator.setItemToBeDeleted(selfContact)
|
||||
await context.sync()
|
||||
await context.signout()
|
||||
|
||||
await context.signIn()
|
||||
selfContact = context.contacts.getSelfContact()
|
||||
expect(selfContact).to.not.be.undefined
|
||||
})
|
||||
|
||||
it('should update self contact on password change', async () => {
|
||||
const selfContact = context.contacts.getSelfContact()
|
||||
|
||||
await context.changePassword('new_password')
|
||||
|
||||
const updatedSelfContact = context.contacts.getSelfContact()
|
||||
|
||||
expect(updatedSelfContact.publicKeySet.encryption).to.not.equal(selfContact.publicKeySet.encryption)
|
||||
expect(updatedSelfContact.publicKeySet.signing).to.not.equal(selfContact.publicKeySet.signing)
|
||||
|
||||
expect(updatedSelfContact.publicKeySet.encryption).to.equal(context.publicKey)
|
||||
expect(updatedSelfContact.publicKeySet.signing).to.equal(context.signingPublicKey)
|
||||
})
|
||||
|
||||
it('should not be able to delete self contact', async () => {
|
||||
const selfContact = context.contacts.getSelfContact()
|
||||
|
||||
await Factory.expectThrowsAsync(() => context.contacts.deleteContact(selfContact), 'Cannot delete self')
|
||||
})
|
||||
|
||||
it('should not be able to delete a trusted contact if it belongs to a vault I administer', async () => {
|
||||
console.error('TODO: implement test')
|
||||
})
|
||||
})
|
||||
204
packages/snjs/mocha/vaults/crypto.test.js
Normal file
204
packages/snjs/mocha/vaults/crypto.test.js
Normal file
@@ -0,0 +1,204 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Collaboration from '../lib/Collaboration.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('shared vault crypto', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
})
|
||||
|
||||
describe('root key', () => {
|
||||
it('root key loaded from disk should have keypairs', async () => {
|
||||
const appIdentifier = context.identifier
|
||||
await context.deinit()
|
||||
|
||||
let recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
|
||||
await recreatedContext.launch()
|
||||
|
||||
expect(recreatedContext.encryption.getKeyPair()).to.not.be.undefined
|
||||
expect(recreatedContext.encryption.getSigningKeyPair()).to.not.be.undefined
|
||||
})
|
||||
|
||||
it('changing user password should re-encrypt all key system root keys', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('changing user password should re-encrypt all trusted contacts', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
})
|
||||
|
||||
describe('persistent content signature', () => {
|
||||
it('storage payloads should include signatureData', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
await contactContext.changeNoteTitleAndSync(note, 'new title')
|
||||
await context.sync()
|
||||
|
||||
const rawPayloads = await context.application.diskStorageService.getAllRawPayloads()
|
||||
const noteRawPayload = rawPayloads.find((payload) => payload.uuid === note.uuid)
|
||||
|
||||
expect(noteRawPayload.signatureData).to.not.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('changing item content should erase existing signatureData', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
await contactContext.changeNoteTitleAndSync(note, 'new title')
|
||||
await context.sync()
|
||||
|
||||
let updatedNote = context.items.findItem(note.uuid)
|
||||
await context.changeNoteTitleAndSync(updatedNote, 'new title 2')
|
||||
|
||||
updatedNote = context.items.findItem(note.uuid)
|
||||
expect(updatedNote.signatureData).to.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('encrypting an item into storage then loading it should verify authenticity of original content rather than most recent symmetric signature', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
await contactContext.changeNoteTitleAndSync(note, 'new title')
|
||||
|
||||
/** Override decrypt result to return failing signature */
|
||||
const objectToSpy = context.encryption
|
||||
sinon.stub(objectToSpy, 'decryptSplit').callsFake(async (split) => {
|
||||
objectToSpy.decryptSplit.restore()
|
||||
|
||||
const decryptedPayloads = await objectToSpy.decryptSplit(split)
|
||||
expect(decryptedPayloads.length).to.equal(1)
|
||||
|
||||
const payload = decryptedPayloads[0]
|
||||
const mutatedPayload = new DecryptedPayload({
|
||||
...payload.ejected(),
|
||||
signatureData: {
|
||||
...payload.signatureData,
|
||||
result: {
|
||||
...payload.signatureData.result,
|
||||
passes: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return [mutatedPayload]
|
||||
})
|
||||
await context.sync()
|
||||
|
||||
let updatedNote = context.items.findItem(note.uuid)
|
||||
expect(updatedNote.content.title).to.equal('new title')
|
||||
expect(updatedNote.signatureData.result.passes).to.equal(false)
|
||||
|
||||
const appIdentifier = context.identifier
|
||||
await context.deinit()
|
||||
|
||||
let recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
|
||||
await recreatedContext.launch()
|
||||
|
||||
updatedNote = recreatedContext.items.findItem(note.uuid)
|
||||
expect(updatedNote.signatureData.result.passes).to.equal(false)
|
||||
|
||||
/** Changing the content now should clear failing signature */
|
||||
await recreatedContext.changeNoteTitleAndSync(updatedNote, 'new title 2')
|
||||
updatedNote = recreatedContext.items.findItem(note.uuid)
|
||||
expect(updatedNote.signatureData).to.be.undefined
|
||||
|
||||
await recreatedContext.deinit()
|
||||
|
||||
recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
|
||||
await recreatedContext.launch()
|
||||
|
||||
/** Decrypting from storage will now verify current user symmetric signature only */
|
||||
updatedNote = recreatedContext.items.findItem(note.uuid)
|
||||
expect(updatedNote.signatureData.result.passes).to.equal(true)
|
||||
|
||||
await recreatedContext.deinit()
|
||||
await deinitContactContext()
|
||||
})
|
||||
})
|
||||
|
||||
describe('symmetrically encrypted items', () => {
|
||||
it('created items with a payload source of remote saved should not have signature data', async () => {
|
||||
const note = await context.createSyncedNote()
|
||||
|
||||
expect(note.payload.source).to.equal(PayloadSource.RemoteSaved)
|
||||
|
||||
expect(note.signatureData).to.be.undefined
|
||||
})
|
||||
|
||||
it('retrieved items that are then remote saved should have their signature data cleared', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
await contactContext.changeNoteTitleAndSync(contactContext.items.findItem(note.uuid), 'new title')
|
||||
|
||||
await context.sync()
|
||||
expect(context.items.findItem(note.uuid).signatureData).to.not.be.undefined
|
||||
|
||||
await context.changeNoteTitleAndSync(context.items.findItem(note.uuid), 'new title')
|
||||
expect(context.items.findItem(note.uuid).signatureData).to.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should allow client verification of authenticity of shared item changes', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
expect(context.contacts.isItemAuthenticallySigned(note)).to.equal('not-applicable')
|
||||
|
||||
const contactNote = contactContext.items.findItem(note.uuid)
|
||||
|
||||
expect(contactContext.contacts.isItemAuthenticallySigned(contactNote)).to.equal('yes')
|
||||
|
||||
await contactContext.changeNoteTitleAndSync(contactNote, 'new title')
|
||||
|
||||
await context.sync()
|
||||
|
||||
let updatedNote = context.items.findItem(note.uuid)
|
||||
|
||||
expect(context.contacts.isItemAuthenticallySigned(updatedNote)).to.equal('yes')
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
})
|
||||
|
||||
describe('keypair revocation', () => {
|
||||
it('should be able to revoke non-current keypair', async () => {
|
||||
console.error('TODO')
|
||||
})
|
||||
|
||||
it('revoking a keypair should send a keypair revocation event to trusted contacts', async () => {
|
||||
console.error('TODO')
|
||||
})
|
||||
|
||||
it('should not be able to revoke current key pair', async () => {
|
||||
console.error('TODO')
|
||||
})
|
||||
|
||||
it('should distrust revoked keypair as contact', async () => {
|
||||
console.error('TODO')
|
||||
})
|
||||
})
|
||||
})
|
||||
159
packages/snjs/mocha/vaults/deletion.test.js
Normal file
159
packages/snjs/mocha/vaults/deletion.test.js
Normal file
@@ -0,0 +1,159 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Collaboration from '../lib/Collaboration.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('shared vault deletion', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let sharedVaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
sharedVaults = context.sharedVaults
|
||||
})
|
||||
|
||||
it('should remove item from all user devices when item is deleted permanently', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
const promise = context.resolveWhenSavedSyncPayloadsIncludesItemUuid(note.uuid)
|
||||
await context.mutator.setItemToBeDeleted(note)
|
||||
await context.sync()
|
||||
await contactContext.sync()
|
||||
await promise
|
||||
|
||||
const originatorNote = context.items.findItem(note.uuid)
|
||||
expect(originatorNote).to.be.undefined
|
||||
|
||||
const collaboratorNote = contactContext.items.findItem(note.uuid)
|
||||
expect(collaboratorNote).to.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('attempting to delete a note received by and already deleted by another person should not cause infinite conflicts', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
const promise = context.resolveWhenSavedSyncPayloadsIncludesItemUuid(note.uuid)
|
||||
|
||||
await context.mutator.setItemToBeDeleted(note)
|
||||
await contactContext.mutator.setItemToBeDeleted(note)
|
||||
|
||||
await context.sync()
|
||||
await contactContext.sync()
|
||||
await promise
|
||||
|
||||
const originatorNote = context.items.findItem(note.uuid)
|
||||
expect(originatorNote).to.be.undefined
|
||||
|
||||
const collaboratorNote = contactContext.items.findItem(note.uuid)
|
||||
expect(collaboratorNote).to.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('deleting a shared vault should remove all vault items from collaborator devices', async () => {
|
||||
const { sharedVault, note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
await sharedVaults.deleteSharedVault(sharedVault)
|
||||
await contactContext.sync()
|
||||
|
||||
const originatorNote = context.items.findItem(note.uuid)
|
||||
expect(originatorNote).to.be.undefined
|
||||
|
||||
const contactNote = contactContext.items.findItem(note.uuid)
|
||||
expect(contactNote).to.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('being removed from shared vault should remove shared vault items locally', async () => {
|
||||
const { sharedVault, note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
const contactNote = contactContext.items.findItem(note.uuid)
|
||||
expect(contactNote).to.not.be.undefined
|
||||
|
||||
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
|
||||
await contactContext.sync()
|
||||
|
||||
const updatedContactNote = contactContext.items.findItem(note.uuid)
|
||||
expect(updatedContactNote).to.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('leaving a shared vault should remove its items locally', async () => {
|
||||
const { sharedVault, note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context, SharedVaultPermission.Admin)
|
||||
|
||||
const originalNote = contactContext.items.findItem(note.uuid)
|
||||
expect(originalNote).to.not.be.undefined
|
||||
|
||||
const contactVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
|
||||
await contactContext.sharedVaults.leaveSharedVault(contactVault)
|
||||
|
||||
const updatedContactNote = contactContext.items.findItem(note.uuid)
|
||||
expect(updatedContactNote).to.be.undefined
|
||||
|
||||
const vault = await contactContext.vaults.getVault({ keySystemIdentifier: contactVault.systemIdentifier })
|
||||
expect(vault).to.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('removing an item from a vault should remove it from collaborator devices', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
await context.vaults.removeItemFromVault(note)
|
||||
|
||||
await context.changeNoteTitleAndSync(note, 'new title')
|
||||
|
||||
const receivedNote = contactContext.items.findItem(note.uuid)
|
||||
|
||||
expect(receivedNote).to.not.be.undefined
|
||||
expect(receivedNote.title).to.not.equal('new title')
|
||||
expect(receivedNote.title).to.equal(note.title)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should remove shared vault member', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const originalSharedVaultUsers = await sharedVaults.getSharedVaultUsers(sharedVault)
|
||||
expect(originalSharedVaultUsers.length).to.equal(2)
|
||||
|
||||
const result = await sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
|
||||
expect(isClientDisplayableError(result)).to.be.false
|
||||
|
||||
const updatedSharedVaultUsers = await sharedVaults.getSharedVaultUsers(sharedVault)
|
||||
expect(updatedSharedVaultUsers.length).to.equal(1)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('being removed from a shared vault should delete respective vault listing', async () => {
|
||||
console.error('TODO: implement test')
|
||||
})
|
||||
})
|
||||
259
packages/snjs/mocha/vaults/files.test.js
Normal file
259
packages/snjs/mocha/vaults/files.test.js
Normal file
@@ -0,0 +1,259 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Files from '../lib/Files.js'
|
||||
import * as Collaboration from '../lib/Collaboration.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('shared vault files', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let vaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
vaults = context.vaults
|
||||
await context.publicMockSubscriptionPurchaseEvent()
|
||||
})
|
||||
|
||||
describe('private vaults', () => {
|
||||
it('should be able to upload and download file to vault as owner', async () => {
|
||||
const vault = await Collaboration.createPrivateVault(context)
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, vault)
|
||||
|
||||
const file = context.items.findItem(uploadedFile.uuid)
|
||||
expect(file).to.not.be.undefined
|
||||
expect(file.remoteIdentifier).to.equal(file.remoteIdentifier)
|
||||
expect(file.key_system_identifier).to.equal(vault.systemIdentifier)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, file)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
})
|
||||
})
|
||||
|
||||
it('should be able to upload and download file to vault as owner', async () => {
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, sharedVault)
|
||||
|
||||
const file = context.items.findItem(uploadedFile.uuid)
|
||||
expect(file).to.not.be.undefined
|
||||
expect(file.remoteIdentifier).to.equal(file.remoteIdentifier)
|
||||
expect(file.key_system_identifier).to.equal(sharedVault.systemIdentifier)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, file)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
})
|
||||
|
||||
it('should be able to move a user file to a vault', async () => {
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000)
|
||||
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
const addedFile = await vaults.moveItemToVault(sharedVault, uploadedFile)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, addedFile)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
})
|
||||
|
||||
it('should be able to move a shared vault file to another shared vault', async () => {
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
|
||||
const firstVault = await Collaboration.createSharedVault(context)
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, firstVault)
|
||||
|
||||
const secondVault = await Collaboration.createSharedVault(context)
|
||||
const movedFile = await vaults.moveItemToVault(secondVault, uploadedFile)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, movedFile)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
})
|
||||
|
||||
it('should be able to move a shared vault file to a non-shared vault', async () => {
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
|
||||
const firstVault = await Collaboration.createSharedVault(context)
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, firstVault)
|
||||
const privateVault = await Collaboration.createPrivateVault(context)
|
||||
|
||||
const addedFile = await vaults.moveItemToVault(privateVault, uploadedFile)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, addedFile)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
})
|
||||
|
||||
it('moving a note to a vault should also moved linked files', async () => {
|
||||
const note = await context.createSyncedNote()
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
const file = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000)
|
||||
|
||||
const updatedFile = await context.application.mutator.associateFileWithNote(file, note)
|
||||
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
vaults.alerts.confirmV2 = () => Promise.resolve(true)
|
||||
|
||||
await vaults.moveItemToVault(sharedVault, note)
|
||||
|
||||
const latestFile = context.items.findItem(updatedFile.uuid)
|
||||
|
||||
expect(vaults.getItemVault(latestFile).uuid).to.equal(sharedVault.uuid)
|
||||
expect(vaults.getItemVault(context.items.findItem(note.uuid)).uuid).to.equal(sharedVault.uuid)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, latestFile)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
})
|
||||
|
||||
it('should be able to move a file out of its vault', async () => {
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, sharedVault)
|
||||
|
||||
const removedFile = await vaults.removeItemFromVault(uploadedFile)
|
||||
expect(removedFile.key_system_identifier).to.not.be.ok
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, removedFile)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
})
|
||||
|
||||
it('should be able to download vault file as collaborator', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, sharedVault)
|
||||
|
||||
await contactContext.sync()
|
||||
|
||||
const sharedFile = contactContext.items.findItem(uploadedFile.uuid)
|
||||
expect(sharedFile).to.not.be.undefined
|
||||
expect(sharedFile.remoteIdentifier).to.equal(uploadedFile.remoteIdentifier)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(contactContext.files, sharedFile)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should be able to upload vault file as collaborator', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
|
||||
const uploadedFile = await Files.uploadFile(contactContext.files, buffer, 'my-file', 'md', 1000, sharedVault)
|
||||
|
||||
await context.sync()
|
||||
|
||||
const file = context.items.findItem(uploadedFile.uuid)
|
||||
expect(file).to.not.be.undefined
|
||||
expect(file.remoteIdentifier).to.equal(file.remoteIdentifier)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(context.files, file)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should be able to delete vault file as write user', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context, SharedVaultPermission.Write)
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, sharedVault)
|
||||
|
||||
await contactContext.sync()
|
||||
|
||||
const file = contactContext.items.findItem(uploadedFile.uuid)
|
||||
const result = await contactContext.files.deleteFile(file)
|
||||
expect(result).to.be.undefined
|
||||
|
||||
const foundFile = contactContext.items.findItem(file.uuid)
|
||||
expect(foundFile).to.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should not be able to delete vault file as read user', async () => {
|
||||
context.anticipateConsoleError('Could not create valet token')
|
||||
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context, SharedVaultPermission.Read)
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, sharedVault)
|
||||
|
||||
await contactContext.sync()
|
||||
|
||||
const file = contactContext.items.findItem(uploadedFile.uuid)
|
||||
const result = await contactContext.files.deleteFile(file)
|
||||
expect(isClientDisplayableError(result)).to.be.true
|
||||
|
||||
const foundFile = contactContext.items.findItem(file.uuid)
|
||||
expect(foundFile).to.not.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should be able to download recently moved vault file as collaborator', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000)
|
||||
const addedFile = await vaults.moveItemToVault(sharedVault, uploadedFile)
|
||||
|
||||
await contactContext.sync()
|
||||
|
||||
const sharedFile = contactContext.items.findItem(addedFile.uuid)
|
||||
expect(sharedFile).to.not.be.undefined
|
||||
expect(sharedFile.remoteIdentifier).to.equal(addedFile.remoteIdentifier)
|
||||
|
||||
const downloadedBytes = await Files.downloadFile(contactContext.files, sharedFile)
|
||||
expect(downloadedBytes).to.eql(buffer)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should not be able to download file after being removed from vault', async () => {
|
||||
context.anticipateConsoleError('Could not create valet token')
|
||||
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
const response = await fetch('/mocha/assets/small_file.md')
|
||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, sharedVault)
|
||||
await contactContext.sync()
|
||||
|
||||
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
|
||||
const file = contactContext.items.findItem(uploadedFile.uuid)
|
||||
await Factory.expectThrowsAsync(() => Files.downloadFile(contactContext.files, file), 'Could not download file')
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
})
|
||||
229
packages/snjs/mocha/vaults/invites.test.js
Normal file
229
packages/snjs/mocha/vaults/invites.test.js
Normal file
@@ -0,0 +1,229 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Collaboration from '../lib/Collaboration.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('shared vault invites', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let sharedVaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
sharedVaults = context.sharedVaults
|
||||
})
|
||||
|
||||
it('should invite contact to vault', async () => {
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
const { contactContext, deinitContactContext } = await Collaboration.createContactContext()
|
||||
const contact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
|
||||
|
||||
const vaultInvite = await sharedVaults.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write)
|
||||
|
||||
expect(vaultInvite).to.not.be.undefined
|
||||
expect(vaultInvite.shared_vault_uuid).to.equal(sharedVault.sharing.sharedVaultUuid)
|
||||
expect(vaultInvite.user_uuid).to.equal(contact.contactUuid)
|
||||
expect(vaultInvite.encrypted_message).to.not.be.undefined
|
||||
expect(vaultInvite.permissions).to.equal(SharedVaultPermission.Write)
|
||||
expect(vaultInvite.updated_at_timestamp).to.not.be.undefined
|
||||
expect(vaultInvite.created_at_timestamp).to.not.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('invites from trusted contact should be pending as trusted', async () => {
|
||||
const { contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context)
|
||||
|
||||
const invites = contactContext.sharedVaults.getCachedPendingInviteRecords()
|
||||
|
||||
expect(invites[0].trusted).to.be.true
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('invites from untrusted contact should be pending as untrusted', async () => {
|
||||
const { contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithUnacceptedAndUntrustedInvite(context)
|
||||
|
||||
const invites = contactContext.sharedVaults.getCachedPendingInviteRecords()
|
||||
|
||||
expect(invites[0].trusted).to.be.false
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('invite should include delegated trusted contacts', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const { thirdPartyContext, deinitThirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
|
||||
context,
|
||||
sharedVault,
|
||||
)
|
||||
|
||||
const invites = thirdPartyContext.sharedVaults.getCachedPendingInviteRecords()
|
||||
|
||||
const message = invites[0].message
|
||||
const delegatedContacts = message.data.trustedContacts
|
||||
expect(delegatedContacts.length).to.equal(1)
|
||||
expect(delegatedContacts[0].contactUuid).to.equal(contactContext.userUuid)
|
||||
|
||||
await deinitThirdPartyContext()
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should sync a shared vault from scratch after accepting an invitation', async () => {
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
await Collaboration.moveItemToVault(context, sharedVault, note)
|
||||
|
||||
/** Create a mutually trusted contact */
|
||||
const { contactContext, deinitContactContext } = await Collaboration.createContactContext()
|
||||
const contact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
|
||||
await Collaboration.createTrustedContactForUserOfContext(contactContext, context)
|
||||
|
||||
/** Sync the contact context so that they wouldn't naturally receive changes made before this point */
|
||||
await contactContext.sync()
|
||||
|
||||
await sharedVaults.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write)
|
||||
|
||||
/** Contact should now sync and expect to find note */
|
||||
const promise = contactContext.awaitNextSyncSharedVaultFromScratchEvent()
|
||||
await contactContext.sync()
|
||||
await Collaboration.acceptAllInvites(contactContext)
|
||||
await promise
|
||||
|
||||
const receivedNote = contactContext.items.findItem(note.uuid)
|
||||
expect(receivedNote).to.not.be.undefined
|
||||
expect(receivedNote.title).to.equal('foo')
|
||||
expect(receivedNote.text).to.equal(note.text)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('received invites from untrusted contact should not be trusted', async () => {
|
||||
await context.createSyncedNote('foo', 'bar')
|
||||
const { contactContext, deinitContactContext } = await Collaboration.createContactContext()
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
|
||||
await sharedVaults.inviteContactToSharedVault(sharedVault, currentContextContact, SharedVaultPermission.Write)
|
||||
|
||||
await contactContext.sharedVaults.downloadInboundInvites()
|
||||
expect(contactContext.sharedVaults.getCachedPendingInviteRecords()[0].trusted).to.be.false
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('received invites from contact who becomes trusted after receipt of invite should be trusted', async () => {
|
||||
await context.createSyncedNote('foo', 'bar')
|
||||
const { contactContext, deinitContactContext } = await Collaboration.createContactContext()
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
|
||||
await sharedVaults.inviteContactToSharedVault(sharedVault, currentContextContact, SharedVaultPermission.Write)
|
||||
|
||||
await contactContext.sharedVaults.downloadInboundInvites()
|
||||
expect(contactContext.sharedVaults.getCachedPendingInviteRecords()[0].trusted).to.be.false
|
||||
|
||||
await Collaboration.createTrustedContactForUserOfContext(contactContext, context)
|
||||
|
||||
expect(contactContext.sharedVaults.getCachedPendingInviteRecords()[0].trusted).to.be.true
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('received items should contain the uuid of the contact who sent the item', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
const receivedNote = contactContext.items.findItem(note.uuid)
|
||||
expect(receivedNote).to.not.be.undefined
|
||||
expect(receivedNote.user_uuid).to.equal(context.userUuid)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('items should contain the uuid of the last person who edited it', async () => {
|
||||
const { note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
const receivedNote = contactContext.items.findItem(note.uuid)
|
||||
expect(receivedNote.last_edited_by_uuid).to.not.be.undefined
|
||||
expect(receivedNote.last_edited_by_uuid).to.equal(context.userUuid)
|
||||
|
||||
await contactContext.changeNoteTitleAndSync(receivedNote, 'new title')
|
||||
await context.sync()
|
||||
|
||||
const updatedNote = context.items.findItem(note.uuid)
|
||||
expect(updatedNote.last_edited_by_uuid).to.not.be.undefined
|
||||
expect(updatedNote.last_edited_by_uuid).to.equal(contactContext.userUuid)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('canceling an invite should remove it from recipient pending invites', async () => {
|
||||
const { invite, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context)
|
||||
|
||||
const preInvites = await contactContext.sharedVaults.downloadInboundInvites()
|
||||
expect(preInvites.length).to.equal(1)
|
||||
|
||||
await sharedVaults.deleteInvite(invite)
|
||||
|
||||
const postInvites = await contactContext.sharedVaults.downloadInboundInvites()
|
||||
expect(postInvites.length).to.equal(0)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('when inviter keypair changes, recipient should still be able to trust and decrypt previous invite', async () => {
|
||||
console.error('TODO: implement test')
|
||||
})
|
||||
|
||||
it('should delete all inbound invites after changing user password', async () => {
|
||||
/** Invites to user are encrypted with old keypair and are no longer decryptable */
|
||||
console.error('TODO: implement test')
|
||||
})
|
||||
|
||||
it('sharing a vault with user inputted and ephemeral password should share the key as synced for the recipient', async () => {
|
||||
const privateVault = await context.vaults.createUserInputtedPasswordVault({
|
||||
name: 'My Private Vault',
|
||||
userInputtedPassword: 'password',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Ephemeral,
|
||||
})
|
||||
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
await context.vaults.moveItemToVault(privateVault, note)
|
||||
|
||||
const sharedVault = await context.sharedVaults.convertVaultToSharedVault(privateVault)
|
||||
|
||||
const { thirdPartyContext, deinitThirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
|
||||
context,
|
||||
sharedVault,
|
||||
)
|
||||
|
||||
await Collaboration.acceptAllInvites(thirdPartyContext)
|
||||
|
||||
const contextNote = thirdPartyContext.items.findItem(note.uuid)
|
||||
expect(contextNote).to.not.be.undefined
|
||||
expect(contextNote.title).to.equal('foo')
|
||||
expect(contextNote.text).to.equal(note.text)
|
||||
|
||||
await deinitThirdPartyContext()
|
||||
})
|
||||
})
|
||||
121
packages/snjs/mocha/vaults/items.test.js
Normal file
121
packages/snjs/mocha/vaults/items.test.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Collaboration from '../lib/Collaboration.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('shared vault items', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let sharedVaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
sharedVaults = context.sharedVaults
|
||||
})
|
||||
|
||||
it('should add item to shared vault with no other members', async () => {
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
await Collaboration.moveItemToVault(context, sharedVault, note)
|
||||
|
||||
const updatedNote = context.items.findItem(note.uuid)
|
||||
expect(updatedNote.key_system_identifier).to.equal(sharedVault.systemIdentifier)
|
||||
expect(updatedNote.shared_vault_uuid).to.equal(sharedVault.sharing.sharedVaultUuid)
|
||||
})
|
||||
|
||||
it('should add item to shared vault with contact', async () => {
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
|
||||
const { sharedVault, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
await Collaboration.moveItemToVault(context, sharedVault, note)
|
||||
|
||||
const updatedNote = context.items.findItem(note.uuid)
|
||||
expect(updatedNote.key_system_identifier).to.equal(sharedVault.systemIdentifier)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('received items from previously trusted contact should be decrypted', async () => {
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
const { contactContext, deinitContactContext } = await Collaboration.createContactContext()
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
await Collaboration.createTrustedContactForUserOfContext(contactContext, context)
|
||||
const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
|
||||
|
||||
contactContext.lockSyncing()
|
||||
await sharedVaults.inviteContactToSharedVault(sharedVault, currentContextContact, SharedVaultPermission.Write)
|
||||
await Collaboration.moveItemToVault(context, sharedVault, note)
|
||||
|
||||
const promise = contactContext.awaitNextSyncSharedVaultFromScratchEvent()
|
||||
contactContext.unlockSyncing()
|
||||
await contactContext.sync()
|
||||
await Collaboration.acceptAllInvites(contactContext)
|
||||
await promise
|
||||
|
||||
const receivedItemsKey = contactContext.keys.getPrimaryKeySystemItemsKey(sharedVault.systemIdentifier)
|
||||
expect(receivedItemsKey).to.not.be.undefined
|
||||
expect(receivedItemsKey.itemsKey).to.not.be.undefined
|
||||
|
||||
const receivedNote = contactContext.items.findItem(note.uuid)
|
||||
expect(receivedNote.title).to.equal('foo')
|
||||
expect(receivedNote.text).to.equal(note.text)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('shared vault creator should receive changes from other members', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
await Collaboration.moveItemToVault(context, sharedVault, note)
|
||||
await contactContext.sync()
|
||||
|
||||
await contactContext.mutator.changeItem({ uuid: note.uuid }, (mutator) => {
|
||||
mutator.title = 'new title'
|
||||
})
|
||||
await contactContext.sync()
|
||||
await context.sync()
|
||||
|
||||
const receivedNote = context.items.findItem(note.uuid)
|
||||
expect(receivedNote.title).to.equal('new title')
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('items added by collaborator should be received by shared vault owner', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
const newNote = await contactContext.createSyncedNote('new note', 'new note text')
|
||||
await Collaboration.moveItemToVault(contactContext, sharedVault, newNote)
|
||||
|
||||
await context.sync()
|
||||
|
||||
const receivedNote = context.items.findItem(newNote.uuid)
|
||||
expect(receivedNote).to.not.be.undefined
|
||||
expect(receivedNote.title).to.equal('new note')
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('adding item to vault while belonging to other vault should move the item to new vault', async () => {
|
||||
console.error('TODO: implement test')
|
||||
})
|
||||
})
|
||||
269
packages/snjs/mocha/vaults/key_rotation.test.js
Normal file
269
packages/snjs/mocha/vaults/key_rotation.test.js
Normal file
@@ -0,0 +1,269 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Collaboration from '../lib/Collaboration.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('shared vault key rotation', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let vaults
|
||||
let sharedVaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
vaults = context.vaults
|
||||
sharedVaults = context.sharedVaults
|
||||
})
|
||||
|
||||
it('should reencrypt all items keys belonging to key system', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const spy = sinon.spy(context.encryption, 'reencryptKeySystemItemsKeysForVault')
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
expect(spy.callCount).to.equal(1)
|
||||
|
||||
deinitContactContext()
|
||||
})
|
||||
|
||||
it("rotating a vault's key should send an asymmetric message to all members", async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
const outboundMessages = await context.asymmetric.getOutboundMessages()
|
||||
const expectedMessages = ['root key change', 'vault metadata change']
|
||||
expect(outboundMessages.length).to.equal(expectedMessages.length)
|
||||
|
||||
const message = outboundMessages[0]
|
||||
expect(message).to.not.be.undefined
|
||||
expect(message.user_uuid).to.equal(contactContext.userUuid)
|
||||
expect(message.encrypted_message).to.not.be.undefined
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should update recipient vault display listing with new key params', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
contactContext.anticipateConsoleError(
|
||||
'(2x) Error decrypting contentKey from parameters',
|
||||
'Items keys are encrypted with new root key and are later decrypted in the test',
|
||||
)
|
||||
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
const rootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)
|
||||
|
||||
contactContext.unlockSyncing()
|
||||
await contactContext.sync()
|
||||
|
||||
const vault = await contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
|
||||
expect(vault.rootKeyParams).to.eql(rootKey.keyParams)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should receive new key system items key', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
contactContext.anticipateConsoleError(
|
||||
'(2x) Error decrypting contentKey from parameters',
|
||||
'Items keys are encrypted with new root key and are later decrypted in the test',
|
||||
)
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const previousPrimaryItemsKey = contactContext.keys.getPrimaryKeySystemItemsKey(sharedVault.systemIdentifier)
|
||||
expect(previousPrimaryItemsKey).to.not.be.undefined
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
const contactPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
|
||||
contactContext.unlockSyncing()
|
||||
await contactContext.sync()
|
||||
await contactPromise
|
||||
|
||||
const newPrimaryItemsKey = contactContext.keys.getPrimaryKeySystemItemsKey(sharedVault.systemIdentifier)
|
||||
expect(newPrimaryItemsKey).to.not.be.undefined
|
||||
|
||||
expect(newPrimaryItemsKey.uuid).to.not.equal(previousPrimaryItemsKey.uuid)
|
||||
expect(newPrimaryItemsKey.itemsKey).to.not.eql(previousPrimaryItemsKey.itemsKey)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it("rotating a vault's key with a pending invite should create new invite and delete old", async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context)
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const originalOutboundInvites = await sharedVaults.getOutboundInvites()
|
||||
expect(originalOutboundInvites.length).to.equal(1)
|
||||
const originalInviteMessage = originalOutboundInvites[0].encrypted_message
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
const updatedOutboundInvites = await sharedVaults.getOutboundInvites()
|
||||
expect(updatedOutboundInvites.length).to.equal(1)
|
||||
|
||||
const joinInvite = updatedOutboundInvites[0]
|
||||
expect(joinInvite.encrypted_message).to.not.be.undefined
|
||||
expect(joinInvite.encrypted_message).to.not.equal(originalInviteMessage)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('new key system items key in rotated shared vault should belong to shared vault', async () => {
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
|
||||
const keySystemItemsKeys = context.keys
|
||||
.getAllKeySystemItemsKeys()
|
||||
.filter((key) => key.key_system_identifier === sharedVault.systemIdentifier)
|
||||
|
||||
expect(keySystemItemsKeys.length).to.equal(2)
|
||||
|
||||
for (const key of keySystemItemsKeys) {
|
||||
expect(key.shared_vault_uuid).to.equal(sharedVault.sharing.sharedVaultUuid)
|
||||
}
|
||||
})
|
||||
|
||||
it('should update existing key-change messages instead of creating new ones', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const firstPromise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await firstPromise
|
||||
|
||||
const asymmetricMessageAfterFirstChange = await context.asymmetric.getOutboundMessages()
|
||||
const expectedMessages = ['root key change', 'vault metadata change']
|
||||
expect(asymmetricMessageAfterFirstChange.length).to.equal(expectedMessages.length)
|
||||
|
||||
const messageAfterFirstChange = asymmetricMessageAfterFirstChange[0]
|
||||
|
||||
const secondPromise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await secondPromise
|
||||
|
||||
const asymmetricMessageAfterSecondChange = await context.asymmetric.getOutboundMessages()
|
||||
expect(asymmetricMessageAfterSecondChange.length).to.equal(expectedMessages.length)
|
||||
|
||||
const messageAfterSecondChange = asymmetricMessageAfterSecondChange[0]
|
||||
expect(messageAfterSecondChange.encrypted_message).to.not.equal(messageAfterFirstChange.encrypted_message)
|
||||
expect(messageAfterSecondChange.uuid).to.not.equal(messageAfterFirstChange.uuid)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('key change messages should be automatically processed by trusted contacts', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
contactContext.anticipateConsoleError(
|
||||
'(2x) Error decrypting contentKey from parameters',
|
||||
'Items keys are encrypted with new root key and are later decrypted in the test',
|
||||
)
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
const acceptMessage = sinon.spy(contactContext.asymmetric, 'handleTrustedSharedVaultRootKeyChangedMessage')
|
||||
|
||||
contactContext.unlockSyncing()
|
||||
await contactContext.sync()
|
||||
|
||||
expect(acceptMessage.callCount).to.equal(1)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should rotate key system root key after removing vault member', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const originalKeySystemRootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)
|
||||
|
||||
await sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
|
||||
const newKeySystemRootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)
|
||||
|
||||
expect(newKeySystemRootKey.keyParams.creationTimestamp).to.be.greaterThan(
|
||||
originalKeySystemRootKey.keyParams.creationTimestamp,
|
||||
)
|
||||
expect(newKeySystemRootKey.key).to.not.equal(originalKeySystemRootKey.key)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should throw if attempting to change password of locked vault', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('should respect storage preference when rotating key system root key', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('should change storage preference from synced to local', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('should change storage preference from local to synced', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('should resync key system items key if it is encrypted with noncurrent key system root key', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('should change password type from user inputted to randomized', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('should change password type from randomized to user inputted', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('should not be able to change storage mode of third party vault', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
})
|
||||
133
packages/snjs/mocha/vaults/permissions.test.js
Normal file
133
packages/snjs/mocha/vaults/permissions.test.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Collaboration from '../lib/Collaboration.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('shared vault permissions', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let sharedVaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
sharedVaults = context.sharedVaults
|
||||
})
|
||||
|
||||
it('non-admin user should not be able to invite user', async () => {
|
||||
context.anticipateConsoleError('Could not create invite')
|
||||
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
const thirdParty = await Collaboration.createContactContext()
|
||||
const thirdPartyContact = await Collaboration.createTrustedContactForUserOfContext(
|
||||
contactContext,
|
||||
thirdParty.contactContext,
|
||||
)
|
||||
const result = await contactContext.sharedVaults.inviteContactToSharedVault(
|
||||
sharedVault,
|
||||
thirdPartyContact,
|
||||
SharedVaultPermission.Write,
|
||||
)
|
||||
|
||||
expect(isClientDisplayableError(result)).to.be.true
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should not be able to leave shared vault as creator', async () => {
|
||||
context.anticipateConsoleError('Could not delete user')
|
||||
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
const result = await sharedVaults.removeUserFromSharedVault(sharedVault, context.userUuid)
|
||||
|
||||
expect(isClientDisplayableError(result)).to.be.true
|
||||
})
|
||||
|
||||
it('should be able to leave shared vault as added admin', async () => {
|
||||
const { contactVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context, SharedVaultPermission.Admin)
|
||||
|
||||
const result = await contactContext.sharedVaults.leaveSharedVault(contactVault)
|
||||
|
||||
expect(isClientDisplayableError(result)).to.be.false
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('non-admin user should not be able to create or update vault items keys with the server', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const keySystemItemsKey = contactContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)[0]
|
||||
|
||||
await contactContext.mutator.changeItem(keySystemItemsKey, () => {})
|
||||
const promise = contactContext.resolveWithConflicts()
|
||||
await contactContext.sync()
|
||||
|
||||
const conflicts = await promise
|
||||
|
||||
expect(conflicts.length).to.equal(1)
|
||||
expect(conflicts[0].unsaved_item.content_type).to.equal(ContentType.KeySystemItemsKey)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('read user should not be able to make changes to items', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context, SharedVaultPermission.Read)
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
await Collaboration.moveItemToVault(context, sharedVault, note)
|
||||
await contactContext.sync()
|
||||
|
||||
await contactContext.mutator.changeItem({ uuid: note.uuid }, (mutator) => {
|
||||
mutator.title = 'new title'
|
||||
})
|
||||
|
||||
const promise = contactContext.resolveWithConflicts()
|
||||
await contactContext.sync()
|
||||
const conflicts = await promise
|
||||
|
||||
expect(conflicts.length).to.equal(1)
|
||||
expect(conflicts[0].unsaved_item.content_type).to.equal(ContentType.Note)
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should be able to move item from vault to user as a write user if the item belongs to me', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const note = await contactContext.createSyncedNote('foo', 'bar')
|
||||
await Collaboration.moveItemToVault(contactContext, sharedVault, note)
|
||||
await contactContext.sync()
|
||||
|
||||
const promise = contactContext.resolveWithConflicts()
|
||||
await contactContext.vaults.removeItemFromVault(note)
|
||||
const conflicts = await promise
|
||||
|
||||
expect(conflicts.length).to.equal(0)
|
||||
|
||||
const duplicateNote = contactContext.findDuplicateNote(note.uuid)
|
||||
expect(duplicateNote).to.be.undefined
|
||||
|
||||
const existingNote = contactContext.items.findItem(note.uuid)
|
||||
expect(existingNote.key_system_identifier).to.not.be.ok
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
})
|
||||
98
packages/snjs/mocha/vaults/pkc.test.js
Normal file
98
packages/snjs/mocha/vaults/pkc.test.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('public key cryptography', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let sessions
|
||||
let encryption
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
sessions = context.application.sessions
|
||||
encryption = context.encryption
|
||||
})
|
||||
|
||||
it('should create keypair during registration', () => {
|
||||
expect(sessions.getPublicKey()).to.not.be.undefined
|
||||
expect(encryption.getKeyPair().privateKey).to.not.be.undefined
|
||||
|
||||
expect(sessions.getSigningPublicKey()).to.not.be.undefined
|
||||
expect(encryption.getSigningKeyPair().privateKey).to.not.be.undefined
|
||||
})
|
||||
|
||||
it('should populate keypair during sign in', async () => {
|
||||
const email = context.email
|
||||
const password = context.password
|
||||
await context.signout()
|
||||
|
||||
const recreatedContext = await Factory.createAppContextWithRealCrypto()
|
||||
await recreatedContext.launch()
|
||||
recreatedContext.email = email
|
||||
recreatedContext.password = password
|
||||
await recreatedContext.signIn()
|
||||
|
||||
expect(recreatedContext.sessions.getPublicKey()).to.not.be.undefined
|
||||
expect(recreatedContext.encryption.getKeyPair().privateKey).to.not.be.undefined
|
||||
|
||||
expect(recreatedContext.sessions.getSigningPublicKey()).to.not.be.undefined
|
||||
expect(recreatedContext.encryption.getSigningKeyPair().privateKey).to.not.be.undefined
|
||||
})
|
||||
|
||||
it('should rotate keypair during password change', async () => {
|
||||
const oldPublicKey = sessions.getPublicKey()
|
||||
const oldPrivateKey = encryption.getKeyPair().privateKey
|
||||
|
||||
const oldSigningPublicKey = sessions.getSigningPublicKey()
|
||||
const oldSigningPrivateKey = encryption.getSigningKeyPair().privateKey
|
||||
|
||||
await context.changePassword('new_password')
|
||||
|
||||
expect(sessions.getPublicKey()).to.not.be.undefined
|
||||
expect(encryption.getKeyPair().privateKey).to.not.be.undefined
|
||||
expect(sessions.getPublicKey()).to.not.equal(oldPublicKey)
|
||||
expect(encryption.getKeyPair().privateKey).to.not.equal(oldPrivateKey)
|
||||
|
||||
expect(sessions.getSigningPublicKey()).to.not.be.undefined
|
||||
expect(encryption.getSigningKeyPair().privateKey).to.not.be.undefined
|
||||
expect(sessions.getSigningPublicKey()).to.not.equal(oldSigningPublicKey)
|
||||
expect(encryption.getSigningKeyPair().privateKey).to.not.equal(oldSigningPrivateKey)
|
||||
})
|
||||
|
||||
it('should allow option to enable collaboration for previously signed in accounts', async () => {
|
||||
const newContext = await Factory.createAppContextWithRealCrypto()
|
||||
await newContext.launch()
|
||||
|
||||
await newContext.register()
|
||||
|
||||
const rootKey = await newContext.encryption.getRootKey()
|
||||
const mutatedRootKey = CreateNewRootKey({
|
||||
...rootKey.content,
|
||||
encryptionKeyPair: undefined,
|
||||
signingKeyPair: undefined,
|
||||
})
|
||||
|
||||
await newContext.encryption.setRootKey(mutatedRootKey)
|
||||
|
||||
expect(newContext.application.sessions.isUserMissingKeyPair()).to.be.true
|
||||
|
||||
const result = await newContext.application.user.updateAccountWithFirstTimeKeyPair()
|
||||
expect(result.error).to.be.undefined
|
||||
|
||||
expect(newContext.application.sessions.isUserMissingKeyPair()).to.be.false
|
||||
})
|
||||
})
|
||||
114
packages/snjs/mocha/vaults/shared_vaults.test.js
Normal file
114
packages/snjs/mocha/vaults/shared_vaults.test.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Collaboration from '../lib/Collaboration.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('shared vaults', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let vaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
vaults = context.vaults
|
||||
})
|
||||
|
||||
it('should update vault name and description', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
await vaults.changeVaultNameAndDescription(sharedVault, {
|
||||
name: 'new vault name',
|
||||
description: 'new vault description',
|
||||
})
|
||||
|
||||
const updatedVault = vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
|
||||
expect(updatedVault.name).to.equal('new vault name')
|
||||
expect(updatedVault.description).to.equal('new vault description')
|
||||
|
||||
const promise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
|
||||
await contactContext.sync()
|
||||
await promise
|
||||
|
||||
const contactVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
|
||||
expect(contactVault.name).to.equal('new vault name')
|
||||
expect(contactVault.description).to.equal('new vault description')
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('being removed from a shared vault should remove the vault', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const result = await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
|
||||
expect(result).to.be.undefined
|
||||
|
||||
const promise = contactContext.resolveWhenUserMessagesProcessingCompletes()
|
||||
await contactContext.sync()
|
||||
await promise
|
||||
|
||||
expect(contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined
|
||||
expect(contactContext.encryption.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined
|
||||
expect(contactContext.encryption.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty
|
||||
|
||||
const recreatedContext = await Factory.createAppContextWithRealCrypto(contactContext.identifier)
|
||||
await recreatedContext.launch()
|
||||
|
||||
expect(recreatedContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined
|
||||
expect(recreatedContext.encryption.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined
|
||||
expect(recreatedContext.encryption.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty
|
||||
|
||||
await deinitContactContext()
|
||||
await recreatedContext.deinit()
|
||||
})
|
||||
|
||||
it('deleting a shared vault should remove vault from contact context', async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const result = await context.sharedVaults.deleteSharedVault(sharedVault)
|
||||
|
||||
expect(result).to.be.undefined
|
||||
|
||||
const promise = contactContext.resolveWhenUserMessagesProcessingCompletes()
|
||||
await contactContext.sync()
|
||||
await promise
|
||||
|
||||
expect(contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined
|
||||
expect(contactContext.encryption.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined
|
||||
expect(contactContext.encryption.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty
|
||||
|
||||
const recreatedContext = await Factory.createAppContextWithRealCrypto(contactContext.identifier)
|
||||
await recreatedContext.launch()
|
||||
|
||||
expect(recreatedContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined
|
||||
expect(recreatedContext.encryption.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined
|
||||
expect(recreatedContext.encryption.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty
|
||||
|
||||
await deinitContactContext()
|
||||
await recreatedContext.deinit()
|
||||
})
|
||||
|
||||
it('should convert a vault to a shared vault', async () => {
|
||||
console.error('TODO')
|
||||
})
|
||||
|
||||
it('should send metadata change message when changing name or description', async () => {
|
||||
console.error('TODO')
|
||||
})
|
||||
})
|
||||
250
packages/snjs/mocha/vaults/vaults.test.js
Normal file
250
packages/snjs/mocha/vaults/vaults.test.js
Normal file
@@ -0,0 +1,250 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('vaults', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let vaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
|
||||
vaults = context.vaults
|
||||
})
|
||||
|
||||
describe('locking', () => {
|
||||
it('should throw if attempting to add item to locked vault', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('should throw if attempting to remove item from locked vault', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('locking vault should remove root key and items keys from memory', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
})
|
||||
|
||||
describe('offline', function () {
|
||||
it('should be able to create an offline vault', async () => {
|
||||
const vault = await vaults.createRandomizedVault({
|
||||
name: 'My Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
|
||||
expect(vault.systemIdentifier).to.not.be.undefined
|
||||
expect(typeof vault.systemIdentifier).to.equal('string')
|
||||
|
||||
const keySystemItemsKey = context.keys.getPrimaryKeySystemItemsKey(vault.systemIdentifier)
|
||||
expect(keySystemItemsKey).to.not.be.undefined
|
||||
expect(keySystemItemsKey.key_system_identifier).to.equal(vault.systemIdentifier)
|
||||
expect(keySystemItemsKey.creationTimestamp).to.not.be.undefined
|
||||
expect(keySystemItemsKey.keyVersion).to.not.be.undefined
|
||||
})
|
||||
|
||||
it('should be able to create an offline vault with app passcode', async () => {
|
||||
await context.application.addPasscode('123')
|
||||
const vault = await vaults.createRandomizedVault({
|
||||
name: 'My Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
|
||||
expect(vault.systemIdentifier).to.not.be.undefined
|
||||
expect(typeof vault.systemIdentifier).to.equal('string')
|
||||
|
||||
const keySystemItemsKey = context.keys.getPrimaryKeySystemItemsKey(vault.systemIdentifier)
|
||||
expect(keySystemItemsKey).to.not.be.undefined
|
||||
expect(keySystemItemsKey.key_system_identifier).to.equal(vault.systemIdentifier)
|
||||
expect(keySystemItemsKey.creationTimestamp).to.not.be.undefined
|
||||
expect(keySystemItemsKey.keyVersion).to.not.be.undefined
|
||||
})
|
||||
|
||||
it('should add item to offline vault', async () => {
|
||||
const vault = await vaults.createRandomizedVault({
|
||||
name: 'My Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
const item = await context.createSyncedNote()
|
||||
|
||||
await vaults.moveItemToVault(vault, item)
|
||||
|
||||
const updatedItem = context.items.findItem(item.uuid)
|
||||
expect(updatedItem.key_system_identifier).to.equal(vault.systemIdentifier)
|
||||
})
|
||||
|
||||
it('should load data in the correct order at startup to allow vault items and their keys to decrypt', async () => {
|
||||
const appIdentifier = context.identifier
|
||||
const vault = await vaults.createRandomizedVault({
|
||||
name: 'My Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
await vaults.moveItemToVault(vault, note)
|
||||
await context.deinit()
|
||||
|
||||
const recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
|
||||
await recreatedContext.launch()
|
||||
|
||||
const updatedNote = recreatedContext.items.findItem(note.uuid)
|
||||
expect(updatedNote.title).to.equal('foo')
|
||||
expect(updatedNote.text).to.equal('bar')
|
||||
|
||||
await recreatedContext.deinit()
|
||||
})
|
||||
|
||||
describe('porting from offline to online', () => {
|
||||
it('should maintain vault system identifiers across items after registration', async () => {
|
||||
const appIdentifier = context.identifier
|
||||
const vault = await vaults.createRandomizedVault({
|
||||
name: 'My Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
await vaults.moveItemToVault(vault, note)
|
||||
|
||||
await context.register()
|
||||
await context.sync()
|
||||
|
||||
await context.deinit()
|
||||
|
||||
const recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
|
||||
await recreatedContext.launch()
|
||||
|
||||
const notes = recreatedContext.notes
|
||||
expect(notes.length).to.equal(1)
|
||||
|
||||
const updatedNote = recreatedContext.items.findItem(note.uuid)
|
||||
expect(updatedNote.title).to.equal('foo')
|
||||
expect(updatedNote.text).to.equal('bar')
|
||||
expect(updatedNote.key_system_identifier).to.equal(vault.systemIdentifier)
|
||||
|
||||
await recreatedContext.deinit()
|
||||
})
|
||||
|
||||
it('should decrypt vault items', async () => {
|
||||
const appIdentifier = context.identifier
|
||||
const vault = await vaults.createRandomizedVault({
|
||||
name: 'My Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
await vaults.moveItemToVault(vault, note)
|
||||
|
||||
await context.register()
|
||||
await context.sync()
|
||||
|
||||
await context.deinit()
|
||||
|
||||
const recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
|
||||
await recreatedContext.launch()
|
||||
|
||||
const updatedNote = recreatedContext.items.findItem(note.uuid)
|
||||
expect(updatedNote.title).to.equal('foo')
|
||||
expect(updatedNote.text).to.equal('bar')
|
||||
|
||||
await recreatedContext.deinit()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('online', () => {
|
||||
beforeEach(async () => {
|
||||
await context.register()
|
||||
})
|
||||
|
||||
it('should create a vault', async () => {
|
||||
const vault = await vaults.createRandomizedVault({
|
||||
name: 'My Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
expect(vault).to.not.be.undefined
|
||||
|
||||
const keySystemItemsKeys = context.keys.getKeySystemItemsKeys(vault.systemIdentifier)
|
||||
expect(keySystemItemsKeys.length).to.equal(1)
|
||||
|
||||
const keySystemItemsKey = keySystemItemsKeys[0]
|
||||
expect(keySystemItemsKey instanceof KeySystemItemsKey).to.be.true
|
||||
expect(keySystemItemsKey.key_system_identifier).to.equal(vault.systemIdentifier)
|
||||
})
|
||||
|
||||
it('should add item to vault', async () => {
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
const vault = await vaults.createRandomizedVault({
|
||||
name: 'My Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
|
||||
await vaults.moveItemToVault(vault, note)
|
||||
|
||||
const updatedNote = context.items.findItem(note.uuid)
|
||||
expect(updatedNote.key_system_identifier).to.equal(vault.systemIdentifier)
|
||||
})
|
||||
|
||||
describe('client timing', () => {
|
||||
it('should load data in the correct order at startup to allow vault items and their keys to decrypt', async () => {
|
||||
const appIdentifier = context.identifier
|
||||
const vault = await vaults.createRandomizedVault({
|
||||
name: 'My Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
await vaults.moveItemToVault(vault, note)
|
||||
await context.deinit()
|
||||
|
||||
const recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
|
||||
await recreatedContext.launch()
|
||||
|
||||
const updatedNote = recreatedContext.items.findItem(note.uuid)
|
||||
expect(updatedNote.title).to.equal('foo')
|
||||
expect(updatedNote.text).to.equal('bar')
|
||||
|
||||
await recreatedContext.deinit()
|
||||
})
|
||||
})
|
||||
|
||||
describe('key system root key rotation', () => {
|
||||
it('rotating a key system root key should create a new vault items key', async () => {
|
||||
const vault = await vaults.createRandomizedVault({
|
||||
name: 'My Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
|
||||
const keySystemItemsKey = context.keys.getKeySystemItemsKeys(vault.systemIdentifier)[0]
|
||||
|
||||
await vaults.rotateVaultRootKey(vault)
|
||||
|
||||
const updatedKeySystemItemsKey = context.keys.getKeySystemItemsKeys(vault.systemIdentifier)[0]
|
||||
|
||||
expect(updatedKeySystemItemsKey).to.not.be.undefined
|
||||
expect(updatedKeySystemItemsKey.uuid).to.not.equal(keySystemItemsKey.uuid)
|
||||
})
|
||||
|
||||
it('deleting a vault should delete all its items', async () => {
|
||||
const vault = await vaults.createRandomizedVault({
|
||||
name: 'My Vault',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Synced,
|
||||
})
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
await vaults.moveItemToVault(vault, note)
|
||||
|
||||
await vaults.deleteVault(vault)
|
||||
|
||||
const updatedNote = context.items.findItem(note.uuid)
|
||||
expect(updatedNote).to.be.undefined
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user