From f196efdf5b77ec78c9cb8ce637c29df52b12db87 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Fri, 15 Aug 2025 14:43:50 -0700 Subject: [PATCH 1/4] fix logic for setting subscription data and event emitter firing twice fix up tests --- .../environment/TestEnvironmentHelpers.ts | 3 +-- index.html | 17 +++++++++++++ package.json | 4 ++-- src/core/CoreModuleDirector.ts | 11 ++++----- .../IdentityOperationExecutor.test.ts | 13 ++++++---- .../LoginUserOperationExecutor.test.ts | 12 +++++----- .../executors/LoginUserOperationExecutor.ts | 11 --------- .../RefreshUserOperationExecutor.test.ts | 19 +++++++++------ .../executors/RefreshUserOperationExecutor.ts | 14 ++++------- .../SubscriptionOperationExecutor.test.ts | 24 ++++++++++++------- .../SubscriptionOperationExecutor.ts | 11 --------- .../UpdateUserOperationExecutor.test.ts | 14 +++++++---- src/core/modelRepo/ModelStore.ts | 6 ++--- src/core/modelRepo/RebuildUserService.ts | 8 ++----- src/core/modelStores/SimpleModelStore.ts | 4 ++-- src/onesignal/OneSignal.test.ts | 21 ++++++++-------- src/shared/database/types.ts | 3 --- src/shared/listeners.ts | 3 --- 18 files changed, 98 insertions(+), 100 deletions(-) diff --git a/__test__/support/environment/TestEnvironmentHelpers.ts b/__test__/support/environment/TestEnvironmentHelpers.ts index 9bc3d2818..50be04968 100644 --- a/__test__/support/environment/TestEnvironmentHelpers.ts +++ b/__test__/support/environment/TestEnvironmentHelpers.ts @@ -2,7 +2,7 @@ import { type DOMWindow, JSDOM, ResourceLoader } from 'jsdom'; import CoreModule from 'src/core/CoreModule'; import { SubscriptionModel } from 'src/core/models/SubscriptionModel'; import { ModelChangeTags } from 'src/core/types/models'; -import { setPushId, setPushToken } from 'src/shared/database/subscription'; +import { setPushToken } from 'src/shared/database/subscription'; import { NotificationType, SubscriptionType, @@ -146,7 +146,6 @@ export const setupSubModelStore = async ({ token, onesignalId, }); - await setPushId(pushModel.id); await setPushToken(pushModel.token); OneSignal.coreDirector.subscriptionModelStore.replaceAll( [pushModel], diff --git a/index.html b/index.html index 781e0e317..f11156a96 100644 --- a/index.html +++ b/index.html @@ -19,6 +19,23 @@ }); }); + function pushSubscriptionChangeListener(event) { + console.warn('event.previous.id', event.previous.id); + console.warn('event.current.id', event.current.id); + console.warn('event.previous.token', event.previous.token); + console.warn('event.current.token', event.current.token); + console.warn('event.previous.optedIn', event.previous.optedIn); + console.warn('event.current.optedIn', event.current.optedIn); + } + + // uncomment to test push subscription change listener + // OneSignalDeferred.push(function (OneSignal) { + // OneSignal.User.PushSubscription.addEventListener( + // 'change', + // pushSubscriptionChangeListener, + // ); + // }); + // uncomment to test login // OneSignalDeferred.push(async function (OneSignal) { // await OneSignal.login('new-web-sdk'); diff --git a/package.json b/package.json index 05ba380ef..620c54a8b 100644 --- a/package.json +++ b/package.json @@ -81,12 +81,12 @@ }, { "path": "./build/releases/OneSignalSDK.page.es6.js", - "limit": "55.08 kB", + "limit": "54.88 kB", "gzip": true }, { "path": "./build/releases/OneSignalSDK.sw.js", - "limit": "15.42 kB", + "limit": "15.36 kB", "gzip": true }, { diff --git a/src/core/CoreModuleDirector.ts b/src/core/CoreModuleDirector.ts index bcb8ab714..8cf686d47 100644 --- a/src/core/CoreModuleDirector.ts +++ b/src/core/CoreModuleDirector.ts @@ -1,5 +1,5 @@ import FuturePushSubscriptionRecord from 'src/page/userModel/FuturePushSubscriptionRecord'; -import { getPushToken, setPushId } from 'src/shared/database/subscription'; +import { getPushToken } from 'src/shared/database/subscription'; import { IDManager } from 'src/shared/managers/IDManager'; import { SubscriptionChannel, @@ -46,6 +46,10 @@ export class CoreModuleDirector { return this.core.subscriptionModelStore; } + get newRecordsState(): NewRecordsState { + return this.core.newRecordsState; + } + get customEventController(): ICustomEventController { return this.core.customEventController; } @@ -61,7 +65,6 @@ export class CoreModuleDirector { new FuturePushSubscriptionRecord(rawPushSubscription).serialize(), ); model.id = IDManager.createLocalId(); - setPushId(model.id); // we enqueue a login operation w/ a create subscription operation the first time we generate/save a push subscription model this.core.subscriptionModelStore.add(model, ModelChangeTags.HYDRATE); @@ -77,10 +80,6 @@ export class CoreModuleDirector { } /* G E T T E R S */ - public getNewRecordsState(): NewRecordsState { - return this.core.newRecordsState; - } - public getEmailSubscriptionModels(): SubscriptionModel[] { logMethodCall('CoreModuleDirector.getEmailSubscriptionModels'); const subscriptions = this.core.subscriptionModelStore.list(); diff --git a/src/core/executors/IdentityOperationExecutor.test.ts b/src/core/executors/IdentityOperationExecutor.test.ts index bd4bd947e..7ca3e6925 100644 --- a/src/core/executors/IdentityOperationExecutor.test.ts +++ b/src/core/executors/IdentityOperationExecutor.test.ts @@ -1,4 +1,5 @@ import { APP_ID, DUMMY_ONESIGNAL_ID } from '__test__/constants'; +import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { SomeOperation } from '__test__/support/helpers/executors'; import { setAddAliasError, @@ -48,16 +49,20 @@ describe('IdentityOperationExecutor', () => { ); }; + beforeAll(async () => { + await TestEnvironment.initialize(); + }); + beforeEach(() => { setAddAliasResponse(); setDeleteAliasResponse(); - identityModelStore = new IdentityModelStore(); + identityModelStore = OneSignal.coreDirector.identityModelStore; identityModelStore.model.onesignalId = DUMMY_ONESIGNAL_ID; - newRecordsState = new NewRecordsState(); - propertiesModelStore = new PropertiesModelStore(); - subscriptionModelStore = new SubscriptionModelStore(); + newRecordsState = OneSignal.coreDirector.newRecordsState; + propertiesModelStore = OneSignal.coreDirector.propertiesModelStore; + subscriptionModelStore = OneSignal.coreDirector.subscriptionModelStore; rebuildUserService = new RebuildUserService( identityModelStore, diff --git a/src/core/executors/LoginUserOperationExecutor.test.ts b/src/core/executors/LoginUserOperationExecutor.test.ts index e62d39231..52443020b 100644 --- a/src/core/executors/LoginUserOperationExecutor.test.ts +++ b/src/core/executors/LoginUserOperationExecutor.test.ts @@ -19,7 +19,7 @@ import { setCreateUserResponse, } from '__test__/support/helpers/requests'; import { clearAll } from 'src/shared/database/client'; -import { getPushId, setPushId } from 'src/shared/database/subscription'; +import { getPushToken, setPushToken } from 'src/shared/database/subscription'; import { NotificationType, SubscriptionType, @@ -57,9 +57,9 @@ describe('LoginUserOperationExecutor', () => { beforeEach(async () => { await clearAll(); - identityModelStore = new IdentityModelStore(); - propertiesModelStore = new PropertiesModelStore(); - subscriptionModelStore = new SubscriptionModelStore(); + identityModelStore = OneSignal.coreDirector.identityModelStore; + propertiesModelStore = OneSignal.coreDirector.propertiesModelStore; + subscriptionModelStore = OneSignal.coreDirector.subscriptionModelStore; rebuildUserService = new RebuildUserService( identityModelStore, propertiesModelStore, @@ -166,7 +166,7 @@ describe('LoginUserOperationExecutor', () => { DUMMY_ONESIGNAL_ID, ); propertiesModelStore.model.setProperty('onesignalId', DUMMY_ONESIGNAL_ID); - await setPushId(DUMMY_SUBSCRIPTION_ID); + await setPushToken(DUMMY_PUSH_TOKEN); const subscriptionModel = new SubscriptionModel(); subscriptionModel.setProperty('id', DUMMY_SUBSCRIPTION_ID); @@ -192,7 +192,7 @@ describe('LoginUserOperationExecutor', () => { expect(propertiesModelStore.model.getProperty('onesignalId')).toEqual( DUMMY_ONESIGNAL_ID_2, ); - expect(await getPushId()).toEqual(DUMMY_SUBSCRIPTION_ID_2); + expect(await getPushToken()).toEqual(DUMMY_PUSH_TOKEN); expect(subscriptionModel.getProperty('id')).toEqual( DUMMY_SUBSCRIPTION_ID_2, ); diff --git a/src/core/executors/LoginUserOperationExecutor.ts b/src/core/executors/LoginUserOperationExecutor.ts index a44a48882..93ac68593 100644 --- a/src/core/executors/LoginUserOperationExecutor.ts +++ b/src/core/executors/LoginUserOperationExecutor.ts @@ -4,11 +4,6 @@ import { type IOperationExecutor, } from 'src/core/types/operation'; import { getConsentGiven } from 'src/shared/database/config'; -import { - getPushId, - setPushId, - setPushToken, -} from 'src/shared/database/subscription'; import { getTimeZoneId } from 'src/shared/helpers/general'; import { getConsentRequired } from 'src/shared/helpers/localStorage'; import { @@ -236,12 +231,6 @@ export class LoginUserOperationExecutor implements IOperationExecutor { if (!backendSub || !('id' in backendSub)) continue; idTranslations[localId] = backendSub.id; - const pushSubscriptionId = await getPushId(); - if (pushSubscriptionId === localId) { - await setPushId(backendSub.id); - await setPushToken(backendSub.token); - } - const model = this._subscriptionsModelStore.getBySubscriptionId(localId); model?.setProperty('id', backendSub.id, ModelChangeTags.HYDRATE); diff --git a/src/core/executors/RefreshUserOperationExecutor.test.ts b/src/core/executors/RefreshUserOperationExecutor.test.ts index a82dfe749..5134c271e 100644 --- a/src/core/executors/RefreshUserOperationExecutor.test.ts +++ b/src/core/executors/RefreshUserOperationExecutor.test.ts @@ -7,13 +7,14 @@ import { DUMMY_SUBSCRIPTION_ID, DUMMY_SUBSCRIPTION_ID_2, } from '__test__/constants'; +import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { SomeOperation } from '__test__/support/helpers/executors'; import { setGetUserError, setGetUserResponse, } from '__test__/support/helpers/requests'; import { clearAll } from 'src/shared/database/client'; -import { setPushId } from 'src/shared/database/subscription'; +import { setPushToken } from 'src/shared/database/subscription'; import { NotificationType, SubscriptionType, @@ -24,7 +25,7 @@ import { RebuildUserService } from '../modelRepo/RebuildUserService'; import { SubscriptionModel } from '../models/SubscriptionModel'; import { IdentityModelStore } from '../modelStores/IdentityModelStore'; import { PropertiesModelStore } from '../modelStores/PropertiesModelStore'; -import { SubscriptionModelStore } from '../modelStores/SubscriptionModelStore'; +import type { SubscriptionModelStore } from '../modelStores/SubscriptionModelStore'; import { NewRecordsState } from '../operationRepo/NewRecordsState'; import { RefreshUserOperation } from '../operations/RefreshUserOperation'; import { ExecutionResult } from '../types/operation'; @@ -40,12 +41,16 @@ let getRebuildOpsSpy: MockInstance; vi.mock('src/shared/libraries/Log'); describe('RefreshUserOperationExecutor', () => { + beforeAll(async () => { + await TestEnvironment.initialize(); + }); + beforeEach(async () => { await clearAll(); // in case subscription model (from previous tests) are loaded from db - identityModelStore = new IdentityModelStore(); - propertiesModelStore = new PropertiesModelStore(); - subscriptionModelStore = new SubscriptionModelStore(); - newRecordsState = new NewRecordsState(); + identityModelStore = OneSignal.coreDirector.identityModelStore; + propertiesModelStore = OneSignal.coreDirector.propertiesModelStore; + subscriptionModelStore = OneSignal.coreDirector.subscriptionModelStore; + newRecordsState = OneSignal.coreDirector.newRecordsState; buildUserService = new RebuildUserService( identityModelStore, propertiesModelStore, @@ -182,7 +187,7 @@ describe('RefreshUserOperationExecutor', () => { pushSubModel.notification_types = NotificationType.Subscribed; subscriptionModelStore.add(pushSubModel); - await setPushId(DUMMY_SUBSCRIPTION_ID_2); + await setPushToken(DUMMY_PUSH_TOKEN); const executor = getExecutor(); const refreshOp = new RefreshUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); diff --git a/src/core/executors/RefreshUserOperationExecutor.ts b/src/core/executors/RefreshUserOperationExecutor.ts index 6011e2446..a02fb49f7 100644 --- a/src/core/executors/RefreshUserOperationExecutor.ts +++ b/src/core/executors/RefreshUserOperationExecutor.ts @@ -1,4 +1,3 @@ -import { getPushId } from 'src/shared/database/subscription'; import { getResponseStatusType, ResponseStatusType, @@ -120,15 +119,10 @@ export class RefreshUserOperationExecutor implements IOperationExecutor { } } - const pushSubscriptionId = await getPushId(); - - if (pushSubscriptionId) { - const cachedPushModel = - this._subscriptionsModelStore.getBySubscriptionId(pushSubscriptionId); - if (cachedPushModel) { - cachedPushModel.onesignalId = op.onesignalId; - subscriptionModels.push(cachedPushModel); - } + const pushModel = await OneSignal.coreDirector.getPushSubscriptionModel(); + if (pushModel) { + pushModel.onesignalId = op.onesignalId; + subscriptionModels.push(pushModel); } this._identityModelStore.replace(identityModel, ModelChangeTags.HYDRATE); diff --git a/src/core/executors/SubscriptionOperationExecutor.test.ts b/src/core/executors/SubscriptionOperationExecutor.test.ts index d73c69299..07cd68436 100644 --- a/src/core/executors/SubscriptionOperationExecutor.test.ts +++ b/src/core/executors/SubscriptionOperationExecutor.test.ts @@ -1,14 +1,16 @@ import { APP_ID, DUMMY_ONESIGNAL_ID, + DUMMY_PUSH_TOKEN, DUMMY_SUBSCRIPTION_ID_3, } from '__test__/constants'; +import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { createPushSub } from '__test__/support/environment/TestEnvironmentHelpers'; import { SomeOperation } from '__test__/support/helpers/executors'; import { server } from '__test__/support/mocks/server'; import { http, HttpResponse } from 'msw'; import { db } from 'src/shared/database/client'; -import { getPushId, setPushId } from 'src/shared/database/subscription'; +import { setPushToken } from 'src/shared/database/subscription'; import { NotificationType, SubscriptionType, @@ -45,13 +47,17 @@ vi.mock('src/shared/libraries/Log'); const pushSubscription = createPushSub(); describe('SubscriptionOperationExecutor', () => { + beforeAll(async () => { + await TestEnvironment.initialize(); + }); + beforeEach(async () => { - subscriptionModelStore = new SubscriptionModelStore(); - newRecordsState = new NewRecordsState(); + subscriptionModelStore = OneSignal.coreDirector.subscriptionModelStore; + newRecordsState = OneSignal.coreDirector.newRecordsState; - identityModelStore = new IdentityModelStore(); - propertiesModelStore = new PropertiesModelStore(); - subscriptionsModelStore = new SubscriptionModelStore(); + identityModelStore = OneSignal.coreDirector.identityModelStore; + propertiesModelStore = OneSignal.coreDirector.propertiesModelStore; + subscriptionsModelStore = OneSignal.coreDirector.subscriptionModelStore; identityModelStore.model.onesignalId = DUMMY_ONESIGNAL_ID; buildUserService = new RebuildUserService( @@ -134,10 +140,11 @@ describe('SubscriptionOperationExecutor', () => { test('should create subscription successfully', async () => { const model = new SubscriptionModel(); model.setProperty('id', DUMMY_SUBSCRIPTION_ID, ModelChangeTags.HYDRATE); + model.setProperty('token', DUMMY_PUSH_TOKEN, ModelChangeTags.HYDRATE); subscriptionModelStore.add(model); setCreateSubscriptionResponse(BACKEND_SUBSCRIPTION_ID); - await setPushId(DUMMY_SUBSCRIPTION_ID); + await setPushToken(DUMMY_PUSH_TOKEN); const executor = getExecutor(); const createOp = new CreateSubscriptionOperation({ @@ -159,7 +166,6 @@ describe('SubscriptionOperationExecutor', () => { }); // Verify models were updated - await expect(getPushId()).resolves.toBe(BACKEND_SUBSCRIPTION_ID); const subscriptionModel = subscriptionModelStore.getBySubscriptionId( BACKEND_SUBSCRIPTION_ID, ); @@ -280,7 +286,7 @@ describe('SubscriptionOperationExecutor', () => { // Missing error with rebuild ops subscriptionsModelStore.add(pushSubscription); - await setPushId(DUMMY_SUBSCRIPTION_ID_3); + await setPushToken(pushSubscription.token); const res6 = await executor.execute([createOp]); expect(res6).toMatchObject({ diff --git a/src/core/executors/SubscriptionOperationExecutor.ts b/src/core/executors/SubscriptionOperationExecutor.ts index 3601d5f91..2653a6bd6 100644 --- a/src/core/executors/SubscriptionOperationExecutor.ts +++ b/src/core/executors/SubscriptionOperationExecutor.ts @@ -3,11 +3,6 @@ import { type IOperationExecutor, } from 'src/core/types/operation'; import type { IRebuildUserService } from 'src/core/types/user'; -import { - getPushId, - setPushId, - setPushToken, -} from 'src/shared/database/subscription'; import { getResponseStatusType, ResponseStatusType, @@ -141,12 +136,6 @@ export class SubscriptionOperationExecutor implements IOperationExecutor { ); } - const pushSubscriptionId = await getPushId(); - if (pushSubscriptionId === createOperation.subscriptionId) { - await setPushId(backendSubscriptionId); - await setPushToken(subscription?.token); - } - return new ExecutionResponse( ExecutionResult.SUCCESS, undefined, diff --git a/src/core/executors/UpdateUserOperationExecutor.test.ts b/src/core/executors/UpdateUserOperationExecutor.test.ts index f9d0e663b..9a395769b 100644 --- a/src/core/executors/UpdateUserOperationExecutor.test.ts +++ b/src/core/executors/UpdateUserOperationExecutor.test.ts @@ -1,4 +1,5 @@ import { APP_ID, DUMMY_ONESIGNAL_ID } from '__test__/constants'; +import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { SomeOperation } from '__test__/support/helpers/executors'; import { setUpdateUserError, @@ -25,12 +26,15 @@ let getRebuildOpsSpy: MockInstance; vi.mock('src/shared/libraries/Log'); describe('UpdateUserOperationExecutor', () => { - beforeEach(() => { - identityModelStore = new IdentityModelStore(); - propertiesModelStore = new PropertiesModelStore(); - newRecordsState = new NewRecordsState(); + beforeAll(async () => { + await TestEnvironment.initialize(); + }); - subscriptionsModelStore = new SubscriptionModelStore(); + beforeEach(() => { + identityModelStore = OneSignal.coreDirector.identityModelStore; + propertiesModelStore = OneSignal.coreDirector.propertiesModelStore; + newRecordsState = OneSignal.coreDirector.newRecordsState; + subscriptionsModelStore = OneSignal.coreDirector.subscriptionModelStore; buildUserService = new RebuildUserService( identityModelStore, propertiesModelStore, diff --git a/src/core/modelRepo/ModelStore.ts b/src/core/modelRepo/ModelStore.ts index d310bc8fe..be2f930d0 100644 --- a/src/core/modelRepo/ModelStore.ts +++ b/src/core/modelRepo/ModelStore.ts @@ -9,7 +9,7 @@ import { type ModelChangeTagValue, } from 'src/core/types/models'; import { db } from 'src/shared/database/client'; -import type { IndexedDBSchema, ModelNameType } from 'src/shared/database/types'; +import type { IDBStoreName, IndexedDBSchema } from 'src/shared/database/types'; import { EventProducer } from 'src/shared/helpers/EventProducer'; import type { IModelChangedHandler, @@ -43,7 +43,7 @@ export abstract class ModelStore< IModelStore, IModelChangedHandler { - public readonly modelName: ModelNameType; + public readonly modelName: IDBStoreName; private changeSubscription: EventProducer> = new EventProducer(); private models: TModel[] = []; @@ -52,7 +52,7 @@ export abstract class ModelStore< /** * @param modelName The persistable name of the model store. If not specified no persisting will occur. */ - constructor(modelName: ModelNameType) { + constructor(modelName: IDBStoreName) { this.modelName = modelName; } diff --git a/src/core/modelRepo/RebuildUserService.ts b/src/core/modelRepo/RebuildUserService.ts index 1ebc64473..6d378aee1 100644 --- a/src/core/modelRepo/RebuildUserService.ts +++ b/src/core/modelRepo/RebuildUserService.ts @@ -1,4 +1,3 @@ -import { getPushId } from 'src/shared/database/subscription'; import { IdentityModel } from '../models/IdentityModel'; import { PropertiesModel } from '../models/PropertiesModel'; import { SubscriptionModel } from '../models/SubscriptionModel'; @@ -54,11 +53,8 @@ export class RebuildUserService implements IRebuildUserService { new LoginUserOperation(appId, onesignalId, identityModel.externalId), ); - const pushSubscriptionId = await getPushId(); - const pushSubscription = subscriptionModels.find( - (s) => s.id === pushSubscriptionId, - ); - + const pushSubscription = + await OneSignal.coreDirector.getPushSubscriptionModel(); if (pushSubscription) { operations.push( new CreateSubscriptionOperation({ diff --git a/src/core/modelStores/SimpleModelStore.ts b/src/core/modelStores/SimpleModelStore.ts index c11dc6696..4aa25f245 100644 --- a/src/core/modelStores/SimpleModelStore.ts +++ b/src/core/modelStores/SimpleModelStore.ts @@ -1,7 +1,7 @@ import { ModelStore } from 'src/core/modelRepo/ModelStore'; import { Model } from 'src/core/models/Model'; import type { DatabaseModel } from 'src/core/types/models'; -import type { ModelNameType } from 'src/shared/database/types'; +import type { IDBStoreName } from 'src/shared/database/types'; // Implements logic similar to Android SDK's SimpleModelStore // Reference: https://github.com/OneSignal/OneSignal-Android-SDK/blob/5.1.31/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/SimpleModelStore.kt @@ -16,7 +16,7 @@ export class SimpleModelStore extends ModelStore { * @param _create A factory function used to instantiate a new model instance. * @param modelName Name for persistence. */ - constructor(_create: () => TModel, modelName: ModelNameType) { + constructor(_create: () => TModel, modelName: IDBStoreName) { super(modelName); this._create = _create; this.load(); // Automatically load on construction diff --git a/src/onesignal/OneSignal.test.ts b/src/onesignal/OneSignal.test.ts index 09ea78bf0..c932d29e5 100644 --- a/src/onesignal/OneSignal.test.ts +++ b/src/onesignal/OneSignal.test.ts @@ -66,7 +66,16 @@ const getIdentityItem = async ( return identity; }; -const getPropertiesItem = async () => (await db.getAll('properties'))?.[0]; +const getDbSubscriptions = async (length: number) => { + let subscriptions: SubscriptionSchema[] = []; + await vi.waitUntil(async () => { + subscriptions = await db.getAll('subscriptions'); + return subscriptions.length === length; + }); + return subscriptions; +}; + +const getPropertiesItem = async () => (await db.getAll('properties'))[0]; const setupIdentity = async () => { await db.put('identity', { @@ -785,15 +794,7 @@ describe('OneSignal', () => { }, }); - await waitForOperations(5); - - let dbSubscriptions: SubscriptionSchema[] = []; - await vi.waitUntil(async () => { - dbSubscriptions = await db.getAll<'subscriptions'>('subscriptions'); - return dbSubscriptions.length === 3; - }); - - expect(dbSubscriptions).toHaveLength(3); + const dbSubscriptions = await getDbSubscriptions(3); const emailSubscriptions = dbSubscriptions.filter( (s) => s.type === 'Email', diff --git a/src/shared/database/types.ts b/src/shared/database/types.ts index 5ebb4ec2d..6f1b35d8c 100644 --- a/src/shared/database/types.ts +++ b/src/shared/database/types.ts @@ -7,9 +7,6 @@ import type { import type { AppState } from '../models/AppState'; import type { SentUniqueOutcome } from '../models/Outcomes'; import type { Session } from '../session/types'; -import { ModelName } from './constants'; - -export type ModelNameType = (typeof ModelName)[keyof typeof ModelName]; export type IdKey = 'appId' | 'registrationId' | 'userId' | 'jwtToken'; diff --git a/src/shared/listeners.ts b/src/shared/listeners.ts index 5450e12ac..00c13bf22 100644 --- a/src/shared/listeners.ts +++ b/src/shared/listeners.ts @@ -34,14 +34,11 @@ export async function checkAndTriggerSubscriptionChanged() { lastKnownPushToken, lastKnownOptedIn, } = appState; - const currentPushToken = await MainHelper.getCurrentPushToken(); const pushModel = await OneSignal.coreDirector.getPushSubscriptionModel(); const pushSubscriptionId = pushModel?.id; - const didStateChange = - lastKnownPushEnabled === null || isPushEnabled !== lastKnownPushEnabled || currentPushToken !== lastKnownPushToken || pushSubscriptionId !== lastKnownPushId; From 58cf9118359e262b0b7c75bbf4cc300022a5d6c7 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Fri, 15 Aug 2025 15:02:36 -0700 Subject: [PATCH 2/4] add more test --- __test__/support/helpers/database.ts | 5 + .../LoginUserOperationExecutor.test.ts | 14 +- .../RefreshUserOperationExecutor.test.ts | 10 +- src/shared/helpers/init.test.ts | 4 +- src/shared/listeners.test.ts | 121 +++++++++++++++++- src/shared/listeners.ts | 1 + 6 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 __test__/support/helpers/database.ts diff --git a/__test__/support/helpers/database.ts b/__test__/support/helpers/database.ts new file mode 100644 index 000000000..76ca0cba0 --- /dev/null +++ b/__test__/support/helpers/database.ts @@ -0,0 +1,5 @@ +import { db } from 'src/shared/database/client'; + +export const setIsPushEnabled = async (isPushEnabled: boolean) => { + await db.put('Options', { key: 'isPushEnabled', value: isPushEnabled }); +}; diff --git a/src/core/executors/LoginUserOperationExecutor.test.ts b/src/core/executors/LoginUserOperationExecutor.test.ts index 52443020b..4a5192eea 100644 --- a/src/core/executors/LoginUserOperationExecutor.test.ts +++ b/src/core/executors/LoginUserOperationExecutor.test.ts @@ -164,8 +164,13 @@ describe('LoginUserOperationExecutor', () => { identityModelStore.model.setProperty( IdentityConstants.ONESIGNAL_ID, DUMMY_ONESIGNAL_ID, + ModelChangeTags.HYDRATE, + ); + propertiesModelStore.model.setProperty( + 'onesignalId', + DUMMY_ONESIGNAL_ID, + ModelChangeTags.HYDRATE, ); - propertiesModelStore.model.setProperty('onesignalId', DUMMY_ONESIGNAL_ID); await setPushToken(DUMMY_PUSH_TOKEN); const subscriptionModel = new SubscriptionModel(); @@ -252,8 +257,13 @@ describe('LoginUserOperationExecutor', () => { identityModelStore.model.setProperty( IdentityConstants.ONESIGNAL_ID, DUMMY_ONESIGNAL_ID, + ModelChangeTags.HYDRATE, + ); + propertiesModelStore.model.setProperty( + 'onesignalId', + DUMMY_ONESIGNAL_ID, + ModelChangeTags.HYDRATE, ); - propertiesModelStore.model.setProperty('onesignalId', DUMMY_ONESIGNAL_ID); const executor = getExecutor(); diff --git a/src/core/executors/RefreshUserOperationExecutor.test.ts b/src/core/executors/RefreshUserOperationExecutor.test.ts index 5134c271e..ce862862f 100644 --- a/src/core/executors/RefreshUserOperationExecutor.test.ts +++ b/src/core/executors/RefreshUserOperationExecutor.test.ts @@ -12,6 +12,7 @@ import { SomeOperation } from '__test__/support/helpers/executors'; import { setGetUserError, setGetUserResponse, + setUpdateSubscriptionResponse, } from '__test__/support/helpers/requests'; import { clearAll } from 'src/shared/database/client'; import { setPushToken } from 'src/shared/database/subscription'; @@ -28,6 +29,7 @@ import { PropertiesModelStore } from '../modelStores/PropertiesModelStore'; import type { SubscriptionModelStore } from '../modelStores/SubscriptionModelStore'; import { NewRecordsState } from '../operationRepo/NewRecordsState'; import { RefreshUserOperation } from '../operations/RefreshUserOperation'; +import { ModelChangeTags } from '../types/models'; import { ExecutionResult } from '../types/operation'; import { RefreshUserOperationExecutor } from './RefreshUserOperationExecutor'; @@ -60,6 +62,11 @@ describe('RefreshUserOperationExecutor', () => { buildUserService, 'getRebuildOperationsIfCurrentUser', ); + + setUpdateSubscriptionResponse({ + subscriptionId: '*', + response: {}, + }); }); const getExecutor = () => { @@ -96,6 +103,7 @@ describe('RefreshUserOperationExecutor', () => { identityModelStore.model.setProperty( IdentityConstants.ONESIGNAL_ID, DUMMY_ONESIGNAL_ID, + ModelChangeTags.HYDRATE, ); }); @@ -186,7 +194,7 @@ describe('RefreshUserOperationExecutor', () => { pushSubModel.token = DUMMY_PUSH_TOKEN; pushSubModel.notification_types = NotificationType.Subscribed; - subscriptionModelStore.add(pushSubModel); + subscriptionModelStore.add(pushSubModel, ModelChangeTags.HYDRATE); await setPushToken(DUMMY_PUSH_TOKEN); const executor = getExecutor(); diff --git a/src/shared/helpers/init.test.ts b/src/shared/helpers/init.test.ts index c4698caaa..a39cb63a3 100644 --- a/src/shared/helpers/init.test.ts +++ b/src/shared/helpers/init.test.ts @@ -2,11 +2,11 @@ import TestContext from '__test__/support/environment/TestContext'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import Context from 'src/page/models/Context'; import { type AppConfig } from 'src/shared/config/types'; -import type { Mock } from 'vitest'; +import type { MockInstance } from 'vitest'; import { db } from '../database/client'; import * as InitHelper from './init'; -let isSubscriptionExpiringSpy: Mock; +let isSubscriptionExpiringSpy: MockInstance; beforeEach(async () => { await TestEnvironment.initialize(); diff --git a/src/shared/listeners.test.ts b/src/shared/listeners.test.ts index b35eb03d1..7bfb5c591 100644 --- a/src/shared/listeners.test.ts +++ b/src/shared/listeners.test.ts @@ -1,16 +1,127 @@ +import { DUMMY_PUSH_TOKEN } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; -import OneSignal from 'src/onesignal/OneSignal'; -import type { Mock } from 'vitest'; +import { createPushSub } from '__test__/support/environment/TestEnvironmentHelpers'; +import { setIsPushEnabled } from '__test__/support/helpers/database'; +import { + getSubscriptionFn, + MockServiceWorker, +} from '__test__/support/mocks/MockServiceWorker'; +import * as eventListeners from 'src/shared/listeners'; +import type { MockInstance } from 'vitest'; +import { getAppState } from './database/config'; +import { setPushToken } from './database/subscription'; -let emitterSpy: Mock; +Object.defineProperty(global.navigator, 'serviceWorker', { + value: new MockServiceWorker(), + writable: true, +}); + +let emitterSpy: MockInstance; beforeEach(async () => { await TestEnvironment.initialize(); emitterSpy = vi.spyOn(OneSignal.emitter, 'on'); }); -afterEach(() => { - vi.resetAllMocks(); +describe('checkAndTriggerSubscriptionChanged', () => { + test('should trigger subscription changed', async () => { + const changeListener = vi.fn(); + OneSignal.User.PushSubscription.addEventListener('change', changeListener); + + // no change + getSubscriptionFn.mockResolvedValue({ + endpoint: undefined, + }); + await setIsPushEnabled(false); + await eventListeners.checkAndTriggerSubscriptionChanged(); + + expect(changeListener).not.toHaveBeenCalled(); + + // push enabled change + await setIsPushEnabled(true); // mimic old opt in + changeListener.mockClear(); + await eventListeners.checkAndTriggerSubscriptionChanged(); + + expect(changeListener).toHaveBeenCalledWith({ + current: { + id: undefined, + optedIn: false, + token: undefined, + }, + previous: { + id: undefined, + optedIn: true, + token: undefined, + }, + }); + expect(await getAppState()).toMatchObject({ + lastKnownOptedIn: false, + lastKnownPushEnabled: false, + lastKnownPushToken: undefined, + lastKnownPushId: undefined, + }); + + // token change + getSubscriptionFn.mockResolvedValue({ + endpoint: DUMMY_PUSH_TOKEN, + }); + await setIsPushEnabled(false); + + changeListener.mockClear(); + await eventListeners.checkAndTriggerSubscriptionChanged(); + + expect(changeListener).toHaveBeenCalledWith({ + current: { + id: undefined, + optedIn: false, + token: DUMMY_PUSH_TOKEN, + }, + previous: { + id: undefined, + optedIn: false, + token: undefined, + }, + }); + expect(await getAppState()).toMatchObject({ + lastKnownOptedIn: false, + lastKnownPushEnabled: false, + lastKnownPushToken: DUMMY_PUSH_TOKEN, + lastKnownPushId: undefined, + }); + + // id change + const token = 'some-token'; + const newID = 'some-id'; + const pushModel = createPushSub({ + id: newID, + token, + }); + await setPushToken(token); + OneSignal.coreDirector.subscriptionModelStore.add(pushModel); + getSubscriptionFn.mockResolvedValue({ + endpoint: token, + }); + + await eventListeners.checkAndTriggerSubscriptionChanged(); + expect(changeListener).toHaveBeenCalledWith({ + current: { + id: newID, + optedIn: false, + token, + }, + previous: { + id: undefined, + optedIn: false, + token, + }, + }); + expect(await getAppState()).toMatchObject({ + lastKnownOptedIn: false, + lastKnownPushEnabled: false, + lastKnownPushToken: token, + lastKnownPushId: newID, + }); + }); }); test('Adding click listener fires internal EventHelper', async () => { diff --git a/src/shared/listeners.ts b/src/shared/listeners.ts index 00c13bf22..e68d5c616 100644 --- a/src/shared/listeners.ts +++ b/src/shared/listeners.ts @@ -38,6 +38,7 @@ export async function checkAndTriggerSubscriptionChanged() { const pushModel = await OneSignal.coreDirector.getPushSubscriptionModel(); const pushSubscriptionId = pushModel?.id; + const didStateChange = isPushEnabled !== lastKnownPushEnabled || currentPushToken !== lastKnownPushToken || From 6ef60ec8258f154b1499dc4377baa93d5919984c Mon Sep 17 00:00:00 2001 From: Fadi George Date: Fri, 15 Aug 2025 15:13:15 -0700 Subject: [PATCH 3/4] add test case for login then accept permissions --- .../notifications/subscriptionmanager.test.ts | 32 - .../LoginUserOperationExecutor.test.ts | 12 +- .../SubscriptionOperationExecutor.test.ts | 8 +- .../UpdateUserOperationExecutor.test.ts | 2 + src/onesignal/OneSignal.test.ts | 615 +++++++++++------- src/shared/database/config.ts | 9 +- src/shared/database/types.ts | 18 +- src/shared/listeners.test.ts | 23 +- .../managers/SubscriptionManager.test.ts | 45 +- 9 files changed, 469 insertions(+), 295 deletions(-) delete mode 100644 __test__/unit/notifications/subscriptionmanager.test.ts diff --git a/__test__/unit/notifications/subscriptionmanager.test.ts b/__test__/unit/notifications/subscriptionmanager.test.ts deleted file mode 100644 index 1149cfff8..000000000 --- a/__test__/unit/notifications/subscriptionmanager.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { SubscriptionManagerPage } from '../../../src/shared/managers/subscription/page'; -import { NotificationPermission } from '../../../src/shared/models/NotificationPermission'; -import MockNotification from '../../support/mocks/MockNotification'; - -describe('SubscriptionManagerPage', () => { - describe('requestNotificationPermission', () => { - beforeEach(() => { - window.Notification = MockNotification; - }); - - test('default', async () => { - MockNotification.permission = 'default'; - expect( - await SubscriptionManagerPage.requestNotificationPermission(), - ).toBe(NotificationPermission.Default); - }); - - test('denied', async () => { - MockNotification.permission = 'denied'; - expect( - await SubscriptionManagerPage.requestNotificationPermission(), - ).toBe(NotificationPermission.Denied); - }); - - test('granted', async () => { - MockNotification.permission = 'granted'; - expect( - await SubscriptionManagerPage.requestNotificationPermission(), - ).toBe(NotificationPermission.Granted); - }); - }); -}); diff --git a/src/core/executors/LoginUserOperationExecutor.test.ts b/src/core/executors/LoginUserOperationExecutor.test.ts index 4a5192eea..c36cb6776 100644 --- a/src/core/executors/LoginUserOperationExecutor.test.ts +++ b/src/core/executors/LoginUserOperationExecutor.test.ts @@ -174,14 +174,22 @@ describe('LoginUserOperationExecutor', () => { await setPushToken(DUMMY_PUSH_TOKEN); const subscriptionModel = new SubscriptionModel(); - subscriptionModel.setProperty('id', DUMMY_SUBSCRIPTION_ID); + subscriptionModel.setProperty( + 'id', + DUMMY_SUBSCRIPTION_ID, + ModelChangeTags.HYDRATE, + ); subscriptionModelStore.add(subscriptionModel, ModelChangeTags.HYDRATE); // perform operations with old onesignal id const executor = getExecutor(); const loginOp = new LoginUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); - loginOp.setProperty('externalId', DUMMY_EXTERNAL_ID); + loginOp.setProperty( + 'externalId', + DUMMY_EXTERNAL_ID, + ModelChangeTags.HYDRATE, + ); const createSubOp = new CreateSubscriptionOperation( mockSubscriptionOpInfo, diff --git a/src/core/executors/SubscriptionOperationExecutor.test.ts b/src/core/executors/SubscriptionOperationExecutor.test.ts index 07cd68436..547e22c05 100644 --- a/src/core/executors/SubscriptionOperationExecutor.test.ts +++ b/src/core/executors/SubscriptionOperationExecutor.test.ts @@ -52,14 +52,20 @@ describe('SubscriptionOperationExecutor', () => { }); beforeEach(async () => { + setCreateSubscriptionResponse(); subscriptionModelStore = OneSignal.coreDirector.subscriptionModelStore; newRecordsState = OneSignal.coreDirector.newRecordsState; + newRecordsState.records.clear(); identityModelStore = OneSignal.coreDirector.identityModelStore; propertiesModelStore = OneSignal.coreDirector.propertiesModelStore; subscriptionsModelStore = OneSignal.coreDirector.subscriptionModelStore; - identityModelStore.model.onesignalId = DUMMY_ONESIGNAL_ID; + identityModelStore.model.setProperty( + 'onesignal_id', + DUMMY_ONESIGNAL_ID, + ModelChangeTags.HYDRATE, + ); buildUserService = new RebuildUserService( identityModelStore, propertiesModelStore, diff --git a/src/core/executors/UpdateUserOperationExecutor.test.ts b/src/core/executors/UpdateUserOperationExecutor.test.ts index 9a395769b..5635f4363 100644 --- a/src/core/executors/UpdateUserOperationExecutor.test.ts +++ b/src/core/executors/UpdateUserOperationExecutor.test.ts @@ -13,6 +13,7 @@ import { PropertiesModelStore } from '../modelStores/PropertiesModelStore'; import { SubscriptionModelStore } from '../modelStores/SubscriptionModelStore'; import { NewRecordsState } from '../operationRepo/NewRecordsState'; import { SetPropertyOperation } from '../operations/SetPropertyOperation'; +import { ModelChangeTags } from '../types/models'; import { ExecutionResult } from '../types/operation'; import { UpdateUserOperationExecutor } from './UpdateUserOperationExecutor'; @@ -49,6 +50,7 @@ describe('UpdateUserOperationExecutor', () => { identityModelStore.model.setProperty( IdentityConstants.ONESIGNAL_ID, DUMMY_ONESIGNAL_ID, + ModelChangeTags.HYDRATE, ); }); diff --git a/src/onesignal/OneSignal.test.ts b/src/onesignal/OneSignal.test.ts index c932d29e5..808c72d8d 100644 --- a/src/onesignal/OneSignal.test.ts +++ b/src/onesignal/OneSignal.test.ts @@ -35,55 +35,26 @@ import { transferSubscriptionFn, updateUserFn, } from '__test__/support/helpers/requests'; +import { MockServiceWorker } from '__test__/support/mocks/MockServiceWorker'; import { server } from '__test__/support/mocks/server'; import { IdentityModel } from 'src/core/models/IdentityModel'; import { PropertiesModel } from 'src/core/models/PropertiesModel'; import { OperationQueueItem } from 'src/core/operationRepo/OperationRepo'; import { type ICreateUserSubscription } from 'src/core/types/api'; import { ModelChangeTags } from 'src/core/types/models'; -import { db } from 'src/shared/database/client'; +import { clearAll, db } from 'src/shared/database/client'; +import { setPushToken } from 'src/shared/database/subscription'; import type { - IndexedDBSchema, + IdentitySchema, SubscriptionSchema, } from 'src/shared/database/types'; +import { registerForPushNotifications } from 'src/shared/helpers/init'; import { setConsentRequired } from 'src/shared/helpers/localStorage'; +import MainHelper from 'src/shared/helpers/MainHelper'; import Log from 'src/shared/libraries/Log'; import { IDManager } from 'src/shared/managers/IDManager'; - -const errorSpy = vi.spyOn(Log, 'error').mockImplementation(() => ''); -const debugSpy = vi.spyOn(Log, 'debug'); - -type IdentityItem = IndexedDBSchema['identity']['value']; - -const getIdentityItem = async ( - condition: (identity: IdentityItem) => boolean = () => true, -) => { - let identity: IdentityItem | undefined; - await vi.waitUntil(async () => { - identity = (await db.getAll('identity'))?.[0]; - return identity && condition(identity); - }); - return identity; -}; - -const getDbSubscriptions = async (length: number) => { - let subscriptions: SubscriptionSchema[] = []; - await vi.waitUntil(async () => { - subscriptions = await db.getAll('subscriptions'); - return subscriptions.length === length; - }); - return subscriptions; -}; - -const getPropertiesItem = async () => (await db.getAll('properties'))[0]; - -const setupIdentity = async () => { - await db.put('identity', { - modelId: '123', - modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, - }); -}; +import { SubscriptionManagerPage } from 'src/shared/managers/subscription/page'; +import { RawPushSubscription } from 'src/shared/models/RawPushSubscription'; describe('OneSignal', () => { beforeAll(async () => { @@ -114,8 +85,8 @@ describe('OneSignal', () => { }); afterEach(async () => { + await clearAll(); window.OneSignal.coreDirector.operationRepo.queue = []; - await db.clear('operations'); window.OneSignal.coreDirector.subscriptionModelStore.replaceAll( [], ModelChangeTags.HYDRATE, @@ -137,7 +108,7 @@ describe('OneSignal', () => { expect(identityModel.getProperty('someLabel')).toBe('someId'); // should make a request to the backend - await waitForOperations(3); + await vi.waitUntil(() => addAliasFn.mock.calls.length === 1); expect(addAliasFn).toHaveBeenCalledWith({ identity: { someLabel: 'someId', @@ -391,187 +362,183 @@ describe('OneSignal', () => { describe('login', () => { const externalId = 'jd-1'; - beforeEach(async () => { - setAddAliasResponse(); - addAliasFn.mockClear(); + describe('login user', () => { + beforeEach(async () => { + setAddAliasResponse(); + addAliasFn.mockClear(); - await setupSubModelStore({ - id: DUMMY_SUBSCRIPTION_ID, - token: 'abc123', + await setupSubModelStore({ + id: DUMMY_SUBSCRIPTION_ID, + token: 'abc123', + }); }); - }); - test('should validate external id', async () => { - // @ts-expect-error - testing invalid argument - await expect(window.OneSignal.login()).rejects.toThrowError( - '"externalId" is empty', - ); + test('should validate external id', async () => { + // @ts-expect-error - testing invalid argument + await expect(window.OneSignal.login()).rejects.toThrowError( + '"externalId" is empty', + ); - // @ts-expect-error - testing invalid argument - await expect(window.OneSignal.login(null)).rejects.toThrowError( - '"externalId" is the wrong type', - ); + // @ts-expect-error - testing invalid argument + await expect(window.OneSignal.login(null)).rejects.toThrowError( + '"externalId" is the wrong type', + ); - // @ts-expect-error - testing invalid argument - await expect(window.OneSignal.login('', 1)).rejects.toThrowError( - '"jwtToken" is the wrong type', - ); + // @ts-expect-error - testing invalid argument + await expect(window.OneSignal.login('', 1)).rejects.toThrowError( + '"jwtToken" is the wrong type', + ); - // TODO: add consent required test - // if needing consent required - setConsentRequired(true); - await window.OneSignal.login(externalId); - await vi.waitUntil(() => errorSpy.mock.calls.length === 1); + // TODO: add consent required test + // if needing consent required + setConsentRequired(true); + await window.OneSignal.login(externalId); + await vi.waitUntil(() => errorSpy.mock.calls.length === 1); - const error = errorSpy.mock.calls[0][1] as Error; - expect(error.message).toBe('Consent required but not given'); - }); + const error = errorSpy.mock.calls[0][1] as Error; + expect(error.message).toBe('Consent required but not given'); + }); - test('can login with a new external id', async () => { - setTransferSubscriptionResponse(); - let identityData = await getIdentityItem(); - await window.OneSignal.login(externalId); + test('can login with a new external id', async () => { + setTransferSubscriptionResponse(); + let identityData = await getIdentityItem(); + await window.OneSignal.login(externalId); - // should not change the identity in the IndexedDB right away - expect(identityData).toEqual({ - modelId: expect.any(String), - modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, - }); + // should not change the identity in the IndexedDB right away + expect(identityData).toEqual({ + modelId: expect.any(String), + modelName: 'identity', + onesignal_id: DUMMY_ONESIGNAL_ID, + }); - // wait for login user operation to complete - expect(addAliasFn).toHaveBeenCalledWith({ - identity: { + // wait for login user operation to complete + expect(addAliasFn).toHaveBeenCalledWith({ + identity: { + external_id: externalId, + }, + }); + + // should also update the identity in the IndexedDB + identityData = await getIdentityItem( + (i) => i.onesignal_id === DUMMY_ONESIGNAL_ID, + ); + expect(identityData).toEqual({ external_id: externalId, - }, - }); + modelId: expect.any(String), + modelName: 'identity', + onesignal_id: DUMMY_ONESIGNAL_ID, + }); - // should also update the identity in the IndexedDB - identityData = await getIdentityItem( - (i) => i.onesignal_id === DUMMY_ONESIGNAL_ID, - ); - expect(identityData).toEqual({ - external_id: externalId, - modelId: expect.any(String), - modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, + const identityModel = + window.OneSignal.coreDirector.getIdentityModel(); + expect(identityModel.externalId).toBe(externalId); + + await waitForOperations(); + expect(transferSubscriptionFn).toHaveBeenCalled(); }); - const identityModel = window.OneSignal.coreDirector.getIdentityModel(); - expect(identityModel.externalId).toBe(externalId); + test('login twice with same user -> only one call to identify user', async () => { + setTransferSubscriptionResponse(); + await window.OneSignal.login(externalId); + await window.OneSignal.login(externalId); - await waitForOperations(); - expect(transferSubscriptionFn).toHaveBeenCalled(); - }); + expect(addAliasFn).toHaveBeenCalledTimes(1); + expect(debugSpy).toHaveBeenCalledWith( + 'Login: External ID already set, skipping login', + ); + await waitForOperations(); + expect(transferSubscriptionFn).toHaveBeenCalledTimes(1); + }); - test('Login twice with same user -> only one call to identify user', async () => { - setTransferSubscriptionResponse(); - await window.OneSignal.login(externalId); - await window.OneSignal.login(externalId); + test('login twice with different user -> logs in to second user', async () => { + const newExternalId = 'jd-2'; + setCreateUserResponse({ + externalId: newExternalId, + }); + setGetUserResponse({ + externalId: newExternalId, + }); + setTransferSubscriptionResponse(); - expect(addAliasFn).toHaveBeenCalledTimes(1); - expect(debugSpy).toHaveBeenCalledWith( - 'Login: External ID already set, skipping login', - ); - await waitForOperations(); - expect(transferSubscriptionFn).toHaveBeenCalledTimes(1); - }); + await window.OneSignal.login(externalId); // should call set alias + expect(addAliasFn).toHaveBeenCalledWith({ + identity: { + external_id: externalId, + }, + }); - test('Login twice with different user -> logs in to second user', async () => { - const newExternalId = 'jd-2'; - setCreateUserResponse({ - externalId: newExternalId, - }); - setGetUserResponse({ - externalId: newExternalId, - }); - setTransferSubscriptionResponse(); + await window.OneSignal.login(newExternalId); // should call create user + expect(createUserFn).toHaveBeenCalledWith({ + identity: { + external_id: newExternalId, + }, + ...baseIdentity, + subscriptions: [ + { + id: DUMMY_SUBSCRIPTION_ID, + }, + ], + }); - await window.OneSignal.login(externalId); // should call set alias - expect(addAliasFn).toHaveBeenCalledWith({ - identity: { - external_id: externalId, - }, - }); + await waitForOperations(3); // should call refresh user op + expect(getUserFn).toHaveBeenCalledWith(); - await window.OneSignal.login(newExternalId); // should call create user - expect(createUserFn).toHaveBeenCalledWith({ - identity: { + const identityData = await getIdentityItem(); + expect(identityData).toEqual({ external_id: newExternalId, - }, - properties: { - language: 'en', - timezone_id: 'America/Los_Angeles', - }, - refresh_device_metadata: true, - subscriptions: [ - { - id: DUMMY_SUBSCRIPTION_ID, - }, - ], - }); - - await waitForOperations(3); // should call refresh user op - expect(getUserFn).toHaveBeenCalledWith(); + modelId: expect.any(String), + modelName: 'identity', + onesignal_id: DUMMY_ONESIGNAL_ID, + }); - const identityData = await getIdentityItem(); - expect(identityData).toEqual({ - external_id: newExternalId, - modelId: expect.any(String), - modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, + const identityModel = + window.OneSignal.coreDirector.getIdentityModel(); + expect(identityModel.externalId).toBe(newExternalId); }); - const identityModel = window.OneSignal.coreDirector.getIdentityModel(); - expect(identityModel.externalId).toBe(newExternalId); - }); + test('login conflict should keep old subscriptions', async () => { + setAddAliasError({ + status: 409, + }); + setCreateUserResponse({}); + setGetUserResponse({ + onesignalId: DUMMY_ONESIGNAL_ID, + newOnesignalId: DUMMY_ONESIGNAL_ID_2, + externalId, + subscriptions: [ + { + id: DUMMY_SUBSCRIPTION_ID_2, + type: 'ChromePush', + token: 'def456', + }, + ], + }); - test('Login conflict should keep old subscriptions', async () => { - setAddAliasError({ - status: 409, - }); - setCreateUserResponse({}); - setGetUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, - newOnesignalId: DUMMY_ONESIGNAL_ID_2, - externalId, - subscriptions: [ - { - id: DUMMY_SUBSCRIPTION_ID_2, - type: 'ChromePush', - token: 'def456', + // calls create user with empty subscriptions + await window.OneSignal.login(externalId); + expect(createUserFn).toHaveBeenCalledWith({ + identity: { + external_id: externalId, }, - ], - }); + ...baseIdentity, + subscriptions: [ + { + id: DUMMY_SUBSCRIPTION_ID, + }, + ], + }); - // calls create user with empty subscriptions - await window.OneSignal.login(externalId); - expect(createUserFn).toHaveBeenCalledWith({ - identity: { + // calls refresh user + // onesignal id should be changed + const identityData = await getIdentityItem( + (i) => i.onesignal_id === DUMMY_ONESIGNAL_ID_2, + ); + expect(identityData).toEqual({ external_id: externalId, - }, - properties: { - language: 'en', - timezone_id: 'America/Los_Angeles', - }, - refresh_device_metadata: true, - subscriptions: [ - { - id: DUMMY_SUBSCRIPTION_ID, - }, - ], - }); - - // calls refresh user - // onesignal id should be changed - const identityData = await getIdentityItem( - (i) => i.onesignal_id === DUMMY_ONESIGNAL_ID_2, - ); - expect(identityData).toEqual({ - external_id: externalId, - modelId: expect.any(String), - modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID_2, + modelId: expect.any(String), + modelName: 'identity', + onesignal_id: DUMMY_ONESIGNAL_ID_2, + }); }); }); @@ -580,31 +547,20 @@ describe('OneSignal', () => { const sms = '+1234567890'; beforeEach(async () => { - await db.delete('subscriptions', DUMMY_SUBSCRIPTION_ID); - - setCreateSubscriptionResponse({ - response: { - id: DUMMY_SUBSCRIPTION_ID_2, - type: 'Email', - token: email, - }, - }); - - setCreateUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, - externalId, - }); + setAddAliasResponse(); + setTransferSubscriptionResponse(); + setCreateSubscriptionResponse(); - setGetUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, - externalId, + await db.clear('subscriptions'); + await setupSubModelStore({ + id: DUMMY_SUBSCRIPTION_ID, + token: DUMMY_PUSH_TOKEN, }); + setPushToken(DUMMY_PUSH_TOKEN); }); test('login before adding email and sms - it should create subscriptions with the external ID', async () => { - setTransferSubscriptionResponse(); setGetUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, externalId, subscriptions: [ { @@ -619,7 +575,6 @@ describe('OneSignal', () => { }, ], }); - await window.OneSignal.login(externalId); const identityData = await getIdentityItem(); @@ -657,11 +612,7 @@ describe('OneSignal', () => { }, }); - let dbSubscriptions: SubscriptionSchema[] = []; - await vi.waitUntil(async () => { - dbSubscriptions = await db.getAll<'subscriptions'>('subscriptions'); - return dbSubscriptions.length === 3; - }); + const dbSubscriptions = await getDbSubscriptions(3); const emailSubscriptions = dbSubscriptions.filter( (s) => s.type === 'Email', @@ -680,16 +631,8 @@ describe('OneSignal', () => { }); test('login without accepting web push permissions - it should create a new user without any subscriptions', async () => { - setCreateSubscriptionResponse({ - response: { - id: DUMMY_SUBSCRIPTION_ID, - type: 'ChromePush', - token: DUMMY_PUSH_TOKEN, - }, - }); - - await db.clear('subscriptions'); - + setGetUserResponse(); + setCreateUserResponse(); const identityModel = OneSignal.coreDirector.getIdentityModel(); identityModel.setProperty( 'external_id', @@ -708,7 +651,9 @@ describe('OneSignal', () => { ); await window.OneSignal.login(externalId); - const identityData = await getIdentityItem(); + const identityData = await getIdentityItem( + (i) => i.onesignal_id === DUMMY_ONESIGNAL_ID, + ); expect(identityData).toEqual({ external_id: externalId, modelId: expect.any(String), @@ -723,26 +668,21 @@ describe('OneSignal', () => { identity: { external_id: externalId, }, - properties: { - language: 'en', - timezone_id: 'America/Los_Angeles', - }, - refresh_device_metadata: true, + ...baseIdentity, subscriptions: [], }); }); test('login then add email, sms, and web push - all subscriptions should be created with the external ID', async () => { - setTransferSubscriptionResponse(); setUpdateSubscriptionResponse(); setGetUserResponse({ onesignalId: DUMMY_ONESIGNAL_ID, externalId, subscriptions: [ { - id: DUMMY_SUBSCRIPTION_ID, + id: 'some-other-push-id', type: 'ChromePush', - token: DUMMY_PUSH_TOKEN, + token: 'some-other-push-token', }, { id: DUMMY_SUBSCRIPTION_ID_2, @@ -813,6 +753,7 @@ describe('OneSignal', () => { }); test('login with a prior web push subscription - it should transfer the subscription', async () => { + setCreateUserResponse(); const identityModel = OneSignal.coreDirector.getIdentityModel(); identityModel.setProperty( 'onesignal_id', @@ -841,11 +782,7 @@ describe('OneSignal', () => { identity: { external_id: externalId, }, - properties: { - language: 'en', - timezone_id: 'America/Los_Angeles', - }, - refresh_device_metadata: true, + ...baseIdentity, subscriptions: [ { id: DUMMY_SUBSCRIPTION_ID, @@ -853,6 +790,92 @@ describe('OneSignal', () => { ], }); }); + + test('login then accept web push permissions - it should make two user calls', async () => { + OneSignal.coreDirector.subscriptionModelStore.replaceAll( + [], + ModelChangeTags.NO_PROPOGATE, + ); + setPushToken(''); + setGetUserResponse(); + subscribeFcmFromPageSpy.mockImplementation( + // @ts-expect-error - subscribeFcmFromPage is a private method of SubscriptionManagerPage + async () => rawPushSubscription, + ); + + setCreateUserResponse({ + onesignalId: DUMMY_ONESIGNAL_ID, + externalId, + subscriptions: [ + { + id: DUMMY_SUBSCRIPTION_ID, + }, + ], + }); + + // new/empty user + const newIdentity = new IdentityModel(); + OneSignal.coreDirector.identityModelStore.replace(newIdentity); + OneSignal.coreDirector.subscriptionModelStore.replaceAll( + [], + ModelChangeTags.NO_PROPOGATE, + ); + + // calling login before accept permissions + window.OneSignal.login(externalId); + + // slidedown manager calls this on allow click + // @ts-expect-error - Notification is not defined in the global scope + global.Notification = { + permission: 'granted', + }; + registerForPushNotifications(); + + // first call just sets the external id + await vi.waitUntil(() => createUserFn.mock.calls.length === 1, { + interval: 1, + }); + expect(createUserFn).toHaveBeenCalledWith({ + identity: { + external_id: externalId, + }, + ...baseIdentity, + subscriptions: [], + }); + + // second call creates the subscription + await vi.waitUntil(() => createUserFn.mock.calls.length === 2); + expect(createUserFn).toHaveBeenCalledWith({ + identity: { + external_id: externalId, + }, + ...baseIdentity, + subscriptions: [ + { + device_model: '', + device_os: DEVICE_OS, + enabled: true, + notification_types: 1, + sdk: __VERSION__, + token: DUMMY_PUSH_TOKEN, + type: 'ChromePush', + web_auth: 'w3cAuth', + web_p256: 'w3cP256dh', + }, + ], + }); + + // const dbSubscriptions = await getDbSubscriptions(1); + // console.dir(dbSubscriptions, { depth: null }); + let pushSub: SubscriptionSchema | undefined; + await vi.waitUntil( + async () => { + pushSub = (await db.getAll('subscriptions'))[0]; + return pushSub && !IDManager.isLocalId(pushSub.id); + }, + { interval: 1 }, + ); + }); }); }); @@ -1182,6 +1205,52 @@ describe('OneSignal', () => { }); }); + describe('Listeners', () => { + test('can listen for subscription changed event', async () => { + await clearAll(); + await db.put('Options', { + key: 'notificationPermission', + value: 'granted', + }); + + setCreateUserResponse({ + subscriptions: [ + { + id: DUMMY_SUBSCRIPTION_ID, + }, + ], + }); + + const changeEvent = vi.fn(); + OneSignal.User.PushSubscription.addEventListener('change', changeEvent); + + subscribeFcmFromPageSpy.mockImplementation( + // @ts-expect-error - subscribeFcmFromPage is a private method of SubscriptionManagerPage + async () => rawPushSubscription, + ); + + // @ts-expect-error - Notification is not defined in the global scope + global.Notification = { + permission: 'granted', + }; + registerForPushNotifications(); + + await vi.waitUntil(() => changeEvent.mock.calls.length === 1); + expect(changeEvent).toHaveBeenCalledWith({ + previous: { + id: undefined, + optedIn: true, + token: undefined, + }, + current: { + id: DUMMY_SUBSCRIPTION_ID, + optedIn: true, + token: DUMMY_PUSH_TOKEN, + }, + }); + }); + }); + test('should preserve operations order without needing await', async () => { await setupSubModelStore({ id: DUMMY_SUBSCRIPTION_ID, @@ -1240,3 +1309,67 @@ describe('OneSignal', () => { }); }); }); + +Object.defineProperty(global.navigator, 'serviceWorker', { + value: new MockServiceWorker(), + writable: true, +}); + +const errorSpy = vi.spyOn(Log, 'error').mockImplementation(() => ''); +const debugSpy = vi.spyOn(Log, 'debug'); + +const baseIdentity = { + properties: { + language: 'en', + timezone_id: 'America/Los_Angeles', + }, + refresh_device_metadata: true, +}; + +const rawPushSubscription = new RawPushSubscription(); +rawPushSubscription.w3cEndpoint = new URL(DUMMY_PUSH_TOKEN); +rawPushSubscription.w3cP256dh = 'w3cP256dh'; +rawPushSubscription.w3cAuth = 'w3cAuth'; +rawPushSubscription.safariDeviceToken = 'safariDeviceToken'; + +const getIdentityItem = async ( + condition: (identity: IdentitySchema) => boolean = () => true, +) => { + let identity: IdentitySchema | undefined; + await vi.waitUntil(async () => { + identity = (await db.getAll('identity'))?.[0]; + return identity && condition(identity); + }); + return identity; +}; + +const getDbSubscriptions = async (length: number) => { + let subscriptions: SubscriptionSchema[] = []; + await vi.waitUntil( + async () => { + subscriptions = await db.getAll('subscriptions'); + return subscriptions.length === length; + }, + { interval: 1 }, + ); + return subscriptions; +}; + +const getPropertiesItem = async () => (await db.getAll('properties'))[0]; + +const setupIdentity = async () => { + await db.put('identity', { + modelId: '123', + modelName: 'identity', + onesignal_id: DUMMY_ONESIGNAL_ID, + }); +}; + +const subscribeFcmFromPageSpy = vi.spyOn( + SubscriptionManagerPage.prototype, + // @ts-expect-error - subscribeFcmFromPage is a private method of SubscriptionManagerPage + 'subscribeFcmFromPage', +); + +const showLocalNotificationSpy = vi.spyOn(MainHelper, 'showLocalNotification'); +showLocalNotificationSpy.mockImplementation(async () => {}); diff --git a/src/shared/database/config.ts b/src/shared/database/config.ts index 27f8a11aa..ca581ffda 100644 --- a/src/shared/database/config.ts +++ b/src/shared/database/config.ts @@ -15,12 +15,15 @@ export const getAppState = async (): Promise => { state.defaultNotificationTitle = await getOptionsValue('defaultTitle'); state.lastKnownPushEnabled = await getOptionsValue('isPushEnabled'); + state.lastKnownOptedIn = await getOptionsValue('lastOptedIn'); // lastKnown are used to track changes to the user's subscription // state. Displayed in the `current` & `previous` fields of the `subscriptionChange` event. - state.lastKnownPushId = await getOptionsValue('lastPushId'); - state.lastKnownPushToken = await getOptionsValue('lastPushToken'); - state.lastKnownOptedIn = await getOptionsValue('lastOptedIn'); + // want undefined instead of null since its used to check for subscription changes + state.lastKnownPushId = + (await getOptionsValue('lastPushId')) ?? undefined; + state.lastKnownPushToken = + (await getOptionsValue('lastPushToken')) ?? undefined; return state; }; diff --git a/src/shared/database/types.ts b/src/shared/database/types.ts index 6f1b35d8c..1da3c1f00 100644 --- a/src/shared/database/types.ts +++ b/src/shared/database/types.ts @@ -57,6 +57,15 @@ export interface SubscriptionSchema { sdk?: string; } +export interface IdentitySchema { + modelId: string; + modelName: 'identity'; + onesignal_id?: string; + onesignalId?: string; + external_id?: string; + externalId?: string; +} + export interface IndexedDBSchema extends DBSchema { /** * @deprecated - should be migrated in openDB() @@ -143,14 +152,7 @@ export interface IndexedDBSchema extends DBSchema { identity: { key: string; - value: { - modelId: string; - modelName: 'identity'; - onesignal_id?: string; - onesignalId?: string; - external_id?: string; - externalId?: string; - }; + value: IdentitySchema; }; properties: { diff --git a/src/shared/listeners.test.ts b/src/shared/listeners.test.ts index 7bfb5c591..5f9b121c5 100644 --- a/src/shared/listeners.test.ts +++ b/src/shared/listeners.test.ts @@ -10,14 +10,16 @@ import * as eventListeners from 'src/shared/listeners'; import type { MockInstance } from 'vitest'; import { getAppState } from './database/config'; import { setPushToken } from './database/subscription'; - -Object.defineProperty(global.navigator, 'serviceWorker', { - value: new MockServiceWorker(), - writable: true, -}); +import { SubscriptionManagerPage } from './managers/subscription/page'; let emitterSpy: MockInstance; +// dont want to make a call to update notification types +vi.spyOn( + SubscriptionManagerPage.prototype, + 'updateNotificationTypes', +).mockImplementation(() => Promise.resolve()); + beforeEach(async () => { await TestEnvironment.initialize(); emitterSpy = vi.spyOn(OneSignal.emitter, 'on'); @@ -128,3 +130,14 @@ test('Adding click listener fires internal EventHelper', async () => { OneSignal.Notifications.addEventListener('click', () => {}); expect(emitterSpy).toHaveBeenCalledTimes(1); }); + +Object.defineProperty(global.navigator, 'serviceWorker', { + value: new MockServiceWorker(), + writable: true, +}); + +// dont want to make a call to update notification types +vi.spyOn( + SubscriptionManagerPage.prototype, + 'updateNotificationTypes', +).mockResolvedValue(); diff --git a/src/shared/managers/SubscriptionManager.test.ts b/src/shared/managers/SubscriptionManager.test.ts index be310c0f3..08bde42b9 100644 --- a/src/shared/managers/SubscriptionManager.test.ts +++ b/src/shared/managers/SubscriptionManager.test.ts @@ -4,15 +4,22 @@ import { setupSubModelStore } from '__test__/support/environment/TestEnvironment import { createUserFn, setCreateUserResponse, + setGetUserResponse, } from '__test__/support/helpers/requests'; +import MockNotification from '__test__/support/mocks/MockNotification'; import { getSubscriptionFn, MockServiceWorker, } from '__test__/support/mocks/MockServiceWorker'; +import { ModelChangeTags } from 'src/core/types/models'; import { setPushToken } from '../database/subscription'; +import { NotificationPermission } from '../models/NotificationPermission'; import { RawPushSubscription } from '../models/RawPushSubscription'; import { IDManager } from './IDManager'; -import { updatePushSubscriptionModelWithRawSubscription } from './subscription/page'; +import { + SubscriptionManagerPage, + updatePushSubscriptionModelWithRawSubscription, +} from './subscription/page'; const getRawSubscription = (): RawPushSubscription => { const rawSubscription = new RawPushSubscription(); @@ -100,8 +107,16 @@ describe('SubscriptionManager', () => { // create push sub with no id const identityModel = OneSignal.coreDirector.getIdentityModel(); - identityModel.onesignalId = IDManager.createLocalId(); - identityModel.externalId = 'some-external-id'; + identityModel.setProperty( + 'onesignal_id', + IDManager.createLocalId(), + ModelChangeTags.HYDRATE, + ); + identityModel.setProperty( + 'external_id', + 'some-external-id', + ModelChangeTags.HYDRATE, + ); await setupSubModelStore({ id: IDManager.createLocalId(), @@ -139,6 +154,7 @@ describe('SubscriptionManager', () => { test('should update the push subscription model if it already exists', async () => { setCreateUserResponse(); + setGetUserResponse(); const rawSubscription = getRawSubscription(); await setPushToken(rawSubscription.w3cEndpoint?.toString()); @@ -164,6 +180,29 @@ describe('SubscriptionManager', () => { }); }); +describe('SubscriptionManagerPage', () => { + test('default', async () => { + MockNotification.permission = 'default'; + expect(await SubscriptionManagerPage.requestNotificationPermission()).toBe( + NotificationPermission.Default, + ); + }); + + test('denied', async () => { + MockNotification.permission = 'denied'; + expect(await SubscriptionManagerPage.requestNotificationPermission()).toBe( + NotificationPermission.Denied, + ); + }); + + test('granted', async () => { + MockNotification.permission = 'granted'; + expect(await SubscriptionManagerPage.requestNotificationPermission()).toBe( + NotificationPermission.Granted, + ); + }); +}); + Object.defineProperty(global.navigator, 'serviceWorker', { value: new MockServiceWorker(), writable: true, From 8b488391a8ac7d83613f962052bd80c12166a640 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Fri, 15 Aug 2025 16:58:28 -0700 Subject: [PATCH 4/4] clean up more clean up --- __test__/constants/constants.ts | 65 +-- __test__/setupTests.ts | 24 +- .../support/environment/TestEnvironment.ts | 26 +- .../environment/TestEnvironmentHelpers.ts | 26 +- __test__/support/helpers/database.ts | 5 - __test__/support/helpers/executors.ts | 9 - __test__/support/helpers/requests.ts | 108 +++- __test__/support/helpers/setup.ts | 114 ++++ __test__/support/mocks/MockServiceWorker.ts | 4 +- __test__/unit/http/sdkVersion.test.ts | 28 +- .../unit/notifications/permission.test.ts | 57 +- .../nativePermissionChange.test.ts | 45 +- package-lock.json | 102 ++-- package.json | 8 +- .../IdentityOperationExecutor.test.ts | 46 +- .../LoginUserOperationExecutor.test.ts | 161 +++--- .../RefreshUserOperationExecutor.test.ts | 63 +-- .../SubscriptionOperationExecutor.test.ts | 307 ++++------ .../UpdateUserOperationExecutor.test.ts | 31 +- src/core/operationRepo/OperationRepo.test.ts | 17 +- src/core/operationRepo/OperationRepo.ts | 4 + src/entries/pageSdkInit2.test.ts | 24 +- src/onesignal/NotificationsNamespace.ts | 5 +- src/onesignal/OneSignal.test.ts | 525 +++++++----------- src/onesignal/UserNamespace.test.ts | 30 +- src/page/bell/Bell.ts | 5 +- .../slidedownManager/SlidedownManager.ts | 3 +- src/shared/database/client.test.ts | 28 +- src/shared/database/client.ts | 43 +- src/shared/database/types.ts | 32 +- src/shared/helpers/init.ts | 5 +- src/shared/listeners.test.ts | 25 +- .../managers/SubscriptionManager.test.ts | 42 +- .../sessionManager/SessionManager.test.ts | 27 +- src/shared/managers/subscription/page.ts | 14 +- src/shared/models/NotificationPermission.ts | 17 - src/sw/serviceWorker/ServiceWorker.test.ts | 14 +- 37 files changed, 928 insertions(+), 1161 deletions(-) delete mode 100644 __test__/support/helpers/database.ts create mode 100644 __test__/support/helpers/setup.ts delete mode 100755 src/shared/models/NotificationPermission.ts diff --git a/__test__/constants/constants.ts b/__test__/constants/constants.ts index ad5975afc..f0b313779 100644 --- a/__test__/constants/constants.ts +++ b/__test__/constants/constants.ts @@ -1,62 +1,15 @@ -/** - * @file constants.ts - */ - -/* S T R I N G C O N S T A N T S */ export const APP_ID = '34fcbe85-278d-4fd2-a4ec-0f80e95072c5'; -export const DUMMY_PUSH_TOKEN = - 'https://fcm.googleapis.com/fcm/send/01010101010101'; -export const DUMMY_PUSH_TOKEN_2 = +export const PUSH_TOKEN = 'https://fcm.googleapis.com/fcm/send/01010101010101'; +export const PUSH_TOKEN_2 = 'https://fcm.googleapis.com/fcm/send/01010101010102'; -export const DUMMY_ONESIGNAL_ID = '1111111111-2222222222-3333333333'; -export const DUMMY_ONESIGNAL_ID_2 = '2222222222-3333333333-4444444444'; -export const DUMMY_EXTERNAL_ID = 'rodrigo'; -export const DUMMY_EXTERNAL_ID_2 = 'iryna'; -export const DUMMY_SUBSCRIPTION_ID = '4444444444-5555555555-6666666666'; -export const DUMMY_SUBSCRIPTION_ID_2 = '7777777777-8888888888-9999999999'; -export const DUMMY_SUBSCRIPTION_ID_3 = '1010101010-1111111111-2222222222'; -export const DUMMY_MODEL_ID = '0000000000'; +export const ONESIGNAL_ID = '1111111111-2222222222-3333333333'; +export const ONESIGNAL_ID_2 = '2222222222-3333333333-4444444444'; +export const EXTERNAL_ID = 'rodrigo'; +export const EXTERNAL_ID_2 = 'iryna'; +export const SUB_ID = '4444444444-5555555555-6666666666'; +export const SUB_ID_2 = '7777777777-8888888888-9999999999'; +export const SUB_ID_3 = '1010101010-1111111111-2222222222'; export const DEVICE_OS = '56'; - -/* REQUEST CONSTANTS */ -export const DUMMY_GET_USER_REQUEST_WITH_PUSH_SUB = { - result: { - properties: { - language: 'en', - timezone_id: 'America/New_York', - first_active: 1689826588, - last_active: 1689826588, - }, - identity: { - external_id: DUMMY_EXTERNAL_ID, - onesignal_id: DUMMY_ONESIGNAL_ID, - }, - subscriptions: [ - { - id: DUMMY_SUBSCRIPTION_ID, - app_id: APP_ID, - type: 'ChromePush', - token: DUMMY_PUSH_TOKEN, - enabled: false, - notification_types: -2, - session_time: 0, - session_count: 1, - sdk: __VERSION__, - device_model: 'MacIntel', - device_os: '114', - rooted: false, - test_type: 0, - app_version: '', - net_type: 0, - carrier: '', - web_auth: 'R5dzF/EvmUbv0IsM3Ria7g==', - web_p256: - 'BNWNgguO0F+id4MjCW2V98cwPiXHs0XyPUOCqlU0OgyqG4W9V3H1R799goSjSSgZ0CMI+7/nZYiVl1bB8ZnDZx0=', - }, - ], - }, - status: 200, -}; diff --git a/__test__/setupTests.ts b/__test__/setupTests.ts index 3ecf68f80..cd264c748 100644 --- a/__test__/setupTests.ts +++ b/__test__/setupTests.ts @@ -1,11 +1,25 @@ +import { clearAll } from 'src/shared/database/client'; import { DEFAULT_USER_AGENT } from './constants'; import { server } from './support/mocks/server'; + beforeAll(() => server.listen({ onUnhandledRequest: 'warn', }), ); -afterEach(() => server.resetHandlers()); + +beforeEach(async () => { + if (typeof OneSignal !== 'undefined') { + OneSignal.coreDirector?.operationRepo.clear(); + OneSignal.emitter?.removeAllListeners(); + } + await clearAll(); +}); + +afterEach(() => { + server.resetHandlers(); +}); + afterAll(() => server.close()); // set timezone @@ -20,10 +34,10 @@ Object.defineProperty(global, 'location', { // in case we want to await operations with real timers vi.mock('src/core/operationRepo/constants', () => ({ - OP_REPO_DEFAULT_FAIL_RETRY_BACKOFF: 5, - OP_REPO_POST_CREATE_DELAY: 5, - OP_REPO_EXECUTION_INTERVAL: 5, - OP_REPO_POST_CREATE_RETRY_UP_TO: 10, + OP_REPO_DEFAULT_FAIL_RETRY_BACKOFF: 10, + OP_REPO_POST_CREATE_DELAY: 10, + OP_REPO_EXECUTION_INTERVAL: 10, + OP_REPO_POST_CREATE_RETRY_UP_TO: 20, })); Object.defineProperty(navigator, 'userAgent', { diff --git a/__test__/support/environment/TestEnvironment.ts b/__test__/support/environment/TestEnvironment.ts index 6fa854df9..e85b004bc 100644 --- a/__test__/support/environment/TestEnvironment.ts +++ b/__test__/support/environment/TestEnvironment.ts @@ -1,13 +1,11 @@ +import { ONESIGNAL_ID } from '__test__/constants'; import type { AppUserConfig, ConfigIntegrationKindValue, ServerAppConfig, } from 'src/shared/config/types'; import type { RecursivePartial } from 'src/shared/context/types'; -import { clearAll } from 'src/shared/database/client'; -import MainHelper from 'src/shared/helpers/MainHelper'; -import { DUMMY_ONESIGNAL_ID, DUMMY_PUSH_TOKEN } from '../../constants'; -import { generateNewSubscription } from '../helpers/core'; +import { updateIdentityModel } from '../helpers/setup'; import { initOSGlobals, stubDomEnvironment, @@ -25,27 +23,19 @@ export interface TestEnvironmentConfig { userAgent?: string; overrideServerConfig?: RecursivePartial; integration?: ConfigIntegrationKindValue; - useMockIdentityModel?: boolean; - useMockPushSubscriptionModel?: boolean; + useMockedIdentity?: boolean; } export class TestEnvironment { static async initialize(config: TestEnvironmentConfig = {}) { // reset db & localStorage - await clearAll(); + // await clearAll(); const oneSignal = await initOSGlobals(config); + OneSignal.coreDirector.operationRepo.queue = []; - if (config.useMockIdentityModel) { - const model = OneSignal.coreDirector.getIdentityModel(); - model.onesignalId = DUMMY_ONESIGNAL_ID; - } - - if (config.useMockPushSubscriptionModel) { - OneSignal.coreDirector.addSubscriptionModel(generateNewSubscription()); - vi.spyOn(MainHelper, 'getCurrentPushToken').mockResolvedValue( - DUMMY_PUSH_TOKEN, - ); - } + // if (config.useMockedIdentity) { + updateIdentityModel('onesignal_id', ONESIGNAL_ID); + // } await stubDomEnvironment(config); config.environment = 'dom'; diff --git a/__test__/support/environment/TestEnvironmentHelpers.ts b/__test__/support/environment/TestEnvironmentHelpers.ts index 50be04968..26def7795 100644 --- a/__test__/support/environment/TestEnvironmentHelpers.ts +++ b/__test__/support/environment/TestEnvironmentHelpers.ts @@ -2,6 +2,7 @@ import { type DOMWindow, JSDOM, ResourceLoader } from 'jsdom'; import CoreModule from 'src/core/CoreModule'; import { SubscriptionModel } from 'src/core/models/SubscriptionModel'; import { ModelChangeTags } from 'src/core/types/models'; +import { db } from 'src/shared/database/client'; import { setPushToken } from 'src/shared/database/subscription'; import { NotificationType, @@ -19,8 +20,8 @@ import { CUSTOM_LINK_CSS_CLASSES } from '../../../src/shared/slidedown/constants import { DEFAULT_USER_AGENT, DEVICE_OS, - DUMMY_ONESIGNAL_ID, - DUMMY_SUBSCRIPTION_ID_3, + ONESIGNAL_ID, + SUB_ID_3, } from '../../constants'; import MockNotification from '../mocks/MockNotification'; import TestContext from './TestContext'; @@ -109,9 +110,9 @@ export async function stubDomEnvironment(config: TestEnvironmentConfig) { } export const createPushSub = ({ - id = DUMMY_SUBSCRIPTION_ID_3, + id = SUB_ID_3, token = 'push-token', - onesignalId = DUMMY_ONESIGNAL_ID, + onesignalId = ONESIGNAL_ID, }: { id?: string; token?: string; @@ -136,21 +137,38 @@ export const setupSubModelStore = async ({ id, token, onesignalId, + web_auth, + web_p256, }: { id?: string; token?: string; onesignalId?: string; + web_auth?: string; + web_p256?: string; } = {}) => { const pushModel = createPushSub({ id, token, onesignalId, }); + if (web_auth) { + pushModel.web_auth = web_auth; + } + if (web_p256) { + pushModel.web_p256 = web_p256; + } await setPushToken(pushModel.token); OneSignal.coreDirector.subscriptionModelStore.replaceAll( [pushModel], ModelChangeTags.NO_PROPOGATE, ); + await vi.waitUntil(async () => { + const subscription = (await db.getAll('subscriptions'))[0]; + return ( + subscription.id === pushModel.id && subscription.token === pushModel.token + ); + }); + return pushModel; }; diff --git a/__test__/support/helpers/database.ts b/__test__/support/helpers/database.ts deleted file mode 100644 index 76ca0cba0..000000000 --- a/__test__/support/helpers/database.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { db } from 'src/shared/database/client'; - -export const setIsPushEnabled = async (isPushEnabled: boolean) => { - await db.put('Options', { key: 'isPushEnabled', value: isPushEnabled }); -}; diff --git a/__test__/support/helpers/executors.ts b/__test__/support/helpers/executors.ts index 690d69bff..0b333b1d7 100644 --- a/__test__/support/helpers/executors.ts +++ b/__test__/support/helpers/executors.ts @@ -1,6 +1,4 @@ -import { OP_REPO_EXECUTION_INTERVAL } from 'src/core/operationRepo/constants'; import { GroupComparisonType, Operation } from 'src/core/operations/Operation'; -import { delay } from 'src/shared/helpers/general'; export class SomeOperation extends Operation { constructor() { @@ -27,10 +25,3 @@ export class SomeOperation extends Operation { return true; } } - -export const fakeWaitForOperations = async (amount = 2) => { - await vi.advanceTimersByTimeAsync(OP_REPO_EXECUTION_INTERVAL * amount); -}; -export const waitForOperations = async (amount = 2) => { - await delay(OP_REPO_EXECUTION_INTERVAL * amount); -}; diff --git a/__test__/support/helpers/requests.ts b/__test__/support/helpers/requests.ts index 8b549adff..eea6fa06b 100644 --- a/__test__/support/helpers/requests.ts +++ b/__test__/support/helpers/requests.ts @@ -1,11 +1,7 @@ import { http, HttpResponse } from 'msw'; import type { ISubscription, IUserProperties } from 'src/core/types/api'; import { ConfigIntegrationKind } from 'src/shared/config/constants'; -import { - APP_ID, - DUMMY_ONESIGNAL_ID, - DUMMY_SUBSCRIPTION_ID, -} from '../../constants'; +import { APP_ID, ONESIGNAL_ID, SUB_ID } from '../../constants'; import TestContext from '../environment/TestContext'; import { server } from '../mocks/server'; @@ -77,9 +73,9 @@ export const getHandler = ({ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // alias -const getSetAliasUri = (onesignalId: string = DUMMY_ONESIGNAL_ID) => +const getSetAliasUri = (onesignalId: string = ONESIGNAL_ID) => `**/apps/${APP_ID}/users/by/onesignal_id/${onesignalId}/identity`; -const getDeleteAliasUri = (onesignalId: string = DUMMY_ONESIGNAL_ID) => +const getDeleteAliasUri = (onesignalId: string = ONESIGNAL_ID) => `**/apps/${APP_ID}/users/by/onesignal_id/${onesignalId}/identity/*`; export const addAliasFn = vi.fn(); @@ -137,7 +133,7 @@ export const setDeleteAliasError = ({ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // subscription -const getSetSubscriptionUri = (onesignalId = DUMMY_ONESIGNAL_ID) => +const getSetSubscriptionUri = (onesignalId = ONESIGNAL_ID) => `**/apps/${APP_ID}/users/by/onesignal_id/${onesignalId}/subscriptions`; export const createSubscriptionFn = vi.fn(); @@ -149,13 +145,31 @@ export const setCreateSubscriptionResponse = ({ uri: getSetSubscriptionUri(onesignalId), method: 'post', status: 200, - response, + response: { + subscription: response, + }, callback: createSubscriptionFn, }); +export const setCreateSubscriptionError = ({ + onesignalId, + status, + retryAfter, +}: { + onesignalId?: string; + status: number; + retryAfter?: number; +}) => + getHandler({ + uri: getSetSubscriptionUri(onesignalId), + method: 'post', + status, + retryAfter, + }); + export const deleteSubscriptionFn = vi.fn(); -const getSetDeleteSubscriptionUri = (subscriptionId = DUMMY_SUBSCRIPTION_ID) => +const getSetDeleteSubscriptionUri = (subscriptionId = SUB_ID) => `**/apps/${APP_ID}/subscriptions/${subscriptionId}`; export const setDeleteSubscriptionResponse = ({ @@ -170,10 +184,25 @@ export const setDeleteSubscriptionResponse = ({ callback: deleteSubscriptionFn, }); +export const setDeleteSubscriptionError = ({ + subscriptionId, + status, + retryAfter, +}: { + subscriptionId?: string; + status: number; + retryAfter?: number; +}) => + getHandler({ + uri: getSetDeleteSubscriptionUri(subscriptionId), + method: 'delete', + status, + retryAfter, + }); + export const updateSubscriptionFn = vi.fn(); -export const getUpdateSubscriptionUri = ( - subscriptionId = DUMMY_SUBSCRIPTION_ID, -) => `**/apps/${APP_ID}/subscriptions/${subscriptionId}`; +export const getUpdateSubscriptionUri = (subscriptionId = SUB_ID) => + `**/apps/${APP_ID}/subscriptions/${subscriptionId}`; export const setUpdateSubscriptionResponse = ({ subscriptionId, response = {}, @@ -186,12 +215,27 @@ export const setUpdateSubscriptionResponse = ({ callback: updateSubscriptionFn, }); +export const setUpdateSubscriptionError = ({ + subscriptionId, + status, + retryAfter, +}: { + subscriptionId?: string; + status: number; + retryAfter?: number; +}) => + getHandler({ + uri: getUpdateSubscriptionUri(subscriptionId), + method: 'patch', + status, + retryAfter, + }); + // transfer subscription export const transferSubscriptionFn = vi.fn(); -export const getTransferSubscriptionUri = ( - subscriptionId = DUMMY_SUBSCRIPTION_ID, -) => `**/apps/${APP_ID}/subscriptions/${subscriptionId}/owner`; +export const getTransferSubscriptionUri = (subscriptionId = SUB_ID) => + `**/apps/${APP_ID}/subscriptions/${subscriptionId}/owner`; export const setTransferSubscriptionResponse = ({ subscriptionId, @@ -205,16 +249,32 @@ export const setTransferSubscriptionResponse = ({ callback: transferSubscriptionFn, }); +export const setTransferSubscriptionError = ({ + subscriptionId, + status, + retryAfter, +}: { + subscriptionId?: string; + status: number; + retryAfter?: number; +}) => + getHandler({ + uri: getTransferSubscriptionUri(subscriptionId), + method: 'patch', + status, + retryAfter, + }); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // user -const getUserUri = (onesignalId = DUMMY_ONESIGNAL_ID) => +const getUserUri = (onesignalId = ONESIGNAL_ID) => `**/apps/${APP_ID}/users/by/onesignal_id/${onesignalId}`; // get user export const getUserFn = vi.fn(); export const setGetUserResponse = ({ - onesignalId = DUMMY_ONESIGNAL_ID, - newOnesignalId = DUMMY_ONESIGNAL_ID, + onesignalId = ONESIGNAL_ID, + newOnesignalId = ONESIGNAL_ID, externalId, subscriptions = [], properties = {}, @@ -242,7 +302,7 @@ export const setGetUserResponse = ({ // get user error export const setGetUserError = ({ - onesignalId = DUMMY_ONESIGNAL_ID, + onesignalId = ONESIGNAL_ID, status, retryAfter, }: { @@ -261,7 +321,7 @@ export const setGetUserError = ({ const getCreateUserUri = () => `**/apps/${APP_ID}/users`; export const createUserFn = vi.fn(); export const setCreateUserResponse = ({ - onesignalId = DUMMY_ONESIGNAL_ID, + onesignalId = ONESIGNAL_ID, subscriptions = [], externalId, }: { @@ -295,11 +355,11 @@ export const setCreateUserError = ({ // update user export const updateUserFn = vi.fn(); -const getUpdateUserUri = (onesignalId = DUMMY_ONESIGNAL_ID) => +const getUpdateUserUri = (onesignalId = ONESIGNAL_ID) => `**/apps/${APP_ID}/users/by/onesignal_id/${onesignalId}`; export const setUpdateUserResponse = ({ - onesignalId = DUMMY_ONESIGNAL_ID, + onesignalId = ONESIGNAL_ID, response = {}, }: { onesignalId?: string; response?: object } = {}) => getHandler({ @@ -311,7 +371,7 @@ export const setUpdateUserResponse = ({ }); export const setUpdateUserError = ({ - onesignalId = DUMMY_ONESIGNAL_ID, + onesignalId = ONESIGNAL_ID, status, retryAfter, }: { diff --git a/__test__/support/helpers/setup.ts b/__test__/support/helpers/setup.ts new file mode 100644 index 000000000..957fac54f --- /dev/null +++ b/__test__/support/helpers/setup.ts @@ -0,0 +1,114 @@ +import { ONESIGNAL_ID } from '__test__/constants'; +import { IdentityModel } from 'src/core/models/IdentityModel'; +import { PropertiesModel } from 'src/core/models/PropertiesModel'; +import { SubscriptionModel } from 'src/core/models/SubscriptionModel'; +import { ModelChangeTags } from 'src/core/types/models'; +import { db } from 'src/shared/database/client'; +import type { + IdentitySchema, + PropertiesSchema, +} from 'src/shared/database/types'; + +export const setIsPushEnabled = async (isPushEnabled: boolean) => { + await db.put('Options', { key: 'isPushEnabled', value: isPushEnabled }); +}; + +export const getIdentityItem = async ( + condition: (identity: IdentitySchema) => boolean = () => true, +) => { + let identity: IdentitySchema | undefined; + await vi.waitUntil(async () => { + identity = (await db.getAll('identity'))?.[0]; + return identity && condition(identity); + }); + return identity; +}; + +export const getPropertiesItem = async ( + condition: (properties: PropertiesSchema) => boolean = () => true, +) => { + let properties: PropertiesSchema | undefined; + await vi.waitUntil(async () => { + properties = (await db.getAll('properties'))?.[0]; + return properties && condition(properties); + }); + return properties; +}; + +export const setupIdentityModel = async ( + { + onesignalID, + }: { + onesignalID?: string; + } = { + onesignalID: ONESIGNAL_ID, + }, +) => { + const newIdentityModel = new IdentityModel(); + if (onesignalID) { + newIdentityModel.onesignalId = onesignalID; + } + OneSignal.coreDirector.identityModelStore.replace( + newIdentityModel, + ModelChangeTags.NO_PROPOGATE, + ); + + // wait for db to be updated + await getIdentityItem((i) => i.onesignal_id === onesignalID); +}; + +export const setupPropertiesModel = async ( + { + onesignalID, + }: { + onesignalID?: string; + } = { + onesignalID: ONESIGNAL_ID, + }, +) => { + const newPropertiesModel = new PropertiesModel(); + if (onesignalID) { + newPropertiesModel.onesignalId = onesignalID; + } + OneSignal.coreDirector.propertiesModelStore.replace( + newPropertiesModel, + ModelChangeTags.NO_PROPOGATE, + ); + + // wait for db to be updated + await getPropertiesItem((p) => p.onesignalId === onesignalID); +}; + +export const updateIdentityModel = async ( + property: T, + value?: IdentitySchema[T], +) => { + const identityModel = OneSignal.coreDirector.getIdentityModel(); + identityModel.setProperty(property, value, ModelChangeTags.NO_PROPOGATE); +}; + +export const updatePropertiesModel = async < + T extends Exclude< + keyof PropertiesSchema, + 'modelId' | 'modelName' | 'first_active' | 'last_active' + >, +>( + property: T, + value?: PropertiesSchema[T], +) => { + const propertiesModel = OneSignal.coreDirector.getPropertiesModel(); + propertiesModel.setProperty(property, value, ModelChangeTags.NO_PROPOGATE); +}; + +export const setupSubscriptionModel = async ( + id: string | undefined, + token: string | undefined, +) => { + const subscriptionModel = new SubscriptionModel(); + subscriptionModel.id = id || ''; + subscriptionModel.token = token || ''; + OneSignal.coreDirector.subscriptionModelStore.replaceAll( + [subscriptionModel], + ModelChangeTags.NO_PROPOGATE, + ); +}; diff --git a/__test__/support/mocks/MockServiceWorker.ts b/__test__/support/mocks/MockServiceWorker.ts index d70951047..6e7f5788d 100644 --- a/__test__/support/mocks/MockServiceWorker.ts +++ b/__test__/support/mocks/MockServiceWorker.ts @@ -1,9 +1,9 @@ -import { DUMMY_PUSH_TOKEN } from '../../constants'; +import { PUSH_TOKEN } from '../../constants'; export const getSubscriptionFn = vi .fn<() => Promise>>() .mockResolvedValue({ - endpoint: DUMMY_PUSH_TOKEN, + endpoint: PUSH_TOKEN, }); export const getRegistrationFn = vi diff --git a/__test__/unit/http/sdkVersion.test.ts b/__test__/unit/http/sdkVersion.test.ts index 17e86d223..c226a1b0b 100644 --- a/__test__/unit/http/sdkVersion.test.ts +++ b/__test__/unit/http/sdkVersion.test.ts @@ -1,8 +1,4 @@ -import { - APP_ID, - DUMMY_EXTERNAL_ID, - DUMMY_SUBSCRIPTION_ID, -} from '__test__/constants'; +import { APP_ID, EXTERNAL_ID, SUB_ID } from '__test__/constants'; import { generateNewSubscription } from '__test__/support/helpers/core'; import { nock } from '__test__/support/helpers/general'; import { IdentityConstants } from 'src/core/constants'; @@ -39,7 +35,7 @@ describe('Sdk Version Header Tests', () => { test('POST /users: header is sent with subscription id', () => { createNewUser( - { appId: APP_ID, subscriptionId: DUMMY_SUBSCRIPTION_ID }, + { appId: APP_ID, subscriptionId: SUB_ID }, // @ts-expect-error - partial identity object {}, ); @@ -51,7 +47,7 @@ describe('Sdk Version Header Tests', () => { { appId: APP_ID }, { label: IdentityConstants.EXTERNAL_ID, - id: DUMMY_EXTERNAL_ID, + id: EXTERNAL_ID, }, ); expectHeaderToBeSent(); @@ -62,7 +58,7 @@ describe('Sdk Version Header Tests', () => { { appId: APP_ID }, { label: IdentityConstants.EXTERNAL_ID, - id: DUMMY_EXTERNAL_ID, + id: EXTERNAL_ID, }, {}, ); @@ -71,10 +67,10 @@ describe('Sdk Version Header Tests', () => { test('PATCH /users/by//: header is sent with subscription id', () => { updateUserByAlias( - { appId: APP_ID, subscriptionId: DUMMY_SUBSCRIPTION_ID }, + { appId: APP_ID, subscriptionId: SUB_ID }, { label: IdentityConstants.EXTERNAL_ID, - id: DUMMY_EXTERNAL_ID, + id: EXTERNAL_ID, }, {}, ); @@ -86,7 +82,7 @@ describe('Sdk Version Header Tests', () => { { appId: APP_ID }, { label: IdentityConstants.EXTERNAL_ID, - id: DUMMY_EXTERNAL_ID, + id: EXTERNAL_ID, }, ); expectHeaderToBeSent(); @@ -97,7 +93,7 @@ describe('Sdk Version Header Tests', () => { { appId: APP_ID }, { label: IdentityConstants.EXTERNAL_ID, - id: DUMMY_EXTERNAL_ID, + id: EXTERNAL_ID, }, { // @ts-expect-error - partial identity object @@ -112,7 +108,7 @@ describe('Sdk Version Header Tests', () => { { appId: APP_ID }, { label: IdentityConstants.EXTERNAL_ID, - id: DUMMY_EXTERNAL_ID, + id: EXTERNAL_ID, }, ); expectHeaderToBeSent(); @@ -123,7 +119,7 @@ describe('Sdk Version Header Tests', () => { { appId: APP_ID }, { label: IdentityConstants.EXTERNAL_ID, - id: DUMMY_EXTERNAL_ID, + id: EXTERNAL_ID, }, IdentityConstants.EXTERNAL_ID, ); @@ -133,7 +129,7 @@ describe('Sdk Version Header Tests', () => { test('PATCH /subscriptions/: header is sent', () => { updateSubscriptionById( { appId: APP_ID }, - DUMMY_EXTERNAL_ID, + EXTERNAL_ID, // @ts-expect-error - partial identity object {}, ); @@ -141,7 +137,7 @@ describe('Sdk Version Header Tests', () => { }); test('DELETE /subscriptions/: header is sent', () => { - deleteSubscriptionById({ appId: APP_ID }, DUMMY_EXTERNAL_ID); + deleteSubscriptionById({ appId: APP_ID }, EXTERNAL_ID); expectHeaderToBeSent(); }); }); diff --git a/__test__/unit/notifications/permission.test.ts b/__test__/unit/notifications/permission.test.ts index 02163a90d..5bd23581f 100644 --- a/__test__/unit/notifications/permission.test.ts +++ b/__test__/unit/notifications/permission.test.ts @@ -1,5 +1,4 @@ import OneSignal from '../../../src/onesignal/OneSignal'; -import { NotificationPermission } from '../../../src/shared/models/NotificationPermission'; import { TestEnvironment } from '../../support/environment/TestEnvironment'; import { PermissionManager } from '../../support/managers/PermissionManager'; @@ -28,67 +27,45 @@ describe('Notifications namespace permission properties', () => { test('When permission changes to granted, ensure permissionChange fires with true', async () => { const expectedPromise = expectPermissionChangeEvent(true); - await PermissionManager.mockNotificationPermissionChange( - NotificationPermission.Granted, - ); + await PermissionManager.mockNotificationPermissionChange('granted'); await expectedPromise; }); test('When permission changes to Denied, ensure permissionChange fires with false', async () => { - await PermissionManager.mockNotificationPermissionChange( - NotificationPermission.Granted, - ); + await PermissionManager.mockNotificationPermissionChange('granted'); const expectedPromise = expectPermissionChangeEvent(false); - await PermissionManager.mockNotificationPermissionChange( - NotificationPermission.Denied, - ); + await PermissionManager.mockNotificationPermissionChange('denied'); await expectedPromise; }); test('When permission changes to Default, ensure permissionChange fires with false', async () => { - await PermissionManager.mockNotificationPermissionChange( - NotificationPermission.Granted, - ); + await PermissionManager.mockNotificationPermissionChange('granted'); const expectedPromise = expectPermissionChangeEvent(false); - await PermissionManager.mockNotificationPermissionChange( - NotificationPermission.Default, - ); + await PermissionManager.mockNotificationPermissionChange('default'); await expectedPromise; }); test('When permission changes to granted, we update the permission properties on the Notifications namespace', async () => { - await PermissionManager.mockNotificationPermissionChange( - NotificationPermission.Granted, - ); + await PermissionManager.mockNotificationPermissionChange('granted'); expect(OneSignal.Notifications.permission).toBe(true); - expect(OneSignal.Notifications.permissionNative).toBe( - NotificationPermission.Granted, - ); + expect(OneSignal.Notifications.permissionNative).toBe('granted'); }); test('When permission changes to default, we update the permission properties on the Notifications namespace', async () => { - await PermissionManager.mockNotificationPermissionChange( - NotificationPermission.Default, - ); + await PermissionManager.mockNotificationPermissionChange('default'); expect(OneSignal.Notifications.permission).toBe(false); - expect(OneSignal.Notifications.permissionNative).toBe( - NotificationPermission.Default, - ); + expect(OneSignal.Notifications.permissionNative).toBe('default'); }); test('When permission changes to denied, we update the permission properties on the Notifications namespace', async () => { - await PermissionManager.mockNotificationPermissionChange( - NotificationPermission.Denied, - ); + await PermissionManager.mockNotificationPermissionChange('denied'); expect(OneSignal.Notifications.permission).toBe(false); - expect(OneSignal.Notifications.permissionNative).toBe( - NotificationPermission.Denied, - ); + expect(OneSignal.Notifications.permissionNative).toBe('denied'); }); test('When permission changes, removeEventListener should stop callback from firing', async () => { @@ -101,14 +78,8 @@ describe('Notifications namespace permission properties', () => { OneSignal.Notifications.removeEventListener('permissionChange', callback); // Change permissions through all possible states to ensure the event has had a chance to fire - await PermissionManager.mockNotificationPermissionChange( - NotificationPermission.Granted, - ); - await PermissionManager.mockNotificationPermissionChange( - NotificationPermission.Default, - ); - await PermissionManager.mockNotificationPermissionChange( - NotificationPermission.Denied, - ); + await PermissionManager.mockNotificationPermissionChange('granted'); + await PermissionManager.mockNotificationPermissionChange('default'); + await PermissionManager.mockNotificationPermissionChange('denied'); }); }); diff --git a/__test__/unit/pushSubscription/nativePermissionChange.test.ts b/__test__/unit/pushSubscription/nativePermissionChange.test.ts index 882b1c096..a6306d817 100644 --- a/__test__/unit/pushSubscription/nativePermissionChange.test.ts +++ b/__test__/unit/pushSubscription/nativePermissionChange.test.ts @@ -1,9 +1,9 @@ import { - DUMMY_PUSH_TOKEN, - DUMMY_PUSH_TOKEN_2, - DUMMY_SUBSCRIPTION_ID, - DUMMY_SUBSCRIPTION_ID_2, - DUMMY_SUBSCRIPTION_ID_3, + PUSH_TOKEN, + PUSH_TOKEN_2, + SUB_ID, + SUB_ID_2, + SUB_ID_3, } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { createPushSub } from '__test__/support/environment/TestEnvironmentHelpers'; @@ -15,7 +15,6 @@ import Emitter from 'src/shared/libraries/Emitter'; import { checkAndTriggerSubscriptionChanged } from 'src/shared/listeners'; import { AppState } from 'src/shared/models/AppState'; import MainHelper from '../../../src/shared/helpers/MainHelper'; -import { NotificationPermission } from '../../../src/shared/models/NotificationPermission'; vi.mock('src/shared/libraries/Log'); const triggerNotificationSpy = vi.spyOn( @@ -47,9 +46,9 @@ describe('Notification Types are set correctly on subscription change', () => { test('should not trigger change if permission status is the same', async () => { vi.stubGlobal('Notification', { ...global.Notification, - permission: NotificationPermission.Granted, + permission: 'granted', }); - await setDbPermission(NotificationPermission.Granted); + await setDbPermission('granted'); await MainHelper.checkAndTriggerNotificationPermissionChanged(); expect(triggerNotificationSpy).not.toHaveBeenCalled(); @@ -58,9 +57,9 @@ describe('Notification Types are set correctly on subscription change', () => { test('should trigger change if permission status is different', async () => { vi.stubGlobal('Notification', { ...global.Notification, - permission: NotificationPermission.Granted, + permission: 'granted', }); - await setDbPermission(NotificationPermission.Denied); + await setDbPermission('denied'); const permChangeStringListener = vi.fn(); const permChangeListener = vi.fn(); @@ -81,7 +80,7 @@ describe('Notification Types are set correctly on subscription change', () => { const dbPermission = await getOptionsValue( 'notificationPermission', ); - expect(dbPermission).toBe(NotificationPermission.Granted); + expect(dbPermission).toBe('granted'); expect(permChangeListener).toHaveBeenCalledWith(true); expect(permChangeStringListener).toHaveBeenCalledWith('granted'); }); @@ -104,14 +103,14 @@ describe('Notification Types are set correctly on subscription change', () => { test('should not do anything if the state has not changed', async () => { addListener(changeListener); const pushModel = createPushSub({ - id: DUMMY_SUBSCRIPTION_ID, - token: DUMMY_PUSH_TOKEN, + id: SUB_ID, + token: PUSH_TOKEN, }); await setAppState({ lastKnownPushEnabled: false, - lastKnownPushToken: DUMMY_PUSH_TOKEN, - lastKnownPushId: DUMMY_SUBSCRIPTION_ID, + lastKnownPushToken: PUSH_TOKEN, + lastKnownPushId: SUB_ID, }); OneSignal.coreDirector.addSubscriptionModel(pushModel); @@ -122,28 +121,28 @@ describe('Notification Types are set correctly on subscription change', () => { test('should emit change if app state data is different', async () => { addListener(changeListener); const pushModel = createPushSub({ - id: DUMMY_SUBSCRIPTION_ID_2, - token: DUMMY_PUSH_TOKEN_2, + id: SUB_ID_2, + token: PUSH_TOKEN_2, }); await setAppState({ lastKnownPushEnabled: true, - lastKnownPushToken: DUMMY_PUSH_TOKEN_2, - lastKnownPushId: DUMMY_SUBSCRIPTION_ID_3, + lastKnownPushToken: PUSH_TOKEN_2, + lastKnownPushId: SUB_ID_3, }); OneSignal.coreDirector.subscriptionModelStore.add(pushModel); await checkAndTriggerSubscriptionChanged(); expect(changeListener).toHaveBeenCalledWith({ current: { - id: DUMMY_SUBSCRIPTION_ID_2, + id: SUB_ID_2, optedIn: false, - token: DUMMY_PUSH_TOKEN, + token: PUSH_TOKEN, }, previous: { - id: DUMMY_SUBSCRIPTION_ID_3, + id: SUB_ID_3, optedIn: true, - token: DUMMY_PUSH_TOKEN_2, + token: PUSH_TOKEN_2, }, }); }); diff --git a/package-lock.json b/package-lock.json index aff95a311..634594393 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,14 +24,14 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^5.36.1", "@typescript-eslint/parser": "^5.36.1", - "@vitest/coverage-v8": "4.0.0-beta.6", + "@vitest/coverage-v8": "4.0.0-beta.8", "concurrently": "^9.2.0", "deepmerge": "^4.2.2", "eslint": "^8.23.0", "eslint-config-prettier": "9.0.0", "fake-indexeddb": "5.0.2", "jsdom": "^26.1.0", - "msw": "^2.10.4", + "msw": "^2.10.5", "prettier": "3.6.2", "prettylint": "^2.0.0", "sass-embedded": "^1.89.2", @@ -41,7 +41,7 @@ "vite-bundle-analyzer": "^1.1.0", "vite-plugin-mkcert": "^1.17.8", "vite-tsconfig-paths": "^5.1.4", - "vitest": "4.0.0-beta.6" + "vitest": "4.0.0-beta.8" } }, "node_modules/@asamuzakjp/css-color": { @@ -1745,14 +1745,14 @@ "license": "ISC" }, "node_modules/@vitest/coverage-v8": { - "version": "4.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.0-beta.6.tgz", - "integrity": "sha512-ktfxAmkue/yDq8mri5GUuDBq+KNWrQfDge/U2S2i5OYM/h4sqy3o4UjmxcP3/thS+4pLJfhk/e84OFfdQksJ0g==", + "version": "4.0.0-beta.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.0-beta.8.tgz", + "integrity": "sha512-vd8D6P/PWtHXjH5Sot8PuGKchYC7Uns46Dpr1fg57kD8SOdXJybxtg/EdYvD5/OUW2q0iQCFes79WbH+LelwaA==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.0-beta.6", + "@vitest/utils": "4.0.0-beta.8", "ast-v8-to-istanbul": "^0.3.3", "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", @@ -1767,8 +1767,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.0.0-beta.6", - "vitest": "4.0.0-beta.6" + "@vitest/browser": "4.0.0-beta.8", + "vitest": "4.0.0-beta.8" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1777,15 +1777,15 @@ } }, "node_modules/@vitest/expect": { - "version": "4.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.0-beta.6.tgz", - "integrity": "sha512-dirPYot23Y+oeTSkWZj1Xv6iPQ+jytpkv6OcaSPWuBmdCX5dg//N3U2VGmI1osbnKcu/Ko2cV+Uxx5I5545CTg==", + "version": "4.0.0-beta.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.0-beta.8.tgz", + "integrity": "sha512-40DZ7nl5AkASkDNaVR7TqsbeJCs5D+dyQNM5cIvgjG3KK+ATeWxtXJbmRNqgdbq+FL3v/pchnrJM1R9BFkTdUQ==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.0-beta.6", - "@vitest/utils": "4.0.0-beta.6", + "@vitest/spy": "4.0.0-beta.8", + "@vitest/utils": "4.0.0-beta.8", "chai": "^5.2.1", "tinyrainbow": "^2.0.0" }, @@ -1794,13 +1794,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.0-beta.6.tgz", - "integrity": "sha512-FVWcdkAdVvpa804Ukpd2MaQQ+6Rto5YlUFpZol/DuQgXM6VXHUUEVfZeJpyv0EF8quOmjwlPWJAnmzAl/hL8Jw==", + "version": "4.0.0-beta.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.0-beta.8.tgz", + "integrity": "sha512-a5ynR/Fsrciuq17i8lzS5NH3ICxwFZMlQ4bXPzGV+KlIcVHu20a/8q6KekqUaVBxCbmohORLFXFx9IptHS9gXA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.0-beta.6", + "@vitest/spy": "4.0.0-beta.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -1821,9 +1821,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.0-beta.6.tgz", - "integrity": "sha512-7pk/LkA2pE2O5dYLy+H7E9F6lNxmnWi+nHVGmd/JotCIbUy4Y0Q9wcSQatUKSRY4xkKrO/Ymb7jU8fSYcZ9Skw==", + "version": "4.0.0-beta.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.0-beta.8.tgz", + "integrity": "sha512-sr5HPeeRff4gTpDwI2Kvz8dS2CmDCCZ1PRu3IOeLTcSJjhEWmk3IJILjqaA8yyj+QzWjnqAxr2rmZNpO0h/5Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -1834,13 +1834,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.0-beta.6.tgz", - "integrity": "sha512-n350+NMd9HC1yXpuEASgABLgpRTc7CO3jOKbfRw/IX5rQbHxqpa1lpNliWrb5b89ZiK/V1DNSEfqOho9qQjeTw==", + "version": "4.0.0-beta.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.0-beta.8.tgz", + "integrity": "sha512-m7jrT+KMEgONclBI7y3ptUD+/uhRzzHLblws84fo+WesCjS8WT8q7RoPMgqymj5kmzIF5sTh6NOvrNEE5LaLCQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.0-beta.6", + "@vitest/utils": "4.0.0-beta.8", "pathe": "^2.0.3", "strip-literal": "^3.0.0" }, @@ -1849,13 +1849,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.0-beta.6.tgz", - "integrity": "sha512-ciqDHmA71fSuO6eB4imTci5g2BwO+nDIuozeu1sXp06WP869xIZ644a83puWthXIv/+2LliHaHx0qQiJ/yYIRg==", + "version": "4.0.0-beta.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.0-beta.8.tgz", + "integrity": "sha512-2hzc/ksGlZ8Rcg11VD/AhTwSaPEsdtrNA+TCV4w6tuZ7I2X+XJXimfk/Cehz5zMgfFuV8tFmGimb/BpyIbNiMg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.0-beta.6", + "@vitest/pretty-format": "4.0.0-beta.8", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -1864,9 +1864,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.0-beta.6.tgz", - "integrity": "sha512-zKGpj93bqs2BkHB4clcXngfu7VOSieIL+dk/afqSZ4ylYKYyMT2b6QCw7DkWn9a7bW9bC9IrizPzJrAVKnkZKQ==", + "version": "4.0.0-beta.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.0-beta.8.tgz", + "integrity": "sha512-WfHF35GCf5xx3B1oRrhWMVAfcBBYO4WHAYLbeeaZ1ZSW5/VBXJ/M37bLxRRKnXcgffwcsWA7xpjWnL0dQ1q5NA==", "dev": true, "license": "MIT", "funding": { @@ -1874,13 +1874,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.0-beta.6.tgz", - "integrity": "sha512-v5cwVYZyB7Vp5T1hipS7wpKg0S18ksSSB7fOGv22iDwQJLGxlYKwuMvPgoQaWFMNWRVsuBgjEjQLY8KhGhTlXw==", + "version": "4.0.0-beta.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.0-beta.8.tgz", + "integrity": "sha512-+eV3yrDooGMnHHVQ1aqzPJIBlxEsaQg/5BVFRSkbBgfiKqM9HZbqYK736s0D8tfdMLOjeB+7u7Tw0emd/h3MlQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.0-beta.6", + "@vitest/pretty-format": "4.0.0-beta.8", "loupe": "^3.2.0", "tinyrainbow": "^2.0.0" }, @@ -4048,9 +4048,9 @@ "license": "MIT" }, "node_modules/msw": { - "version": "2.10.4", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.10.4.tgz", - "integrity": "sha512-6R1or/qyele7q3RyPwNuvc0IxO8L8/Aim6Sz5ncXEgcWUNxSKE+udriTOWHtpMwmfkLYlacA2y7TIx4cL5lgHA==", + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.10.5.tgz", + "integrity": "sha512-0EsQCrCI1HbhpBWd89DvmxY6plmvrM96b0sCIztnvcNHQbXn5vqwm1KlXslo6u4wN9LFGLC1WFjjgljcQhe40A==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5706,20 +5706,20 @@ } }, "node_modules/vitest": { - "version": "4.0.0-beta.6", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.0-beta.6.tgz", - "integrity": "sha512-vCmDHLkvmshANLjl2DkwdlwLsZDu99/831HjqHWtWkqhSPooUHAuBDFBbKrZeGFfv8I9qn0WhuXPuzItfhNSJg==", + "version": "4.0.0-beta.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.0-beta.8.tgz", + "integrity": "sha512-wN/RDeCd5uXHV6tELw4AJzeP5rxR4YWXN3ems+59ZummmiovNjlfwG+CEZp5GitlxDQu7muoY4VPrSUxPzzKiQ==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/expect": "4.0.0-beta.6", - "@vitest/mocker": "4.0.0-beta.6", - "@vitest/pretty-format": "^4.0.0-beta.6", - "@vitest/runner": "4.0.0-beta.6", - "@vitest/snapshot": "4.0.0-beta.6", - "@vitest/spy": "4.0.0-beta.6", - "@vitest/utils": "4.0.0-beta.6", + "@vitest/expect": "4.0.0-beta.8", + "@vitest/mocker": "4.0.0-beta.8", + "@vitest/pretty-format": "^4.0.0-beta.8", + "@vitest/runner": "4.0.0-beta.8", + "@vitest/snapshot": "4.0.0-beta.8", + "@vitest/spy": "4.0.0-beta.8", + "@vitest/utils": "4.0.0-beta.8", "chai": "^5.2.1", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", @@ -5749,8 +5749,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "4.0.0-beta.6", - "@vitest/ui": "4.0.0-beta.6", + "@vitest/browser": "4.0.0-beta.8", + "@vitest/ui": "4.0.0-beta.8", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index 620c54a8b..5ae7f645b 100644 --- a/package.json +++ b/package.json @@ -54,14 +54,14 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^5.36.1", "@typescript-eslint/parser": "^5.36.1", - "@vitest/coverage-v8": "4.0.0-beta.6", + "@vitest/coverage-v8": "4.0.0-beta.8", "concurrently": "^9.2.0", "deepmerge": "^4.2.2", "eslint": "^8.23.0", "eslint-config-prettier": "9.0.0", "fake-indexeddb": "5.0.2", "jsdom": "^26.1.0", - "msw": "^2.10.4", + "msw": "^2.10.5", "prettier": "3.6.2", "prettylint": "^2.0.0", "sass-embedded": "^1.89.2", @@ -71,7 +71,7 @@ "vite-bundle-analyzer": "^1.1.0", "vite-plugin-mkcert": "^1.17.8", "vite-tsconfig-paths": "^5.1.4", - "vitest": "4.0.0-beta.6" + "vitest": "4.0.0-beta.8" }, "size-limit": [ { @@ -81,7 +81,7 @@ }, { "path": "./build/releases/OneSignalSDK.page.es6.js", - "limit": "54.88 kB", + "limit": "54.84 kB", "gzip": true }, { diff --git a/src/core/executors/IdentityOperationExecutor.test.ts b/src/core/executors/IdentityOperationExecutor.test.ts index 7ca3e6925..1b2ed32dd 100644 --- a/src/core/executors/IdentityOperationExecutor.test.ts +++ b/src/core/executors/IdentityOperationExecutor.test.ts @@ -1,4 +1,4 @@ -import { APP_ID, DUMMY_ONESIGNAL_ID } from '__test__/constants'; +import { APP_ID, ONESIGNAL_ID } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { SomeOperation } from '__test__/support/helpers/executors'; import { @@ -7,9 +7,11 @@ import { setDeleteAliasError, setDeleteAliasResponse, } from '__test__/support/helpers/requests'; +import { updateIdentityModel } from '__test__/support/helpers/setup'; import { ExecutionResult } from 'src/core/types/operation'; +import type { IdentitySchema } from 'src/shared/database/types'; import type { MockInstance } from 'vitest'; -import { IdentityConstants, OPERATION_NAME } from '../constants'; +import { OPERATION_NAME } from '../constants'; import { RebuildUserService } from '../modelRepo/RebuildUserService'; import { IdentityModelStore } from '../modelStores/IdentityModelStore'; import { PropertiesModelStore } from '../modelStores/PropertiesModelStore'; @@ -21,17 +23,8 @@ import { IdentityOperationExecutor } from './IdentityOperationExecutor'; const label = 'test-label'; const value = 'test-value'; -const setAliasOp = new SetAliasOperation( - APP_ID, - DUMMY_ONESIGNAL_ID, - label, - value, -); -const deleteAliasOp = new DeleteAliasOperation( - APP_ID, - DUMMY_ONESIGNAL_ID, - label, -); +const setAliasOp = new SetAliasOperation(APP_ID, ONESIGNAL_ID, label, value); +const deleteAliasOp = new DeleteAliasOperation(APP_ID, ONESIGNAL_ID, label); let identityModelStore: IdentityModelStore; let newRecordsState: NewRecordsState; @@ -58,8 +51,6 @@ describe('IdentityOperationExecutor', () => { setDeleteAliasResponse(); identityModelStore = OneSignal.coreDirector.identityModelStore; - identityModelStore.model.onesignalId = DUMMY_ONESIGNAL_ID; - newRecordsState = OneSignal.coreDirector.newRecordsState; propertiesModelStore = OneSignal.coreDirector.propertiesModelStore; subscriptionModelStore = OneSignal.coreDirector.subscriptionModelStore; @@ -107,10 +98,7 @@ describe('IdentityOperationExecutor', () => { }); test('can execute set alias op', async () => { - identityModelStore.model.setProperty( - IdentityConstants.ONESIGNAL_ID, - DUMMY_ONESIGNAL_ID, - ); + updateIdentityModel('onesignal_id', ONESIGNAL_ID); const executor = getExecutor(); const ops = [setAliasOp]; await executor.execute(ops); @@ -120,11 +108,8 @@ describe('IdentityOperationExecutor', () => { }); test('can execute delete alias op', async () => { - identityModelStore.model.setProperty( - IdentityConstants.ONESIGNAL_ID, - DUMMY_ONESIGNAL_ID, - ); - identityModelStore.model.setProperty(label, value); + updateIdentityModel('onesignal_id', ONESIGNAL_ID); + updateIdentityModel(label as keyof IdentitySchema, value); const executor = getExecutor(); @@ -169,13 +154,12 @@ describe('IdentityOperationExecutor', () => { const res5 = await executor.execute(ops); // no rebuild ops - // @ts-expect-error - for testing purposes - identityModelStore.model.onesignalId = undefined; + updateIdentityModel('onesignal_id', undefined); expect(res5.result).toBe(ExecutionResult.FAIL_NORETRY); expect(res5.retryAfterSeconds).toBeUndefined(); // with rebuild ops - identityModelStore.model.onesignalId = DUMMY_ONESIGNAL_ID; + identityModelStore.model.onesignalId = ONESIGNAL_ID; const res7 = await executor.execute(ops); expect(res7.result).toBe(ExecutionResult.FAIL_RETRY); expect(res7.retryAfterSeconds).toBeUndefined(); @@ -183,17 +167,17 @@ describe('IdentityOperationExecutor', () => { { name: 'login-user', appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }, { name: 'refresh-user', appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }, ]); // in missing retry window - newRecordsState.add(DUMMY_ONESIGNAL_ID); + newRecordsState.add(ONESIGNAL_ID); setAddAliasError({ status: 404, retryAfter: 20 }); const res6 = await executor.execute(ops); expect(res6.result).toBe(ExecutionResult.FAIL_RETRY); @@ -232,7 +216,7 @@ describe('IdentityOperationExecutor', () => { expect(res5.result).toBe(ExecutionResult.SUCCESS); // in missing retry window - newRecordsState.add(DUMMY_ONESIGNAL_ID); + newRecordsState.add(ONESIGNAL_ID); setDeleteAliasError({ status: 404, retryAfter: 20 }); const res6 = await executor.execute(ops); expect(res6.result).toBe(ExecutionResult.FAIL_RETRY); diff --git a/src/core/executors/LoginUserOperationExecutor.test.ts b/src/core/executors/LoginUserOperationExecutor.test.ts index c36cb6776..7e40bbf80 100644 --- a/src/core/executors/LoginUserOperationExecutor.test.ts +++ b/src/core/executors/LoginUserOperationExecutor.test.ts @@ -1,13 +1,13 @@ import { APP_ID, DEVICE_OS, - DUMMY_EXTERNAL_ID, - DUMMY_ONESIGNAL_ID, - DUMMY_ONESIGNAL_ID_2, - DUMMY_PUSH_TOKEN, - DUMMY_PUSH_TOKEN_2, - DUMMY_SUBSCRIPTION_ID, - DUMMY_SUBSCRIPTION_ID_2, + EXTERNAL_ID, + ONESIGNAL_ID, + ONESIGNAL_ID_2, + PUSH_TOKEN, + PUSH_TOKEN_2, + SUB_ID, + SUB_ID_2, } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { SomeOperation } from '__test__/support/helpers/executors'; @@ -18,7 +18,10 @@ import { setCreateUserError, setCreateUserResponse, } from '__test__/support/helpers/requests'; -import { clearAll } from 'src/shared/database/client'; +import { + updateIdentityModel, + updatePropertiesModel, +} from '__test__/support/helpers/setup'; import { getPushToken, setPushToken } from 'src/shared/database/subscription'; import { NotificationType, @@ -56,7 +59,6 @@ describe('LoginUserOperationExecutor', () => { }); beforeEach(async () => { - await clearAll(); identityModelStore = OneSignal.coreDirector.identityModelStore; propertiesModelStore = OneSignal.coreDirector.propertiesModelStore; subscriptionModelStore = OneSignal.coreDirector.subscriptionModelStore; @@ -102,7 +104,7 @@ describe('LoginUserOperationExecutor', () => { describe('create user', () => { beforeEach(() => { setCreateUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }); }); @@ -110,12 +112,12 @@ describe('LoginUserOperationExecutor', () => { const executor = getExecutor(); // login op with create subscription op and no externalId - const loginOp = new LoginUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); + const loginOp = new LoginUserOperation(APP_ID, ONESIGNAL_ID); // login op with subscription op and random operation const transferSubOp = new TransferSubscriptionOperation( APP_ID, - DUMMY_ONESIGNAL_ID, + ONESIGNAL_ID, mockSubscriptionOpInfo.subscriptionId, ); const someOp = new SomeOperation(); @@ -129,7 +131,7 @@ describe('LoginUserOperationExecutor', () => { test('can create user if there is no onesignal id or externalId', async () => { const executor = getExecutor(); - const loginOp = new LoginUserOperation(APP_ID, DUMMY_ONESIGNAL_ID_2); + const loginOp = new LoginUserOperation(APP_ID, ONESIGNAL_ID_2); const createSubOp = new CreateSubscriptionOperation( mockSubscriptionOpInfo, ); @@ -142,7 +144,7 @@ describe('LoginUserOperationExecutor', () => { retryAfterSeconds: undefined, operations: undefined, idTranslations: { - [DUMMY_ONESIGNAL_ID_2]: DUMMY_ONESIGNAL_ID, + [ONESIGNAL_ID_2]: ONESIGNAL_ID, }, }); }); @@ -150,46 +152,33 @@ describe('LoginUserOperationExecutor', () => { test('can create user with existing subscription and follow up operations', async () => { const someSubscription = { app_id: APP_ID, - id: DUMMY_SUBSCRIPTION_ID_2, + id: SUB_ID_2, type: SubscriptionType.ChromePush, - token: DUMMY_PUSH_TOKEN, + token: PUSH_TOKEN, }; setCreateUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID_2, + onesignalId: ONESIGNAL_ID_2, subscriptions: [someSubscription], }); // have identity model, config, properties model have the same onesignalId to check // that their properties are updated - identityModelStore.model.setProperty( - IdentityConstants.ONESIGNAL_ID, - DUMMY_ONESIGNAL_ID, - ModelChangeTags.HYDRATE, - ); - propertiesModelStore.model.setProperty( - 'onesignalId', - DUMMY_ONESIGNAL_ID, - ModelChangeTags.HYDRATE, - ); - await setPushToken(DUMMY_PUSH_TOKEN); + updateIdentityModel('onesignal_id', ONESIGNAL_ID); + updatePropertiesModel('onesignalId', ONESIGNAL_ID); + await setPushToken(PUSH_TOKEN); const subscriptionModel = new SubscriptionModel(); - subscriptionModel.setProperty( - 'id', - DUMMY_SUBSCRIPTION_ID, - ModelChangeTags.HYDRATE, + subscriptionModel.setProperty('id', SUB_ID, ModelChangeTags.NO_PROPOGATE); + subscriptionModelStore.add( + subscriptionModel, + ModelChangeTags.NO_PROPOGATE, ); - subscriptionModelStore.add(subscriptionModel, ModelChangeTags.HYDRATE); // perform operations with old onesignal id const executor = getExecutor(); - const loginOp = new LoginUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); - loginOp.setProperty( - 'externalId', - DUMMY_EXTERNAL_ID, - ModelChangeTags.HYDRATE, - ); + const loginOp = new LoginUserOperation(APP_ID, ONESIGNAL_ID); + loginOp.setProperty('externalId', EXTERNAL_ID); const createSubOp = new CreateSubscriptionOperation( mockSubscriptionOpInfo, @@ -201,25 +190,23 @@ describe('LoginUserOperationExecutor', () => { // should update model properties expect( identityModelStore.model.getProperty(IdentityConstants.ONESIGNAL_ID), - ).toEqual(DUMMY_ONESIGNAL_ID_2); + ).toEqual(ONESIGNAL_ID_2); expect(propertiesModelStore.model.getProperty('onesignalId')).toEqual( - DUMMY_ONESIGNAL_ID_2, - ); - expect(await getPushToken()).toEqual(DUMMY_PUSH_TOKEN); - expect(subscriptionModel.getProperty('id')).toEqual( - DUMMY_SUBSCRIPTION_ID_2, + ONESIGNAL_ID_2, ); + expect(await getPushToken()).toEqual(PUSH_TOKEN); + expect(subscriptionModel.getProperty('id')).toEqual(SUB_ID_2); // should have a refresh user operation - const refreshOp = new RefreshUserOperation(APP_ID, DUMMY_ONESIGNAL_ID_2); + const refreshOp = new RefreshUserOperation(APP_ID, ONESIGNAL_ID_2); refreshOp.modelId = res.operations![0].modelId; expect(res).toEqual({ result: ExecutionResult.SUCCESS, retryAfterSeconds: undefined, operations: [refreshOp], idTranslations: { - [DUMMY_ONESIGNAL_ID]: DUMMY_ONESIGNAL_ID_2, - [DUMMY_SUBSCRIPTION_ID]: DUMMY_SUBSCRIPTION_ID_2, + [ONESIGNAL_ID]: ONESIGNAL_ID_2, + [SUB_ID]: SUB_ID_2, }, }); }); @@ -227,7 +214,7 @@ describe('LoginUserOperationExecutor', () => { test('should handle network errors', async () => { const executor = getExecutor(); - const loginOp = new LoginUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); + const loginOp = new LoginUserOperation(APP_ID, ONESIGNAL_ID); const createSubOp = new CreateSubscriptionOperation( mockSubscriptionOpInfo, ); @@ -262,25 +249,17 @@ describe('LoginUserOperationExecutor', () => { describe('login user', () => { test('can add/set alias when logging in a user with existing onesignal id', async () => { - identityModelStore.model.setProperty( - IdentityConstants.ONESIGNAL_ID, - DUMMY_ONESIGNAL_ID, - ModelChangeTags.HYDRATE, - ); - propertiesModelStore.model.setProperty( - 'onesignalId', - DUMMY_ONESIGNAL_ID, - ModelChangeTags.HYDRATE, - ); + updateIdentityModel('onesignal_id', ONESIGNAL_ID); + updatePropertiesModel('onesignalId', ONESIGNAL_ID); const executor = getExecutor(); - const loginOp = new LoginUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); - loginOp.setProperty('externalId', DUMMY_EXTERNAL_ID); + const loginOp = new LoginUserOperation(APP_ID, ONESIGNAL_ID); + loginOp.setProperty('externalId', EXTERNAL_ID); // different id for testing id translations - loginOp.setProperty('existingOnesignalId', DUMMY_ONESIGNAL_ID_2); - setAddAliasResponse({ onesignalId: DUMMY_ONESIGNAL_ID_2 }); + loginOp.setProperty('existingOnesignalId', ONESIGNAL_ID_2); + setAddAliasResponse({ onesignalId: ONESIGNAL_ID_2 }); const ops = [loginOp]; const res = await executor.execute(ops); @@ -288,15 +267,15 @@ describe('LoginUserOperationExecutor', () => { // should update model properties expect( identityModelStore.model.getProperty(IdentityConstants.ONESIGNAL_ID), - ).toEqual(DUMMY_ONESIGNAL_ID_2); + ).toEqual(ONESIGNAL_ID_2); expect(propertiesModelStore.model.getProperty('onesignalId')).toEqual( - DUMMY_ONESIGNAL_ID_2, + ONESIGNAL_ID_2, ); expect(res).toEqual({ result: ExecutionResult.SUCCESS_STARTING_ONLY, idTranslations: { - [DUMMY_ONESIGNAL_ID]: DUMMY_ONESIGNAL_ID_2, + [ONESIGNAL_ID]: ONESIGNAL_ID_2, }, }); }); @@ -304,9 +283,9 @@ describe('LoginUserOperationExecutor', () => { test('can handle network errors', async () => { const executor = getExecutor(); - const loginOp = new LoginUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); - loginOp.setProperty('externalId', DUMMY_EXTERNAL_ID); - loginOp.setProperty('existingOnesignalId', DUMMY_ONESIGNAL_ID); + const loginOp = new LoginUserOperation(APP_ID, ONESIGNAL_ID); + loginOp.setProperty('externalId', EXTERNAL_ID); + loginOp.setProperty('existingOnesignalId', ONESIGNAL_ID); // Conflict error - should create user setAddAliasError({ status: 409 }); @@ -318,7 +297,7 @@ describe('LoginUserOperationExecutor', () => { expect(res).toMatchObject({ result: ExecutionResult.SUCCESS, idTranslations: { - [DUMMY_ONESIGNAL_ID]: '123', + [ONESIGNAL_ID]: '123', }, }); @@ -332,7 +311,7 @@ describe('LoginUserOperationExecutor', () => { expect(res2).toMatchObject({ result: ExecutionResult.SUCCESS, idTranslations: { - [DUMMY_ONESIGNAL_ID]: '456', + [ONESIGNAL_ID]: '456', }, }); @@ -349,12 +328,12 @@ describe('LoginUserOperationExecutor', () => { describe('create subscriptions', () => { test('can create a subscriptions', async () => { setCreateUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }); const executor = getExecutor(); - const loginOp = new LoginUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); - loginOp.setProperty('externalId', DUMMY_EXTERNAL_ID); + const loginOp = new LoginUserOperation(APP_ID, ONESIGNAL_ID); + loginOp.setProperty('externalId', EXTERNAL_ID); const createSubOp = new CreateSubscriptionOperation( mockSubscriptionOpInfo, @@ -365,7 +344,7 @@ describe('LoginUserOperationExecutor', () => { expect(createUserFn).toHaveBeenCalledWith({ identity: { - external_id: DUMMY_EXTERNAL_ID, + external_id: EXTERNAL_ID, }, properties: { language: 'en', @@ -386,12 +365,12 @@ describe('LoginUserOperationExecutor', () => { test('can update a subscription', async () => { setCreateUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }); const executor = getExecutor(); - const loginOp = new LoginUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); - loginOp.setProperty('externalId', DUMMY_EXTERNAL_ID); + const loginOp = new LoginUserOperation(APP_ID, ONESIGNAL_ID); + loginOp.setProperty('externalId', EXTERNAL_ID); // needs to be created first for update to do anything const createSubOp = new CreateSubscriptionOperation( @@ -400,7 +379,7 @@ describe('LoginUserOperationExecutor', () => { const updates = { enabled: false, notification_types: NotificationType.NotSubscribed, - token: DUMMY_PUSH_TOKEN_2, + token: PUSH_TOKEN_2, }; const updateSubOp = new UpdateSubscriptionOperation({ ...mockSubscriptionOpInfo, @@ -437,12 +416,12 @@ describe('LoginUserOperationExecutor', () => { test('can transfer a subscription', async () => { setCreateUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }); const executor = getExecutor(); - const loginOp = new LoginUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); - loginOp.setProperty('externalId', DUMMY_EXTERNAL_ID); + const loginOp = new LoginUserOperation(APP_ID, ONESIGNAL_ID); + loginOp.setProperty('externalId', EXTERNAL_ID); // need to create subscription first to test transfer const createSubOp = new CreateSubscriptionOperation( @@ -450,7 +429,7 @@ describe('LoginUserOperationExecutor', () => { ); const transferSubOp = new TransferSubscriptionOperation( APP_ID, - DUMMY_ONESIGNAL_ID, + ONESIGNAL_ID, createSubOp.subscriptionId, ); @@ -464,7 +443,7 @@ describe('LoginUserOperationExecutor', () => { { device_model: '', device_os: DEVICE_OS, - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, sdk: __VERSION__, type: mockSubscriptionOpInfo.type, token: mockSubscriptionOpInfo.token, @@ -476,13 +455,13 @@ describe('LoginUserOperationExecutor', () => { test('can delete a subscription', async () => { setCreateUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, subscriptions: [mockSubscriptionOpInfo], }); const executor = getExecutor(); - const loginOp = new LoginUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); - loginOp.setProperty('externalId', DUMMY_EXTERNAL_ID); + const loginOp = new LoginUserOperation(APP_ID, ONESIGNAL_ID); + loginOp.setProperty('externalId', EXTERNAL_ID); // need to create subscription first to test delete const createSubOp = new CreateSubscriptionOperation( @@ -490,7 +469,7 @@ describe('LoginUserOperationExecutor', () => { ); const deleteSubOp = new DeleteSubscriptionOperation( APP_ID, - DUMMY_ONESIGNAL_ID, + ONESIGNAL_ID, createSubOp.subscriptionId, ); @@ -506,8 +485,8 @@ describe('LoginUserOperationExecutor', () => { const mockSubscriptionOpInfo = { appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, - token: DUMMY_PUSH_TOKEN, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, + token: PUSH_TOKEN, type: SubscriptionType.ChromePush, } satisfies SubscriptionWithAppId; diff --git a/src/core/executors/RefreshUserOperationExecutor.test.ts b/src/core/executors/RefreshUserOperationExecutor.test.ts index ce862862f..3610eec37 100644 --- a/src/core/executors/RefreshUserOperationExecutor.test.ts +++ b/src/core/executors/RefreshUserOperationExecutor.test.ts @@ -1,11 +1,11 @@ import { APP_ID, DEVICE_OS, - DUMMY_ONESIGNAL_ID, - DUMMY_ONESIGNAL_ID_2, - DUMMY_PUSH_TOKEN, - DUMMY_SUBSCRIPTION_ID, - DUMMY_SUBSCRIPTION_ID_2, + ONESIGNAL_ID, + ONESIGNAL_ID_2, + PUSH_TOKEN, + SUB_ID, + SUB_ID_2, } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { SomeOperation } from '__test__/support/helpers/executors'; @@ -14,14 +14,14 @@ import { setGetUserResponse, setUpdateSubscriptionResponse, } from '__test__/support/helpers/requests'; -import { clearAll } from 'src/shared/database/client'; +import { updateIdentityModel } from '__test__/support/helpers/setup'; import { setPushToken } from 'src/shared/database/subscription'; import { NotificationType, SubscriptionType, } from 'src/shared/subscriptions/constants'; import type { MockInstance } from 'vitest'; -import { IdentityConstants, OPERATION_NAME } from '../constants'; +import { OPERATION_NAME } from '../constants'; import { RebuildUserService } from '../modelRepo/RebuildUserService'; import { SubscriptionModel } from '../models/SubscriptionModel'; import { IdentityModelStore } from '../modelStores/IdentityModelStore'; @@ -48,7 +48,6 @@ describe('RefreshUserOperationExecutor', () => { }); beforeEach(async () => { - await clearAll(); // in case subscription model (from previous tests) are loaded from db identityModelStore = OneSignal.coreDirector.identityModelStore; propertiesModelStore = OneSignal.coreDirector.propertiesModelStore; subscriptionModelStore = OneSignal.coreDirector.subscriptionModelStore; @@ -62,11 +61,6 @@ describe('RefreshUserOperationExecutor', () => { buildUserService, 'getRebuildOperationsIfCurrentUser', ); - - setUpdateSubscriptionResponse({ - subscriptionId: '*', - response: {}, - }); }); const getExecutor = () => { @@ -100,22 +94,18 @@ describe('RefreshUserOperationExecutor', () => { describe('getUser', () => { beforeEach(async () => { // Set up initial model state - identityModelStore.model.setProperty( - IdentityConstants.ONESIGNAL_ID, - DUMMY_ONESIGNAL_ID, - ModelChangeTags.HYDRATE, - ); + updateIdentityModel('onesignal_id', ONESIGNAL_ID); }); test('should ignore refresh if id is different in identity model store', async () => { setGetUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID_2, + onesignalId: ONESIGNAL_ID_2, properties: { language: 'fr', }, }); const executor = getExecutor(); - const refreshOp = new RefreshUserOperation(APP_ID, DUMMY_ONESIGNAL_ID_2); + const refreshOp = new RefreshUserOperation(APP_ID, ONESIGNAL_ID_2); const result = await executor.execute([refreshOp]); expect(result.result).toBe(ExecutionResult.SUCCESS); @@ -137,7 +127,7 @@ describe('RefreshUserOperationExecutor', () => { subscriptions: [ { app_id: APP_ID, - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, type: SubscriptionType.Email, token: 'test@example.com', notification_types: NotificationType.UserOptedOut, @@ -149,14 +139,14 @@ describe('RefreshUserOperationExecutor', () => { }); const executor = getExecutor(); - const refreshOp = new RefreshUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); + const refreshOp = new RefreshUserOperation(APP_ID, ONESIGNAL_ID); const result = await executor.execute([refreshOp]); expect(result.result).toBe(ExecutionResult.SUCCESS); // Check identity model updates expect(identityModelStore.model.getProperty('onesignal_id')).toBe( - DUMMY_ONESIGNAL_ID, + ONESIGNAL_ID, ); expect(identityModelStore.model.getProperty('external_id')).toBe( 'test_user', @@ -175,7 +165,7 @@ describe('RefreshUserOperationExecutor', () => { const subscriptions = subscriptionModelStore.list(); expect(subscriptions.length).toBe(1); expect(subscriptions[0]).toMatchObject({ - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, notification_types: NotificationType.UserOptedOut, enabled: false, token: 'test@example.com', @@ -187,22 +177,23 @@ describe('RefreshUserOperationExecutor', () => { }); test('should preserve cached push subscription when updating models', async () => { + setUpdateSubscriptionResponse({ subscriptionId: SUB_ID_2 }); // Set up a push subscription in the store const pushSubModel = new SubscriptionModel(); - pushSubModel.id = DUMMY_SUBSCRIPTION_ID_2; + pushSubModel.id = SUB_ID_2; pushSubModel.type = SubscriptionType.ChromePush; - pushSubModel.token = DUMMY_PUSH_TOKEN; + pushSubModel.token = PUSH_TOKEN; pushSubModel.notification_types = NotificationType.Subscribed; - subscriptionModelStore.add(pushSubModel, ModelChangeTags.HYDRATE); - await setPushToken(DUMMY_PUSH_TOKEN); + subscriptionModelStore.add(pushSubModel, ModelChangeTags.NO_PROPOGATE); + await setPushToken(PUSH_TOKEN); const executor = getExecutor(); - const refreshOp = new RefreshUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); + const refreshOp = new RefreshUserOperation(APP_ID, ONESIGNAL_ID); // Mock response without push subscription setGetUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, subscriptions: [ { id: 'email-sub-id', @@ -223,13 +214,13 @@ describe('RefreshUserOperationExecutor', () => { (sub: SubscriptionModel) => sub.type === SubscriptionType.ChromePush, ); expect(pushSub).toBeDefined(); - expect(pushSub?.id).toBe(DUMMY_SUBSCRIPTION_ID_2); - expect(pushSub?.token).toBe(DUMMY_PUSH_TOKEN); + expect(pushSub?.id).toBe(SUB_ID_2); + expect(pushSub?.token).toBe(PUSH_TOKEN); }); test('should handle network errors', async () => { const executor = getExecutor(); - const refreshOp = new RefreshUserOperation(APP_ID, DUMMY_ONESIGNAL_ID); + const refreshOp = new RefreshUserOperation(APP_ID, ONESIGNAL_ID); // retryable error setGetUserError({ @@ -275,18 +266,18 @@ describe('RefreshUserOperationExecutor', () => { { name: 'login-user', appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }, { name: 'refresh-user', appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }, ], }); // -- in missing retry window - newRecordsState.add(DUMMY_ONESIGNAL_ID); + newRecordsState.add(ONESIGNAL_ID); setGetUserError({ status: 404, retryAfter: 20, diff --git a/src/core/executors/SubscriptionOperationExecutor.test.ts b/src/core/executors/SubscriptionOperationExecutor.test.ts index 547e22c05..74fbf9b36 100644 --- a/src/core/executors/SubscriptionOperationExecutor.test.ts +++ b/src/core/executors/SubscriptionOperationExecutor.test.ts @@ -1,15 +1,21 @@ -import { - APP_ID, - DUMMY_ONESIGNAL_ID, - DUMMY_PUSH_TOKEN, - DUMMY_SUBSCRIPTION_ID_3, -} from '__test__/constants'; +import { APP_ID, ONESIGNAL_ID, PUSH_TOKEN, SUB_ID } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { createPushSub } from '__test__/support/environment/TestEnvironmentHelpers'; import { SomeOperation } from '__test__/support/helpers/executors'; -import { server } from '__test__/support/mocks/server'; -import { http, HttpResponse } from 'msw'; -import { db } from 'src/shared/database/client'; +import { + createSubscriptionFn, + setCreateSubscriptionError, + setCreateSubscriptionResponse, + setDeleteSubscriptionError, + setDeleteSubscriptionResponse, + setTransferSubscriptionError, + setTransferSubscriptionResponse, + setUpdateSubscriptionError, + setUpdateSubscriptionResponse, + transferSubscriptionFn, + updateSubscriptionFn, +} from '__test__/support/helpers/requests'; +import { setupSubscriptionModel } from '__test__/support/helpers/setup'; import { setPushToken } from 'src/shared/database/subscription'; import { NotificationType, @@ -18,7 +24,6 @@ import { import type { MockInstance } from 'vitest'; import { OPERATION_NAME } from '../constants'; import { RebuildUserService } from '../modelRepo/RebuildUserService'; -import { SubscriptionModel } from '../models/SubscriptionModel'; import { IdentityModelStore } from '../modelStores/IdentityModelStore'; import { PropertiesModelStore } from '../modelStores/PropertiesModelStore'; import { SubscriptionModelStore } from '../modelStores/SubscriptionModelStore'; @@ -39,20 +44,18 @@ let propertiesModelStore: PropertiesModelStore; let subscriptionsModelStore: SubscriptionModelStore; let getRebuildOpsSpy: MockInstance; -const DUMMY_SUBSCRIPTION_ID = 'test-subscription-id'; +const pushSubscription = createPushSub(); const BACKEND_SUBSCRIPTION_ID = 'backend-subscription-id'; vi.mock('src/shared/libraries/Log'); -const pushSubscription = createPushSub(); - describe('SubscriptionOperationExecutor', () => { beforeAll(async () => { await TestEnvironment.initialize(); }); beforeEach(async () => { - setCreateSubscriptionResponse(); + setCreateSubscriptionResponse({}); subscriptionModelStore = OneSignal.coreDirector.subscriptionModelStore; newRecordsState = OneSignal.coreDirector.newRecordsState; newRecordsState.records.clear(); @@ -61,11 +64,6 @@ describe('SubscriptionOperationExecutor', () => { propertiesModelStore = OneSignal.coreDirector.propertiesModelStore; subscriptionsModelStore = OneSignal.coreDirector.subscriptionModelStore; - identityModelStore.model.setProperty( - 'onesignal_id', - DUMMY_ONESIGNAL_ID, - ModelChangeTags.HYDRATE, - ); buildUserService = new RebuildUserService( identityModelStore, propertiesModelStore, @@ -77,10 +75,6 @@ describe('SubscriptionOperationExecutor', () => { ); }); - afterEach(async () => { - await db.clear('subscriptions'); - }); - const getExecutor = () => { return new SubscriptionOperationExecutor( subscriptionModelStore, @@ -111,13 +105,13 @@ describe('SubscriptionOperationExecutor', () => { const deleteOp = new DeleteSubscriptionOperation( APP_ID, - DUMMY_ONESIGNAL_ID, - DUMMY_SUBSCRIPTION_ID, + ONESIGNAL_ID, + SUB_ID, ); const updateOp = new UpdateSubscriptionOperation({ appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, token: 'test', type: SubscriptionType.ChromePush, }); @@ -129,8 +123,8 @@ describe('SubscriptionOperationExecutor', () => { const transferOp = new TransferSubscriptionOperation( APP_ID, - DUMMY_ONESIGNAL_ID, - DUMMY_SUBSCRIPTION_ID, + ONESIGNAL_ID, + SUB_ID, ); const res3 = executor.execute([transferOp, updateOp]); await expect(() => res3).rejects.toThrow( @@ -144,19 +138,20 @@ describe('SubscriptionOperationExecutor', () => { }); test('should create subscription successfully', async () => { - const model = new SubscriptionModel(); - model.setProperty('id', DUMMY_SUBSCRIPTION_ID, ModelChangeTags.HYDRATE); - model.setProperty('token', DUMMY_PUSH_TOKEN, ModelChangeTags.HYDRATE); - subscriptionModelStore.add(model); + setupSubscriptionModel(SUB_ID, PUSH_TOKEN); - setCreateSubscriptionResponse(BACKEND_SUBSCRIPTION_ID); - await setPushToken(DUMMY_PUSH_TOKEN); + setCreateSubscriptionResponse({ + response: { + id: BACKEND_SUBSCRIPTION_ID, + }, + }); + await setPushToken(PUSH_TOKEN); const executor = getExecutor(); const createOp = new CreateSubscriptionOperation({ appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, type: SubscriptionType.ChromePush, token: 'test-token', enabled: true, @@ -167,7 +162,7 @@ describe('SubscriptionOperationExecutor', () => { expect(result).toMatchObject({ result: ExecutionResult.SUCCESS, idTranslations: { - [DUMMY_SUBSCRIPTION_ID]: BACKEND_SUBSCRIPTION_ID, + [SUB_ID]: BACKEND_SUBSCRIPTION_ID, }, }); @@ -182,8 +177,8 @@ describe('SubscriptionOperationExecutor', () => { const executor = getExecutor(); const createOp = new CreateSubscriptionOperation({ appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, type: SubscriptionType.ChromePush, token: 'old-token', enabled: true, @@ -192,8 +187,8 @@ describe('SubscriptionOperationExecutor', () => { const updateOp = new UpdateSubscriptionOperation({ appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, type: SubscriptionType.Email, token: 'new-token', enabled: false, @@ -218,8 +213,8 @@ describe('SubscriptionOperationExecutor', () => { const executor = getExecutor(); const createOp = new CreateSubscriptionOperation({ appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, type: SubscriptionType.ChromePush, token: 'test-token', enabled: true, @@ -228,8 +223,8 @@ describe('SubscriptionOperationExecutor', () => { const deleteOp = new DeleteSubscriptionOperation( APP_ID, - DUMMY_ONESIGNAL_ID, - DUMMY_SUBSCRIPTION_ID, + ONESIGNAL_ID, + SUB_ID, ); const result = await executor.execute([createOp, deleteOp]); @@ -243,8 +238,8 @@ describe('SubscriptionOperationExecutor', () => { const executor = getExecutor(); const createOp = new CreateSubscriptionOperation({ appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, type: SubscriptionType.ChromePush, token: 'test-token', enabled: true, @@ -252,7 +247,7 @@ describe('SubscriptionOperationExecutor', () => { }); // Retryable error - setCreateSubscriptionError(429, 10); + setCreateSubscriptionError({ status: 429, retryAfter: 10 }); const res1 = await executor.execute([createOp]); expect(res1).toMatchObject({ result: ExecutionResult.FAIL_RETRY, @@ -260,21 +255,21 @@ describe('SubscriptionOperationExecutor', () => { }); // Conflict error - setCreateSubscriptionError(400, 10); + setCreateSubscriptionError({ status: 400, retryAfter: 10 }); const res2 = await executor.execute([createOp]); expect(res2).toMatchObject({ result: ExecutionResult.FAIL_NORETRY, }); // Invalid error - setCreateSubscriptionError(409, 10); + setCreateSubscriptionError({ status: 409, retryAfter: 10 }); const res3 = await executor.execute([createOp]); expect(res3).toMatchObject({ result: ExecutionResult.FAIL_NORETRY, }); // Unauthorized error - setCreateSubscriptionError(401, 15); + setCreateSubscriptionError({ status: 401, retryAfter: 15 }); const res4 = await executor.execute([createOp]); expect(res4).toMatchObject({ result: ExecutionResult.FAIL_UNAUTHORIZED, @@ -282,7 +277,7 @@ describe('SubscriptionOperationExecutor', () => { }); // Missing error without rebuild ops - setCreateSubscriptionError(404, 5); + setCreateSubscriptionError({ status: 404, retryAfter: 5 }); getRebuildOpsSpy.mockReturnValueOnce(null); const res5 = await executor.execute([createOp]); @@ -291,7 +286,7 @@ describe('SubscriptionOperationExecutor', () => { }); // Missing error with rebuild ops - subscriptionsModelStore.add(pushSubscription); + subscriptionsModelStore.add(pushSubscription, ModelChangeTags.HYDRATE); await setPushToken(pushSubscription.token); const res6 = await executor.execute([createOp]); @@ -302,28 +297,28 @@ describe('SubscriptionOperationExecutor', () => { { name: 'login-user', appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }, { name: 'create-subscription', appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, type: SubscriptionType.ChromePush, - token: 'push-token', + token: pushSubscription.token, enabled: true, - subscriptionId: DUMMY_SUBSCRIPTION_ID_3, + subscriptionId: pushSubscription.id, }, { name: 'refresh-user', appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }, ], }); // Missing error in retry window - newRecordsState.add(DUMMY_ONESIGNAL_ID); - setCreateSubscriptionError(404, 20); + newRecordsState.add(ONESIGNAL_ID); + setCreateSubscriptionError({ status: 404, retryAfter: 20 }); const res7 = await executor.execute([createOp]); expect(res7).toMatchObject({ result: ExecutionResult.FAIL_RETRY, @@ -331,7 +326,7 @@ describe('SubscriptionOperationExecutor', () => { }); // Other errors - setCreateSubscriptionError(400); + setCreateSubscriptionError({ status: 400 }); const res8 = await executor.execute([createOp]); expect(res8).toMatchObject({ result: ExecutionResult.FAIL_NORETRY, @@ -348,8 +343,8 @@ describe('SubscriptionOperationExecutor', () => { const executor = getExecutor(); const updateOp = new UpdateSubscriptionOperation({ appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, type: SubscriptionType.ChromePush, token: 'updated-token', enabled: false, @@ -377,8 +372,8 @@ describe('SubscriptionOperationExecutor', () => { const executor = getExecutor(); const updateOp1 = new UpdateSubscriptionOperation({ appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, type: SubscriptionType.Email, token: 'first-update', enabled: true, @@ -389,8 +384,8 @@ describe('SubscriptionOperationExecutor', () => { const updateOp2 = new UpdateSubscriptionOperation({ appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, type: SubscriptionType.ChromePush, token: 'second-update', enabled: false, @@ -419,8 +414,8 @@ describe('SubscriptionOperationExecutor', () => { const executor = getExecutor(); const updateOp = new UpdateSubscriptionOperation({ appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, type: SubscriptionType.ChromePush, token: 'test-token', enabled: true, @@ -428,7 +423,7 @@ describe('SubscriptionOperationExecutor', () => { }); // Retryable error - setUpdateSubscriptionError(429, 15); + setUpdateSubscriptionError({ status: 429, retryAfter: 15 }); const res1 = await executor.execute([updateOp]); expect(res1).toMatchObject({ result: ExecutionResult.FAIL_RETRY, @@ -436,14 +431,14 @@ describe('SubscriptionOperationExecutor', () => { }); // Missing error - setUpdateSubscriptionError(404, 10); + setUpdateSubscriptionError({ status: 404, retryAfter: 10 }); const res2 = await executor.execute([updateOp]); const subOp = new CreateSubscriptionOperation({ appId: APP_ID, enabled: true, notification_types: NotificationType.Subscribed, - onesignalId: DUMMY_ONESIGNAL_ID, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + onesignalId: ONESIGNAL_ID, + subscriptionId: SUB_ID, token: 'test-token', type: SubscriptionType.ChromePush, }); @@ -454,7 +449,7 @@ describe('SubscriptionOperationExecutor', () => { }); // Missing error with record in retry window - newRecordsState.add(DUMMY_SUBSCRIPTION_ID); + newRecordsState.add(SUB_ID); const res3 = await executor.execute([updateOp]); expect(res3).toMatchObject({ result: ExecutionResult.FAIL_RETRY, @@ -462,7 +457,7 @@ describe('SubscriptionOperationExecutor', () => { }); // Other errors - setUpdateSubscriptionError(400); + setUpdateSubscriptionError({ status: 400 }); const res4 = await executor.execute([updateOp]); expect(res4).toMatchObject({ result: ExecutionResult.FAIL_NORETRY, @@ -477,40 +472,38 @@ describe('SubscriptionOperationExecutor', () => { test('should delete subscription successfully', async () => { // Set up a subscription model to be deleted - const model = new SubscriptionModel(); - model.setProperty('id', DUMMY_SUBSCRIPTION_ID, ModelChangeTags.HYDRATE); - subscriptionModelStore.add(model); + setupSubscriptionModel(SUB_ID, PUSH_TOKEN); const executor = getExecutor(); const deleteOp = new DeleteSubscriptionOperation( APP_ID, - DUMMY_ONESIGNAL_ID, - DUMMY_SUBSCRIPTION_ID, + ONESIGNAL_ID, + SUB_ID, ); const result = await executor.execute([deleteOp]); expect(result.result).toBe(ExecutionResult.SUCCESS); // Verify model was removed - expect(subscriptionModelStore.get(DUMMY_SUBSCRIPTION_ID)).toBeUndefined(); + expect(subscriptionModelStore.get(SUB_ID)).toBeUndefined(); }); test('should handle network errors', async () => { const executor = getExecutor(); const deleteOp = new DeleteSubscriptionOperation( APP_ID, - DUMMY_ONESIGNAL_ID, - DUMMY_SUBSCRIPTION_ID, + ONESIGNAL_ID, + SUB_ID, ); // Missing error - setDeleteSubscriptionError(404); + setDeleteSubscriptionError({ status: 404 }); const result = await executor.execute([deleteOp]); expect(result.result).toBe(ExecutionResult.SUCCESS); // Missing error with record in retry window - newRecordsState.add(DUMMY_SUBSCRIPTION_ID); - setDeleteSubscriptionError(404, 5); + newRecordsState.add(SUB_ID); + setDeleteSubscriptionError({ status: 404, retryAfter: 5 }); const res2 = await executor.execute([deleteOp]); expect(res2).toMatchObject({ result: ExecutionResult.FAIL_RETRY, @@ -518,7 +511,11 @@ describe('SubscriptionOperationExecutor', () => { }); // Retryable error - setDeleteSubscriptionError(429, 10); + setDeleteSubscriptionError({ + status: 429, + retryAfter: 10, + subscriptionId: SUB_ID, + }); const res3 = await executor.execute([deleteOp]); expect(res3).toMatchObject({ result: ExecutionResult.FAIL_RETRY, @@ -526,7 +523,10 @@ describe('SubscriptionOperationExecutor', () => { }); // Other errors - setDeleteSubscriptionError(400); + setDeleteSubscriptionError({ + status: 400, + subscriptionId: SUB_ID, + }); const res4 = await executor.execute([deleteOp]); expect(res4).toMatchObject({ result: ExecutionResult.FAIL_NORETRY, @@ -543,8 +543,8 @@ describe('SubscriptionOperationExecutor', () => { const executor = getExecutor(); const transferOp = new TransferSubscriptionOperation( APP_ID, - DUMMY_ONESIGNAL_ID, - DUMMY_SUBSCRIPTION_ID, + ONESIGNAL_ID, + SUB_ID, ); const result = await executor.execute([transferOp]); @@ -552,7 +552,7 @@ describe('SubscriptionOperationExecutor', () => { expect(transferSubscriptionFn).toHaveBeenCalledWith({ identity: { - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, }, retain_previous_owner: false, }); @@ -562,12 +562,12 @@ describe('SubscriptionOperationExecutor', () => { const executor = getExecutor(); const transferOp = new TransferSubscriptionOperation( APP_ID, - DUMMY_ONESIGNAL_ID, - DUMMY_SUBSCRIPTION_ID, + ONESIGNAL_ID, + SUB_ID, ); // Retryable error - setTransferSubscriptionError(429, 10); + setTransferSubscriptionError({ status: 429, retryAfter: 10 }); const res2 = await executor.execute([transferOp]); expect(res2).toMatchObject({ result: ExecutionResult.FAIL_RETRY, @@ -575,7 +575,7 @@ describe('SubscriptionOperationExecutor', () => { }); // Other errors - setTransferSubscriptionError(400); + setTransferSubscriptionError({ status: 400 }); const res3 = await executor.execute([transferOp]); expect(res3).toMatchObject({ result: ExecutionResult.FAIL_NORETRY, @@ -583,118 +583,3 @@ describe('SubscriptionOperationExecutor', () => { }); }); }); - -const createSubscriptionFn = vi.fn(); -const updateSubscriptionFn = vi.fn(); -const deleteSubscriptionFn = vi.fn(); -const transferSubscriptionFn = vi.fn(); - -const getCreateSubscriptionUri = () => - `**/apps/${APP_ID}/users/by/onesignal_id/${DUMMY_ONESIGNAL_ID}/subscriptions`; -const setCreateSubscriptionResponse = ( - subscriptionId = DUMMY_SUBSCRIPTION_ID, -) => { - server.use( - http.post(getCreateSubscriptionUri(), async ({ request }) => { - createSubscriptionFn(await request?.json()); - return HttpResponse.json({ - subscription: { - id: subscriptionId, - }, - }); - }), - ); -}; -const setCreateSubscriptionError = (status: number, retryAfter?: number) => { - server.use( - http.post(getCreateSubscriptionUri(), () => - HttpResponse.json( - {}, - { - status, - headers: retryAfter - ? { 'Retry-After': retryAfter?.toString() } - : undefined, - }, - ), - ), - ); -}; - -const getUpdateSubscriptionUri = (subscriptionId = DUMMY_SUBSCRIPTION_ID) => - `**/apps/${APP_ID}/subscriptions/${subscriptionId}`; -const setUpdateSubscriptionResponse = () => { - server.use( - http.patch(getUpdateSubscriptionUri(), async ({ request }) => { - updateSubscriptionFn(await request?.json()); - return HttpResponse.json({ success: true }); - }), - ); -}; -const setUpdateSubscriptionError = (status: number, retryAfter?: number) => { - server.use( - http.patch(getUpdateSubscriptionUri(), () => - HttpResponse.json( - {}, - { - status, - headers: retryAfter - ? { 'Retry-After': retryAfter?.toString() } - : undefined, - }, - ), - ), - ); -}; - -const getDeleteSubscriptionUri = (subscriptionId = DUMMY_SUBSCRIPTION_ID) => - `**/apps/${APP_ID}/subscriptions/${subscriptionId}`; -const setDeleteSubscriptionResponse = () => { - server.use( - http.delete(getDeleteSubscriptionUri(), () => { - deleteSubscriptionFn(); - return HttpResponse.json({ success: true }); - }), - ); -}; -const setDeleteSubscriptionError = (status: number, retryAfter?: number) => { - server.use( - http.delete(getDeleteSubscriptionUri(), () => - HttpResponse.json( - {}, - { - status, - headers: retryAfter - ? { 'Retry-After': retryAfter?.toString() } - : undefined, - }, - ), - ), - ); -}; - -const getTransferSubscriptionUri = () => - `**/apps/${APP_ID}/subscriptions/${DUMMY_SUBSCRIPTION_ID}/owner`; -const setTransferSubscriptionResponse = () => { - server.use( - http.patch(getTransferSubscriptionUri(), async ({ request }) => { - transferSubscriptionFn(await request?.json()); - return HttpResponse.json({ success: true }); - }), - ); -}; -const setTransferSubscriptionError = (status: number, retryAfter?: number) => { - server.use( - http.patch(getTransferSubscriptionUri(), () => - HttpResponse.json( - {}, - { - status, - headers: retryAfter - ? { 'Retry-After': retryAfter?.toString() } - : undefined, - }, - ), - ), - ); -}; diff --git a/src/core/executors/UpdateUserOperationExecutor.test.ts b/src/core/executors/UpdateUserOperationExecutor.test.ts index 5635f4363..7e417d595 100644 --- a/src/core/executors/UpdateUserOperationExecutor.test.ts +++ b/src/core/executors/UpdateUserOperationExecutor.test.ts @@ -1,19 +1,19 @@ -import { APP_ID, DUMMY_ONESIGNAL_ID } from '__test__/constants'; +import { APP_ID, ONESIGNAL_ID } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { SomeOperation } from '__test__/support/helpers/executors'; import { setUpdateUserError, setUpdateUserResponse, } from '__test__/support/helpers/requests'; +import { updateIdentityModel } from '__test__/support/helpers/setup'; import { type MockInstance } from 'vitest'; -import { IdentityConstants, OPERATION_NAME } from '../constants'; +import { OPERATION_NAME } from '../constants'; import { RebuildUserService } from '../modelRepo/RebuildUserService'; import { IdentityModelStore } from '../modelStores/IdentityModelStore'; import { PropertiesModelStore } from '../modelStores/PropertiesModelStore'; import { SubscriptionModelStore } from '../modelStores/SubscriptionModelStore'; import { NewRecordsState } from '../operationRepo/NewRecordsState'; import { SetPropertyOperation } from '../operations/SetPropertyOperation'; -import { ModelChangeTags } from '../types/models'; import { ExecutionResult } from '../types/operation'; import { UpdateUserOperationExecutor } from './UpdateUserOperationExecutor'; @@ -47,11 +47,7 @@ describe('UpdateUserOperationExecutor', () => { ); // Set up initial model state - identityModelStore.model.setProperty( - IdentityConstants.ONESIGNAL_ID, - DUMMY_ONESIGNAL_ID, - ModelChangeTags.HYDRATE, - ); + updateIdentityModel('onesignal_id', ONESIGNAL_ID); }); const getExecutor = () => { @@ -88,7 +84,7 @@ describe('UpdateUserOperationExecutor', () => { const executor = getExecutor(); const setPropertyOp = new SetPropertyOperation( APP_ID, - DUMMY_ONESIGNAL_ID, + ONESIGNAL_ID, 'language', 'fr', ); @@ -102,7 +98,7 @@ describe('UpdateUserOperationExecutor', () => { const executor = getExecutor(); const setPropertyOp = new SetPropertyOperation( APP_ID, - DUMMY_ONESIGNAL_ID, + ONESIGNAL_ID, 'tags', { tagA: 'valueA', tagB: 'valueB' }, ); @@ -119,12 +115,9 @@ describe('UpdateUserOperationExecutor', () => { describe('Error Handling', () => { test('should handle network errors', async () => { const executor = getExecutor(); - const setTagOp = new SetPropertyOperation( - APP_ID, - DUMMY_ONESIGNAL_ID, - 'tags', - { test_tag: 'test_value' }, - ); + const setTagOp = new SetPropertyOperation(APP_ID, ONESIGNAL_ID, 'tags', { + test_tag: 'test_value', + }); // Retryable error setUpdateUserError({ status: 429, retryAfter: 10 }); @@ -159,18 +152,18 @@ describe('UpdateUserOperationExecutor', () => { { name: 'login-user', appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }, { name: 'refresh-user', appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }, ], }); // Missing error in retry window - newRecordsState.add(DUMMY_ONESIGNAL_ID); + newRecordsState.add(ONESIGNAL_ID); setUpdateUserError({ status: 404, retryAfter: 20 }); const res5 = await executor.execute([setTagOp]); expect(res5).toMatchObject({ diff --git a/src/core/operationRepo/OperationRepo.test.ts b/src/core/operationRepo/OperationRepo.test.ts index 08d35178b..801dccaf1 100644 --- a/src/core/operationRepo/OperationRepo.test.ts +++ b/src/core/operationRepo/OperationRepo.test.ts @@ -1,9 +1,4 @@ -import { - APP_ID, - DUMMY_ONESIGNAL_ID, - DUMMY_SUBSCRIPTION_ID, -} from '__test__/constants'; -import { fakeWaitForOperations } from '__test__/support/helpers/executors'; +import { APP_ID, ONESIGNAL_ID, SUB_ID } from '__test__/constants'; import { db } from 'src/shared/database/client'; import type { IndexedDBSchema } from 'src/shared/database/types'; import Log from 'src/shared/libraries/Log'; @@ -110,7 +105,7 @@ describe('OperationRepo', () => { const op1 = new SetAliasOperation( APP_ID, - DUMMY_ONESIGNAL_ID, + ONESIGNAL_ID, 'some-label', 'some-value', ); @@ -118,10 +113,10 @@ describe('OperationRepo', () => { const op2 = new CreateSubscriptionOperation({ appId: APP_ID, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, token: 'some-token', type: SubscriptionType.ChromePush, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + subscriptionId: SUB_ID, }); opRepo.enqueue(op2); expect(mockOperationModelStore.list()).toEqual([op1, op2]); @@ -205,7 +200,7 @@ describe('OperationRepo', () => { expect(opRepo.queue.length).toBe(1); // index will be 1 if enqueue is after start - await fakeWaitForOperations(); + await vi.waitUntil(() => getNextOpsSpy.mock.calls.length > 0); expect(getNextOpsSpy).toHaveBeenCalledWith(0); expect(opRepo.queue.length).toBe(0); @@ -514,7 +509,7 @@ class Operation extends OperationBase<{ value: string }> { applyToRecordId = '', canStartExecute = true, ) { - super('mock-op', APP_ID, DUMMY_ONESIGNAL_ID); + super('mock-op', APP_ID, ONESIGNAL_ID); this.value = value; this._groupComparisonTypeValue = groupComparisonTypeValue; this._createComparisonKey = createComparisonKey; diff --git a/src/core/operationRepo/OperationRepo.ts b/src/core/operationRepo/OperationRepo.ts index 8a54ce181..b0fce44de 100644 --- a/src/core/operationRepo/OperationRepo.ts +++ b/src/core/operationRepo/OperationRepo.ts @@ -70,6 +70,10 @@ export class OperationRepo implements IOperationRepo, IStartableService { } } + public clear(): void { + this.queue = []; + } + public get isPaused(): boolean { return this.paused; } diff --git a/src/entries/pageSdkInit2.test.ts b/src/entries/pageSdkInit2.test.ts index 53fbe1a47..e24f56b0c 100644 --- a/src/entries/pageSdkInit2.test.ts +++ b/src/entries/pageSdkInit2.test.ts @@ -2,10 +2,10 @@ import { APP_ID, DEVICE_OS, - DUMMY_ONESIGNAL_ID, - DUMMY_PUSH_TOKEN, - DUMMY_SUBSCRIPTION_ID, - DUMMY_SUBSCRIPTION_ID_2, + ONESIGNAL_ID, + PUSH_TOKEN, + SUB_ID, + SUB_ID_2, } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { setupSubModelStore } from '__test__/support/environment/TestEnvironmentHelpers'; @@ -15,6 +15,7 @@ import { setCreateUserResponse, setGetUserResponse, } from '__test__/support/helpers/requests'; +import { updateIdentityModel } from '__test__/support/helpers/setup'; import { server } from '__test__/support/mocks/server'; import { SubscriptionModel } from 'src/core/models/SubscriptionModel'; import { db } from 'src/shared/database/client'; @@ -25,18 +26,19 @@ import { IDManager } from 'src/shared/managers/IDManager'; describe('pageSdkInit 2', () => { beforeEach(async () => { await TestEnvironment.initialize(); + updateIdentityModel('onesignal_id', undefined); server.use(mockServerConfig()); }); test('can login and addEmail', async () => { const email = 'joe@example.com'; const subModel = await setupSubModelStore({ - id: DUMMY_SUBSCRIPTION_ID, - token: DUMMY_PUSH_TOKEN, + id: SUB_ID, + token: PUSH_TOKEN, }); const emailSubModel = new SubscriptionModel(); emailSubModel.mergeData({ - id: DUMMY_SUBSCRIPTION_ID_2, + id: SUB_ID_2, token: email, type: 'Email', }); @@ -50,7 +52,7 @@ describe('pageSdkInit 2', () => { ], }); setCreateUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }); setCreateSubscriptionResponse({ response: emailSubModel, @@ -79,8 +81,8 @@ describe('pageSdkInit 2', () => { expect(subModels).toMatchObject([ { - id: DUMMY_SUBSCRIPTION_ID, - onesignalId: DUMMY_ONESIGNAL_ID, + id: SUB_ID, + onesignalId: ONESIGNAL_ID, type: 'ChromePush', }, { @@ -115,7 +117,7 @@ describe('pageSdkInit 2', () => { ...shared, id: subModel.id, modelId: subModel.modelId, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, token: subModel.token, type: 'ChromePush', }, diff --git a/src/onesignal/NotificationsNamespace.ts b/src/onesignal/NotificationsNamespace.ts index fa58de873..710a04fed 100644 --- a/src/onesignal/NotificationsNamespace.ts +++ b/src/onesignal/NotificationsNamespace.ts @@ -10,7 +10,6 @@ import type { NotificationEventTypeMap, } from 'src/shared/notifications/types'; import { EventListenerBase } from '../page/userModel/EventListenerBase'; -import { NotificationPermission } from '../shared/models/NotificationPermission'; import { awaitOneSignalInitAndSupported, logMethodCall, @@ -25,13 +24,13 @@ export default class NotificationsNamespace extends EventListenerBase { super(); this._permissionNative = permissionNative; - this._permission = permissionNative === NotificationPermission.Granted; + this._permission = permissionNative === 'granted'; OneSignal.emitter.on( OneSignal.EVENTS.NOTIFICATION_PERMISSION_CHANGED_AS_STRING, (permissionNative: NotificationPermission) => { this._permissionNative = permissionNative; - this._permission = permissionNative === NotificationPermission.Granted; + this._permission = permissionNative === 'granted'; }, ); } diff --git a/src/onesignal/OneSignal.test.ts b/src/onesignal/OneSignal.test.ts index 808c72d8d..218ccc8b5 100644 --- a/src/onesignal/OneSignal.test.ts +++ b/src/onesignal/OneSignal.test.ts @@ -1,16 +1,15 @@ import { APP_ID, DEVICE_OS, - DUMMY_ONESIGNAL_ID, - DUMMY_ONESIGNAL_ID_2, - DUMMY_PUSH_TOKEN, - DUMMY_SUBSCRIPTION_ID, - DUMMY_SUBSCRIPTION_ID_2, - DUMMY_SUBSCRIPTION_ID_3, + ONESIGNAL_ID, + ONESIGNAL_ID_2, + PUSH_TOKEN, + SUB_ID, + SUB_ID_2, + SUB_ID_3, } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { setupSubModelStore } from '__test__/support/environment/TestEnvironmentHelpers'; -import { waitForOperations } from '__test__/support/helpers/executors'; import { addAliasFn, createSubscriptionFn, @@ -35,19 +34,20 @@ import { transferSubscriptionFn, updateUserFn, } from '__test__/support/helpers/requests'; +import { + getIdentityItem, + setupIdentityModel, + setupPropertiesModel, + updateIdentityModel, +} from '__test__/support/helpers/setup'; import { MockServiceWorker } from '__test__/support/mocks/MockServiceWorker'; import { server } from '__test__/support/mocks/server'; -import { IdentityModel } from 'src/core/models/IdentityModel'; -import { PropertiesModel } from 'src/core/models/PropertiesModel'; import { OperationQueueItem } from 'src/core/operationRepo/OperationRepo'; import { type ICreateUserSubscription } from 'src/core/types/api'; import { ModelChangeTags } from 'src/core/types/models'; -import { clearAll, db } from 'src/shared/database/client'; +import { db } from 'src/shared/database/client'; import { setPushToken } from 'src/shared/database/subscription'; -import type { - IdentitySchema, - SubscriptionSchema, -} from 'src/shared/database/types'; +import type { SubscriptionSchema } from 'src/shared/database/types'; import { registerForPushNotifications } from 'src/shared/helpers/init'; import { setConsentRequired } from 'src/shared/helpers/localStorage'; import MainHelper from 'src/shared/helpers/MainHelper'; @@ -59,39 +59,19 @@ import { RawPushSubscription } from 'src/shared/models/RawPushSubscription'; describe('OneSignal', () => { beforeAll(async () => { server.use(mockServerConfig(), mockPageStylesCss()); - const _onesignal = await TestEnvironment.initialize(); - window.OneSignal = _onesignal; - - await setupIdentity(); - - await window.OneSignal.init({ appId: APP_ID }); + await TestEnvironment.initialize(); + await OneSignal.init({ appId: APP_ID }); }); beforeEach(async () => { - setConsentRequired(false); - - // reset the identity model - const newIdentityModel = new IdentityModel(); - newIdentityModel.onesignalId = DUMMY_ONESIGNAL_ID; - window.OneSignal.coreDirector - .getIdentityModel() - .initializeFromJson(newIdentityModel.toJSON()); - - const newPropertiesModel = new PropertiesModel(); - newPropertiesModel.onesignalId = DUMMY_ONESIGNAL_ID; - window.OneSignal.coreDirector - .getPropertiesModel() - .initializeFromJson(newPropertiesModel.toJSON()); - }); - - afterEach(async () => { - await clearAll(); - window.OneSignal.coreDirector.operationRepo.queue = []; - window.OneSignal.coreDirector.subscriptionModelStore.replaceAll( + OneSignal.coreDirector.subscriptionModelStore.replaceAll( [], - ModelChangeTags.HYDRATE, + ModelChangeTags.NO_PROPOGATE, ); - await setupIdentity(); + // OneSignal.coreDirector.operationRepo.clear(); + setConsentRequired(false); + setupPropertiesModel(); + await setupIdentityModel(); }); describe('User', () => { @@ -103,8 +83,8 @@ describe('OneSignal', () => { }); test('can add an alias to the current user', async () => { - window.OneSignal.User.addAlias('someLabel', 'someId'); - const identityModel = window.OneSignal.coreDirector.getIdentityModel(); + OneSignal.User.addAlias('someLabel', 'someId'); + const identityModel = OneSignal.coreDirector.getIdentityModel(); expect(identityModel.getProperty('someLabel')).toBe('someId'); // should make a request to the backend @@ -117,14 +97,14 @@ describe('OneSignal', () => { }); test('can add multiple aliases to the current user', async () => { - window.OneSignal.User.addAlias('someLabel', 'someId'); - window.OneSignal.User.addAlias('someLabel2', 'someId2'); + OneSignal.User.addAlias('someLabel', 'someId'); + OneSignal.User.addAlias('someLabel2', 'someId2'); - const identityModel = window.OneSignal.coreDirector.getIdentityModel(); + const identityModel = OneSignal.coreDirector.getIdentityModel(); expect(identityModel.getProperty('someLabel')).toBe('someId'); expect(identityModel.getProperty('someLabel2')).toBe('someId2'); - await waitForOperations(4); + await vi.waitUntil(() => addAliasFn.mock.calls.length === 2); expect(addAliasFn).toHaveBeenCalledWith({ identity: { someLabel: 'someId', @@ -140,11 +120,11 @@ describe('OneSignal', () => { test('can delete an alias from the current user', async () => { setDeleteAliasResponse(); - window.OneSignal.User.addAlias('someLabel', 'someId'); - await waitForOperations(); - window.OneSignal.User.removeAlias('someLabel'); + OneSignal.User.addAlias('someLabel', 'someId'); + await vi.waitUntil(() => addAliasFn.mock.calls.length === 1); + OneSignal.User.removeAlias('someLabel'); - const identityModel = window.OneSignal.coreDirector.getIdentityModel(); + const identityModel = OneSignal.coreDirector.getIdentityModel(); expect(identityModel.getProperty('someLabel')).toBeUndefined(); await vi.waitUntil(() => deleteAliasFn.mock.calls.length === 1); @@ -153,21 +133,21 @@ describe('OneSignal', () => { test('can delete multiple aliases from the current user', async () => { setDeleteAliasResponse(); - window.OneSignal.User.addAlias('someLabel', 'someId'); - window.OneSignal.User.addAlias('someLabel2', 'someId2'); + OneSignal.User.addAlias('someLabel', 'someId'); + OneSignal.User.addAlias('someLabel2', 'someId2'); - let identityModel = window.OneSignal.coreDirector.getIdentityModel(); + let identityModel = OneSignal.coreDirector.getIdentityModel(); expect(identityModel.getProperty('someLabel')).toBe('someId'); expect(identityModel.getProperty('someLabel2')).toBe('someId2'); await vi.waitUntil(async () => addAliasFn.mock.calls.length === 2); - window.OneSignal.User.removeAlias('someLabel'); - window.OneSignal.User.removeAlias('someLabel2'); + OneSignal.User.removeAlias('someLabel'); + OneSignal.User.removeAlias('someLabel2'); await vi.waitUntil(async () => deleteAliasFn.mock.calls.length === 2); - identityModel = window.OneSignal.coreDirector.getIdentityModel(); + identityModel = OneSignal.coreDirector.getIdentityModel(); expect(identityModel.getProperty('someLabel')).toBeUndefined(); expect(identityModel.getProperty('someLabel2')).toBeUndefined(); }); @@ -187,10 +167,10 @@ describe('OneSignal', () => { response: {}, }); setGetUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, subscriptions: [ { - id: DUMMY_SUBSCRIPTION_ID_2, + id: SUB_ID_2, token: 'test@test.com', type: 'Email', device_os: DEVICE_OS, @@ -204,8 +184,8 @@ describe('OneSignal', () => { }); test('can add an email subscription to the current user', async () => { - window.OneSignal.User.addEmail(email); - await waitForOperations(3); + OneSignal.User.addEmail(email); + await vi.waitUntil(() => createSubscriptionFn.mock.calls.length === 1); // should make a request to the backend const subscription: ICreateUserSubscription = { @@ -229,20 +209,19 @@ describe('OneSignal', () => { id: expect.any(String), modelId: expect.any(String), modelName: 'subscriptions', - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, sdk: __VERSION__, }, ]); // cant add the same email twice - window.OneSignal.User.addEmail(email); - expect(createSubscriptionFn).toHaveBeenCalledTimes(1); - await waitForOperations(1); + OneSignal.User.addEmail(email); + await vi.waitUntil(() => createSubscriptionFn.mock.calls.length === 1); dbSubscriptions = await getEmailSubscriptionDbItems(); expect(dbSubscriptions).toMatchObject([ { modelId: expect.any(String), - id: DUMMY_SUBSCRIPTION_ID_2, + id: SUB_ID_2, ...subscription, }, ]); @@ -250,15 +229,16 @@ describe('OneSignal', () => { test('can remove an email subscription from the current user', async () => { setDeleteSubscriptionResponse({ - subscriptionId: DUMMY_SUBSCRIPTION_ID_2, + subscriptionId: SUB_ID_2, }); const email = 'test@test.com'; - window.OneSignal.User.addEmail(email); + OneSignal.User.addEmail(email); + await vi.waitUntil(() => createSubscriptionFn.mock.calls.length === 1); + let dbSubscriptions = await getEmailSubscriptionDbItems(); expect(dbSubscriptions).toHaveLength(1); - await vi.waitUntil(() => createSubscriptionFn.mock.calls.length === 1); - window.OneSignal.User.removeEmail(email); + OneSignal.User.removeEmail(email); await vi.waitUntil(() => deleteSubscriptionFn.mock.calls.length === 1); dbSubscriptions = await getEmailSubscriptionDbItems(); @@ -285,10 +265,10 @@ describe('OneSignal', () => { response: {}, }); setGetUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, subscriptions: [ { - id: DUMMY_SUBSCRIPTION_ID_3, + id: SUB_ID_3, token: sms, type: 'SMS', device_os: DEVICE_OS, @@ -300,8 +280,8 @@ describe('OneSignal', () => { }); test('can add an sms subscription to the current user', async () => { - window.OneSignal.User.addSms(sms); - await waitForOperations(3); + OneSignal.User.addSms(sms); + await vi.waitUntil(() => createSubscriptionFn.mock.calls.length === 1); // should make a request to the backend const subscription: ICreateUserSubscription = { @@ -326,21 +306,20 @@ describe('OneSignal', () => { id: expect.any(String), modelId: expect.any(String), modelName: 'subscriptions', - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, sdk: __VERSION__, }, ]); // cant add the same sms twice - window.OneSignal.User.addSms(sms); - expect(createSubscriptionFn).toHaveBeenCalledTimes(1); - await waitForOperations(2); + OneSignal.User.addSms(sms); + await vi.waitUntil(() => createSubscriptionFn.mock.calls.length === 1); dbSubscriptions = await getSmsSubscriptionDbItems(1); expect(dbSubscriptions).toMatchObject([ { modelId: expect.any(String), - id: DUMMY_SUBSCRIPTION_ID_3, + id: SUB_ID_3, ...subscription, }, ]); @@ -348,12 +327,12 @@ describe('OneSignal', () => { test('can remove an sms subscription from the current user', async () => { setDeleteSubscriptionResponse({ - subscriptionId: DUMMY_SUBSCRIPTION_ID_3, + subscriptionId: SUB_ID_3, }); - window.OneSignal.User.addSms(sms); + OneSignal.User.addSms(sms); await getSmsSubscriptionDbItems(1); - window.OneSignal.User.removeSms(sms); + OneSignal.User.removeSms(sms); await getSmsSubscriptionDbItems(0); }); @@ -365,34 +344,33 @@ describe('OneSignal', () => { describe('login user', () => { beforeEach(async () => { setAddAliasResponse(); - addAliasFn.mockClear(); await setupSubModelStore({ - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, token: 'abc123', }); }); test('should validate external id', async () => { // @ts-expect-error - testing invalid argument - await expect(window.OneSignal.login()).rejects.toThrowError( + await expect(OneSignal.login()).rejects.toThrowError( '"externalId" is empty', ); // @ts-expect-error - testing invalid argument - await expect(window.OneSignal.login(null)).rejects.toThrowError( + await expect(OneSignal.login(null)).rejects.toThrowError( '"externalId" is the wrong type', ); // @ts-expect-error - testing invalid argument - await expect(window.OneSignal.login('', 1)).rejects.toThrowError( + await expect(OneSignal.login('', 1)).rejects.toThrowError( '"jwtToken" is the wrong type', ); // TODO: add consent required test // if needing consent required setConsentRequired(true); - await window.OneSignal.login(externalId); + await OneSignal.login(externalId); await vi.waitUntil(() => errorSpy.mock.calls.length === 1); const error = errorSpy.mock.calls[0][1] as Error; @@ -402,13 +380,13 @@ describe('OneSignal', () => { test('can login with a new external id', async () => { setTransferSubscriptionResponse(); let identityData = await getIdentityItem(); - await window.OneSignal.login(externalId); + await OneSignal.login(externalId); // should not change the identity in the IndexedDB right away expect(identityData).toEqual({ modelId: expect.any(String), modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, }); // wait for login user operation to complete @@ -420,34 +398,35 @@ describe('OneSignal', () => { // should also update the identity in the IndexedDB identityData = await getIdentityItem( - (i) => i.onesignal_id === DUMMY_ONESIGNAL_ID, + (i) => i.onesignal_id === ONESIGNAL_ID, ); expect(identityData).toEqual({ external_id: externalId, modelId: expect.any(String), modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, }); - const identityModel = - window.OneSignal.coreDirector.getIdentityModel(); + const identityModel = OneSignal.coreDirector.getIdentityModel(); expect(identityModel.externalId).toBe(externalId); - await waitForOperations(); - expect(transferSubscriptionFn).toHaveBeenCalled(); + await vi.waitUntil( + () => transferSubscriptionFn.mock.calls.length === 1, + ); }); test('login twice with same user -> only one call to identify user', async () => { setTransferSubscriptionResponse(); - await window.OneSignal.login(externalId); - await window.OneSignal.login(externalId); + await OneSignal.login(externalId); + await OneSignal.login(externalId); expect(addAliasFn).toHaveBeenCalledTimes(1); expect(debugSpy).toHaveBeenCalledWith( 'Login: External ID already set, skipping login', ); - await waitForOperations(); - expect(transferSubscriptionFn).toHaveBeenCalledTimes(1); + await vi.waitUntil( + () => transferSubscriptionFn.mock.calls.length === 1, + ); }); test('login twice with different user -> logs in to second user', async () => { @@ -460,14 +439,14 @@ describe('OneSignal', () => { }); setTransferSubscriptionResponse(); - await window.OneSignal.login(externalId); // should call set alias + await OneSignal.login(externalId); // should call set alias expect(addAliasFn).toHaveBeenCalledWith({ identity: { external_id: externalId, }, }); - await window.OneSignal.login(newExternalId); // should call create user + await OneSignal.login(newExternalId); // should call create user expect(createUserFn).toHaveBeenCalledWith({ identity: { external_id: newExternalId, @@ -475,24 +454,22 @@ describe('OneSignal', () => { ...baseIdentity, subscriptions: [ { - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, }, ], }); - await waitForOperations(3); // should call refresh user op - expect(getUserFn).toHaveBeenCalledWith(); + await vi.waitUntil(() => getUserFn.mock.calls.length === 1); const identityData = await getIdentityItem(); expect(identityData).toEqual({ external_id: newExternalId, modelId: expect.any(String), modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, }); - const identityModel = - window.OneSignal.coreDirector.getIdentityModel(); + const identityModel = OneSignal.coreDirector.getIdentityModel(); expect(identityModel.externalId).toBe(newExternalId); }); @@ -502,12 +479,12 @@ describe('OneSignal', () => { }); setCreateUserResponse({}); setGetUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, - newOnesignalId: DUMMY_ONESIGNAL_ID_2, + onesignalId: ONESIGNAL_ID, + newOnesignalId: ONESIGNAL_ID_2, externalId, subscriptions: [ { - id: DUMMY_SUBSCRIPTION_ID_2, + id: SUB_ID_2, type: 'ChromePush', token: 'def456', }, @@ -515,7 +492,7 @@ describe('OneSignal', () => { }); // calls create user with empty subscriptions - await window.OneSignal.login(externalId); + await OneSignal.login(externalId); expect(createUserFn).toHaveBeenCalledWith({ identity: { external_id: externalId, @@ -523,7 +500,7 @@ describe('OneSignal', () => { ...baseIdentity, subscriptions: [ { - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, }, ], }); @@ -531,18 +508,18 @@ describe('OneSignal', () => { // calls refresh user // onesignal id should be changed const identityData = await getIdentityItem( - (i) => i.onesignal_id === DUMMY_ONESIGNAL_ID_2, + (i) => i.onesignal_id === ONESIGNAL_ID_2, ); expect(identityData).toEqual({ external_id: externalId, modelId: expect.any(String), modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID_2, + onesignal_id: ONESIGNAL_ID_2, }); }); }); - describe('subscription creation after login', () => { + describe('subscription after login', () => { const email = 'test@example.com'; const sms = '+1234567890'; @@ -551,42 +528,44 @@ describe('OneSignal', () => { setTransferSubscriptionResponse(); setCreateSubscriptionResponse(); - await db.clear('subscriptions'); await setupSubModelStore({ - id: DUMMY_SUBSCRIPTION_ID, - token: DUMMY_PUSH_TOKEN, + id: SUB_ID, + token: PUSH_TOKEN, }); - setPushToken(DUMMY_PUSH_TOKEN); }); test('login before adding email and sms - it should create subscriptions with the external ID', async () => { setGetUserResponse({ externalId, - subscriptions: [ - { - id: DUMMY_SUBSCRIPTION_ID_2, - type: 'Email', - token: email, - }, - { - id: DUMMY_SUBSCRIPTION_ID_3, - type: 'SMS', - token: sms, - }, - ], }); - await window.OneSignal.login(externalId); + + await OneSignal.login(externalId); + await vi.waitUntil(() => addAliasFn.mock.calls.length === 1); const identityData = await getIdentityItem(); expect(identityData).toEqual({ external_id: externalId, modelId: expect.any(String), modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, }); - window.OneSignal.User.addEmail(email); - window.OneSignal.User.addSms(sms); + // add email + setCreateSubscriptionResponse({ + response: { + id: SUB_ID_2, + }, + }); + OneSignal.User.addEmail(email); + + // want to use different subscription id for sms + await vi.waitUntil(() => createSubscriptionFn.mock.calls.length > 0); + setCreateSubscriptionResponse({ + response: { + id: SUB_ID_3, + }, + }); + OneSignal.User.addSms(sms); await vi.waitUntil( () => createSubscriptionFn.mock.calls.length === 2, @@ -622,46 +601,48 @@ describe('OneSignal', () => { ); expect(emailSubscriptions).toHaveLength(1); + expect(emailSubscriptions[0].id).toBe(SUB_ID_2); expect(emailSubscriptions[0].token).toBe(email); - expect(emailSubscriptions[0].onesignalId).toBe(DUMMY_ONESIGNAL_ID); + expect(emailSubscriptions[0].onesignalId).toBe(ONESIGNAL_ID); expect(smsSubscriptions).toHaveLength(1); + expect(smsSubscriptions[0].id).toBe(SUB_ID_3); expect(smsSubscriptions[0].token).toBe(sms); - expect(smsSubscriptions[0].onesignalId).toBe(DUMMY_ONESIGNAL_ID); + expect(smsSubscriptions[0].onesignalId).toBe(ONESIGNAL_ID); }); test('login without accepting web push permissions - it should create a new user without any subscriptions', async () => { - setGetUserResponse(); - setCreateUserResponse(); - const identityModel = OneSignal.coreDirector.getIdentityModel(); - identityModel.setProperty( - 'external_id', - '', - ModelChangeTags.NO_PROPOGATE, - ); - identityModel.setProperty( - 'onesignal_id', - '', - ModelChangeTags.NO_PROPOGATE, - ); + setGetUserResponse({ + externalId, + }); + setCreateUserResponse({}); + setupIdentityModel({ + onesignalID: undefined, + }); - window.OneSignal.coreDirector.subscriptionModelStore.replaceAll( + OneSignal.coreDirector.subscriptionModelStore.replaceAll( [], ModelChangeTags.NO_PROPOGATE, ); - await window.OneSignal.login(externalId); + // wait for db to be updated + await getIdentityItem((i) => i.onesignal_id === undefined); + OneSignal.login(externalId); + + await vi.waitUntil(() => getUserFn.mock.calls.length === 1); const identityData = await getIdentityItem( - (i) => i.onesignal_id === DUMMY_ONESIGNAL_ID, + (i) => + i.onesignal_id === ONESIGNAL_ID && i.external_id === externalId, ); + expect(identityData).toEqual({ external_id: externalId, modelId: expect.any(String), modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, }); - await window.OneSignal.User.PushSubscription.optIn(); + await OneSignal.User.PushSubscription.optIn(); expect(createUserFn).toHaveBeenCalledTimes(1); expect(createUserFn).toHaveBeenCalledWith({ @@ -673,108 +654,21 @@ describe('OneSignal', () => { }); }); - test('login then add email, sms, and web push - all subscriptions should be created with the external ID', async () => { - setUpdateSubscriptionResponse(); - setGetUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, - externalId, - subscriptions: [ - { - id: 'some-other-push-id', - type: 'ChromePush', - token: 'some-other-push-token', - }, - { - id: DUMMY_SUBSCRIPTION_ID_2, - type: 'Email', - token: email, - }, - { - id: DUMMY_SUBSCRIPTION_ID_3, - type: 'SMS', - token: sms, - }, - ], - }); - - await window.OneSignal.login(externalId); - - const identityData = await getIdentityItem(); - expect(identityData).toEqual({ - external_id: externalId, - modelId: expect.any(String), - modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, - }); - - window.OneSignal.User.addEmail(email); - window.OneSignal.User.addSms(sms); - - await vi.waitUntil( - () => createSubscriptionFn.mock.calls.length === 2, - ); - - expect(createSubscriptionFn).toHaveBeenCalledWith({ - subscription: { - enabled: true, - notification_types: 1, - sdk: __VERSION__, - token: email, - type: 'Email', - }, - }); - - expect(createSubscriptionFn).toHaveBeenCalledWith({ - subscription: { - enabled: true, - notification_types: 1, - sdk: __VERSION__, - token: sms, - type: 'SMS', - }, - }); - - const dbSubscriptions = await getDbSubscriptions(3); - - const emailSubscriptions = dbSubscriptions.filter( - (s) => s.type === 'Email', - ); - const smsSubscriptions = dbSubscriptions.filter( - (s) => s.type === 'SMS', - ); - - expect(emailSubscriptions).toHaveLength(1); - expect(emailSubscriptions[0].token).toBe(email); - expect(emailSubscriptions[0].onesignalId).toBe(DUMMY_ONESIGNAL_ID); - - expect(smsSubscriptions).toHaveLength(1); - expect(smsSubscriptions[0].token).toBe(sms); - expect(smsSubscriptions[0].onesignalId).toBe(DUMMY_ONESIGNAL_ID); - }); - test('login with a prior web push subscription - it should transfer the subscription', async () => { + setGetUserResponse(); setCreateUserResponse(); - const identityModel = OneSignal.coreDirector.getIdentityModel(); - identityModel.setProperty( - 'onesignal_id', - '', - ModelChangeTags.NO_PROPOGATE, - ); + updateIdentityModel('onesignal_id', ''); - let dbSubscriptions: SubscriptionSchema[] = []; - await vi.waitUntil(async () => { - dbSubscriptions = await db.getAll<'subscriptions'>('subscriptions'); - return dbSubscriptions.length === 1; - }); + await getDbSubscriptions(1); - await window.OneSignal.login(externalId); + await OneSignal.login(externalId); const identityData = await getIdentityItem(); expect(identityData).toEqual({ external_id: externalId, modelId: expect.any(String), modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, }); expect(createUserFn).toHaveBeenCalledTimes(1); @@ -785,44 +679,39 @@ describe('OneSignal', () => { ...baseIdentity, subscriptions: [ { - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, }, ], }); }); test('login then accept web push permissions - it should make two user calls', async () => { + setGetUserResponse(); OneSignal.coreDirector.subscriptionModelStore.replaceAll( [], ModelChangeTags.NO_PROPOGATE, ); setPushToken(''); - setGetUserResponse(); subscribeFcmFromPageSpy.mockImplementation( // @ts-expect-error - subscribeFcmFromPage is a private method of SubscriptionManagerPage async () => rawPushSubscription, ); setCreateUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, externalId, subscriptions: [ { - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, }, ], }); // new/empty user - const newIdentity = new IdentityModel(); - OneSignal.coreDirector.identityModelStore.replace(newIdentity); - OneSignal.coreDirector.subscriptionModelStore.replaceAll( - [], - ModelChangeTags.NO_PROPOGATE, - ); + setupIdentityModel({ onesignalID: undefined }); // calling login before accept permissions - window.OneSignal.login(externalId); + OneSignal.login(externalId); // slidedown manager calls this on allow click // @ts-expect-error - Notification is not defined in the global scope @@ -857,7 +746,7 @@ describe('OneSignal', () => { enabled: true, notification_types: 1, sdk: __VERSION__, - token: DUMMY_PUSH_TOKEN, + token: PUSH_TOKEN, type: 'ChromePush', web_auth: 'w3cAuth', web_p256: 'w3cP256dh', @@ -865,8 +754,6 @@ describe('OneSignal', () => { ], }); - // const dbSubscriptions = await getDbSubscriptions(1); - // console.dir(dbSubscriptions, { depth: null }); let pushSub: SubscriptionSchema | undefined; await vi.waitUntil( async () => { @@ -881,10 +768,10 @@ describe('OneSignal', () => { describe('logout', () => { test('should not do anything if user has no external id', async () => { - const identityModel = window.OneSignal.coreDirector.getIdentityModel(); + const identityModel = OneSignal.coreDirector.getIdentityModel(); expect(identityModel.externalId).toBeUndefined(); - window.OneSignal.logout(); + OneSignal.logout(); expect(debugSpy).toHaveBeenCalledWith( 'Logout: User is not logged in, skipping logout', ); @@ -892,12 +779,12 @@ describe('OneSignal', () => { test('can logout the user with existing external id and subscription', async () => { const pushSub = await setupSubModelStore({ - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, token: 'abc123', }); // existing user - let identityModel = window.OneSignal.coreDirector.getIdentityModel(); + let identityModel = OneSignal.coreDirector.getIdentityModel(); identityModel.setProperty( 'external_id', 'jd-1', @@ -907,10 +794,10 @@ describe('OneSignal', () => { setCreateUserResponse({}); setUpdateSubscriptionResponse(); - window.OneSignal.logout(); + OneSignal.logout(); // identity model should be reset - identityModel = window.OneSignal.coreDirector.getIdentityModel(); + identityModel = OneSignal.coreDirector.getIdentityModel(); const onesignalId = identityModel.onesignalId; expect(identityModel.toJSON()).toEqual({ onesignal_id: expect.any(String), @@ -925,8 +812,7 @@ describe('OneSignal', () => { }); // properties model should be reset - const propertiesModel = - window.OneSignal.coreDirector.getPropertiesModel(); + const propertiesModel = OneSignal.coreDirector.getPropertiesModel(); expect(propertiesModel.toJSON()).toEqual({ onesignalId, }); @@ -938,26 +824,26 @@ describe('OneSignal', () => { onesignalId, }); - await waitForOperations(3); + await vi.waitUntil(() => createUserFn.mock.calls.length === 1); // should update models and db - identityModel = window.OneSignal.coreDirector.getIdentityModel(); + identityModel = OneSignal.coreDirector.getIdentityModel(); expect(identityModel.toJSON()).toEqual({ - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, }); identityData = await getIdentityItem(); expect(identityData).toEqual({ modelId: expect.any(String), modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, }); propertiesData = await getPropertiesItem(); expect(propertiesData).toEqual({ modelId: expect.any(String), modelName: 'properties', - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, }); const subscriptions = await db.getAll('subscriptions'); @@ -970,7 +856,7 @@ describe('OneSignal', () => { modelId: expect.any(String), modelName: 'subscriptions', notification_types: 1, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, sdk: __VERSION__, token: pushSub.token, type: 'ChromePush', @@ -995,7 +881,7 @@ describe('OneSignal', () => { const getQueue = async (length: number) => { const queue = await vi.waitUntil( () => { - const _queue = window.OneSignal.coreDirector.operationRepo.queue; + const _queue = OneSignal.coreDirector.operationRepo.queue; return _queue.length === length ? _queue : null; }, { interval: 0 }, @@ -1009,7 +895,7 @@ describe('OneSignal', () => { OneSignal.coreDirector .getIdentityModel() .setProperty('external_id', 'some-id', ModelChangeTags.NO_PROPOGATE); - window.OneSignal.User.trackEvent(name); + OneSignal.User.trackEvent(name); await vi.waitUntil(() => sendCustomEventFn.mock.calls.length === 1); @@ -1018,7 +904,7 @@ describe('OneSignal', () => { { name, external_id: 'some-id', - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, payload: { os_sdk: OS_SDK, }, @@ -1031,20 +917,20 @@ describe('OneSignal', () => { test('can send a custom event after login', async () => { setCreateUserResponse({}); setGetUserResponse({ - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, externalId: 'some-id', }); setSendCustomEventResponse(); - const identityModel = window.OneSignal.coreDirector.getIdentityModel(); + const identityModel = OneSignal.coreDirector.getIdentityModel(); identityModel.setProperty( 'external_id', 'some-id', ModelChangeTags.NO_PROPOGATE, ); - window.OneSignal.login('some-id-2'); - window.OneSignal.User.trackEvent(name, properties); + OneSignal.login('some-id-2'); + OneSignal.User.trackEvent(name, properties); const queue = await getQueue(2); @@ -1066,7 +952,7 @@ describe('OneSignal', () => { { external_id: 'some-id-2', name, - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, payload: { ...properties, os_sdk: OS_SDK, @@ -1081,17 +967,17 @@ describe('OneSignal', () => { setAddAliasResponse(); setSendCustomEventResponse(); - window.OneSignal.User.trackEvent('test_event_1', { + OneSignal.User.trackEvent('test_event_1', { test_property_1: 'test_value_1', }); - window.OneSignal.login('some-id'); - window.OneSignal.User.trackEvent('test_event_2', { + OneSignal.login('some-id'); + OneSignal.User.trackEvent('test_event_2', { test_property_2: 'test_value_2', }); const queue = await getQueue(3); - expect(queue[0].operation.onesignalId).toBe(DUMMY_ONESIGNAL_ID); + expect(queue[0].operation.onesignalId).toBe(ONESIGNAL_ID); const localID = queue[1].operation.onesignalId; expect(queue[2].operation.onesignalId).toBe(localID); @@ -1103,7 +989,7 @@ describe('OneSignal', () => { events: [ { name: 'test_event_1', - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, payload: { os_sdk: OS_SDK, test_property_1: 'test_value_1', @@ -1132,7 +1018,7 @@ describe('OneSignal', () => { { external_id: 'some-id', name: 'test_event_2', - onesignal_id: DUMMY_ONESIGNAL_ID, + onesignal_id: ONESIGNAL_ID, payload: { os_sdk: OS_SDK, test_property_2: 'test_value_2', @@ -1154,16 +1040,16 @@ describe('OneSignal', () => { }); setGetUserResponse(); - window.OneSignal.User.trackEvent('test_event_1', { + OneSignal.User.trackEvent('test_event_1', { test_property_1: 'test_value_1', }); - window.OneSignal.login('some-id-2'); - window.OneSignal.User.trackEvent('test_event_2', { + OneSignal.login('some-id-2'); + OneSignal.User.trackEvent('test_event_2', { test_property_2: 'test_value_2', }); const queue = await getQueue(3); - expect(queue[0].operation.onesignalId).toBe(DUMMY_ONESIGNAL_ID); + expect(queue[0].operation.onesignalId).toBe(ONESIGNAL_ID); const localID = queue[1].operation.onesignalId; expect(queue[2].operation.onesignalId).toBe(localID); @@ -1207,7 +1093,6 @@ describe('OneSignal', () => { describe('Listeners', () => { test('can listen for subscription changed event', async () => { - await clearAll(); await db.put('Options', { key: 'notificationPermission', value: 'granted', @@ -1216,7 +1101,7 @@ describe('OneSignal', () => { setCreateUserResponse({ subscriptions: [ { - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, }, ], }); @@ -1243,9 +1128,9 @@ describe('OneSignal', () => { token: undefined, }, current: { - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, optedIn: true, - token: DUMMY_PUSH_TOKEN, + token: PUSH_TOKEN, }, }); }); @@ -1253,21 +1138,21 @@ describe('OneSignal', () => { test('should preserve operations order without needing await', async () => { await setupSubModelStore({ - id: DUMMY_SUBSCRIPTION_ID, + id: SUB_ID, token: 'def456', }); setAddAliasResponse(); setTransferSubscriptionResponse(); setUpdateUserResponse(); - window.OneSignal.login('some-id'); - window.OneSignal.User.addTag('some-tag', 'some-value'); - const tags = window.OneSignal.User.getTags(); + OneSignal.login('some-id'); + OneSignal.User.addTag('some-tag', 'some-value'); + const tags = OneSignal.User.getTags(); let queue: OperationQueueItem[] = []; await vi.waitUntil( () => { - queue = window.OneSignal.coreDirector.operationRepo.queue; + queue = OneSignal.coreDirector.operationRepo.queue; return queue.length === 3; }, { interval: 1 }, @@ -1327,44 +1212,22 @@ const baseIdentity = { }; const rawPushSubscription = new RawPushSubscription(); -rawPushSubscription.w3cEndpoint = new URL(DUMMY_PUSH_TOKEN); +rawPushSubscription.w3cEndpoint = new URL(PUSH_TOKEN); rawPushSubscription.w3cP256dh = 'w3cP256dh'; rawPushSubscription.w3cAuth = 'w3cAuth'; rawPushSubscription.safariDeviceToken = 'safariDeviceToken'; -const getIdentityItem = async ( - condition: (identity: IdentitySchema) => boolean = () => true, -) => { - let identity: IdentitySchema | undefined; - await vi.waitUntil(async () => { - identity = (await db.getAll('identity'))?.[0]; - return identity && condition(identity); - }); - return identity; -}; - const getDbSubscriptions = async (length: number) => { let subscriptions: SubscriptionSchema[] = []; - await vi.waitUntil( - async () => { - subscriptions = await db.getAll('subscriptions'); - return subscriptions.length === length; - }, - { interval: 1 }, - ); + await vi.waitUntil(async () => { + subscriptions = await db.getAll('subscriptions'); + return subscriptions.length === length; + }); return subscriptions; }; const getPropertiesItem = async () => (await db.getAll('properties'))[0]; -const setupIdentity = async () => { - await db.put('identity', { - modelId: '123', - modelName: 'identity', - onesignal_id: DUMMY_ONESIGNAL_ID, - }); -}; - const subscribeFcmFromPageSpy = vi.spyOn( SubscriptionManagerPage.prototype, // @ts-expect-error - subscribeFcmFromPage is a private method of SubscriptionManagerPage diff --git a/src/onesignal/UserNamespace.test.ts b/src/onesignal/UserNamespace.test.ts index 421947751..0fee921ea 100644 --- a/src/onesignal/UserNamespace.test.ts +++ b/src/onesignal/UserNamespace.test.ts @@ -1,4 +1,6 @@ -import { DUMMY_ONESIGNAL_ID, DUMMY_PUSH_TOKEN } from '__test__/constants'; +import { ONESIGNAL_ID, PUSH_TOKEN } from '__test__/constants'; +import { setAddAliasResponse } from '__test__/support/helpers/requests'; +import { updateIdentityModel } from '__test__/support/helpers/setup'; import { ModelChangeTags } from 'src/core/types/models'; import Log from 'src/shared/libraries/Log'; import { IDManager } from 'src/shared/managers/IDManager'; @@ -9,6 +11,7 @@ import User from './User'; import UserNamespace from './UserNamespace'; const errorSpy = vi.spyOn(Log, 'error').mockImplementation(() => ''); +vi.useFakeTimers(); describe('UserNamespace', () => { let userNamespace: UserNamespace; @@ -21,29 +24,31 @@ describe('UserNamespace', () => { describe('User Identity Properties', () => { test('should return correct onesignalId', () => { - const identityModel = OneSignal.coreDirector.getIdentityModel(); userNamespace = new UserNamespace(true); - identityModel.setProperty('onesignal_id', undefined); + updateIdentityModel('onesignal_id', undefined); expect(userNamespace.onesignalId).toBe(undefined); - identityModel.onesignalId = IDManager.createLocalId(); + updateIdentityModel('onesignal_id', IDManager.createLocalId()); expect(userNamespace.onesignalId).toBe(undefined); - identityModel.onesignalId = DUMMY_ONESIGNAL_ID; - expect(userNamespace.onesignalId).toBe(DUMMY_ONESIGNAL_ID); + updateIdentityModel('onesignal_id', ONESIGNAL_ID); + expect(userNamespace.onesignalId).toBe(ONESIGNAL_ID); }); test('should return correct externalId', () => { - const identityModel = OneSignal.coreDirector.getIdentityModel(); const externalId = 'some-external-id'; - identityModel.setProperty('external_id', externalId); + updateIdentityModel('external_id', externalId); expect(userNamespace.externalId).toBe(externalId); }); }); describe('Alias Management', () => { + beforeEach(() => { + setAddAliasResponse(); + }); + test('can add a single alias', () => { const label = 'some-label'; const id = 'some-id'; @@ -371,7 +376,7 @@ describe('UserNamespace', () => { const mockListener = vi.fn(); const event: UserChangeEvent = { current: { - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, externalId: undefined, }, }; @@ -386,7 +391,7 @@ describe('UserNamespace', () => { const mockListener = vi.fn(); const event: UserChangeEvent = { current: { - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, externalId: undefined, }, }; @@ -416,7 +421,7 @@ describe('UserNamespace', () => { test('should use provided subscription and permission when initializing', () => { const subscription = new Subscription(); subscription.deviceId = 'device-123'; - subscription.subscriptionToken = DUMMY_PUSH_TOKEN; + subscription.subscriptionToken = PUSH_TOKEN; subscription.optedOut = false; subscription.createdAt = Date.now(); @@ -436,6 +441,7 @@ describe('UserNamespace', () => { test_property: 'test_value', }; + updateIdentityModel('onesignal_id', undefined); userNamespace.trackEvent(name, {}); expect(errorSpy).toHaveBeenCalledWith('User must be logged in first.'); errorSpy.mockClear(); @@ -443,7 +449,7 @@ describe('UserNamespace', () => { const identityModel = OneSignal.coreDirector.getIdentityModel(); identityModel.setProperty( 'onesignal_id', - DUMMY_ONESIGNAL_ID, + ONESIGNAL_ID, ModelChangeTags.NO_PROPOGATE, ); diff --git a/src/page/bell/Bell.ts b/src/page/bell/Bell.ts index 1eb1a5745..2b0e113e6 100755 --- a/src/page/bell/Bell.ts +++ b/src/page/bell/Bell.ts @@ -18,7 +18,6 @@ import OneSignal from '../../onesignal/OneSignal'; import { DismissHelper } from '../../shared/helpers/DismissHelper'; import MainHelper from '../../shared/helpers/MainHelper'; import Log from '../../shared/libraries/Log'; -import { NotificationPermission } from '../../shared/models/NotificationPermission'; import OneSignalEvent from '../../shared/services/OneSignalEvent'; import { once } from '../../shared/utils/utils'; import { DismissPrompt } from '../models/Dismiss'; @@ -354,7 +353,7 @@ export default class Bell { let bellState: BellState; if (isSubscribed.current.optedIn) { bellState = Bell.STATES.SUBSCRIBED; - } else if (permission === NotificationPermission.Denied) { + } else if (permission === 'denied') { bellState = Bell.STATES.BLOCKED; } else { bellState = Bell.STATES.UNSUBSCRIBED; @@ -692,7 +691,7 @@ export default class Bell { this.setState( isEnabled ? Bell.STATES.SUBSCRIBED : Bell.STATES.UNSUBSCRIBED, ); - if (permission === NotificationPermission.Denied) { + if (permission === 'denied') { this.setState(Bell.STATES.BLOCKED); } }) diff --git a/src/page/managers/slidedownManager/SlidedownManager.ts b/src/page/managers/slidedownManager/SlidedownManager.ts index e7e1612b2..44a4e5fa7 100644 --- a/src/page/managers/slidedownManager/SlidedownManager.ts +++ b/src/page/managers/slidedownManager/SlidedownManager.ts @@ -21,7 +21,6 @@ import { logMethodCall } from 'src/shared/utils/utils'; import { CoreModuleDirector } from '../../../core/CoreModuleDirector'; import { DismissHelper } from '../../../shared/helpers/DismissHelper'; import Log from '../../../shared/libraries/Log'; -import { NotificationPermission } from '../../../shared/models/NotificationPermission'; import type { PushSubscriptionState } from '../../../shared/models/PushSubscriptionState'; import TagUtils from '../../../shared/utils/TagUtils'; import { DismissPrompt } from '../../models/Dismiss'; @@ -52,7 +51,7 @@ export class SlidedownManager { ): Promise { const permissionDenied = (await OneSignal.context.permissionManager.getPermissionStatus()) === - NotificationPermission.Denied; + 'denied'; let wasDismissed: boolean; const subscriptionInfo: PushSubscriptionState = diff --git a/src/shared/database/client.test.ts b/src/shared/database/client.test.ts index 4658abe61..31ae11e46 100644 --- a/src/shared/database/client.test.ts +++ b/src/shared/database/client.test.ts @@ -1,8 +1,4 @@ -import { - APP_ID, - DUMMY_EXTERNAL_ID, - DUMMY_ONESIGNAL_ID, -} from '__test__/constants'; +import { APP_ID, EXTERNAL_ID, ONESIGNAL_ID } from '__test__/constants'; import { deleteDB, type IDBPDatabase } from 'idb'; import { SubscriptionType } from '../subscriptions/constants'; import { closeDb, getDb } from './client'; @@ -206,21 +202,21 @@ describe('migrations', () => { await db.put('emailSubscriptions', { modelId: '1', modelName: 'emailSubscriptions', - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, type: SubscriptionType.Email, token: 'email-token', }); await db.put('pushSubscriptions', { modelId: '2', modelName: 'pushSubscriptions', - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, type: SubscriptionType.ChromePush, token: 'push-token', }); await db.put('smsSubscriptions', { modelId: '3', modelName: 'smsSubscriptions', - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, type: SubscriptionType.SMS, token: 'sms-token', }); @@ -231,7 +227,7 @@ describe('migrations', () => { modelId: '1', modelName: 'subscriptions', externalId: undefined, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, type: SubscriptionType.Email, token: 'email-token', }, @@ -239,7 +235,7 @@ describe('migrations', () => { modelId: '2', modelName: 'subscriptions', externalId: undefined, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, type: SubscriptionType.ChromePush, token: 'push-token', }, @@ -247,7 +243,7 @@ describe('migrations', () => { modelId: '3', modelName: 'subscriptions', externalId: undefined, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, type: SubscriptionType.SMS, token: 'sms-token', }, @@ -296,8 +292,8 @@ describe('migrations', () => { await db.put('identity', { modelId: '4', modelName: 'identity', - onesignalId: DUMMY_ONESIGNAL_ID, - externalId: DUMMY_EXTERNAL_ID, + onesignalId: ONESIGNAL_ID, + externalId: EXTERNAL_ID, }); await closeDb(); @@ -306,15 +302,15 @@ describe('migrations', () => { expect(result).toEqual([ { ...migratedSubscriptions.email, - externalId: DUMMY_EXTERNAL_ID, + externalId: EXTERNAL_ID, }, { ...migratedSubscriptions.push, - externalId: DUMMY_EXTERNAL_ID, + externalId: EXTERNAL_ID, }, { ...migratedSubscriptions.sms, - externalId: DUMMY_EXTERNAL_ID, + externalId: EXTERNAL_ID, }, ]); }); diff --git a/src/shared/database/client.ts b/src/shared/database/client.ts index 6dc2e5e1c..4763a1321 100644 --- a/src/shared/database/client.ts +++ b/src/shared/database/client.ts @@ -10,12 +10,8 @@ import { migrateOutcomesNotificationReceivedTableForV5, } from './upgrade'; -let dbInstance: Awaited>> | null = - null; - -export const getDb = async (version = VERSION) => { - if (dbInstance) return dbInstance; - dbInstance = await openDB(DATABASE_NAME, version, { +const open = async (version = VERSION) => { + return openDB(DATABASE_NAME, version, { upgrade(_db, oldVersion, newVersion, transaction) { const newDbVersion = newVersion || version; if (newDbVersion >= 1 && oldVersion < 1) { @@ -80,7 +76,12 @@ export const getDb = async (version = VERSION) => { Log.debug('IndexedDB: Blocked event'); }, }); - return dbInstance; +}; +let dbPromise = open(); + +export const getDb = (version = VERSION) => { + dbPromise = open(version); + return dbPromise; }; // Export db object with the same API as before @@ -89,38 +90,37 @@ export const db = { storeName: K, key: IndexedDBSchema[K]['key'], ): Promise { - const _db = await getDb(); - return _db.get(storeName, key); + return (await dbPromise).get(storeName, key); }, async getAll( storeName: K, ): Promise { - const _db = await getDb(); - return _db.getAll(storeName); + return (await dbPromise).getAll(storeName); }, async put( storeName: K, value: IndexedDBSchema[K]['value'], ) { - const _db = await getDb(); - return _db.put(storeName, value); + return (await dbPromise).put(storeName, value); }, async delete( storeName: K, key: IndexedDBSchema[K]['key'], ) { - const _db = await getDb(); - return _db.delete(storeName, key); + return (await dbPromise).delete(storeName, key); }, async clear(storeName: K) { - const _db = await getDb(); - return _db.clear(storeName); + return (await dbPromise).clear(storeName); }, - get objectStoreNames() { - return dbInstance?.objectStoreNames || []; + async close() { + return (await dbPromise).close(); }, }; +export const getObjectStoreNames = async () => { + return Array.from((await dbPromise).objectStoreNames); +}; + export const getOptionsValue = async ( key: OptionKey, ): Promise => { @@ -144,13 +144,12 @@ export const cleanupCurrentSession = async () => { }; export const clearAll = async () => { - const objectStoreNames = db.objectStoreNames; + const objectStoreNames = await getObjectStoreNames(); for (const storeName of objectStoreNames) { await db.clear(storeName); } }; export const closeDb = async () => { - await dbInstance?.close(); - dbInstance = null; + (await dbPromise).close(); }; diff --git a/src/shared/database/types.ts b/src/shared/database/types.ts index 1da3c1f00..159c1c04c 100644 --- a/src/shared/database/types.ts +++ b/src/shared/database/types.ts @@ -61,11 +61,30 @@ export interface IdentitySchema { modelId: string; modelName: 'identity'; onesignal_id?: string; + /** + * @deprecated - use onesignal_id instead + */ onesignalId?: string; external_id?: string; + /** + * @deprecated - use external_id instead + */ externalId?: string; } +export interface PropertiesSchema { + modelId: string; + modelName: 'properties'; + country: string; + first_active: number; + ip: string; + language: string; + last_active: number; + onesignalId: string; + tags: Record; + timezone_id: string; +} + export interface IndexedDBSchema extends DBSchema { /** * @deprecated - should be migrated in openDB() @@ -157,18 +176,7 @@ export interface IndexedDBSchema extends DBSchema { properties: { key: string; - value: { - modelId: string; - modelName: 'properties'; - country: string; - first_active: number; - ip: string; - language: string; - last_active: number; - onesignalId: string; - tags: Record; - timezone_id: string; - }; + value: PropertiesSchema; }; subscriptions: { diff --git a/src/shared/helpers/init.ts b/src/shared/helpers/init.ts index cd8aafc15..5584e040a 100755 --- a/src/shared/helpers/init.ts +++ b/src/shared/helpers/init.ts @@ -6,7 +6,6 @@ import { getSubscription, setSubscription } from '../database/subscription'; import type { OptionKey } from '../database/types'; import Log from '../libraries/Log'; import { CustomLinkManager } from '../managers/CustomLinkManager'; -import { NotificationPermission } from '../models/NotificationPermission'; import { SubscriptionStrategyKind } from '../models/SubscriptionStrategyKind'; import LimitStore from '../services/LimitStore'; import OneSignalEvent from '../services/OneSignalEvent'; @@ -157,7 +156,7 @@ async function setWelcomeNotificationFlag(): Promise { await OneSignal.context.permissionManager.getNotificationPermission( OneSignal.context.appConfig.safariWebId, ); - if (permission === NotificationPermission.Granted) { + if (permission === 'granted') { OneSignal.__doNotShowWelcomeNotification = true; } } @@ -380,7 +379,7 @@ async function handleAutoResubscribe(isOptedOut: boolean) { await OneSignal.context.permissionManager.getNotificationPermission( OneSignal.context.appConfig.safariWebId, ); - if (currentPermission == NotificationPermission.Granted) { + if (currentPermission == 'granted') { await SubscriptionHelper.registerForPush(); } } diff --git a/src/shared/listeners.test.ts b/src/shared/listeners.test.ts index 5f9b121c5..5253a9b52 100644 --- a/src/shared/listeners.test.ts +++ b/src/shared/listeners.test.ts @@ -1,28 +1,20 @@ -import { DUMMY_PUSH_TOKEN } from '__test__/constants'; +import { PUSH_TOKEN } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { createPushSub } from '__test__/support/environment/TestEnvironmentHelpers'; -import { setIsPushEnabled } from '__test__/support/helpers/database'; +import { setIsPushEnabled } from '__test__/support/helpers/setup'; import { getSubscriptionFn, MockServiceWorker, } from '__test__/support/mocks/MockServiceWorker'; import * as eventListeners from 'src/shared/listeners'; -import type { MockInstance } from 'vitest'; import { getAppState } from './database/config'; import { setPushToken } from './database/subscription'; import { SubscriptionManagerPage } from './managers/subscription/page'; -let emitterSpy: MockInstance; - -// dont want to make a call to update notification types -vi.spyOn( - SubscriptionManagerPage.prototype, - 'updateNotificationTypes', -).mockImplementation(() => Promise.resolve()); +vi.useFakeTimers(); beforeEach(async () => { await TestEnvironment.initialize(); - emitterSpy = vi.spyOn(OneSignal.emitter, 'on'); }); describe('checkAndTriggerSubscriptionChanged', () => { @@ -65,7 +57,7 @@ describe('checkAndTriggerSubscriptionChanged', () => { // token change getSubscriptionFn.mockResolvedValue({ - endpoint: DUMMY_PUSH_TOKEN, + endpoint: PUSH_TOKEN, }); await setIsPushEnabled(false); @@ -76,7 +68,7 @@ describe('checkAndTriggerSubscriptionChanged', () => { current: { id: undefined, optedIn: false, - token: DUMMY_PUSH_TOKEN, + token: PUSH_TOKEN, }, previous: { id: undefined, @@ -87,7 +79,7 @@ describe('checkAndTriggerSubscriptionChanged', () => { expect(await getAppState()).toMatchObject({ lastKnownOptedIn: false, lastKnownPushEnabled: false, - lastKnownPushToken: DUMMY_PUSH_TOKEN, + lastKnownPushToken: PUSH_TOKEN, lastKnownPushId: undefined, }); @@ -126,11 +118,6 @@ describe('checkAndTriggerSubscriptionChanged', () => { }); }); -test('Adding click listener fires internal EventHelper', async () => { - OneSignal.Notifications.addEventListener('click', () => {}); - expect(emitterSpy).toHaveBeenCalledTimes(1); -}); - Object.defineProperty(global.navigator, 'serviceWorker', { value: new MockServiceWorker(), writable: true, diff --git a/src/shared/managers/SubscriptionManager.test.ts b/src/shared/managers/SubscriptionManager.test.ts index 08bde42b9..eb7ae0486 100644 --- a/src/shared/managers/SubscriptionManager.test.ts +++ b/src/shared/managers/SubscriptionManager.test.ts @@ -1,19 +1,20 @@ -import { DEVICE_OS, DUMMY_EXTERNAL_ID } from '__test__/constants'; +import { DEVICE_OS, EXTERNAL_ID } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; import { setupSubModelStore } from '__test__/support/environment/TestEnvironmentHelpers'; import { createUserFn, setCreateUserResponse, setGetUserResponse, + setUpdateSubscriptionResponse, + updateSubscriptionFn, } from '__test__/support/helpers/requests'; +import { updateIdentityModel } from '__test__/support/helpers/setup'; import MockNotification from '__test__/support/mocks/MockNotification'; import { getSubscriptionFn, MockServiceWorker, } from '__test__/support/mocks/MockServiceWorker'; -import { ModelChangeTags } from 'src/core/types/models'; import { setPushToken } from '../database/subscription'; -import { NotificationPermission } from '../models/NotificationPermission'; import { RawPushSubscription } from '../models/RawPushSubscription'; import { IDManager } from './IDManager'; import { @@ -32,12 +33,12 @@ const getRawSubscription = (): RawPushSubscription => { describe('SubscriptionManager', () => { beforeEach(async () => { - vi.resetModules(); await TestEnvironment.initialize(); }); describe('updatePushSubscriptionModelWithRawSubscription', () => { test('should create the push subscription model if it does not exist', async () => { + setGetUserResponse(); setCreateUserResponse(); const rawSubscription = getRawSubscription(); @@ -106,22 +107,14 @@ describe('SubscriptionManager', () => { }); // create push sub with no id - const identityModel = OneSignal.coreDirector.getIdentityModel(); - identityModel.setProperty( - 'onesignal_id', - IDManager.createLocalId(), - ModelChangeTags.HYDRATE, - ); - identityModel.setProperty( - 'external_id', - 'some-external-id', - ModelChangeTags.HYDRATE, - ); + const onesignalId = IDManager.createLocalId(); + updateIdentityModel('onesignal_id', onesignalId); + updateIdentityModel('external_id', 'some-external-id'); await setupSubModelStore({ id: IDManager.createLocalId(), token: rawSubscription.w3cEndpoint?.toString(), - onesignalId: identityModel.onesignalId, + onesignalId, }); await updatePushSubscriptionModelWithRawSubscription(rawSubscription); @@ -155,17 +148,18 @@ describe('SubscriptionManager', () => { test('should update the push subscription model if it already exists', async () => { setCreateUserResponse(); setGetUserResponse(); + setUpdateSubscriptionResponse({ subscriptionId: '123' }); const rawSubscription = getRawSubscription(); await setPushToken(rawSubscription.w3cEndpoint?.toString()); - const pushModel = await setupSubModelStore({ + await setupSubModelStore({ id: '123', token: rawSubscription.w3cEndpoint?.toString(), - onesignalId: DUMMY_EXTERNAL_ID, + onesignalId: EXTERNAL_ID, + web_auth: 'old-web-auth', + web_p256: 'old-web-p256', }); - pushModel.web_auth = 'old-web-auth'; - pushModel.web_p256 = 'old-web-p256'; await updatePushSubscriptionModelWithRawSubscription(rawSubscription); @@ -176,6 +170,8 @@ describe('SubscriptionManager', () => { ); expect(updatedPushModel.web_auth).toBe(rawSubscription.w3cAuth); expect(updatedPushModel.web_p256).toBe(rawSubscription.w3cP256dh); + + await vi.waitUntil(() => updateSubscriptionFn.mock.calls.length > 0); }); }); }); @@ -184,21 +180,21 @@ describe('SubscriptionManagerPage', () => { test('default', async () => { MockNotification.permission = 'default'; expect(await SubscriptionManagerPage.requestNotificationPermission()).toBe( - NotificationPermission.Default, + 'default', ); }); test('denied', async () => { MockNotification.permission = 'denied'; expect(await SubscriptionManagerPage.requestNotificationPermission()).toBe( - NotificationPermission.Denied, + 'denied', ); }); test('granted', async () => { MockNotification.permission = 'granted'; expect(await SubscriptionManagerPage.requestNotificationPermission()).toBe( - NotificationPermission.Granted, + 'granted', ); }); }); diff --git a/src/shared/managers/sessionManager/SessionManager.test.ts b/src/shared/managers/sessionManager/SessionManager.test.ts index df0a65f09..a4f6743b8 100644 --- a/src/shared/managers/sessionManager/SessionManager.test.ts +++ b/src/shared/managers/sessionManager/SessionManager.test.ts @@ -1,6 +1,11 @@ -import { DUMMY_EXTERNAL_ID } from '__test__/constants'; +import { EXTERNAL_ID } from '__test__/constants'; import { TestEnvironment } from '__test__/support/environment/TestEnvironment'; -import { setAddAliasResponse } from '__test__/support/helpers/requests'; +import { + setAddAliasResponse, + setCreateUserResponse, + setGetUserResponse, + setUpdateUserResponse, +} from '__test__/support/helpers/requests'; import LoginManager from 'src/page/managers/LoginManager'; import Log from 'src/shared/libraries/Log'; import { SessionOrigin } from 'src/shared/session/constants'; @@ -11,16 +16,16 @@ vi.spyOn(Log, 'error').mockImplementation(() => ''); describe('SessionManager', () => { describe('Switching Users', () => { beforeEach(async () => { - await TestEnvironment.initialize({ - useMockIdentityModel: true, - }); - + setGetUserResponse(); + setCreateUserResponse(); + setUpdateUserResponse(); setAddAliasResponse(); + await TestEnvironment.initialize(); }); test('handleOnFocus should wait for login promise', async () => { const loginPromise = (async function () { - await LoginManager.login(DUMMY_EXTERNAL_ID); + await LoginManager.login(EXTERNAL_ID); return 'login'; })(); @@ -52,7 +57,7 @@ describe('SessionManager', () => { test('handleOnBlur should wait for login promise', async () => { const loginPromise = (async function () { - await LoginManager.login(DUMMY_EXTERNAL_ID); + await LoginManager.login(EXTERNAL_ID); return 'login'; })(); @@ -84,7 +89,7 @@ describe('SessionManager', () => { test('handleVisibilityChange should wait for login promise', async () => { const loginPromise = (async function () { - await LoginManager.login(DUMMY_EXTERNAL_ID); + await LoginManager.login(EXTERNAL_ID); return 'login'; })(); @@ -100,7 +105,7 @@ describe('SessionManager', () => { test('handleOnBeforeUnload should wait for login promise', async () => { const loginPromise = (async function () { - await LoginManager.login(DUMMY_EXTERNAL_ID); + await LoginManager.login(EXTERNAL_ID); return 'login'; })(); @@ -116,7 +121,7 @@ describe('SessionManager', () => { test('upsertSession should wait for login promise', async () => { const loginPromise = (async function () { - await LoginManager.login(DUMMY_EXTERNAL_ID); + await LoginManager.login(EXTERNAL_ID); return 'login'; })(); diff --git a/src/shared/managers/subscription/page.ts b/src/shared/managers/subscription/page.ts index 51e373f2b..f2e1537f4 100644 --- a/src/shared/managers/subscription/page.ts +++ b/src/shared/managers/subscription/page.ts @@ -20,7 +20,6 @@ import { import { triggerNotificationPermissionChanged } from 'src/shared/helpers/permissions'; import { ServiceWorkerActiveState } from 'src/shared/helpers/service-worker'; import Log from 'src/shared/libraries/Log'; -import { NotificationPermission } from 'src/shared/models/NotificationPermission'; import type { PushSubscriptionState } from 'src/shared/models/PushSubscriptionState'; import { RawPushSubscription } from 'src/shared/models/RawPushSubscription'; import type { SubscriptionStrategyKindValue } from 'src/shared/models/SubscriptionStrategyKind'; @@ -218,7 +217,7 @@ export class SubscriptionManagerPage extends SubscriptionManagerBase { beforeEach(async () => { isServiceWorker = false; - await clearAll(); + // await clearAll(); await db.put('Ids', { type: 'appId', id: appId, @@ -406,7 +402,7 @@ describe('ServiceWorker', () => { appId: appId, enableSessionDuration: true, isSafari: true, - onesignalId: DUMMY_ONESIGNAL_ID, + onesignalId: ONESIGNAL_ID, outcomesConfig: { direct: { enabled: true, @@ -422,7 +418,7 @@ describe('ServiceWorker', () => { }, sessionOrigin: SessionOrigin.UserCreate, sessionThreshold: 10, - subscriptionId: DUMMY_SUBSCRIPTION_ID, + subscriptionId: SUB_ID, }; const session: Session = {