chore: vault tests refactors and lint (#2374)
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user