feat: add subscription manager to handle subscription sharing (#1517)

* feat: add subscription manager to handle subscription sharing

* fix(services): add missing methods to the interface

* fix(services): add subscription manager specs

* feat(snjs): add subscriptions e2e tests

* fix(snjs): add wait in subscription cancelling test

* fix(snjs): checking for canceled invitations in tests

* fix(snjs): add e2e test for restored limit of subscription invitations

* chore(lint): fix linter issues
This commit is contained in:
Karol Sójko
2022-09-13 10:28:11 +02:00
committed by GitHub
parent 2b830c0fae
commit 55b1409a80
55 changed files with 512 additions and 91 deletions

View File

@@ -0,0 +1,8 @@
import { Uuid } from '@standardnotes/common'
import { Invitation } from '@standardnotes/models'
export interface SubscriptionClientInterface {
listSubscriptionInvitations(): Promise<Invitation[]>
inviteToSubscription(inviteeEmail: string): Promise<boolean>
cancelInvitation(inviteUuid: Uuid): Promise<boolean>
}

View File

@@ -0,0 +1,83 @@
import { SubscriptionApiServiceInterface } from '@standardnotes/api'
import { Invitation } from '@standardnotes/models'
import { InternalEventBusInterface } from '..'
import { SubscriptionManager } from './SubscriptionManager'
describe('SubscriptionManager', () => {
let subscriptionApiService: SubscriptionApiServiceInterface
let internalEventBus: InternalEventBusInterface
const createManager = () => new SubscriptionManager(subscriptionApiService, internalEventBus)
beforeEach(() => {
subscriptionApiService = {} as jest.Mocked<SubscriptionApiServiceInterface>
subscriptionApiService.cancelInvite = jest.fn()
subscriptionApiService.invite = jest.fn()
subscriptionApiService.listInvites = jest.fn()
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
})
it('should invite user by email to a shared subscription', async () => {
subscriptionApiService.invite = jest.fn().mockReturnValue({ data: { success: true } })
expect(await createManager().inviteToSubscription('test@test.te')).toBeTruthy()
})
it('should not invite user by email if the api fails to do so', async () => {
subscriptionApiService.invite = jest.fn().mockReturnValue({ data: { error: 'foobar' } })
expect(await createManager().inviteToSubscription('test@test.te')).toBeFalsy()
})
it('should not invite user by email if the api throws an error', async () => {
subscriptionApiService.invite = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
expect(await createManager().inviteToSubscription('test@test.te')).toBeFalsy()
})
it('should cancel invite to a shared subscription', async () => {
subscriptionApiService.cancelInvite = jest.fn().mockReturnValue({ data: { success: true } })
expect(await createManager().cancelInvitation('1-2-3')).toBeTruthy()
})
it('should not cancel invite if the api fails to do so', async () => {
subscriptionApiService.cancelInvite = jest.fn().mockReturnValue({ data: { error: 'foobar' } })
expect(await createManager().cancelInvitation('1-2-3')).toBeFalsy()
})
it('should not cancel invite if the api throws an error', async () => {
subscriptionApiService.cancelInvite = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
expect(await createManager().cancelInvitation('1-2-3')).toBeFalsy()
})
it('should list invites to a shared subscription', async () => {
const invitation = {
uuid: '1-2-3',
} as jest.Mocked<Invitation>
subscriptionApiService.listInvites = jest.fn().mockReturnValue({ data: { invitations: [invitation] } })
expect(await createManager().listSubscriptionInvitations()).toEqual([invitation])
})
it('should return an empty list of invites if the api fails to fetch them', async () => {
subscriptionApiService.listInvites = jest.fn().mockReturnValue({ data: { error: 'foobar' } })
expect(await createManager().listSubscriptionInvitations()).toEqual([])
})
it('should return an empty list of invites if the api throws an error', async () => {
subscriptionApiService.listInvites = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
expect(await createManager().listSubscriptionInvitations()).toEqual([])
})
})

View File

@@ -0,0 +1,45 @@
import { Invitation } from '@standardnotes/models'
import { SubscriptionApiServiceInterface } from '@standardnotes/api'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { AbstractService } from '../Service/AbstractService'
import { SubscriptionClientInterface } from './SubscriptionClientInterface'
import { Uuid } from '@standardnotes/common'
export class SubscriptionManager extends AbstractService implements SubscriptionClientInterface {
constructor(
private subscriptionApiService: SubscriptionApiServiceInterface,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
}
async listSubscriptionInvitations(): Promise<Invitation[]> {
try {
const response = await this.subscriptionApiService.listInvites()
return response.data.invitations ?? []
} catch (error) {
return []
}
}
async inviteToSubscription(inviteeEmail: string): Promise<boolean> {
try {
const result = await this.subscriptionApiService.invite(inviteeEmail)
return result.data.success === true
} catch (error) {
return false
}
}
async cancelInvitation(inviteUuid: Uuid): Promise<boolean> {
try {
const result = await this.subscriptionApiService.cancelInvite(inviteUuid)
return result.data.success === true
} catch (error) {
return false
}
}
}