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

@@ -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)
}