[PM-7766] Add `clientType` to MigrationHelper (#8945)

* Add `clientType` to MigrationHelper

* PM-7766 - Fix migration builder tests to take new clientType into account.

Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>

* PM-7766 - Add client type to migration builder tests.

* PM-7766 - Fix migration-helper.spec tests.

* PM-7766 - Fix migrator.spec.ts

---------

Co-authored-by: Jared Snider <jsnider@bitwarden.com>
This commit is contained in:
Justin Baur 2024-04-29 07:28:58 -04:00 committed by GitHub
parent 42f1f965af
commit 3caa6cb635
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 295 additions and 210 deletions

View File

@ -71,6 +71,7 @@ import {
} from "@bitwarden/common/autofill/services/user-notification-settings.service"; } from "@bitwarden/common/autofill/services/user-notification-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { ClientType } from "@bitwarden/common/enums";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@ -520,6 +521,7 @@ export default class MainBackground {
this.storageService, this.storageService,
this.logService, this.logService,
new MigrationBuilderService(), new MigrationBuilderService(),
ClientType.Browser,
); );
this.stateService = new DefaultBrowserStateService( this.stateService = new DefaultBrowserStateService(

View File

@ -1,3 +1,4 @@
import { ClientType } from "@bitwarden/common/enums";
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
@ -27,6 +28,7 @@ export async function migrationRunnerFactory(
await diskStorageServiceFactory(cache, opts), await diskStorageServiceFactory(cache, opts),
await logServiceFactory(cache, opts), await logServiceFactory(cache, opts),
new MigrationBuilderService(), new MigrationBuilderService(),
ClientType.Browser,
), ),
); );
} }

View File

