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

@@ -1,10 +1,5 @@
import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage'
import { DeleteContact } from './UseCase/DeleteContact'
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData'
import { SessionEvent } from './../Session/SessionEvent'
import { InternalEventInterface } from './../Internal/InternalEventInterface'
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { SharedVaultInviteServerHash, SharedVaultUserServerHash } from '@standardnotes/responses'
import { TrustedContactInterface, TrustedContactMutator, DecryptedItemInterface } from '@standardnotes/models'
@@ -25,10 +20,7 @@ import { GetAllContacts } from './UseCase/GetAllContacts'
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
import { Result } from '@standardnotes/domain-core'
export class ContactService
extends AbstractService<ContactServiceEvent>
implements ContactServiceInterface, InternalEventHandlerInterface
{
export class ContactService extends AbstractService<ContactServiceEvent> implements ContactServiceInterface {
constructor(
private sync: SyncServiceInterface,
private mutator: MutatorClientInterface,
@@ -43,48 +35,25 @@ export class ContactService
private _createOrEditContact: CreateOrEditContact,
private _editContact: EditContact,
private _validateItemSigner: ValidateItemSigner,
private _sendOwnContactChangedMessage: SendOwnContactChangeMessage,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
eventBus.addEventHandler(this, SessionEvent.UserKeyPairChanged)
}
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === SessionEvent.UserKeyPairChanged) {
const data = event.payload as UserKeyPairChangedEventData
await this.selfContactManager.updateWithNewPublicKeySet({
encryption: data.current.encryption.publicKey,
signing: data.current.signing.publicKey,
})
void this.sendOwnContactChangeEventToAllContacts(event.payload as UserKeyPairChangedEventData)
}
}
private async sendOwnContactChangeEventToAllContacts(data: UserKeyPairChangedEventData): Promise<void> {
if (!data.previous) {
return
}
const contacts = this._getAllContacts.execute()
if (contacts.isFailed()) {
return
}
for (const contact of contacts.getValue()) {
if (contact.isMe) {
continue
}
await this._sendOwnContactChangedMessage.execute({
senderOldKeyPair: data.previous.encryption,
senderOldSigningKeyPair: data.previous.signing,
senderNewKeyPair: data.current.encryption,
senderNewSigningKeyPair: data.current.signing,
contact,
})
}
override deinit(): void {
super.deinit()
;(this.sync as unknown) = undefined
;(this.mutator as unknown) = undefined
;(this.session as unknown) = undefined
;(this.crypto as unknown) = undefined
;(this.user as unknown) = undefined
;(this.selfContactManager as unknown) = undefined
;(this.encryption as unknown) = undefined
;(this._findContact as unknown) = undefined
;(this._getAllContacts as unknown) = undefined
;(this._createOrEditContact as unknown) = undefined
;(this._editContact as unknown) = undefined
;(this._validateItemSigner as unknown) = undefined
}
getSelfContact(): TrustedContactInterface | undefined {
@@ -183,6 +152,8 @@ export class ContactService
): Promise<TrustedContactInterface> {
const updatedContact = await this._editContact.execute(contact, params)
void this.sync.sync()
return updatedContact
}
@@ -194,6 +165,9 @@ export class ContactService
isMe?: boolean
}): Promise<TrustedContactInterface | undefined> {
const contact = await this._createOrEditContact.execute(params)
void this.sync.sync()
return contact
}
@@ -233,20 +207,4 @@ export class ContactService
getItemSignatureStatus(item: DecryptedItemInterface): ItemSignatureValidationResult {
return this._validateItemSigner.execute(item)
}
override deinit(): void {
super.deinit()
;(this.sync as unknown) = undefined
;(this.mutator as unknown) = undefined
;(this.session as unknown) = undefined
;(this.crypto as unknown) = undefined
;(this.user as unknown) = undefined
;(this.selfContactManager as unknown) = undefined
;(this.encryption as unknown) = undefined
;(this._findContact as unknown) = undefined
;(this._getAllContacts as unknown) = undefined
;(this._createOrEditContact as unknown) = undefined
;(this._editContact as unknown) = undefined
;(this._validateItemSigner as unknown) = undefined
}
}

