chore: vault tests refactors and lint (#2374)
This commit is contained in:
@@ -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}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
54
packages/snjs/mocha/lib/VaultsContext.js
Normal file
54
packages/snjs/mocha/lib/VaultsContext.js
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user