@ -13,6 +13,7 @@ import {
SYSTEM_THEME_OBSERVABLE, SYSTEM_THEME_OBSERVABLE,
SafeInjectionToken, SafeInjectionToken,
INTRAPROCESS_MESSAGING_SUBJECT, INTRAPROCESS_MESSAGING_SUBJECT,
CLIENT_TYPE,
} from "@bitwarden/angular/services/injection-tokens"; } from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
@ -45,6 +46,7 @@ import {
UserNotificationSettingsServiceAbstraction, UserNotificationSettingsServiceAbstraction,
} from "@bitwarden/common/autofill/services/user-notification-settings.service"; } from "@bitwarden/common/autofill/services/user-notification-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { ClientType } from "@bitwarden/common/enums";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@ -558,6 +560,10 @@ const safeProviders: SafeProvider[] = [
OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE, OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE,
], ],
}), }),
safeProvider({
provide: CLIENT_TYPE,
useValue: ClientType.Browser,
}),
]; ];
@NgModule({ @NgModule({

View File

@ -344,6 +344,7 @@ export class Main {
this.storageService, this.storageService,
this.logService, this.logService,
new MigrationBuilderService(), new MigrationBuilderService(),
ClientType.Cli,
); );
this.stateService = new StateService( this.stateService = new StateService(

View File

@ -15,6 +15,7 @@ import {
SafeInjectionToken, SafeInjectionToken,
STATE_FACTORY, STATE_FACTORY,
INTRAPROCESS_MESSAGING_SUBJECT, INTRAPROCESS_MESSAGING_SUBJECT,
CLIENT_TYPE,
} from "@bitwarden/angular/services/injection-tokens"; } from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
@ -25,6 +26,7 @@ import { KdfConfigService as KdfConfigServiceAbstraction } from "@bitwarden/comm
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { ClientType } from "@bitwarden/common/enums";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@ -275,6 +277,10 @@ const safeProviders: SafeProvider[] = [
useClass: NativeMessagingManifestService, useClass: NativeMessagingManifestService,
deps: [], deps: [],
}), }),
safeProvider({
provide: CLIENT_TYPE,
useValue: ClientType.Desktop,
}),
]; ];
@NgModule({ @NgModule({

View File

@ -6,6 +6,7 @@ import { Subject, firstValueFrom } from "rxjs";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { TokenService } from "@bitwarden/common/auth/services/token.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service";
import { ClientType } from "@bitwarden/common/enums";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
@ -190,6 +191,7 @@ export class Main {
this.storageService, this.storageService,
this.logService, this.logService,
new MigrationBuilderService(), new MigrationBuilderService(),
ClientType.Desktop,
); );
// TODO: this state service will have access to on disk storage, but not in memory storage. // TODO: this state service will have access to on disk storage, but not in memory storage.

View File

@ -13,10 +13,12 @@ import {
OBSERVABLE_DISK_LOCAL_STORAGE, OBSERVABLE_DISK_LOCAL_STORAGE,
WINDOW, WINDOW,
SafeInjectionToken, SafeInjectionToken,
CLIENT_TYPE,
} from "@bitwarden/angular/services/injection-tokens"; } from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service"; import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ClientType } from "@bitwarden/common/enums";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -157,6 +159,10 @@ const safeProviders: SafeProvider[] = [
new DefaultThemeStateService(globalStateProvider, ThemeType.Light), new DefaultThemeStateService(globalStateProvider, ThemeType.Light),
deps: [GlobalStateProvider], deps: [GlobalStateProvider],
}), }),
safeProvider({
provide: CLIENT_TYPE,
useValue: ClientType.Web,
}),
]; ];
@NgModule({ @NgModule({

View File

@ -1,3 +1,4 @@
import { ClientType } from "@bitwarden/common/enums";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
@ -14,7 +15,7 @@ export class WebMigrationRunner extends MigrationRunner {
migrationBuilderService: MigrationBuilderService, migrationBuilderService: MigrationBuilderService,
private diskLocalStorage: WindowStorageService, private diskLocalStorage: WindowStorageService,
) { ) {
super(diskStorage, logService, migrationBuilderService); super(diskStorage, logService, migrationBuilderService, ClientType.Web);
} }
override async run(): Promise<void> { override async run(): Promise<void> {
@ -46,7 +47,7 @@ class WebMigrationHelper extends MigrationHelper {
storageService: WindowStorageService, storageService: WindowStorageService,
logService: LogService, logService: LogService,
) { ) {
super(currentVersion, storageService, logService, "web-disk-local"); super(currentVersion, storageService, logService, "web-disk-local", ClientType.Web);
this.diskLocalStorageService = storageService; this.diskLocalStorageService = storageService;
} }

View File

@ -1,6 +1,7 @@
import { InjectionToken } from "@angular/core"; import { InjectionToken } from "@angular/core";
import { Observable, Subject } from "rxjs"; import { Observable, Subject } from "rxjs";
import { ClientType } from "@bitwarden/common/enums";
import { import {
AbstractMemoryStorageService, AbstractMemoryStorageService,
AbstractStorageService, AbstractStorageService,
@ -52,3 +53,4 @@ export const SYSTEM_THEME_OBSERVABLE = new SafeInjectionToken<Observable<ThemeTy
export const INTRAPROCESS_MESSAGING_SUBJECT = new SafeInjectionToken<Subject<Message<object>>>( export const INTRAPROCESS_MESSAGING_SUBJECT = new SafeInjectionToken<Subject<Message<object>>>(
"INTRAPROCESS_MESSAGING_SUBJECT", "INTRAPROCESS_MESSAGING_SUBJECT",
); );
export const CLIENT_TYPE = new SafeInjectionToken<ClientType>("CLIENT_TYPE");

View File

@ -276,6 +276,7 @@ import {
SYSTEM_THEME_OBSERVABLE, SYSTEM_THEME_OBSERVABLE,
WINDOW, WINDOW,
INTRAPROCESS_MESSAGING_SUBJECT, INTRAPROCESS_MESSAGING_SUBJECT,
CLIENT_TYPE,
} from "./injection-tokens"; } from "./injection-tokens";
import { ModalService } from "./modal.service"; import { ModalService } from "./modal.service";
@ -1099,7 +1100,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({ safeProvider({
provide: MigrationRunner, provide: MigrationRunner,
useClass: MigrationRunner, useClass: MigrationRunner,
deps: [AbstractStorageService, LogService, MigrationBuilderService], deps: [AbstractStorageService, LogService, MigrationBuilderService, CLIENT_TYPE],
}), }),
safeProvider({ safeProvider({
provide: MigrationBuilderService, provide: MigrationBuilderService,

View File

@ -1,6 +1,7 @@
import { mock } from "jest-mock-extended"; import { mock } from "jest-mock-extended";
import { FakeStorageService } from "../../../spec/fake-storage.service"; import { FakeStorageService } from "../../../spec/fake-storage.service";
import { ClientType } from "../../enums";
import { MigrationHelper } from "../../state-migrations/migration-helper"; import { MigrationHelper } from "../../state-migrations/migration-helper";
import { MigrationBuilderService } from "./migration-builder.service"; import { MigrationBuilderService } from "./migration-builder.service";
@ -66,25 +67,38 @@ describe("MigrationBuilderService", () => {
global: {}, global: {},
}; };
it.each([ const startingStates = [
noAccounts, { data: noAccounts, description: "No Accounts" },
nullAndUndefinedAccounts, { data: nullAndUndefinedAccounts, description: "Null and Undefined Accounts" },
emptyAccountObject, { data: emptyAccountObject, description: "Empty Account Object" },
nullCommonAccountProperties, { data: nullCommonAccountProperties, description: "Null Common Account Properties" },
emptyCommonAccountProperties, { data: emptyCommonAccountProperties, description: "Empty Common Account Properties" },
nullGlobal, { data: nullGlobal, description: "Null Global" },
undefinedGlobal, { data: undefinedGlobal, description: "Undefined Global" },
emptyGlobalObject, { data: emptyGlobalObject, description: "Empty Global Object" },
])("should not produce migrations that throw when given data: %s", async (startingState) => { ];
const sut = new MigrationBuilderService();
const helper = new MigrationHelper( const clientTypes = Object.values(ClientType);
startingStateVersion,
new FakeStorageService(startingState),
mock(),
"general",
);
await sut.build().migrate(helper); // Generate all possible test cases
}); const testCases = startingStates.flatMap((startingState) =>
clientTypes.map((clientType) => ({ startingState, clientType })),
);
it.each(testCases)(
"should not produce migrations that throw when given $startingState.description for client $clientType",
async ({ startingState, clientType }) => {
const sut = new MigrationBuilderService();
const helper = new MigrationHelper(
startingStateVersion,
new FakeStorageService(startingState),
mock(),
"general",
clientType,
);
await sut.build().migrate(helper);
},
);
}); });

View File

@ -1,6 +1,7 @@
import { mock } from "jest-mock-extended"; import { mock } from "jest-mock-extended";
import { awaitAsync } from "../../../spec"; import { awaitAsync } from "../../../spec";
import { ClientType } from "../../enums";
import { CURRENT_VERSION } from "../../state-migrations"; import { CURRENT_VERSION } from "../../state-migrations";
import { MigrationBuilder } from "../../state-migrations/migration-builder"; import { MigrationBuilder } from "../../state-migrations/migration-builder";
import { LogService } from "../abstractions/log.service"; import { LogService } from "../abstractions/log.service";
@ -17,7 +18,7 @@ describe("MigrationRunner", () => {
migrationBuilderService.build.mockReturnValue(mockMigrationBuilder); migrationBuilderService.build.mockReturnValue(mockMigrationBuilder);
const sut = new MigrationRunner(storage, logService, migrationBuilderService); const sut = new MigrationRunner(storage, logService, migrationBuilderService, ClientType.Web);
describe("migrate", () => { describe("migrate", () => {
it("should not run migrations if state is empty", async () => { it("should not run migrations if state is empty", async () => {

View File

@ -1,3 +1,4 @@
import { ClientType } from "../../enums";
import { waitForMigrations } from "../../state-migrations"; import { waitForMigrations } from "../../state-migrations";
import { CURRENT_VERSION, currentVersion } from "../../state-migrations/migrate"; import { CURRENT_VERSION, currentVersion } from "../../state-migrations/migrate";
import { MigrationHelper } from "../../state-migrations/migration-helper"; import { MigrationHelper } from "../../state-migrations/migration-helper";
@ -11,6 +12,7 @@ export class MigrationRunner {
protected diskStorage: AbstractStorageService, protected diskStorage: AbstractStorageService,
protected logService: LogService, protected logService: LogService,
protected migrationBuilderService: MigrationBuilderService, protected migrationBuilderService: MigrationBuilderService,
private clientType: ClientType,
) {} ) {}
async run(): Promise<void> { async run(): Promise<void> {
@ -19,6 +21,7 @@ export class MigrationRunner {
this.diskStorage, this.diskStorage,
this.logService, this.logService,
"general", "general",
this.clientType,
); );
if (migrationHelper.currentVersion < 0) { if (migrationHelper.currentVersion < 0) {

View File

@ -1,5 +1,8 @@
import { mock } from "jest-mock-extended"; import { mock } from "jest-mock-extended";
// eslint-disable-next-line import/no-restricted-paths
import { ClientType } from "../enums";
import { MigrationBuilder } from "./migration-builder"; import { MigrationBuilder } from "./migration-builder";
import { MigrationHelper } from "./migration-helper"; import { MigrationHelper } from "./migration-helper";
import { Migrator } from "./migrator"; import { Migrator } from "./migrator";
@ -72,65 +75,69 @@ describe("MigrationBuilder", () => {
expect(migrations[1]).toMatchObject({ migrator: expect.any(TestMigrator), direction: "down" }); expect(migrations[1]).toMatchObject({ migrator: expect.any(TestMigrator), direction: "down" });
}); });
describe("migrate", () => { const clientTypes = Object.values(ClientType);
let migrator: TestMigrator;
let rollback_migrator: TestMigrator;
beforeEach(() => { describe.each(clientTypes)("for client %s", (clientType) => {
sut = sut.with(TestMigrator, 0, 1).rollback(TestMigrator, 1, 0); describe("migrate", () => {
migrator = (sut as any).migrations[0].migrator; let migrator: TestMigrator;
rollback_migrator = (sut as any).migrations[1].migrator; let rollback_migrator: TestMigrator;
beforeEach(() => {
sut = sut.with(TestMigrator, 0, 1).rollback(TestMigrator, 1, 0);
migrator = (sut as any).migrations[0].migrator;
rollback_migrator = (sut as any).migrations[1].migrator;
});
it("should migrate", async () => {
const helper = new MigrationHelper(0, mock(), mock(), "general", clientType);
const spy = jest.spyOn(migrator, "migrate");
await sut.migrate(helper);
expect(spy).toBeCalledWith(helper);
});
it("should rollback", async () => {
const helper = new MigrationHelper(1, mock(), mock(), "general", clientType);
const spy = jest.spyOn(rollback_migrator, "rollback");
await sut.migrate(helper);
expect(spy).toBeCalledWith(helper);
});
it("should update version on migrate", async () => {
const helper = new MigrationHelper(0, mock(), mock(), "general", clientType);
const spy = jest.spyOn(migrator, "updateVersion");
await sut.migrate(helper);
expect(spy).toBeCalledWith(helper, "up");
});
it("should update version on rollback", async () => {
const helper = new MigrationHelper(1, mock(), mock(), "general", clientType);
const spy = jest.spyOn(rollback_migrator, "updateVersion");
await sut.migrate(helper);
expect(spy).toBeCalledWith(helper, "down");
});
it("should not run the migrator if the current version does not match the from version", async () => {
const helper = new MigrationHelper(3, mock(), mock(), "general", clientType);
const migrate = jest.spyOn(migrator, "migrate");
const rollback = jest.spyOn(rollback_migrator, "rollback");
await sut.migrate(helper);
expect(migrate).not.toBeCalled();
expect(rollback).not.toBeCalled();
});
it("should not update version if the current version does not match the from version", async () => {
const helper = new MigrationHelper(3, mock(), mock(), "general", clientType);
const migrate = jest.spyOn(migrator, "updateVersion");
const rollback = jest.spyOn(rollback_migrator, "updateVersion");
await sut.migrate(helper);
expect(migrate).not.toBeCalled();
expect(rollback).not.toBeCalled();
});
}); });
it("should migrate", async () => { it("should be able to call instance methods", async () => {
const helper = new MigrationHelper(0, mock(), mock(), "general"); const helper = new MigrationHelper(0, mock(), mock(), "general", clientType);
const spy = jest.spyOn(migrator, "migrate"); await sut.with(TestMigratorWithInstanceMethod, 0, 1).migrate(helper);
await sut.migrate(helper);
expect(spy).toBeCalledWith(helper);
}); });
it("should rollback", async () => {
const helper = new MigrationHelper(1, mock(), mock(), "general");
const spy = jest.spyOn(rollback_migrator, "rollback");
await sut.migrate(helper);
expect(spy).toBeCalledWith(helper);
});
it("should update version on migrate", async () => {
const helper = new MigrationHelper(0, mock(), mock(), "general");
const spy = jest.spyOn(migrator, "updateVersion");
await sut.migrate(helper);
expect(spy).toBeCalledWith(helper, "up");
});
it("should update version on rollback", async () => {
const helper = new MigrationHelper(1, mock(), mock(), "general");
const spy = jest.spyOn(rollback_migrator, "updateVersion");
await sut.migrate(helper);
expect(spy).toBeCalledWith(helper, "down");
});
it("should not run the migrator if the current version does not match the from version", async () => {
const helper = new MigrationHelper(3, mock(), mock(), "general");
const migrate = jest.spyOn(migrator, "migrate");
const rollback = jest.spyOn(rollback_migrator, "rollback");
await sut.migrate(helper);
expect(migrate).not.toBeCalled();
expect(rollback).not.toBeCalled();
});
it("should not update version if the current version does not match the from version", async () => {
const helper = new MigrationHelper(3, mock(), mock(), "general");
const migrate = jest.spyOn(migrator, "updateVersion");
const rollback = jest.spyOn(rollback_migrator, "updateVersion");
await sut.migrate(helper);
expect(migrate).not.toBeCalled();
expect(rollback).not.toBeCalled();
});
});
it("should be able to call instance methods", async () => {
const helper = new MigrationHelper(0, mock(), mock(), "general");
await sut.with(TestMigratorWithInstanceMethod, 0, 1).migrate(helper);
}); });
}); });

View File

@ -2,6 +2,8 @@ import { MockProxy, mock } from "jest-mock-extended";
// eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages // eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages
import { FakeStorageService } from "../../spec/fake-storage.service"; import { FakeStorageService } from "../../spec/fake-storage.service";
// eslint-disable-next-line import/no-restricted-paths -- Needed client type enum
import { ClientType } from "../enums";
// eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages // eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages
import { LogService } from "../platform/abstractions/log.service"; import { LogService } from "../platform/abstractions/log.service";
// eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations // eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations
@ -32,116 +34,129 @@ describe("RemoveLegacyEtmKeyMigrator", () => {
let logService: MockProxy<LogService>; let logService: MockProxy<LogService>;
let sut: MigrationHelper; let sut: MigrationHelper;
beforeEach(() => { const clientTypes = Object.values(ClientType);
logService = mock();
storage = mock();
storage.get.mockImplementation((key) => (exampleJSON as any)[key]);
sut = new MigrationHelper(0, storage, logService, "general"); describe.each(clientTypes)("for client %s", (clientType) => {
}); beforeEach(() => {
logService = mock();
storage = mock();
storage.get.mockImplementation((key) => (exampleJSON as any)[key]);
describe("get", () => { sut = new MigrationHelper(0, storage, logService, "general", clientType);
it("should delegate to storage.get", async () => {
await sut.get("key");
expect(storage.get).toHaveBeenCalledWith("key");
});
});
describe("set", () => {
it("should delegate to storage.save", async () => {
await sut.set("key", "value");
expect(storage.save).toHaveBeenCalledWith("key", "value");
});
});
describe("getAccounts", () => {
it("should return all accounts", async () => {
const accounts = await sut.getAccounts();
expect(accounts).toEqual([
{ userId: "c493ed01-4e08-4e88-abc7-332f380ca760", account: { otherStuff: "otherStuff1" } },
{ userId: "23e61a5f-2ece-4f5e-b499-f0bc489482a9", account: { otherStuff: "otherStuff2" } },
]);
}); });
it("should handle missing authenticatedAccounts", async () => { describe("get", () => {
storage.get.mockImplementation((key) => it("should delegate to storage.get", async () => {
key === "authenticatedAccounts" ? undefined : (exampleJSON as any)[key], await sut.get("key");
); expect(storage.get).toHaveBeenCalledWith("key");
const accounts = await sut.getAccounts();
expect(accounts).toEqual([]);
});
});
describe("getFromGlobal", () => {
it("should return the correct value", async () => {
sut.currentVersion = 9;
const value = await sut.getFromGlobal({
stateDefinition: { name: "serviceName" },
key: "key",
}); });
expect(value).toEqual("global_serviceName_key");
}); });
it("should throw if the current version is less than 9", () => { describe("set", () => {
expect(() => it("should delegate to storage.save", async () => {
sut.getFromGlobal({ stateDefinition: { name: "serviceName" }, key: "key" }), await sut.set("key", "value");
).toThrowError("No key builder should be used for versions prior to 9."); expect(storage.save).toHaveBeenCalledWith("key", "value");
}); });
});
describe("setToGlobal", () => {
it("should set the correct value", async () => {
sut.currentVersion = 9;
await sut.setToGlobal({ stateDefinition: { name: "serviceName" }, key: "key" }, "new_value");
expect(storage.save).toHaveBeenCalledWith("global_serviceName_key", "new_value");
}); });
it("should throw if the current version is less than 9", () => { describe("getAccounts", () => {
expect(() => it("should return all accounts", async () => {
sut.setToGlobal( const accounts = await sut.getAccounts();
expect(accounts).toEqual([
{
userId: "c493ed01-4e08-4e88-abc7-332f380ca760",
account: { otherStuff: "otherStuff1" },
},
{
userId: "23e61a5f-2ece-4f5e-b499-f0bc489482a9",
account: { otherStuff: "otherStuff2" },
},
]);
});
it("should handle missing authenticatedAccounts", async () => {
storage.get.mockImplementation((key) =>
key === "authenticatedAccounts" ? undefined : (exampleJSON as any)[key],
);
const accounts = await sut.getAccounts();
expect(accounts).toEqual([]);
});
});
describe("getFromGlobal", () => {
it("should return the correct value", async () => {
sut.currentVersion = 9;
const value = await sut.getFromGlobal({
stateDefinition: { name: "serviceName" },
key: "key",
});
expect(value).toEqual("global_serviceName_key");
});
it("should throw if the current version is less than 9", () => {
expect(() =>
sut.getFromGlobal({ stateDefinition: { name: "serviceName" }, key: "key" }),
).toThrowError("No key builder should be used for versions prior to 9.");
});
});
describe("setToGlobal", () => {
it("should set the correct value", async () => {
sut.currentVersion = 9;
await sut.setToGlobal(
{ stateDefinition: { name: "serviceName" }, key: "key" }, { stateDefinition: { name: "serviceName" }, key: "key" },
"global_serviceName_key", "new_value",
), );
).toThrowError("No key builder should be used for versions prior to 9."); expect(storage.save).toHaveBeenCalledWith("global_serviceName_key", "new_value");
}); });
});
it("should throw if the current version is less than 9", () => {
describe("getFromUser", () => { expect(() =>
it("should return the correct value", async () => { sut.setToGlobal(
sut.currentVersion = 9; { stateDefinition: { name: "serviceName" }, key: "key" },
const value = await sut.getFromUser("userId", { "global_serviceName_key",
stateDefinition: { name: "serviceName" }, ),
key: "key", ).toThrowError("No key builder should be used for versions prior to 9.");
}); });
expect(value).toEqual("user_userId_serviceName_key");
}); });
it("should throw if the current version is less than 9", () => { describe("getFromUser", () => {
expect(() => it("should return the correct value", async () => {
sut.getFromUser("userId", { stateDefinition: { name: "serviceName" }, key: "key" }), sut.currentVersion = 9;
).toThrowError("No key builder should be used for versions prior to 9."); const value = await sut.getFromUser("userId", {
}); stateDefinition: { name: "serviceName" },
}); key: "key",
});
expect(value).toEqual("user_userId_serviceName_key");
});
describe("setToUser", () => { it("should throw if the current version is less than 9", () => {
it("should set the correct value", async () => { expect(() =>
sut.currentVersion = 9; sut.getFromUser("userId", { stateDefinition: { name: "serviceName" }, key: "key" }),
await sut.setToUser( ).toThrowError("No key builder should be used for versions prior to 9.");
"userId", });
{ stateDefinition: { name: "serviceName" }, key: "key" },
"new_value",
);
expect(storage.save).toHaveBeenCalledWith("user_userId_serviceName_key", "new_value");
}); });
it("should throw if the current version is less than 9", () => { describe("setToUser", () => {
expect(() => it("should set the correct value", async () => {
sut.setToUser( sut.currentVersion = 9;
await sut.setToUser(
"userId", "userId",
{ stateDefinition: { name: "serviceName" }, key: "key" }, { stateDefinition: { name: "serviceName" }, key: "key" },
"new_value", "new_value",
), );
).toThrowError("No key builder should be used for versions prior to 9."); expect(storage.save).toHaveBeenCalledWith("user_userId_serviceName_key", "new_value");
});
it("should throw if the current version is less than 9", () => {
expect(() =>
sut.setToUser(
"userId",
{ stateDefinition: { name: "serviceName" }, key: "key" },
"new_value",
),
).toThrowError("No key builder should be used for versions prior to 9.");
});
}); });
}); });
}); });
@ -151,6 +166,7 @@ export function mockMigrationHelper(
storageJson: any, storageJson: any,
stateVersion = 0, stateVersion = 0,
type: MigrationHelperType = "general", type: MigrationHelperType = "general",
clientType: ClientType = ClientType.Web,
): MockProxy<MigrationHelper> { ): MockProxy<MigrationHelper> {
const logService: MockProxy<LogService> = mock(); const logService: MockProxy<LogService> = mock();
const storage: MockProxy<AbstractStorageService> = mock(); const storage: MockProxy<AbstractStorageService> = mock();
@ -158,7 +174,7 @@ export function mockMigrationHelper(
storage.save.mockImplementation(async (key, value) => { storage.save.mockImplementation(async (key, value) => {
(storageJson as any)[key] = value; (storageJson as any)[key] = value;
}); });
const helper = new MigrationHelper(stateVersion, storage, logService, type); const helper = new MigrationHelper(stateVersion, storage, logService, type, clientType);
const mockHelper = mock<MigrationHelper>(); const mockHelper = mock<MigrationHelper>();
mockHelper.get.mockImplementation((key) => helper.get(key)); mockHelper.get.mockImplementation((key) => helper.get(key));
@ -295,7 +311,13 @@ export async function runMigrator<
const allInjectedData = injectData(initalData, []); const allInjectedData = injectData(initalData, []);
const fakeStorageService = new FakeStorageService(initalData); const fakeStorageService = new FakeStorageService(initalData);
const helper = new MigrationHelper(migrator.fromVersion, fakeStorageService, mock(), "general"); const helper = new MigrationHelper(
migrator.fromVersion,
fakeStorageService,
mock(),
"general",
ClientType.Web,
);
// Run their migrations // Run their migrations
if (direction === "rollback") { if (direction === "rollback") {

View File

@ -1,3 +1,5 @@
// eslint-disable-next-line import/no-restricted-paths -- Needed to provide client type to migrations
import { ClientType } from "../enums";
// eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages // eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages
import { LogService } from "../platform/abstractions/log.service"; import { LogService } from "../platform/abstractions/log.service";
// eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations // eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations
@ -17,6 +19,7 @@ export class MigrationHelper {
private storageService: AbstractStorageService, private storageService: AbstractStorageService,
public logService: LogService, public logService: LogService,
type: MigrationHelperType, type: MigrationHelperType,
public clientType: ClientType,
) { ) {
this.type = type; this.type = type;
} }

View File

@ -1,5 +1,7 @@
import { mock, MockProxy } from "jest-mock-extended"; import { mock, MockProxy } from "jest-mock-extended";
// eslint-disable-next-line import/no-restricted-paths -- Needed client type enum
import { ClientType } from "../enums";
// eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages // eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages
import { LogService } from "../platform/abstractions/log.service"; import { LogService } from "../platform/abstractions/log.service";
// eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations // eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations
@ -23,52 +25,56 @@ describe("migrator default methods", () => {
let helper: MigrationHelper; let helper: MigrationHelper;
let sut: TestMigrator; let sut: TestMigrator;
beforeEach(() => { const clientTypes = Object.values(ClientType);
storage = mock();
logService = mock();
helper = new MigrationHelper(0, storage, logService, "general");
sut = new TestMigrator(0, 1);
});
describe("shouldMigrate", () => { describe.each(clientTypes)("for client %s", (clientType) => {
describe("up", () => { beforeEach(() => {
it("should return true if the current version equals the from version", async () => { storage = mock();
expect(await sut.shouldMigrate(helper, "up")).toBe(true); logService = mock();
helper = new MigrationHelper(0, storage, logService, "general", clientType);
sut = new TestMigrator(0, 1);
});
describe("shouldMigrate", () => {
describe("up", () => {
it("should return true if the current version equals the from version", async () => {
expect(await sut.shouldMigrate(helper, "up")).toBe(true);
});
it("should return false if the current version does not equal the from version", async () => {
helper.currentVersion = 1;
expect(await sut.shouldMigrate(helper, "up")).toBe(false);
});
}); });
it("should return false if the current version does not equal the from version", async () => { describe("down", () => {
helper.currentVersion = 1; it("should return true if the current version equals the to version", async () => {
expect(await sut.shouldMigrate(helper, "up")).toBe(false); helper.currentVersion = 1;
expect(await sut.shouldMigrate(helper, "down")).toBe(true);
});
it("should return false if the current version does not equal the to version", async () => {
expect(await sut.shouldMigrate(helper, "down")).toBe(false);
});
}); });
}); });
describe("down", () => { describe("updateVersion", () => {
it("should return true if the current version equals the to version", async () => { describe("up", () => {
helper.currentVersion = 1; it("should update the version", async () => {
expect(await sut.shouldMigrate(helper, "down")).toBe(true); await sut.updateVersion(helper, "up");
expect(storage.save).toBeCalledWith("stateVersion", 1);
expect(helper.currentVersion).toBe(1);
});
}); });
it("should return false if the current version does not equal the to version", async () => { describe("down", () => {
expect(await sut.shouldMigrate(helper, "down")).toBe(false); it("should update the version", async () => {
}); helper.currentVersion = 1;
}); await sut.updateVersion(helper, "down");
}); expect(storage.save).toBeCalledWith("stateVersion", 0);
expect(helper.currentVersion).toBe(0);
describe("updateVersion", () => { });
describe("up", () => {
it("should update the version", async () => {
await sut.updateVersion(helper, "up");
expect(storage.save).toBeCalledWith("stateVersion", 1);
expect(helper.currentVersion).toBe(1);
});
});
describe("down", () => {
it("should update the version", async () => {
helper.currentVersion = 1;
await sut.updateVersion(helper, "down");
expect(storage.save).toBeCalledWith("stateVersion", 0);
expect(helper.currentVersion).toBe(0);
}); });
}); });
}); });