View File

@@ -17,9 +17,7 @@ import {
TrustedContactContent,
TrustedContactContentSpecialized,
TrustedContactInterface,
PortablePublicKeySet,
} from '@standardnotes/models'
import { CreateOrEditContact } from './UseCase/CreateOrEditContact'
import { ContentType } from '@standardnotes/domain-core'
const SelfContactName = 'Me'
@@ -35,7 +33,6 @@ export class SelfContactManager implements InternalEventHandlerInterface {
items: ItemManagerInterface,
private session: SessionsClientInterface,
private singletons: SingletonManagerInterface,
private createOrEditContact: CreateOrEditContact,
) {
this.eventDisposers.push(
sync.addEventObserver((event) => {
@@ -82,23 +79,6 @@ export class SelfContactManager implements InternalEventHandlerInterface {
)
}
public async updateWithNewPublicKeySet(publicKeySet: PortablePublicKeySet) {
if (!InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
return
}
if (!this.selfContact) {
return
}
await this.createOrEditContact.execute({
name: SelfContactName,
contactUuid: this.selfContact.contactUuid,
publicKey: publicKeySet.encryption,
signingPublicKey: publicKeySet.signing,
})
}
private async reloadSelfContactAndCreateIfNecessary() {
if (!InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
return
@@ -146,6 +126,5 @@ export class SelfContactManager implements InternalEventHandlerInterface {
this.eventDisposers.forEach((disposer) => disposer())
;(this.session as unknown) = undefined
;(this.singletons as unknown) = undefined
;(this.createOrEditContact as unknown) = undefined
}
}

View File

@@ -1,4 +1,3 @@
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import {
ContactPublicKeySet,
@@ -15,7 +14,6 @@ import { ContentType } from '@standardnotes/domain-core'
export class CreateOrEditContact {
constructor(
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private findContact: FindContact,
private editContact: EditContact,
) {}
@@ -54,8 +52,6 @@ export class CreateOrEditContact {
true,
)
await this.sync.sync()
return contact
}
}

View File

@@ -1,12 +1,8 @@
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import { TrustedContactInterface, TrustedContactMutator } from '@standardnotes/models'
export class EditContact {
constructor(
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
) {}
constructor(private mutator: MutatorClientInterface) {}
async execute(
contact: TrustedContactInterface,
@@ -28,8 +24,6 @@ export class EditContact {
},
)
await this.sync.sync()
return updatedContact
}
}

View File

@@ -8,16 +8,20 @@ export class FindContact implements SyncUseCaseInterface<TrustedContactInterface
execute(query: FindContactQuery): Result<TrustedContactInterface> {
if ('userUuid' in query && query.userUuid) {
const contact = this.items.itemsMatchingPredicate<TrustedContactInterface>(
const contacts = this.items.itemsMatchingPredicate<TrustedContactInterface>(
ContentType.TYPES.TrustedContact,
new Predicate<TrustedContactInterface>('contactUuid', '=', query.userUuid),
)[0]
)
if (contact) {
return Result.ok(contact)
} else {
return Result.fail('Contact not found')
if (contacts.length === 0) {
return Result.fail(`Contact not found for user ${query.userUuid}`)
}
if (contacts.length > 1) {
return Result.fail(`Multiple contacts found for user ${query.userUuid}`)
}
return Result.ok(contacts[0])
}
if ('signingPublicKey' in query && query.signingPublicKey) {

View File

@@ -0,0 +1,161 @@
import { HandleKeyPairChange } from './HandleKeyPairChange'
import { Result } from '@standardnotes/domain-core'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { LoggerInterface } from '@standardnotes/utils'
describe('HandleKeyPairChange', () => {
let useCase: HandleKeyPairChange
let mockSelfContactManager: any
let mockInvitesServer: any
let mockMessageServer: any
let mockReuploadAllInvites: any
let mockResendAllMessages: any
let mockGetAllContacts: any
let mockCreateOrEditContact: any
let mockSendOwnContactChangedMessage: any
let logger: LoggerInterface
const dto = {
newKeys: {
encryption: <PkcKeyPair>{
publicKey: 'new-encryption-public-key',
privateKey: 'new-encryption-private-key',
},
signing: <PkcKeyPair>{
publicKey: 'new-signing-public-key',
privateKey: 'new-signing-private-key',
},
},
previousKeys: {
encryption: <PkcKeyPair>{
publicKey: 'previous-encryption-public-key',
privateKey: 'previous-encryption-private-key',
},
signing: <PkcKeyPair>{
publicKey: 'previous-signing-public-key',
privateKey: 'previous-signing-private-key',
},
},
}
beforeEach(() => {
mockSelfContactManager = {
updateWithNewPublicKeySet: jest.fn().mockReturnValue({}),
}
mockInvitesServer = {
deleteAllInboundInvites: jest.fn().mockReturnValue({}),
}
mockMessageServer = {
deleteAllInboundMessages: jest.fn().mockReturnValue({}),
}
mockReuploadAllInvites = {
execute: jest.fn().mockReturnValue(Result.ok()),
}
mockResendAllMessages = {
execute: jest.fn().mockReturnValue(Result.ok()),
}
mockGetAllContacts = {
execute: jest.fn().mockReturnValue(Result.ok()),
}
mockSendOwnContactChangedMessage = {
execute: jest.fn().mockReturnValue(Result.ok()),
}
mockCreateOrEditContact = {
execute: jest.fn().mockReturnValue(Result.ok()),
}
logger = {} as jest.Mocked<LoggerInterface>
logger.error = jest.fn()
useCase = new HandleKeyPairChange(
mockSelfContactManager,
mockInvitesServer,
mockMessageServer,
mockReuploadAllInvites,
mockResendAllMessages,
mockGetAllContacts,
mockSendOwnContactChangedMessage,
mockCreateOrEditContact,
logger,
)
})
it('should handle key pair change correctly', async () => {
mockGetAllContacts.execute.mockReturnValue(Result.ok([]))
const result = await useCase.execute(dto)
expect(mockReuploadAllInvites.execute).toBeCalledWith({ keys: dto.newKeys, previousKeys: dto.previousKeys })
expect(mockResendAllMessages.execute).toBeCalledWith({ keys: dto.newKeys, previousKeys: dto.previousKeys })
expect(mockSendOwnContactChangedMessage.execute).not.toBeCalled()
expect(mockMessageServer.deleteAllInboundMessages).toBeCalled()
expect(mockInvitesServer.deleteAllInboundInvites).toBeCalled()
expect(result.isFailed()).toBe(false)
})
it('should handle sending contact change event to all contacts', async () => {
const contact = { isMe: false }
mockGetAllContacts.execute.mockReturnValue(Result.ok([contact]))
await useCase.execute(dto)
expect(mockSendOwnContactChangedMessage.execute).toBeCalledWith({
senderOldKeyPair: dto.previousKeys.encryption,
senderOldSigningKeyPair: dto.previousKeys.signing,
senderNewKeyPair: dto.newKeys.encryption,
senderNewSigningKeyPair: dto.newKeys.signing,
contact,
})
})
it('should not send contact change event if previous keys are missing', async () => {
const contact = { isMe: false }
mockGetAllContacts.execute.mockReturnValue(Result.ok([contact]))
await useCase.execute({ newKeys: dto.newKeys })
expect(mockSendOwnContactChangedMessage.execute).not.toBeCalled()
})
it('should not send contact change event if getAllContacts fails', async () => {
mockGetAllContacts.execute.mockReturnValue(Result.fail('Some error'))
await useCase.execute(dto)
expect(mockSendOwnContactChangedMessage.execute).not.toBeCalled()
})
it('should not send contact change event for self contact', async () => {
const contact = { isMe: true }
mockGetAllContacts.execute.mockReturnValue(Result.ok([contact]))
await useCase.execute(dto)
expect(mockSendOwnContactChangedMessage.execute).not.toBeCalled()
})
it('should reupload invites and resend messages before sending contact change message', async () => {
const contact = { isMe: false }
mockGetAllContacts.execute.mockReturnValue(Result.ok([contact]))
await useCase.execute(dto)
const callOrder = [
mockReuploadAllInvites.execute,
mockResendAllMessages.execute,
mockSendOwnContactChangedMessage.execute,
].map((fn) => fn.mock.invocationCallOrder[0])
for (let i = 0; i < callOrder.length - 1; i++) {
expect(callOrder[i]).toBeLessThan(callOrder[i + 1])
}
})
})

View File

@@ -1,34 +1,121 @@
import { InternalFeatureService } from './../../InternalFeatures/InternalFeatureService'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { ReuploadAllInvites } from '../../VaultInvite/UseCase/ReuploadAllInvites'
import { ResendAllMessages } from '../../AsymmetricMessage/UseCase/ResendAllMessages'
import { SelfContactManager } from '../SelfContactManager'
import { GetAllContacts } from './GetAllContacts'
import { SendOwnContactChangeMessage } from './SendOwnContactChangeMessage'
import { AsymmetricMessageServer, SharedVaultInvitesServer } from '@standardnotes/api'
import { PortablePublicKeySet } from '@standardnotes/models'
import { InternalFeature } from '../../InternalFeatures/InternalFeature'
import { CreateOrEditContact } from './CreateOrEditContact'
import { isErrorResponse } from '@standardnotes/responses'
import { LoggerInterface } from '@standardnotes/utils'
type Dto = {
newKeys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
previousKeys?: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
}
export class HandleKeyPairChange implements UseCaseInterface<void> {
constructor(
private reuploadAllInvites: ReuploadAllInvites,
private resendAllMessages: ResendAllMessages,
private selfContactManager: SelfContactManager,
private invitesServer: SharedVaultInvitesServer,
private messageServer: AsymmetricMessageServer,
private _reuploadAllInvites: ReuploadAllInvites,
private _resendAllMessages: ResendAllMessages,
private _getAllContacts: GetAllContacts,
private _sendOwnContactChangedMessage: SendOwnContactChangeMessage,
private _createOrEditContact: CreateOrEditContact,
private logger: LoggerInterface,
) {}
async execute(dto: {
newKeys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
previousKeys?: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
}): Promise<Result<void>> {
await this.reuploadAllInvites.execute({
keys: dto.newKeys,
previousKeys: dto.previousKeys,
async execute(dto: Dto): Promise<Result<void>> {
await this.updateSelfContact({
encryption: dto.newKeys.encryption.publicKey,
signing: dto.newKeys.signing.publicKey,
})
await this.resendAllMessages.execute({
keys: dto.newKeys,
previousKeys: dto.previousKeys,
})
const results = await Promise.all([
this._reuploadAllInvites.execute({
keys: dto.newKeys,
previousKeys: dto.previousKeys,
}),
this._resendAllMessages.execute({
keys: dto.newKeys,
previousKeys: dto.previousKeys,
}),
])
for (const result of results) {
if (result.isFailed()) {
this.logger.error(result.getError())
}
}
await this.sendOwnContactChangeEventToAllContacts(dto)
const deleteResponses = await Promise.all([
this.messageServer.deleteAllInboundMessages(),
this.invitesServer.deleteAllInboundInvites(),
])
for (const response of deleteResponses) {
if (isErrorResponse(response)) {
this.logger.error(JSON.stringify(response))
}
}
return Result.ok()
}
private async updateSelfContact(publicKeySet: PortablePublicKeySet) {
if (!InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
return
}
const selfContact = this.selfContactManager.selfContact
if (!selfContact) {
return
}
await this._createOrEditContact.execute({
contactUuid: selfContact.contactUuid,
publicKey: publicKeySet.encryption,
signingPublicKey: publicKeySet.signing,
})
}
private async sendOwnContactChangeEventToAllContacts(data: Dto): Promise<void> {
if (!data.previousKeys) {
return
}
const contacts = this._getAllContacts.execute()
if (contacts.isFailed()) {
return
}
for (const contact of contacts.getValue()) {
if (contact.isMe) {
continue
}
await this._sendOwnContactChangedMessage.execute({
senderOldKeyPair: data.previousKeys.encryption,
senderOldSigningKeyPair: data.previousKeys.signing,
senderNewKeyPair: data.newKeys.encryption,
senderNewSigningKeyPair: data.newKeys.signing,
contact,
})
}
}
}