chore: vault tests refactors and lint (#2374)

This commit is contained in:
Karol Sójko
2023-08-02 00:23:56 +02:00
committed by GitHub
parent a0bc1d2488
commit 247daddf5a
96 changed files with 1463 additions and 751 deletions

View File

@@ -112,7 +112,7 @@ describe('application instances', () => {
await app.lock()
})
describe.skip('signOut()', () => {
describe('signOut()', () => {
let testNote1
let confirmAlert
let deinit
@@ -129,7 +129,7 @@ describe('application instances', () => {
beforeEach(async () => {
testSNApp = await Factory.createAndInitializeApplication('test-application')
testNote1 = await Factory.createMappedNote(testSNApp, 'Note 1', 'This is a test note!', false)
confirmAlert = sinon.spy(testSNApp.alertService, 'confirm')
confirmAlert = sinon.spy(testSNApp.alerts, 'confirm')
deinit = sinon.spy(testSNApp, 'deinit')
})
@@ -164,7 +164,7 @@ describe('application instances', () => {
it('cancels sign out if confirmation dialog is rejected', async () => {
confirmAlert.restore()
confirmAlert = sinon.stub(testSNApp.alertService, 'confirm').callsFake((_message) => false)
confirmAlert = sinon.stub(testSNApp.alerts, 'confirm').callsFake((_message) => false)
await testSNApp.mutator.setItemDirty(testNote1)
await testSNApp.user.signOut()

View File

@@ -207,7 +207,6 @@ describe('basic auth', function () {
await specContext.launch()
await specContext.register()
await specContext.signout()
await specContext.deinit()
specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), uppercase, password)
@@ -217,6 +216,7 @@ describe('basic auth', function () {
expect(response).to.be.ok
expect(response.data.error).to.not.be.ok
expect(await specContext.application.encryption.getRootKey()).to.be.ok
await specContext.deinit()
}).timeout(20000)
it('can sign into account regardless of whitespace', async function () {
@@ -232,7 +232,6 @@ describe('basic auth', function () {
await specContext.launch()
await specContext.register()
await specContext.signout()
await specContext.deinit()
specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), withspace, password)
await specContext.launch()
@@ -241,6 +240,7 @@ describe('basic auth', function () {
expect(response).to.be.ok
expect(response.data.error).to.not.be.ok
expect(await specContext.application.encryption.getRootKey()).to.be.ok
await specContext.deinit()
}).timeout(20000)
it('fails login with wrong password', async function () {
@@ -367,7 +367,7 @@ describe('basic auth', function () {
it('successfully changes password', changePassword).timeout(40000)
it.skip('successfully changes password when passcode is set', async function () {
it('successfully changes password when passcode is set', async function () {
const passcode = 'passcode'
const promptValueReply = (prompts) => {
const values = []
@@ -393,7 +393,7 @@ describe('basic auth', function () {
context.application.submitValuesForChallenge(challenge, initialValues)
},
})
await context.application.setPasscode(passcode)
await context.application.addPasscode(passcode)
await changePassword.bind(this)()
}).timeout(20000)
@@ -550,6 +550,8 @@ describe('basic auth', function () {
expect(response.status).to.equal(401)
expect(response.data.error.message).to.equal('Operation not allowed.')
await secondContext.deinit()
})
})
})

View File

@@ -81,7 +81,6 @@ describe('backups', function () {
})
it('passcode + account backup file should have correct number of items', async function () {
this.timeout(10000)
const passcode = 'passcode'
await this.application.register(this.email, this.password)
Factory.handlePasswordChallenges(this.application, this.password)
@@ -101,7 +100,7 @@ describe('backups', function () {
// Encrypted backup with authorization
const authorizedEncryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2)
})
}).timeout(10000)
it('backup file item should have correct fields', async function () {
await Factory.createSyncedNote(this.application)

View File

@@ -133,11 +133,12 @@ describe('features', () => {
await promise
})
it.skip('migrated ext repo should have property indicating it was migrated', async () => {
it('migrated ext repo should have property indicating it was migrated', async () => {
sinon.stub(application.legacyApi, 'isThirdPartyHostUsed').callsFake(() => {
return false
})
expect(await application.settings.getDoesSensitiveSettingExist(SettingName.ExtensionKey)).to.equal(false)
const setting = SettingName.create(SettingName.NAMES.ExtensionKey).getValue()
expect(await application.settings.getDoesSensitiveSettingExist(setting)).to.equal(false)
const extensionKey = UuidGenerator.GenerateUuid().split('-').join('')
const promise = new Promise((resolve) => {
application.streamItems(ContentType.TYPES.ExtensionRepo, ({ changed }) => {

View File

@@ -404,7 +404,7 @@ describe('history manager', () => {
expect(noteHistory.length).to.equal(expectedRevisions)
expect(dupeHistory.length).to.equal(expectedRevisions + 1)
})
}).timeout(Factory.ThirtySecondTimeout)
it('can decrypt revisions for duplicate_of items', async function () {
const note = await Factory.createSyncedNote(this.application)

View File

@@ -449,6 +449,7 @@ describe('keys', function () {
},
})
expect(payload.items_key_id).to.equal(newDefaultItemsKey.uuid)
await Factory.safeDeinit(application)
})
it('compares root keys', async function () {
@@ -642,7 +643,7 @@ describe('keys', function () {
await contextB.deinit()
})
describe('changing password on 003 client while signed into 004 client', function () {
describe('changing password on 003 account 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
@@ -859,5 +860,6 @@ describe('keys', function () {
const notePayload = rawPayloads.find((p) => p.content_type === ContentType.TYPES.Note)
expect(notePayload.items_key_id).to.equal(itemsKey.uuid)
await otherClient.deinit()
})
})

View File

@@ -47,6 +47,30 @@ export class AppContext {
this.host,
this.crypto || new FakeWebCrypto(),
)
this.application.dependencies.get(TYPES.Logger).setLevel('error')
this.disableSubscriptionFetching()
}
disableSubscriptionFetching() {
this.application.subscriptions.fetchAvailableSubscriptions = () => {}
}
async launch({ awaitDatabaseLoad = true, receiveChallenge } = { awaitDatabaseLoad: true }) {
await this.application.prepareForLaunch({
receiveChallenge: receiveChallenge || this.handleChallenge,
})
await this.application.launch(awaitDatabaseLoad)
await this.awaitUserPrefsSingletonCreation()
this.application.http.loggingEnabled = true
}
async deinit() {
await Utils.safeDeinit(this.application)
}
get sessions() {
@@ -253,10 +277,10 @@ export class AppContext {
awaitNextSucessfulSync() {
return new Promise((resolve) => {
const removeObserver = this.application.sync.addEventObserver((event) => {
const removeObserver = this.application.sync.addEventObserver((event, data) => {
if (event === SyncEvent.SyncCompletedWithAllItemsUploadedAndDownloaded) {
removeObserver()
resolve()
resolve(data)
}
})
})
@@ -294,6 +318,16 @@ export class AppContext {
})
}
resolveWithSyncRetrievedPayloads() {
return new Promise((resolve) => {
this.application.sync.addEventObserver((event, data) => {
if (event === SyncEvent.PaginatedSyncRequestCompleted) {
resolve(data.retrievedPayloads)
}
})
})
}
resolveWithConflicts() {
return new Promise((resolve) => {
this.application.sync.addEventObserver((event, response) => {
@@ -359,10 +393,16 @@ export class AppContext {
}
resolveWhenAsyncFunctionCompletes(object, functionName) {
if (!object[functionName]) {
throw new Error(`Object does not have function ${functionName}`)
}
const originalFunction = object[functionName].bind(object)
return new Promise((resolve) => {
sinon.stub(object, functionName).callsFake(async (params) => {
object[functionName].restore()
const result = await object[functionName](params)
const result = await originalFunction(params)
resolve()
return result
})
@@ -370,23 +410,26 @@ export class AppContext {
}
spyOnFunctionResult(object, functionName) {
return new Promise((resolve) => {
sinon.stub(object, functionName).callsFake(async (params) => {
object[functionName].restore()
const result = await object[functionName](params)
resolve(result)
return result
})
const originalFunction = object[functionName].bind(object)
return new Promise((resolve, reject) => {
try {
sinon.stub(object, functionName).callsFake(async (params) => {
const result = await originalFunction(params)
object[functionName].restore()
setTimeout(() => {
resolve(result)
}, 0)
return result
})
} catch (err) {
reject(err)
}
})
}
resolveWhenAsymmetricMessageProcessingCompletes() {
return this.resolveWhenAsyncFunctionCompletes(this.asymmetric, 'handleRemoteReceivedAsymmetricMessages')
}
resolveWhenUserMessagesProcessingCompletes() {
const objectToSpy = this.application.dependencies.get(TYPES.UserEventService)
return this.resolveWhenAsyncFunctionCompletes(objectToSpy, 'handleReceivedUserEvents')
const objectToSpy = this.application.dependencies.get(TYPES.NotificationService)
return this.resolveWhenAsyncFunctionCompletes(objectToSpy, 'handleReceivedNotifications')
}
resolveWhenAllInboundAsymmetricMessagesAreDeleted() {
@@ -466,18 +509,6 @@ export class AppContext {
})
}
async launch({ awaitDatabaseLoad = true, receiveChallenge } = { awaitDatabaseLoad: true }) {
await this.application.prepareForLaunch({
receiveChallenge: receiveChallenge || this.handleChallenge,
})
await this.application.launch(awaitDatabaseLoad)
await this.awaitUserPrefsSingletonCreation()
}
async deinit() {
await Utils.safeDeinit(this.application)
}
async sync(options) {
await this.application.sync.sync(options || { awaitAll: true })
}
@@ -628,6 +659,10 @@ export class AppContext {
console.warn('Anticipating a console error with message:', message)
}
awaitPromiseOrThrow(promise, maxWait = 2.0, reason = 'Awaiting promise timed out; No description provided') {
return Utils.awaitPromiseOrThrow(promise, maxWait, reason)
}
async activatePaidSubscriptionForUser(options = {}) {
const dateInAnHour = new Date()
dateInAnHour.setHours(dateInAnHour.getHours() + 1)
@@ -655,17 +690,17 @@ export class AppContext {
await Utils.sleep(2)
} catch (error) {
console.warn(
`Mock events service not available. You are probalby running a test suite for home server: ${error.message}`,
`Mock events service not available. You are probably running a test suite for home server: ${error.message}`,
)
}
try {
await HomeServer.activatePremiumFeatures(this.email, options.subscriptionPlanName, options.expiresAt)
await Utils.sleep(1)
await Utils.sleep(1, 'Waiting for premium features to be activated')
} catch (error) {
console.warn(
`Home server not available. You are probalby running a test suite for self hosted setup: ${error.message}`,
`Home server not available. You are probably running a test suite for self hosted setup: ${error.message}`,
)
}
}

View File

@@ -1,7 +1,8 @@
import * as Factory from './factory.js'
import * as Utils from './Utils.js'
export const createContactContext = async () => {
const contactContext = await Factory.createAppContextWithRealCrypto()
const contactContext = await Factory.createVaultsContextWithRealCrypto()
await contactContext.launch()
await contactContext.register()
@@ -15,6 +16,8 @@ export const createTrustedContactForUserOfContext = async (
contextAddingNewContact,
contextImportingContactInfoFrom,
) => {
const syncPromisme = contextAddingNewContact.awaitNextSucessfulSync()
const contact = await contextAddingNewContact.contacts.createOrEditTrustedContact({
name: 'John Doe',
publicKey: contextImportingContactInfoFrom.publicKey,
@@ -22,6 +25,8 @@ export const createTrustedContactForUserOfContext = async (
contactUuid: contextImportingContactInfoFrom.userUuid,
})
await syncPromisme
return contact
}
@@ -36,7 +41,35 @@ export const acceptAllInvites = async (context) => {
}
}
export const createSharedVaultWithAcceptedInvite = async (context, permission = SharedVaultUserPermission.PERMISSIONS.Write) => {
const inviteContext = async (context, contactContext, sharedVault, contact, permission) => {
contactContext.lockSyncing()
const inviteOrError = await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, permission)
if (inviteOrError.isFailed()) {
throw new Error(inviteOrError.getError())
}
const invite = inviteOrError.getValue()
const promise = contactContext.resolveWhenAsyncFunctionCompletes(contactContext.vaultInvites, 'processInboundInvites')
contactContext.unlockSyncing()
await contactContext.sync()
await Utils.awaitPromiseOrThrow(promise, 2.0, '[inviteContext] processInboundInvites was not called in time')
const inviteRecords = contactContext.vaultInvites.getCachedPendingInviteRecords()
if (inviteRecords.length === 0) {
throw new Error('Invite was not properly received')
}
return invite
}
export const createSharedVaultWithAcceptedInvite = async (
context,
permission = SharedVaultUserPermission.PERMISSIONS.Write,
) => {
const { sharedVault, contact, contactContext, deinitContactContext } =
await createSharedVaultWithUnacceptedButTrustedInvite(context, permission)
@@ -44,7 +77,7 @@ export const createSharedVaultWithAcceptedInvite = async (context, permission =
await acceptAllInvites(contactContext)
await promise
await Utils.awaitPromiseOrThrow(promise, 2.0, 'Waiting for vault to sync')
const contactVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
@@ -61,7 +94,10 @@ export const createSharedVaultWithAcceptedInviteAndNote = async (
)
const note = await context.createSyncedNote('foo', 'bar')
const updatedNote = await moveItemToVault(context, sharedVault, note)
const promise = contactContext.awaitNextSucessfulSync()
await contactContext.sync()
await Utils.awaitPromiseOrThrow(promise, 2.0, 'Waiting for contactContext to sync added note')
return { sharedVault, note: updatedNote, contact, contactContext, deinitContactContext }
}
@@ -76,34 +112,20 @@ export const createSharedVaultWithUnacceptedButTrustedInvite = async (
const contact = await createTrustedContactForUserOfContext(context, contactContext)
await createTrustedContactForUserOfContext(contactContext, context)
const inviteOrError = await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, permission)
if (inviteOrError.isFailed()) {
throw new Error(inviteOrError.getError())
}
const invite = inviteOrError.getValue()
await contactContext.sync()
const invite = await inviteContext(context, contactContext, sharedVault, contact, permission)
return { sharedVault, contact, contactContext, deinitContactContext, invite }
}
export const createSharedVaultAndInviteContact = async (
createInContext,
inviteContext,
inviteContact,
context,
contactContext,
contact,
permission = SharedVaultUserPermission.PERMISSIONS.Write,
) => {
const sharedVault = await createSharedVault(createInContext)
const sharedVault = await createSharedVault(context)
await createInContext.vaultInvites.inviteContactToSharedVault(sharedVault, inviteContact, permission)
const promise = inviteContext.awaitNextSyncSharedVaultFromScratchEvent()
await inviteContext.sync()
await acceptAllInvites(inviteContext)
await promise
await inviteContext(context, contactContext, sharedVault, contact, permission)
return { sharedVault }
}
@@ -118,20 +140,33 @@ export const createSharedVaultWithUnacceptedAndUntrustedInvite = async (
const contact = await createTrustedContactForUserOfContext(context, contactContext)
const invite = (await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, permission)).getValue()
const promise = contactContext.resolveWhenAsyncFunctionCompletes(contactContext.vaultInvites, 'processInboundInvites')
await contactContext.sync()
await Utils.awaitPromiseOrThrow(
promise,
2.0,
'[createSharedVaultWithUnacceptedAndUntrustedInvite] Waiting to process invites',
)
return { sharedVault, contact, contactContext, deinitContactContext, invite }
}
export const inviteNewPartyToSharedVault = async (context, sharedVault, permission = SharedVaultUserPermission.PERMISSIONS.Write) => {
export const inviteNewPartyToSharedVault = async (
context,
sharedVault,
permission = SharedVaultUserPermission.PERMISSIONS.Write,
) => {
const { contactContext: thirdPartyContext, deinitContactContext: deinitThirdPartyContext } =
await createContactContext()
const thirdPartyContact = await createTrustedContactForUserOfContext(context, thirdPartyContext)
await createTrustedContactForUserOfContext(thirdPartyContext, context)
await context.vaultInvites.inviteContactToSharedVault(sharedVault, thirdPartyContact, permission)
await thirdPartyContext.sync()
await createTrustedContactForUserOfContext(thirdPartyContext, context)
await inviteContext(context, thirdPartyContext, sharedVault, thirdPartyContact, permission)
return { thirdPartyContext, thirdPartyContact, deinitThirdPartyContext }
}

View File

@@ -11,15 +11,15 @@ export async function safeDeinit(application) {
await application.storage.awaitPersist()
/** Limit waiting to 1s */
await Promise.race([sleep(1), application.sync?.awaitCurrentSyncs()])
await Promise.race([sleep(1, 'Deinit'), application.sync?.awaitCurrentSyncs()])
await application.prepareForDeinit()
application.deinit(DeinitMode.Soft, DeinitSource.SignOut)
}
export async function sleep(seconds) {
console.warn(`Test sleeping for ${seconds}s`)
export async function sleep(seconds, reason) {
console.warn(`Test sleeping for ${seconds}s. Reason: ${reason}`)
return new Promise((resolve, reject) => {
setTimeout(function () {
@@ -32,3 +32,22 @@ export function generateUuid() {
const crypto = new FakeWebCrypto()
return crypto.generateUUID()
}
export async function awaitPromiseOrThrow(promise, maxWait, reason) {
let timer = undefined
// Create a promise that rejects in <maxWait> milliseconds
const timeout = new Promise((resolve, reject) => {
timer = setTimeout(() => {
clearTimeout(timer)
console.error(reason)
reject(new Error(reason || `Promise timed out after ${maxWait} milliseconds: ${reason}`))
}, maxWait * 1000)
})
// Returns a race between our timeout and the passed in promise
return Promise.race([promise, timeout]).then((result) => {
clearTimeout(timer)
return result
})
}

View File

@@ -0,0 +1,54 @@
import { AppContext } from './AppContext.js'
export class VaultsContext extends AppContext {
constructor(params) {
super(params)
}
async changeVaultName(vault, nameAndDesc) {
const sendDataChangePromise = this.resolveWhenAsyncFunctionCompletes(
this.sharedVaults._sendVaultDataChangeMessage,
'execute',
)
await this.vaults.changeVaultNameAndDescription(vault, {
name: nameAndDesc.name,
description: nameAndDesc.description,
})
await this.awaitPromiseOrThrow(sendDataChangePromise, undefined, 'Waiting for vault data change message to process')
}
async changePassword(password) {
const promise = this.resolveWhenAsyncFunctionCompletes(this.sharedVaults._handleKeyPairChange, 'execute')
await super.changePassword(password)
await this.awaitPromiseOrThrow(promise, undefined, 'Waiting for keypair change message to process')
}
async syncAndAwaitMessageProcessing() {
const promise = this.resolveWhenAsyncFunctionCompletes(this.asymmetric, 'handleRemoteReceivedAsymmetricMessages')
await this.sync()
await this.awaitPromiseOrThrow(promise, undefined, 'Waiting for messages to process')
}
async syncAndAwaitInviteProcessing() {
const promise = this.resolveWhenAsyncFunctionCompletes(this.vaultInvites, 'processInboundInvites')
await this.sync()
await this.awaitPromiseOrThrow(promise, undefined, 'Waiting for invites to process')
}
/**
* Run a request to keep refresh token from expiring due to long bouts of inactivity for contact context
* while main context changes password. Tests have a refresh token age of 10s typically, and changing password
* on CI environment may be time consuming.
*/
async runAnyRequestToPreventRefreshTokenFromExpiring() {
await this.asymmetric.getInboundMessages()
}
}

View File

@@ -2,6 +2,7 @@
/* eslint-disable no-undef */
import FakeWebCrypto from './fake_web_crypto.js'
import { AppContext } from './AppContext.js'
import { VaultsContext } from './VaultsContext.js'
import * as Applications from './Applications.js'
import * as Defaults from './Defaults.js'
import * as Utils from './Utils.js'
@@ -57,6 +58,16 @@ export async function createAppContext({ identifier, crypto, email, password, ho
return context
}
export async function createVaultsContextWithRealCrypto(identifier) {
return createVaultsContext({ identifier, crypto: new SNWebCrypto() })
}
export async function createVaultsContext({ identifier, crypto, email, password, host } = {}) {
const context = new VaultsContext({ identifier, crypto, email, password, host })
await context.initialize()
return context
}
export function disableIntegrityAutoHeal(application) {
application.sync.emitOutOfSyncRemotePayloads = () => {
console.warn('Integrity self-healing is disabled for this test')
@@ -288,7 +299,7 @@ export function tomorrow() {
}
export async function sleep(seconds, reason) {
console.log('Sleeping for reason', reason)
console.log('[Factory] Sleeping for reason', reason)
return Utils.sleep(seconds)
}

View File

@@ -843,8 +843,6 @@ describe('importing', function () {
})
it('importing another accounts notes/tags should correctly keep relationships', async function () {
this.timeout(Factory.TwentySecondTimeout)
await setup({ fakeCrypto: true })
await Factory.registerUserToApplication({
@@ -881,5 +879,5 @@ describe('importing', function () {
const importedTag = application.items.getDisplayableTags()[0]
expect(application.items.referencesForItem(importedTag).length).to.equal(1)
expect(application.items.itemsReferencingItem(importedNote).length).to.equal(1)
})
}).timeout(Factory.TwentySecondTimeout)
})

View File

@@ -4,7 +4,9 @@ const expect = chai.expect
describe('note display criteria', function () {
beforeEach(async function () {
this.payloadManager = new PayloadManager()
const logger = new Logger('test')
this.payloadManager = new PayloadManager(logger)
this.itemManager = new ItemManager(this.payloadManager)
this.mutator = new MutatorService(this.itemManager, this.payloadManager)
@@ -616,7 +618,10 @@ describe('note display criteria', function () {
describe.skip('multiple tags', function () {
it('normal note', async function () {
await this.createNote()
/**
* This test presently fails because the compound predicate created
* when using multiple views is an AND predicate instead of OR
*/
expect(
notesAndFilesMatchingOptions(
{

View File

@@ -6,7 +6,8 @@ const expect = chai.expect
describe('payload manager', () => {
beforeEach(async function () {
this.payloadManager = new PayloadManager()
const logger = new Logger('test')
this.payloadManager = new PayloadManager(logger)
this.createNotePayload = async () => {
return new DecryptedPayload({
uuid: Factory.generateUuidish(),

View File

@@ -58,16 +58,15 @@ describe('preferences', function () {
})
it('emits an event when preferences change', async function () {
let callTimes = 0
this.application.addEventObserver(() => {
callTimes++
}, ApplicationEvent.PreferencesChanged)
callTimes += 1
await Factory.sleep(0) /** Await next tick */
expect(callTimes).to.equal(1) /** App start */
await register.call(this)
const promise = new Promise((resolve) => {
this.application.addEventObserver(() => {
resolve()
}, ApplicationEvent.PreferencesChanged)
})
await this.application.setPreference('editorLeft', 300)
expect(callTimes).to.equal(2)
await promise
expect(promise).to.be.fulfilled
})
it('discards existing preferences when signing in', async function () {

View File

@@ -46,8 +46,6 @@ describe('server session', function () {
}
it('should succeed when a sync request is perfomed with an expired access token', async function () {
this.timeout(Factory.TwentySecondTimeout)
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
@@ -59,7 +57,7 @@ describe('server session', function () {
const response = await this.application.legacyApi.sync([])
expect(response.status).to.equal(200)
})
}).timeout(Factory.TwentySecondTimeout)
it('should return the new session in the response when refreshed', async function () {
await Factory.registerUserToApplication({
@@ -78,8 +76,6 @@ describe('server session', function () {
})
it('should be refreshed on any api call if access token is expired', async function () {
this.timeout(Factory.TwentySecondTimeout)
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
@@ -103,11 +99,9 @@ describe('server session', function () {
expect(sessionBeforeSync.accessToken.expiresAt).to.be.lessThan(sessionAfterSync.accessToken.expiresAt)
// New token should expire in the future.
expect(sessionAfterSync.accessToken.expiresAt).to.be.greaterThan(Date.now())
})
}).timeout(Factory.TwentySecondTimeout)
it('should not deadlock while renewing session', async function () {
this.timeout(Factory.TwentySecondTimeout)
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
@@ -125,7 +119,7 @@ describe('server session', function () {
const sessionAfterSync = this.application.legacyApi.getSession()
expect(sessionAfterSync.accessToken.expiresAt).to.be.greaterThan(Date.now())
})
}).timeout(Factory.TwentySecondTimeout)
it('should succeed when a sync request is perfomed after signing into an ephemeral session', async function () {
await Factory.registerUserToApplication({
@@ -199,8 +193,6 @@ describe('server session', function () {
})
it('sign out request should be performed successfully and terminate session with expired access token', async function () {
this.timeout(Factory.TwentySecondTimeout)
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
@@ -218,11 +210,9 @@ describe('server session', function () {
expect(syncResponse.status).to.equal(401)
expect(syncResponse.data.error.tag).to.equal('invalid-auth')
expect(syncResponse.data.error.message).to.equal('Invalid login credentials.')
})
}).timeout(Factory.TwentySecondTimeout)
it('change email request should be successful with a valid access token', async function () {
this.timeout(Factory.TwentySecondTimeout)
let { application, password } = await Factory.createAndInitSimpleAppContext({
registerUser: true,
})
@@ -241,11 +231,9 @@ describe('server session', function () {
expect(loginResponse).to.be.ok
expect(loginResponse.status).to.equal(200)
await Factory.safeDeinit(application)
})
}).timeout(Factory.TwentySecondTimeout)
it('change email request should fail with an invalid access token', async function () {
this.timeout(Factory.TwentySecondTimeout)
let { application, password } = await Factory.createAndInitSimpleAppContext({
registerUser: true,
})
@@ -266,14 +254,13 @@ describe('server session', function () {
expect(changeEmailResponse.error.message).to.equal('Invalid login credentials.')
await Factory.safeDeinit(application)
})
}).timeout(Factory.TwentySecondTimeout)
it('change email request should fail with an expired refresh token', async function () {
this.timeout(Factory.ThirtySecondTimeout)
let { application, email, password } = await Factory.createAndInitSimpleAppContext({
let { application, password } = await Factory.createAndInitSimpleAppContext({
registerUser: true,
})
application.sync.lockSyncing()
/** Waiting for the refresh token to expire. */
await sleepUntilSessionExpires(application, false)
@@ -285,11 +272,9 @@ describe('server session', function () {
expect(changeEmailResponse.error.message).to.equal('Invalid login credentials.')
await Factory.safeDeinit(application)
})
}).timeout(Factory.ThirtySecondTimeout)
it('change password request should be successful with a valid access token', async function () {
this.timeout(Factory.TwentySecondTimeout)
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
@@ -309,11 +294,9 @@ describe('server session', function () {
expect(loginResponse).to.be.ok
expect(loginResponse.status).to.be.equal(200)
})
}).timeout(Factory.TwentySecondTimeout)
it('change password request should be successful after the expired access token is refreshed', async function () {
this.timeout(Factory.TwentySecondTimeout)
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
@@ -336,7 +319,7 @@ describe('server session', function () {
expect(loginResponse).to.be.ok
expect(loginResponse.status).to.be.equal(200)
})
}).timeout(Factory.TwentySecondTimeout)
it('change password request should fail with an invalid access token', async function () {
await Factory.registerUserToApplication({
@@ -360,14 +343,14 @@ describe('server session', function () {
})
it('change password request should fail with an expired refresh token', async function () {
this.timeout(Factory.TwentySecondTimeout)
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
password: this.password,
})
this.application.sync.lockSyncing()
/** Waiting for the refresh token to expire. */
await sleepUntilSessionExpires(this.application, false)
@@ -399,14 +382,14 @@ describe('server session', function () {
})
it('should fail when renewing a session with an expired refresh token', async function () {
this.timeout(Factory.TwentySecondTimeout)
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
password: this.password,
})
this.application.sync.lockSyncing()
await sleepUntilSessionExpires(this.application, false)
const refreshSessionResponse = await this.application.legacyApi.refreshSession()
@@ -424,7 +407,7 @@ describe('server session', function () {
expect(syncResponse.status).to.equal(401)
expect(syncResponse.data.error.tag).to.equal('invalid-auth')
expect(syncResponse.data.error.message).to.equal('Invalid login credentials.')
})
}).timeout(Factory.TwentySecondTimeout)
it('should fail when renewing a session with an invalid refresh token', async function () {
await Factory.registerUserToApplication({
@@ -474,8 +457,6 @@ describe('server session', function () {
})
it('notes should be synced as expected after refreshing a session', async function () {
this.timeout(Factory.TwentySecondTimeout)
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
@@ -500,7 +481,7 @@ describe('server session', function () {
const noteResult = await this.application.items.findItem(aNoteBeforeSync.uuid)
expect(aNoteBeforeSync.isItemContentEqualWith(noteResult)).to.equal(true)
}
})
}).timeout(Factory.TwentySecondTimeout)
it('changing password on one client should not invalidate other sessions', async function () {
await Factory.registerUserToApplication({
@@ -635,8 +616,6 @@ describe('server session', function () {
})
it('revoking a session should destroy local data', async function () {
this.timeout(Factory.TwentySecondTimeout)
Factory.handlePasswordChallenges(this.application, this.password)
await Factory.registerUserToApplication({
application: this.application,
@@ -662,11 +641,9 @@ describe('server session', function () {
const deviceInterface = new WebDeviceInterface()
const payloads = await deviceInterface.getAllDatabaseEntries(app2identifier)
expect(payloads).to.be.empty
})
}).timeout(Factory.TwentySecondTimeout)
it('revoking other sessions should destroy their local data', async function () {
this.timeout(Factory.TwentySecondTimeout)
Factory.handlePasswordChallenges(this.application, this.password)
await Factory.registerUserToApplication({
application: this.application,
@@ -690,7 +667,7 @@ describe('server session', function () {
const deviceInterface = new WebDeviceInterface()
const payloads = await deviceInterface.getAllDatabaseEntries(app2identifier)
expect(payloads).to.be.empty
})
}).timeout(Factory.TwentySecondTimeout)
it('signing out with invalid session token should still delete local data', async function () {
await Factory.registerUserToApplication({

View File

@@ -14,7 +14,6 @@ describe('settings service', function () {
let application
let context
let subscriptionId = 2001
beforeEach(async function () {
localStorage.clear()

View File

@@ -7,17 +7,19 @@ const expect = chai.expect
describe('storage manager', function () {
this.timeout(Factory.TenSecondTimeout)
/**
* Items are saved in localStorage in tests.
* Base keys are `storage`, `snjs_version`, and `keychain`
*/
const BASE_KEY_COUNT = 3
const BASE_KEY_COUNT = ['storage', 'snjs_version', 'keychain'].length
beforeEach(async function () {
localStorage.clear()
this.expectedKeyCount = BASE_KEY_COUNT
this.context = await Factory.createAppContext()
await this.context.launch()
this.application = this.context.application
this.email = UuidGenerator.GenerateUuid()
this.password = UuidGenerator.GenerateUuid()
@@ -62,18 +64,19 @@ describe('storage manager', function () {
expect(keychainValue.serverPassword).to.not.be.ok
})
it.skip('regular session should persist data', async function () {
it('regular session should persist data', async function () {
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
password: this.password,
ephemeral: false,
})
const key = 'foo'
const value = 'bar'
await this.application.storage.setValue(key, value)
/** Items are stored in local storage */
expect(Object.keys(localStorage).length).to.equal(this.expectedKeyCount + BaseItemCounts.DefaultItems)
expect(Object.keys(localStorage).length).to.equal(this.expectedKeyCount + BaseItemCounts.DefaultItemsWithAccount)
const retrievedValue = await this.application.storage.getValue(key)
expect(retrievedValue).to.equal(value)
})

View File

@@ -479,8 +479,7 @@ describe('online conflict handling', function () {
await this.sharedFinalAssertions()
})
/** Temporarily skipping due to long run time */
it.skip('handles stale data in bulk', async function () {
it('handles stale data in bulk', async function () {
/** This number must be greater than the pagination limit per sync request.
* For example if the limit per request is 150 items sent/received, this number should
* be something like 160. */
@@ -728,43 +727,39 @@ describe('online conflict handling', function () {
await this.sharedFinalAssertions()
})
/** Temporarily skipping due to long run time */
it.skip(
'registering for account with bulk offline data belonging to another account should be error-free',
async function () {
/**
* When performing a multi-page sync request where we are uploading data imported from a backup,
* if the first page of the sync request returns conflicted items keys, we rotate their UUID.
* The second page of sync waiting to be sent up is still encrypted with the old items key UUID.
* This causes a problem because when that second page is returned as conflicts, we will be looking
* for an items_key_id that no longer exists (has been rotated). Rather than modifying the entire
* sync paradigm to allow multi-page requests to consider side-effects of each page, we will instead
* take the approach of making sure the decryption function is liberal with regards to searching
* for the right items key. It will now consider (as a result of this test) an items key as being
* the correct key to decrypt an item if the itemskey.uuid == item.items_key_id OR if the itemsKey.duplicateOf
* value is equal to item.items_key_id.
*/
it('registering for account with bulk offline data belonging to another account should be error-free', async function () {
/**
* When performing a multi-page sync request where we are uploading data imported from a backup,
* if the first page of the sync request returns conflicted items keys, we rotate their UUID.
* The second page of sync waiting to be sent up is still encrypted with the old items key UUID.
* This causes a problem because when that second page is returned as conflicts, we will be looking
* for an items_key_id that no longer exists (has been rotated). Rather than modifying the entire
* sync paradigm to allow multi-page requests to consider side-effects of each page, we will instead
* take the approach of making sure the decryption function is liberal with regards to searching
* for the right items key. It will now consider (as a result of this test) an items key as being
* the correct key to decrypt an item if the itemskey.uuid == item.items_key_id OR if the itemsKey.duplicateOf
* value is equal to item.items_key_id.
*/
/** Create bulk data belonging to another account and sync */
const largeItemCount = SyncUpDownLimit + 10
await Factory.createManyMappedNotes(this.application, largeItemCount)
await this.application.sync.sync(syncOptions)
const priorData = this.application.items.items
/** Create bulk data belonging to another account and sync */
const largeItemCount = SyncUpDownLimit + 10
await Factory.createManyMappedNotes(this.application, largeItemCount)
await this.application.sync.sync(syncOptions)
const priorData = this.application.items.items
/** Register new account and import this same data */
const newApp = await Factory.signOutApplicationAndReturnNew(this.application)
await Factory.registerUserToApplication({
application: newApp,
email: Utils.generateUuid(),
password: Utils.generateUuid(),
})
await newApp.mutator.emitItemsFromPayloads(priorData.map((i) => i.payload))
await newApp.sync.markAllItemsAsNeedingSyncAndPersist()
await newApp.sync.sync(syncOptions)
expect(newApp.payloads.invalidPayloads.length).to.equal(0)
await Factory.safeDeinit(newApp)
},
).timeout(80000)
/** Register new account and import this same data */
const newApp = await Factory.signOutApplicationAndReturnNew(this.application)
await Factory.registerUserToApplication({
application: newApp,
email: Utils.generateUuid(),
password: Utils.generateUuid(),
})
await newApp.mutator.emitItemsFromPayloads(priorData.map((i) => i.payload))
await newApp.sync.markAllItemsAsNeedingSyncAndPersist()
await newApp.sync.sync(syncOptions)
expect(newApp.payloads.invalidPayloads.length).to.equal(0)
await Factory.safeDeinit(newApp)
}).timeout(80000)
it('importing data belonging to another account should not result in duplication', async function () {
/** Create primary account and export data */

View File

@@ -528,8 +528,7 @@ describe('online syncing', function () {
await this.application.sync.sync(syncOptions)
})
/** Temporarily skipping due to long run time */
it.skip('should handle uploading with sync pagination', async function () {
it('should handle uploading with sync pagination', async function () {
const largeItemCount = SyncUpDownLimit + 10
for (let i = 0; i < largeItemCount; i++) {
const note = await Factory.createMappedNote(this.application)
@@ -541,10 +540,9 @@ describe('online syncing', function () {
await this.application.sync.sync(syncOptions)
const rawPayloads = await this.application.storage.getAllRawPayloads()
expect(rawPayloads.length).to.equal(this.expectedItemCount)
}).timeout(15000)
}).timeout(Factory.TwentySecondTimeout)
/** Temporarily skipping due to long run time */
it.skip('should handle downloading with sync pagination', async function () {
it('should handle downloading with sync pagination', async function () {
const largeItemCount = SyncUpDownLimit + 10
for (let i = 0; i < largeItemCount; i++) {
const note = await Factory.createMappedNote(this.application)

View File

@@ -17,7 +17,7 @@ describe('asymmetric messages', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
@@ -29,7 +29,7 @@ describe('asymmetric messages', function () {
contactContext.lockSyncing()
await context.vaults.changeVaultNameAndDescription(sharedVault, {
await context.changeVaultName(sharedVault, {
name: 'new vault name',
description: 'new vault description',
})
@@ -38,11 +38,8 @@ describe('asymmetric messages', function () {
get: () => 'invalid user uuid',
})
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
contactContext.unlockSyncing()
await contactContext.sync()
await completedProcessingMessagesPromise
await contactContext.syncAndAwaitMessageProcessing()
const updatedVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
@@ -53,33 +50,22 @@ describe('asymmetric messages', function () {
})
it('should delete message after processing it', async () => {
const { contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(context)
const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
const eventData = {
current: {
encryption: context.encryption.getKeyPair(),
signing: context.encryption.getSigningKeyPair(),
},
previous: {
encryption: context.encryption.getKeyPair(),
signing: context.encryption.getSigningKeyPair(),
},
}
await context.contacts.sendOwnContactChangeEventToAllContacts(eventData)
await context.changeVaultName(sharedVault, {
name: 'New Name',
description: 'New Description',
})
const deleteFunction = sinon.spy(contactContext.asymmetric, 'deleteMessageAfterProcessing')
const promise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
await contactContext.sync()
await promise
await contactContext.syncAndAwaitMessageProcessing()
expect(deleteFunction.callCount).to.equal(1)
const messages = await contactContext.asymmetric.getInboundMessages()
expect(messages.length).to.equal(0)
expect(messages.getValue().length).to.equal(0)
await deinitContactContext()
})
@@ -106,10 +92,7 @@ describe('asymmetric messages', function () {
await sendContactSharePromise
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
await contactContext.sync()
await completedProcessingMessagesPromise
await contactContext.syncAndAwaitMessageProcessing()
const updatedContact = contactContext.contacts.findContact(thirdPartyContext.userUuid)
expect(updatedContact.name).to.equal('Changed 3rd Party Name')
@@ -122,7 +105,10 @@ describe('asymmetric messages', function () {
const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
const handleInitialContactShareMessage = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
const handleInitialContactShareMessage = contactContext.resolveWhenAsyncFunctionCompletes(
contactContext.asymmetric,
'handleRemoteReceivedAsymmetricMessages',
)
const { thirdPartyContext, deinitThirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
context,
@@ -162,6 +148,7 @@ describe('asymmetric messages', function () {
await Collaboration.acceptAllInvites(thirdPartyContext)
await contactContext.sync()
contactContext.lockSyncing()
const sendContactSharePromise = context.resolveWhenSharedVaultServiceSendsContactShareMessage()
@@ -179,6 +166,7 @@ describe('asymmetric messages', function () {
const thirdPartySpy = sinon.spy(thirdPartyContext.asymmetric, 'handleTrustedContactShareMessage')
await context.sync()
contactContext.unlockSyncing()
await contactContext.sync()
await thirdPartyContext.sync()
@@ -194,12 +182,26 @@ describe('asymmetric messages', function () {
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.resolveWhenAsyncFunctionCompletes(
context.sharedVaults._notifyVaultUsersOfKeyRotation,
'execute',
)
await context.vaults.rotateVaultRootKey(sharedVault)
await promise
const firstPartySpy = sinon.spy(context.asymmetric, 'handleTrustedSharedVaultRootKeyChangedMessage')
const secondPartySpy = sinon.spy(contactContext.asymmetric, 'handleTrustedSharedVaultRootKeyChangedMessage')
await context.sync()
contactContext.unlockSyncing()
await contactContext.sync()
expect(firstPartySpy.callCount).to.equal(0)
@@ -212,7 +214,7 @@ describe('asymmetric messages', function () {
const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
await context.vaults.changeVaultNameAndDescription(sharedVault, {
await context.changeVaultName(sharedVault, {
name: 'New Name',
description: 'New Description',
})
@@ -238,20 +240,13 @@ describe('asymmetric messages', function () {
contactContext.lockSyncing()
const sendPromise = context.resolveWhenAsyncFunctionCompletes(
context.contacts,
'sendOwnContactChangeEventToAllContacts',
)
await context.changePassword('new password')
await sendPromise
const firstPartySpy = sinon.spy(context.asymmetric, 'handleTrustedSenderKeypairChangedMessage')
const secondPartySpy = sinon.spy(contactContext.asymmetric, 'handleTrustedSenderKeypairChangedMessage')
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
contactContext.unlockSyncing()
await contactContext.sync()
await completedProcessingMessagesPromise
await contactContext.syncAndAwaitMessageProcessing()
expect(firstPartySpy.callCount).to.equal(0)
expect(secondPartySpy.callCount).to.equal(1)
@@ -269,14 +264,12 @@ describe('asymmetric messages', function () {
await context.changePassword('new password')
await context.vaults.changeVaultNameAndDescription(sharedVault, {
await context.changeVaultName(sharedVault, {
name: 'New Name',
description: 'New Description',
})
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
await contactContext.sync()
await completedProcessingMessagesPromise
await contactContext.syncAndAwaitMessageProcessing()
const updatedVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
expect(updatedVault.name).to.equal('New Name')
@@ -295,16 +288,14 @@ describe('asymmetric messages', function () {
await context.changePassword('new password')
await context.vaults.changeVaultNameAndDescription(sharedVault, {
await context.changeVaultName(sharedVault, {
name: 'New Name',
description: 'New Description',
})
context.lockSyncing()
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
await contactContext.sync()
await completedProcessingMessagesPromise
await contactContext.syncAndAwaitMessageProcessing()
/**
* There's really no good way to await the exact call since
@@ -313,12 +304,12 @@ describe('asymmetric messages', function () {
await context.sleep(0.25)
const messages = await context.asymmetric.getInboundMessages()
expect(messages.length).to.equal(0)
expect(messages.getValue().length).to.equal(0)
await deinitContactContext()
})
it.skip('should process sender keypair changed message', async () => {
it('should process sender keypair changed message', async () => {
const { contactContext, deinitContactContext } = await Collaboration.createContactContext()
await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
await Collaboration.createTrustedContactForUserOfContext(contactContext, context)
@@ -326,9 +317,7 @@ describe('asymmetric messages', function () {
await context.changePassword('new_password')
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
await contactContext.sync()
await completedProcessingMessagesPromise
await contactContext.syncAndAwaitMessageProcessing()
const updatedContact = contactContext.contacts.findContact(context.userUuid)
@@ -341,7 +330,7 @@ describe('asymmetric messages', function () {
await deinitContactContext()
})
it.skip('sender keypair changed message should be signed using old key pair', async () => {
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()
@@ -352,9 +341,7 @@ describe('asymmetric messages', function () {
const secondPartySpy = sinon.spy(contactContext.asymmetric, 'handleTrustedSenderKeypairChangedMessage')
await context.sync()
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
await contactContext.sync()
await completedProcessingMessagesPromise
await contactContext.syncAndAwaitMessageProcessing()
const message = secondPartySpy.args[0][0]
const encryptedMessage = message.encrypted_message
@@ -376,9 +363,7 @@ describe('asymmetric messages', function () {
const newKeyPair = context.encryption.getKeyPair()
const newSigningKeyPair = context.encryption.getSigningKeyPair()
const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
await contactContext.sync()
await completedProcessingMessagesPromise
await contactContext.syncAndAwaitMessageProcessing()
const updatedContact = contactContext.contacts.findContact(context.userUuid)
expect(updatedContact.publicKeySet.encryption).to.equal(newKeyPair.publicKey)
@@ -395,7 +380,7 @@ describe('asymmetric messages', function () {
contactContext.lockSyncing()
await context.vaults.changeVaultNameAndDescription(sharedVault, {
await context.changeVaultName(sharedVault, {
name: 'New Name',
description: 'New Description',
})
@@ -405,7 +390,7 @@ describe('asymmetric messages', function () {
await promise
const messages = await contactContext.asymmetric.getInboundMessages()
expect(messages.length).to.equal(0)
expect(messages.getValue().length).to.equal(0)
const updatedVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
expect(updatedVault.name).to.not.equal('New Name')
@@ -413,4 +398,85 @@ describe('asymmetric messages', function () {
await deinitContactContext()
})
it('should be able to decrypt previously sent own messages', async () => {
const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
contactContext.lockSyncing()
await context.changeVaultName(sharedVault, {
name: 'New Name',
description: 'New Description',
})
const usecase = context.application.dependencies.get(TYPES.ResendAllMessages)
const result = await usecase.execute({
keys: {
encryption: context.encryption.getKeyPair(),
signing: context.encryption.getSigningKeyPair(),
},
previousKeys: {
encryption: context.encryption.getKeyPair(),
signing: context.encryption.getSigningKeyPair(),
},
})
expect(result.isFailed()).to.be.false
await deinitContactContext()
})
it('sending a new vault invite to a trusted contact then changing account password should still allow contact to trust invite', async () => {
const { contactContext, contact, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(
context,
)
contactContext.lockSyncing()
const newVault = await Collaboration.createSharedVault(context)
await context.vaultInvites.inviteContactToSharedVault(
newVault,
contact,
SharedVaultUserPermission.PERMISSIONS.Write,
)
await contactContext.runAnyRequestToPreventRefreshTokenFromExpiring()
await context.changePassword('new password')
await contactContext.runAnyRequestToPreventRefreshTokenFromExpiring()
/**
* When resending keypair changed messages here, we expect that one of their previous messages will fail to decrypt.
* This is because the first contact keypair change message was encrypted using their keypair N (original), then after
* the second password change, the reference to "previous" key will be N + 1 instead of N, so there is no longer a reference
* to the original keypair. This is not a problem, and in fact even if the message were decryptable, it would be skipped
* because we do not want to re-send keypair changed messages.
*/
await context.changePassword('new password 2')
const messages = await contactContext.asymmetric.getInboundMessages()
if (messages.isFailed()) {
console.error(messages.getError())
}
expect(messages.isFailed()).to.be.false
expect(messages.getValue().length).to.equal(2)
contactContext.unlockSyncing()
await contactContext.syncAndAwaitInviteProcessing()
const invites = contactContext.vaultInvites.getCachedPendingInviteRecords()
expect(invites.length).to.equal(1)
const invite = invites[0]
expect(invite.trusted).to.equal(true)
await contactContext.vaultInvites.acceptInvite(invite)
await deinitContactContext()
}).timeout(Factory.ThirtySecondTimeout)
})

View File

@@ -17,7 +17,7 @@ describe('shared vault conflicts', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()

View File

@@ -17,7 +17,7 @@ describe('contacts', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
@@ -101,6 +101,5 @@ describe('contacts', function () {
await deinitContactContext()
})
it.skip('should be able to refresh a contact using a collaborationID that includes full chain of previous public keys', async () => {
})
it.skip('should be able to refresh a contact using a collaborationID that includes full chain of previous public keys', async () => {})
})

View File

@@ -17,7 +17,7 @@ describe('shared vault crypto', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
@@ -28,11 +28,13 @@ describe('shared vault crypto', function () {
const appIdentifier = context.identifier
await context.deinit()
let recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
let recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier)
await recreatedContext.launch()
expect(recreatedContext.encryption.getKeyPair()).to.not.be.undefined
expect(recreatedContext.encryption.getSigningKeyPair()).to.not.be.undefined
await recreatedContext.deinit()
})
it('changing user password should re-encrypt all key system root keys and contacts with new user root key', async () => {
@@ -90,11 +92,13 @@ describe('shared vault crypto', function () {
await deinitContactContext()
})
it.skip('encrypting an item into storage then loading it should verify authenticity of original content rather than most recent symmetric signature', async () => {
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')
const contactNote = contactContext.items.findItem(note.uuid)
await contactContext.changeNoteTitleAndSync(contactNote, 'new title')
/** Override decrypt result to return failing signature */
const objectToSpy = context.encryption
@@ -103,6 +107,7 @@ describe('shared vault crypto', function () {
const decryptedPayloads = await objectToSpy.decryptSplit(split)
expect(decryptedPayloads.length).to.equal(1)
expect(decryptedPayloads[0].content_type).to.equal(ContentType.TYPES.Note)
const payload = decryptedPayloads[0]
const mutatedPayload = new DecryptedPayload({
@@ -118,6 +123,7 @@ describe('shared vault crypto', function () {
return [mutatedPayload]
})
await context.sync()
let updatedNote = context.items.findItem(note.uuid)
@@ -127,7 +133,7 @@ describe('shared vault crypto', function () {
const appIdentifier = context.identifier
await context.deinit()
let recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
let recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier)
await recreatedContext.launch()
updatedNote = recreatedContext.items.findItem(note.uuid)
@@ -140,7 +146,7 @@ describe('shared vault crypto', function () {
await recreatedContext.deinit()
recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier)
await recreatedContext.launch()
/** Decrypting from storage will now verify current user symmetric signature only */

View File

@@ -18,7 +18,7 @@ describe('shared vault deletion', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()

View File

@@ -5,7 +5,7 @@ import * as Collaboration from '../lib/Collaboration.js'
chai.use(chaiAsPromised)
const expect = chai.expect
describe.skip('shared vault files', function () {
describe('shared vault files', function () {
this.timeout(Factory.TwentySecondTimeout)
let context
@@ -19,7 +19,7 @@ describe.skip('shared vault files', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
@@ -29,7 +29,7 @@ describe.skip('shared vault files', function () {
})
describe('private vaults', () => {
it('should be able to upload and download file to vault as owner', async () => {
it('should be able to upload and download file to private vault as owner', async () => {
const vault = await Collaboration.createPrivateVault(context)
const response = await fetch('/mocha/assets/small_file.md')
const buffer = new Uint8Array(await response.arrayBuffer())
@@ -45,7 +45,7 @@ describe.skip('shared vault files', function () {
})
})
it('should be able to upload and download file to vault as owner', async () => {
it('should be able to upload and download file to shared 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())

View File

@@ -17,7 +17,7 @@ describe.skip('vault importing', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
@@ -39,7 +39,7 @@ describe.skip('vault importing', function () {
const backupData = await context.application.createEncryptedBackupFileForAutomatedDesktopBackups()
const otherContext = await Factory.createAppContextWithRealCrypto()
const otherContext = await Factory.createVaultsContextWithRealCrypto()
await otherContext.launch()
await otherContext.application.importData(backupData)

View File

@@ -17,7 +17,7 @@ describe('shared vault invites', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
})

View File

@@ -17,7 +17,7 @@ describe('shared vault items', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
@@ -38,13 +38,19 @@ describe('shared vault items', function () {
it('should add item to shared vault with contact', async () => {
const note = await context.createSyncedNote('foo', 'bar')
const { sharedVault, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(context)
const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
await Collaboration.moveItemToVault(context, sharedVault, note)
await contactContext.sync()
const updatedNote = context.items.findItem(note.uuid)
expect(updatedNote.key_system_identifier).to.equal(sharedVault.systemIdentifier)
const contactNote = contactContext.items.findItem(note.uuid)
expect(contactNote.key_system_identifier).to.equal(sharedVault.systemIdentifier)
await deinitContactContext()
})
@@ -117,7 +123,7 @@ describe('shared vault items', function () {
})
it('adding item to vault while belonging to other vault should move the item to new vault', async () => {
const { note, sharedVault, contactContext, contact, deinitContactContext } =
const { note, contactContext, contact, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
const { sharedVault: otherSharedVault } = await Collaboration.createSharedVaultAndInviteContact(
@@ -126,6 +132,8 @@ describe('shared vault items', function () {
contact,
)
await Collaboration.acceptAllInvites(contactContext)
const updatedNote = await Collaboration.moveItemToVault(context, otherSharedVault, note)
expect(updatedNote.key_system_identifier).to.equal(otherSharedVault.systemIdentifier)
@@ -134,7 +142,6 @@ describe('shared vault items', function () {
await contactContext.sync()
const receivedNote = contactContext.items.findItem(note.uuid)
expect(receivedNote).to.not.be.undefined
expect(receivedNote.title).to.equal(note.title)
expect(receivedNote.key_system_identifier).to.equal(otherSharedVault.systemIdentifier)
expect(receivedNote.shared_vault_uuid).to.equal(otherSharedVault.sharing.sharedVaultUuid)

View File

@@ -16,7 +16,7 @@ describe('vault key management', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
})

View File

@@ -17,7 +17,7 @@ describe('vault key rotation', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
@@ -99,7 +99,7 @@ describe('vault key rotation', function () {
await context.vaults.rotateVaultRootKey(sharedVault)
await promise
const outboundMessages = await context.asymmetric.getOutboundMessages()
const outboundMessages = (await context.asymmetric.getOutboundMessages()).getValue()
const expectedMessages = ['root key change', 'vault metadata change']
expect(outboundMessages.length).to.equal(expectedMessages.length)
@@ -154,10 +154,8 @@ describe('vault key rotation', function () {
await context.vaults.rotateVaultRootKey(sharedVault)
await promise
const contactPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
contactContext.unlockSyncing()
await contactContext.sync()
await contactPromise
await contactContext.syncAndAwaitMessageProcessing()
const newPrimaryItemsKey = contactContext.keys.getPrimaryKeySystemItemsKey(sharedVault.systemIdentifier)
expect(newPrimaryItemsKey).to.not.be.undefined
@@ -217,7 +215,7 @@ describe('vault key rotation', function () {
await context.vaults.rotateVaultRootKey(sharedVault)
await firstPromise
const asymmetricMessageAfterFirstChange = await context.asymmetric.getOutboundMessages()
const asymmetricMessageAfterFirstChange = (await context.asymmetric.getOutboundMessages()).getValue()
const expectedMessages = ['root key change', 'vault metadata change']
expect(asymmetricMessageAfterFirstChange.length).to.equal(expectedMessages.length)
@@ -227,7 +225,7 @@ describe('vault key rotation', function () {
await context.vaults.rotateVaultRootKey(sharedVault)
await secondPromise
const asymmetricMessageAfterSecondChange = await context.asymmetric.getOutboundMessages()
const asymmetricMessageAfterSecondChange = (await context.asymmetric.getOutboundMessages()).getValue()
expect(asymmetricMessageAfterSecondChange.length).to.equal(expectedMessages.length)
const messageAfterSecondChange = asymmetricMessageAfterSecondChange[0]

View File

@@ -17,13 +17,13 @@ describe('keypair change', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
})
it.skip('contacts should be able to handle receiving multiple keypair changed messages and trust them in order', async () => {
it('contacts should be able to handle receiving multiple keypair changed messages and trust them in order', async () => {
const { note, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
@@ -39,20 +39,24 @@ describe('keypair change', function () {
publicKeyChain.push(context.publicKey)
signingPublicKeyChain.push(context.signingPublicKey)
await contactContext.runAnyRequestToPreventRefreshTokenFromExpiring()
await context.changePassword('new_password-2')
publicKeyChain.push(context.publicKey)
signingPublicKeyChain.push(context.signingPublicKey)
await contactContext.runAnyRequestToPreventRefreshTokenFromExpiring()
await context.changePassword('new_password-3')
publicKeyChain.push(context.publicKey)
signingPublicKeyChain.push(context.signingPublicKey)
await contactContext.runAnyRequestToPreventRefreshTokenFromExpiring()
await context.changeNoteTitleAndSync(note, 'new title')
contactContext.unlockSyncing()
const promise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
await contactContext.sync()
await promise
await contactContext.syncAndAwaitMessageProcessing()
const originatorContact = contactContext.contacts.findContact(context.userUuid)
let currentKeySet = originatorContact.publicKeySet
@@ -70,7 +74,7 @@ describe('keypair change', function () {
expect(receivedNote.signatureData.result.passes).to.be.true
await deinitContactContext()
})
}).timeout(Factory.ThirtySecondTimeout)
it('should not trust messages sent with previous key pair', async () => {
const { sharedVault, contactContext, deinitContactContext } =
@@ -86,15 +90,13 @@ describe('keypair change', function () {
sinon.stub(context.encryption, 'getKeyPair').returns(previousKeyPair)
sinon.stub(context.encryption, 'getSigningKeyPair').returns(previousSigningKeyPair)
await context.vaults.changeVaultNameAndDescription(sharedVault, {
await context.changeVaultName(sharedVault, {
name: 'New Name',
description: 'New Description',
})
contactContext.unlockSyncing()
const promise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
await contactContext.sync()
await promise
await contactContext.syncAndAwaitMessageProcessing()
const updatedVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
expect(updatedVault.name).to.equal(sharedVault.name)
@@ -133,23 +135,18 @@ describe('keypair change', function () {
contactContext.lockSyncing()
await context.vaults.changeVaultNameAndDescription(sharedVault, {
await context.changeVaultName(sharedVault, {
name: 'New Name',
description: 'New Description',
})
const originalMessages = await contactContext.asymmetric.getInboundMessages()
const originalMessages = (await contactContext.asymmetric.getInboundMessages()).getValue()
expect(originalMessages.length).to.equal(1)
const originalMessage = originalMessages[0]
const promise = context.resolveWhenAsyncFunctionCompletes(
context.application.dependencies.get(TYPES.HandleKeyPairChange),
'execute',
)
await context.changePassword('new_password')
await promise
const updatedMessages = await contactContext.asymmetric.getInboundMessages()
const updatedMessages = (await contactContext.asymmetric.getInboundMessages()).getValue()
const expectedMessages = ['keypair-change', 'vault-change']
expect(updatedMessages.length).to.equal(expectedMessages.length)

View File

@@ -17,7 +17,7 @@ describe('shared vault permissions', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()

View File

@@ -18,7 +18,7 @@ describe('public key cryptography', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
@@ -40,7 +40,7 @@ describe('public key cryptography', function () {
const password = context.password
await context.signout()
const recreatedContext = await Factory.createAppContextWithRealCrypto()
const recreatedContext = await Factory.createVaultsContextWithRealCrypto()
await recreatedContext.launch()
recreatedContext.email = email
recreatedContext.password = password
@@ -51,6 +51,8 @@ describe('public key cryptography', function () {
expect(recreatedContext.sessions.getSigningPublicKey()).to.not.be.undefined
expect(recreatedContext.encryption.getSigningKeyPair().privateKey).to.not.be.undefined
await recreatedContext.deinit()
})
it('should rotate keypair during password change', async () => {
@@ -74,7 +76,7 @@ describe('public key cryptography', function () {
})
it('should allow option to enable collaboration for previously signed in accounts', async () => {
const newContext = await Factory.createAppContextWithRealCrypto()
const newContext = await Factory.createVaultsContextWithRealCrypto()
await newContext.launch()
await newContext.register()
@@ -94,5 +96,7 @@ describe('public key cryptography', function () {
expect(result.error).to.be.undefined
expect(newContext.application.sessions.isUserMissingKeyPair()).to.be.false
await newContext.deinit()
})
})

View File

@@ -10,15 +10,10 @@ describe('shared vaults', function () {
let context
let vaults
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
@@ -26,11 +21,18 @@ describe('shared vaults', function () {
vaults = context.vaults
})
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
it('should update vault name and description', async () => {
const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
await vaults.changeVaultNameAndDescription(sharedVault, {
contactContext.lockSyncing()
await context.changeVaultName(sharedVault, {
name: 'new vault name',
description: 'new vault description',
})
@@ -39,9 +41,8 @@ describe('shared vaults', function () {
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
contactContext.unlockSyncing()
await contactContext.syncAndAwaitMessageProcessing()
const contactVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
expect(contactVault.name).to.equal('new vault name')
@@ -65,7 +66,7 @@ describe('shared vaults', function () {
expect(contactContext.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined
expect(contactContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty
const recreatedContext = await Factory.createAppContextWithRealCrypto(contactContext.identifier)
const recreatedContext = await Factory.createVaultsContextWithRealCrypto(contactContext.identifier)
await recreatedContext.launch()
expect(recreatedContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined
@@ -92,7 +93,7 @@ describe('shared vaults', function () {
expect(contactContext.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined
expect(contactContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty
const recreatedContext = await Factory.createAppContextWithRealCrypto(contactContext.identifier)
const recreatedContext = await Factory.createVaultsContextWithRealCrypto(contactContext.identifier)
await recreatedContext.launch()
expect(recreatedContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined
@@ -127,4 +128,52 @@ describe('shared vaults', function () {
await deinitThirdPartyContext()
})
it('syncing a shared vault exclusively should not retrieve non vault items', async () => {
const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
await contactContext.createSyncedNote('foo', 'bar')
const syncPromise = contactContext.awaitNextSyncSharedVaultFromScratchEvent()
await contactContext.application.sync.syncSharedVaultsFromScratch([sharedVault.sharing.sharedVaultUuid])
const syncResponse = await syncPromise
const expectedItems = ['key system items key']
expect(syncResponse.retrievedPayloads.length).to.equal(expectedItems.length)
await deinitContactContext()
})
it('syncing a shared vault with note exclusively should retrieve note and items key', async () => {
const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
const syncPromise = contactContext.awaitNextSyncSharedVaultFromScratchEvent()
await contactContext.application.sync.syncSharedVaultsFromScratch([sharedVault.sharing.sharedVaultUuid])
const syncResponse = await syncPromise
const expectedItems = ['key system items key', 'note']
expect(syncResponse.retrievedPayloads.length).to.equal(expectedItems.length)
await deinitContactContext()
})
it('regular sync should not needlessly return vault items', async () => {
await Collaboration.createSharedVault(context)
const promise = context.resolveWithSyncRetrievedPayloads()
await context.sync()
const retrievedPayloads = await promise
expect(retrievedPayloads.length).to.equal(0)
})
})

View File

@@ -17,7 +17,7 @@ describe('signatures', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()

View File

@@ -17,7 +17,7 @@ describe('vaults', function () {
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
@@ -77,7 +77,7 @@ describe('vaults', function () {
await vaults.moveItemToVault(vault, note)
await context.deinit()
const recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier)
await recreatedContext.launch()
const updatedNote = recreatedContext.items.findItem(note.uuid)
@@ -101,7 +101,7 @@ describe('vaults', function () {
await context.deinit()
const recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier)
await recreatedContext.launch()
const notes = recreatedContext.notes
@@ -128,7 +128,7 @@ describe('vaults', function () {
await context.deinit()
const recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier)
await recreatedContext.launch()
const updatedNote = recreatedContext.items.findItem(note.uuid)
@@ -181,7 +181,7 @@ describe('vaults', function () {
await vaults.moveItemToVault(vault, note)
await context.deinit()
const recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier)
const recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier)
await recreatedContext.launch()
const updatedNote = recreatedContext.items.findItem(note.uuid)