chore: running paid subscription e2e tests on both self-hosted and home-server setup (#2355)
* chore: add activating paid subscription in e2e on both self-hosted and home-server setup * chore: fix activating premium features on e2e test suites * chore: remove unnecessary sleep duplication * chore: add defining the subscription expires at date in e2e
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import * as Factory from './lib/factory.js'
|
import * as Factory from './lib/factory.js'
|
||||||
import * as Events from './lib/Events.js'
|
|
||||||
import * as Utils from './lib/Utils.js'
|
import * as Utils from './lib/Utils.js'
|
||||||
import * as Files from './lib/Files.js'
|
import * as Files from './lib/Files.js'
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@ describe('files', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (subscription) {
|
if (subscription) {
|
||||||
await context.publicMockSubscriptionPurchaseEvent()
|
await context.activatePaidSubscriptionForUser()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ describe('files', function () {
|
|||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should create valet token from server - @paidfeature', async function () {
|
it('should create valet token from server', async function () {
|
||||||
await setup({ fakeCrypto: true, subscription: true })
|
await setup({ fakeCrypto: true, subscription: true })
|
||||||
|
|
||||||
const remoteIdentifier = Utils.generateUuid()
|
const remoteIdentifier = Utils.generateUuid()
|
||||||
@@ -66,26 +65,15 @@ describe('files', function () {
|
|||||||
expect(isClientDisplayableError(tokenOrError)).to.equal(true)
|
expect(isClientDisplayableError(tokenOrError)).to.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not create valet token from server when user has an expired subscription - @paidfeature', async function () {
|
it('should not create valet token from server when user has an expired subscription', async function () {
|
||||||
await setup({ fakeCrypto: true, subscription: false })
|
await setup({ fakeCrypto: true, subscription: false })
|
||||||
|
|
||||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
const dateAnHourBefore = new Date()
|
||||||
userEmail: context.email,
|
dateAnHourBefore.setHours(dateAnHourBefore.getHours() - 1)
|
||||||
subscriptionId: subscriptionId++,
|
|
||||||
subscriptionName: 'PLUS_PLAN',
|
|
||||||
subscriptionExpiresAt: (new Date().getTime() - 3_600_000) * 1_000,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
offline: false,
|
|
||||||
discountCode: null,
|
|
||||||
limitedDiscountPurchased: false,
|
|
||||||
newSubscriber: true,
|
|
||||||
totalActiveSubscriptionsCount: 1,
|
|
||||||
userRegisteredAt: 1,
|
|
||||||
billingFrequency: 12,
|
|
||||||
payAmount: 59.0,
|
|
||||||
})
|
|
||||||
|
|
||||||
await Factory.sleep(2)
|
await context.activatePaidSubscriptionForUser({
|
||||||
|
expiresAt: dateAnHourBefore,
|
||||||
|
})
|
||||||
|
|
||||||
const remoteIdentifier = Utils.generateUuid()
|
const remoteIdentifier = Utils.generateUuid()
|
||||||
const tokenOrError = await application.apiService.createUserFileValetToken(remoteIdentifier, 'write')
|
const tokenOrError = await application.apiService.createUserFileValetToken(remoteIdentifier, 'write')
|
||||||
@@ -93,7 +81,7 @@ describe('files', function () {
|
|||||||
expect(isClientDisplayableError(tokenOrError)).to.equal(true)
|
expect(isClientDisplayableError(tokenOrError)).to.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('creating two upload sessions successively should succeed - @paidfeature', async function () {
|
it('creating two upload sessions successively should succeed', async function () {
|
||||||
await setup({ fakeCrypto: true, subscription: true })
|
await setup({ fakeCrypto: true, subscription: true })
|
||||||
|
|
||||||
const firstToken = await application.apiService.createUserFileValetToken(Utils.generateUuid(), 'write')
|
const firstToken = await application.apiService.createUserFileValetToken(Utils.generateUuid(), 'write')
|
||||||
@@ -107,7 +95,7 @@ describe('files', function () {
|
|||||||
expect(secondSession.uploadId).to.be.ok
|
expect(secondSession.uploadId).to.be.ok
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should encrypt and upload small file - @paidfeature', async function () {
|
it('should encrypt and upload small file', async function () {
|
||||||
await setup({ fakeCrypto: false, subscription: true })
|
await setup({ fakeCrypto: false, subscription: true })
|
||||||
|
|
||||||
const response = await fetch('/packages/snjs/mocha/assets/small_file.md')
|
const response = await fetch('/packages/snjs/mocha/assets/small_file.md')
|
||||||
@@ -120,7 +108,7 @@ describe('files', function () {
|
|||||||
expect(downloadedBytes).to.eql(buffer)
|
expect(downloadedBytes).to.eql(buffer)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should encrypt and upload big file - @paidfeature', async function () {
|
it('should encrypt and upload big file', async function () {
|
||||||
await setup({ fakeCrypto: false, subscription: true })
|
await setup({ fakeCrypto: false, subscription: true })
|
||||||
|
|
||||||
const response = await fetch('/packages/snjs/mocha/assets/two_mb_file.md')
|
const response = await fetch('/packages/snjs/mocha/assets/two_mb_file.md')
|
||||||
@@ -133,7 +121,7 @@ describe('files', function () {
|
|||||||
expect(downloadedBytes).to.eql(buffer)
|
expect(downloadedBytes).to.eql(buffer)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should delete file - @paidfeature', async function () {
|
it('should delete file', async function () {
|
||||||
await setup({ fakeCrypto: false, subscription: true })
|
await setup({ fakeCrypto: false, subscription: true })
|
||||||
|
|
||||||
const response = await fetch('/packages/snjs/mocha/assets/small_file.md')
|
const response = await fetch('/packages/snjs/mocha/assets/small_file.md')
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as Applications from './Applications.js'
|
|||||||
import * as Utils from './Utils.js'
|
import * as Utils from './Utils.js'
|
||||||
import * as Defaults from './Defaults.js'
|
import * as Defaults from './Defaults.js'
|
||||||
import * as Events from './Events.js'
|
import * as Events from './Events.js'
|
||||||
|
import * as HomeServer from './HomeServer.js'
|
||||||
import { createNotePayload } from './Items.js'
|
import { createNotePayload } from './Items.js'
|
||||||
|
|
||||||
UuidGenerator.SetGenerator(new FakeWebCrypto().generateUUID)
|
UuidGenerator.SetGenerator(new FakeWebCrypto().generateUUID)
|
||||||
@@ -597,23 +598,41 @@ export class AppContext {
|
|||||||
console.warn('Anticipating a console error with message:', message)
|
console.warn('Anticipating a console error with message:', message)
|
||||||
}
|
}
|
||||||
|
|
||||||
async publicMockSubscriptionPurchaseEvent() {
|
async activatePaidSubscriptionForUser(options = {}) {
|
||||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
const dateInAnHour = new Date()
|
||||||
userEmail: this.email,
|
dateInAnHour.setHours(dateInAnHour.getHours() + 1)
|
||||||
subscriptionId: GlobalSubscriptionIdCounter++,
|
|
||||||
subscriptionName: 'PRO_PLAN',
|
|
||||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
offline: false,
|
|
||||||
discountCode: null,
|
|
||||||
limitedDiscountPurchased: false,
|
|
||||||
newSubscriber: true,
|
|
||||||
totalActiveSubscriptionsCount: 1,
|
|
||||||
userRegisteredAt: 1,
|
|
||||||
billingFrequency: 12,
|
|
||||||
payAmount: 59.0,
|
|
||||||
})
|
|
||||||
|
|
||||||
await Utils.sleep(2)
|
options.expiresAt = options.expiresAt || dateInAnHour
|
||||||
|
options.subscriptionPlanName = options.subscriptionPlanName || 'PRO_PLAN'
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||||
|
userEmail: this.email,
|
||||||
|
subscriptionId: GlobalSubscriptionIdCounter++,
|
||||||
|
subscriptionName: options.subscriptionPlanName,
|
||||||
|
subscriptionExpiresAt: options.expiresAt.getTime() * 1_000,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
offline: false,
|
||||||
|
discountCode: null,
|
||||||
|
limitedDiscountPurchased: false,
|
||||||
|
newSubscriber: true,
|
||||||
|
totalActiveSubscriptionsCount: 1,
|
||||||
|
userRegisteredAt: 1,
|
||||||
|
billingFrequency: 12,
|
||||||
|
payAmount: 59.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
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}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await HomeServer.activatePremiumFeatures(this.email, options.subscriptionPlanName, options.expiresAt)
|
||||||
|
|
||||||
|
await Utils.sleep(1)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Home server not available. You are probalby running a test suite for self hosted setup: ${error.message}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as Defaults from './Defaults.js'
|
import * as Defaults from './Defaults.js'
|
||||||
|
|
||||||
export async function publishMockedEvent(eventType, eventPayload) {
|
export async function publishMockedEvent(eventType, eventPayload) {
|
||||||
const response = await fetch(`${Defaults.getDefaultMockedEventServiceUrl()}/events`, {
|
await fetch(`${Defaults.getDefaultMockedEventServiceUrl()}/events`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
@@ -12,8 +12,4 @@ export async function publishMockedEvent(eventType, eventPayload) {
|
|||||||
eventPayload,
|
eventPayload,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error(`Failed to publish mocked event: ${response.status} ${response.statusText}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
16
packages/snjs/mocha/lib/HomeServer.js
Normal file
16
packages/snjs/mocha/lib/HomeServer.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import * as Defaults from './Defaults.js'
|
||||||
|
|
||||||
|
export async function activatePremiumFeatures(username, subscriptionPlanName, endsAt) {
|
||||||
|
await fetch(`${Defaults.getDefaultHost()}/e2e/activate-premium`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username,
|
||||||
|
subscriptionPlanName,
|
||||||
|
endsAt,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -134,50 +134,19 @@ describe('settings service', function () {
|
|||||||
expect(settings.getSettingValue(SettingName.create(SettingName.NAMES.MfaSecret).getValue())).to.not.be.ok
|
expect(settings.getSettingValue(SettingName.create(SettingName.NAMES.MfaSecret).getValue())).to.not.be.ok
|
||||||
})
|
})
|
||||||
|
|
||||||
it('reads a subscription setting - @paidfeature', async () => {
|
it('reads a subscription setting', async () => {
|
||||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
await context.activatePaidSubscriptionForUser()
|
||||||
userEmail: context.email,
|
|
||||||
subscriptionId: subscriptionId++,
|
|
||||||
subscriptionName: 'PRO_PLAN',
|
|
||||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
offline: false,
|
|
||||||
discountCode: null,
|
|
||||||
limitedDiscountPurchased: false,
|
|
||||||
newSubscriber: true,
|
|
||||||
totalActiveSubscriptionsCount: 1,
|
|
||||||
userRegisteredAt: 1,
|
|
||||||
billingFrequency: 12,
|
|
||||||
payAmount: 59.0,
|
|
||||||
})
|
|
||||||
|
|
||||||
await Factory.sleep(2)
|
|
||||||
|
|
||||||
const setting = await application.settings.getSubscriptionSetting(
|
const setting = await application.settings.getSubscriptionSetting(
|
||||||
SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue(),
|
SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(),
|
||||||
)
|
)
|
||||||
expect(setting).to.be.a('string')
|
expect(setting).to.be.a('string')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('persist irreplaceable subscription settings between subsequent subscriptions - @paidfeature', async () => {
|
it('persist irreplaceable subscription settings between subsequent subscriptions', async () => {
|
||||||
await reInitializeApplicationWithRealCrypto()
|
await reInitializeApplicationWithRealCrypto()
|
||||||
|
|
||||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
await context.activatePaidSubscriptionForUser()
|
||||||
userEmail: context.email,
|
|
||||||
subscriptionId: subscriptionId,
|
|
||||||
subscriptionName: 'PRO_PLAN',
|
|
||||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
offline: false,
|
|
||||||
discountCode: null,
|
|
||||||
limitedDiscountPurchased: false,
|
|
||||||
newSubscriber: true,
|
|
||||||
totalActiveSubscriptionsCount: 1,
|
|
||||||
userRegisteredAt: 1,
|
|
||||||
billingFrequency: 12,
|
|
||||||
payAmount: 59.0,
|
|
||||||
})
|
|
||||||
await Factory.sleep(1)
|
|
||||||
|
|
||||||
const response = await fetch('/packages/snjs/mocha/assets/small_file.md')
|
const response = await fetch('/packages/snjs/mocha/assets/small_file.md')
|
||||||
const buffer = new Uint8Array(await response.arrayBuffer())
|
const buffer = new Uint8Array(await response.arrayBuffer())
|
||||||
@@ -189,42 +158,15 @@ describe('settings service', function () {
|
|||||||
const limitSettingBefore = await application.settings.getSubscriptionSetting(
|
const limitSettingBefore = await application.settings.getSubscriptionSetting(
|
||||||
SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue(),
|
SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue(),
|
||||||
)
|
)
|
||||||
expect(limitSettingBefore).to.equal('107374182400')
|
|
||||||
|
|
||||||
const usedSettingBefore = await application.settings.getSubscriptionSetting(
|
const usedSettingBefore = await application.settings.getSubscriptionSetting(
|
||||||
SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(),
|
SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(),
|
||||||
)
|
)
|
||||||
expect(usedSettingBefore).to.equal('196')
|
expect(usedSettingBefore).to.equal('196')
|
||||||
|
|
||||||
await Events.publishMockedEvent('SUBSCRIPTION_EXPIRED', {
|
await context.activatePaidSubscriptionForUser()
|
||||||
userEmail: context.email,
|
|
||||||
subscriptionId: subscriptionId++,
|
|
||||||
subscriptionName: 'PRO_PLAN',
|
|
||||||
timestamp: Date.now(),
|
|
||||||
offline: false,
|
|
||||||
totalActiveSubscriptionsCount: 1,
|
|
||||||
userExistingSubscriptionsCount: 1,
|
|
||||||
billingFrequency: 12,
|
|
||||||
payAmount: 59.0,
|
|
||||||
})
|
|
||||||
await Factory.sleep(1)
|
|
||||||
|
|
||||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
await context.activatePaidSubscriptionForUser()
|
||||||
userEmail: context.email,
|
|
||||||
subscriptionId: subscriptionId++,
|
|
||||||
subscriptionName: 'PRO_PLAN',
|
|
||||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
offline: false,
|
|
||||||
discountCode: null,
|
|
||||||
limitedDiscountPurchased: false,
|
|
||||||
newSubscriber: false,
|
|
||||||
totalActiveSubscriptionsCount: 2,
|
|
||||||
userRegisteredAt: 1,
|
|
||||||
billingFrequency: 12,
|
|
||||||
payAmount: 59.0,
|
|
||||||
})
|
|
||||||
await Factory.sleep(1)
|
|
||||||
|
|
||||||
const limitSettingAfter = await application.settings.getSubscriptionSetting(
|
const limitSettingAfter = await application.settings.getSubscriptionSetting(
|
||||||
SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue(),
|
SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue(),
|
||||||
|
|||||||
@@ -32,25 +32,10 @@ describe('subscriptions', function () {
|
|||||||
password: context.password,
|
password: context.password,
|
||||||
})
|
})
|
||||||
|
|
||||||
await Events.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
await context.activatePaidSubscriptionForUser()
|
||||||
userEmail: context.email,
|
|
||||||
subscriptionId: subscriptionId++,
|
|
||||||
subscriptionName: 'PRO_PLAN',
|
|
||||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
offline: false,
|
|
||||||
discountCode: null,
|
|
||||||
limitedDiscountPurchased: false,
|
|
||||||
newSubscriber: true,
|
|
||||||
totalActiveSubscriptionsCount: 1,
|
|
||||||
userRegisteredAt: 1,
|
|
||||||
billingFrequency: 12,
|
|
||||||
payAmount: 59.00
|
|
||||||
})
|
|
||||||
await Factory.sleep(2)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should invite a user by email to a shared subscription - @paidfeature', async () => {
|
it('should invite a user by email to a shared subscription', async () => {
|
||||||
await subscriptionManager.inviteToSubscription('test@test.te')
|
await subscriptionManager.inviteToSubscription('test@test.te')
|
||||||
|
|
||||||
const existingInvites = await subscriptionManager.listSubscriptionInvitations()
|
const existingInvites = await subscriptionManager.listSubscriptionInvitations()
|
||||||
@@ -60,7 +45,7 @@ describe('subscriptions', function () {
|
|||||||
expect(newlyCreatedInvite.status).to.equal('sent')
|
expect(newlyCreatedInvite.status).to.equal('sent')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not invite a user by email if the limit of shared subscription is breached - @paidfeature', async () => {
|
it('should not invite a user by email if the limit of shared subscription is breached', async () => {
|
||||||
await subscriptionManager.inviteToSubscription('test1@test.te')
|
await subscriptionManager.inviteToSubscription('test1@test.te')
|
||||||
await subscriptionManager.inviteToSubscription('test2@test.te')
|
await subscriptionManager.inviteToSubscription('test2@test.te')
|
||||||
await subscriptionManager.inviteToSubscription('test3@test.te')
|
await subscriptionManager.inviteToSubscription('test3@test.te')
|
||||||
@@ -78,7 +63,7 @@ describe('subscriptions', function () {
|
|||||||
expect(existingInvites.length).to.equal(5)
|
expect(existingInvites.length).to.equal(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should cancel a user invitation to a shared subscription - @paidfeature', async () => {
|
it('should cancel a user invitation to a shared subscription', async () => {
|
||||||
await subscriptionManager.inviteToSubscription('test@test.te')
|
await subscriptionManager.inviteToSubscription('test@test.te')
|
||||||
await subscriptionManager.inviteToSubscription('test2@test.te')
|
await subscriptionManager.inviteToSubscription('test2@test.te')
|
||||||
|
|
||||||
@@ -97,7 +82,7 @@ describe('subscriptions', function () {
|
|||||||
expect(existingInvites.filter(invite => invite.status === 'canceled').length).to.equal(1)
|
expect(existingInvites.filter(invite => invite.status === 'canceled').length).to.equal(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should invite a user by email if the limit of shared subscription is restored - @paidfeature', async () => {
|
it('should invite a user by email if the limit of shared subscription is restored', async () => {
|
||||||
await subscriptionManager.inviteToSubscription('test1@test.te')
|
await subscriptionManager.inviteToSubscription('test1@test.te')
|
||||||
await subscriptionManager.inviteToSubscription('test2@test.te')
|
await subscriptionManager.inviteToSubscription('test2@test.te')
|
||||||
await subscriptionManager.inviteToSubscription('test3@test.te')
|
await subscriptionManager.inviteToSubscription('test3@test.te')
|
||||||
|
|||||||
@@ -27,16 +27,12 @@
|
|||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const bail = urlParams.get('bail') === 'false' ? false : true;
|
const bail = urlParams.get('bail') === 'false' ? false : true;
|
||||||
const skipPaidFeatures = urlParams.get('skip_paid_features') === 'true' ? true : false;
|
|
||||||
|
|
||||||
mocha.setup({
|
mocha.setup({
|
||||||
ui: 'bdd',
|
ui: 'bdd',
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
bail: bail,
|
bail: bail,
|
||||||
});
|
});
|
||||||
if (skipPaidFeatures) {
|
|
||||||
mocha.grep('@paidfeature').invert();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
@@ -81,4 +77,4 @@
|
|||||||
<div id="mocha"></div>
|
<div id="mocha"></div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ describe('shared vault files', function () {
|
|||||||
await context.register()
|
await context.register()
|
||||||
|
|
||||||
vaults = context.vaults
|
vaults = context.vaults
|
||||||
await context.publicMockSubscriptionPurchaseEvent()
|
await context.activatePaidSubscriptionForUser()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('private vaults', () => {
|
describe('private vaults', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user