1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 16:23:44 +00:00

[SM-1065] SmOnboarding state provider (#8037)

* Create state definition

* Create SmOnboardingTaskService

* Replace usage of stateService value with state new state provider

* Migrate old state values to state provider

* Fix injection of SmOnboardingTasksService

* Remove smOnboardingTasks from state

* Fix state provider imports

* Fix migration after merge from main

* Move null handling to SMOnboardingTasksService
This commit is contained in:
Robyn MacCallum
2024-02-23 13:16:42 -05:00
committed by GitHub
parent dee0b20554
commit 8ab4eecc8a
9 changed files with 307 additions and 38 deletions

View File

@@ -18,6 +18,7 @@ import { PrivateKeyMigrator } from "./migrations/20-move-private-key-to-state-pr
import { CollectionMigrator } from "./migrations/21-move-collections-state-to-state-provider";
import { CollapsedGroupingsMigrator } from "./migrations/22-move-collapsed-groupings-to-state-provider";
import { MoveBiometricPromptsToStateProviders } from "./migrations/23-move-biometric-prompts-to-state-providers";
import { SmOnboardingTasksMigrator } from "./migrations/24-move-sm-onboarding-key-to-state-providers";
import { FixPremiumMigrator } from "./migrations/3-fix-premium";
import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked";
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
@@ -28,7 +29,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
import { MinVersionMigrator } from "./migrations/min-version";
export const MIN_VERSION = 2;
export const CURRENT_VERSION = 23;
export const CURRENT_VERSION = 24;
export type MinVersion = typeof MIN_VERSION;
export function createMigrationBuilder() {
@@ -54,7 +55,8 @@ export function createMigrationBuilder() {
.with(PrivateKeyMigrator, 19, 20)
.with(CollectionMigrator, 20, 21)
.with(CollapsedGroupingsMigrator, 21, 22)
.with(MoveBiometricPromptsToStateProviders, 22, CURRENT_VERSION);
.with(MoveBiometricPromptsToStateProviders, 22, 23)
.with(SmOnboardingTasksMigrator, 23, CURRENT_VERSION);
}
export async function currentVersion(

View File

@@ -0,0 +1,200 @@
import { MockProxy, any } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import { SmOnboardingTasksMigrator } from "./24-move-sm-onboarding-key-to-state-providers";
function exampleJSON() {
return {
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
smOnboardingTasks: {
"0bd005de-c722-473b-a00c-b10101006fcd": {
createProject: true,
createSecret: true,
createServiceAccount: true,
importSecrets: true,
},
"2f0d26ec-493a-4ed7-9183-b10d013597c8": {
createProject: false,
createSecret: true,
createServiceAccount: false,
importSecrets: true,
},
},
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
},
"user-2": {
settings: {
smOnboardingTasks: {
"000000-0000000-0000000-000000000": {
createProject: false,
createSecret: false,
createServiceAccount: false,
importSecrets: false,
},
},
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
},
};
}
function rollbackJSON() {
return {
"user_user-1_smOnboarding_tasks": {
"0bd005de-c722-473b-a00c-b10101006fcd": {
createProject: true,
createSecret: true,
createServiceAccount: true,
importSecrets: true,
},
"2f0d26ec-493a-4ed7-9183-b10d013597c8": {
createProject: false,
createSecret: true,
createServiceAccount: false,
importSecrets: true,
},
},
"user_user-2_smOnboarding_tasks": {
"000000-0000000-0000000-000000000": {
createProject: false,
createSecret: false,
createServiceAccount: false,
importSecrets: false,
},
},
authenticatedAccounts: ["user-1", "user-2"],
"user-1": {
settings: {
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
},
"user-2": {
settings: {
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
},
};
}
describe("SmOnboardingTasksMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: SmOnboardingTasksMigrator;
const keyDefinitionLike = {
key: "tasks",
stateDefinition: { name: "smOnboarding" },
};
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(exampleJSON(), 23);
sut = new SmOnboardingTasksMigrator(23, 24);
});
it("should remove smOnboardingTasks from all accounts", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
});
});
it("should set smOnboardingTasks provider value for each account", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledWith("user-1", keyDefinitionLike, {
"0bd005de-c722-473b-a00c-b10101006fcd": {
createProject: true,
createSecret: true,
createServiceAccount: true,
importSecrets: true,
},
"2f0d26ec-493a-4ed7-9183-b10d013597c8": {
createProject: false,
createSecret: true,
createServiceAccount: false,
importSecrets: true,
},
});
expect(helper.setToUser).toHaveBeenCalledWith("user-2", keyDefinitionLike, {
"000000-0000000-0000000-000000000": {
createProject: false,
createSecret: false,
createServiceAccount: false,
importSecrets: false,
},
});
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 23);
sut = new SmOnboardingTasksMigrator(23, 24);
});
it.each(["user-1", "user-2"])("should null out new values", async (userId) => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledWith(userId, keyDefinitionLike, null);
});
it("should add smOnboardingTasks back to accounts", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
smOnboardingTasks: {
"0bd005de-c722-473b-a00c-b10101006fcd": {
createProject: true,
createSecret: true,
createServiceAccount: true,
importSecrets: true,
},
"2f0d26ec-493a-4ed7-9183-b10d013597c8": {
createProject: false,
createSecret: true,
createServiceAccount: false,
importSecrets: true,
},
},
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user-2", {
settings: {
smOnboardingTasks: {
"000000-0000000-0000000-000000000": {
createProject: false,
createSecret: false,
createServiceAccount: false,
importSecrets: false,
},
},
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
});
});
it("should not try to restore values to missing accounts", async () => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith("user-3", any());
});
});
});

View File

@@ -0,0 +1,53 @@
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedAccountType = {
settings?: {
smOnboardingTasks?: Record<string, Record<string, boolean>>;
};
};
export const SM_ONBOARDING_TASKS: KeyDefinitionLike = {
key: "tasks",
stateDefinition: { name: "smOnboarding" },
};
export class SmOnboardingTasksMigrator extends Migrator<23, 24> {
async migrate(helper: MigrationHelper): Promise<void> {
const legacyAccounts = await helper.getAccounts<ExpectedAccountType>();
await Promise.all(
legacyAccounts.map(async ({ userId, account }) => {
// Move account data
if (account?.settings?.smOnboardingTasks != null) {
await helper.setToUser(userId, SM_ONBOARDING_TASKS, account.settings.smOnboardingTasks);
// Delete old account data
delete account.settings.smOnboardingTasks;
await helper.set(userId, account);
}
}),
);
}
async rollback(helper: MigrationHelper): Promise<void> {
async function rollbackUser(userId: string, account: ExpectedAccountType) {
const smOnboardingTasks = await helper.getFromUser<Record<string, Record<string, boolean>>>(
userId,
SM_ONBOARDING_TASKS,
);
if (smOnboardingTasks) {
account ??= {};
account.settings ??= {};
account.settings.smOnboardingTasks = smOnboardingTasks;
await helper.setToUser(userId, SM_ONBOARDING_TASKS, null);
await helper.set(userId, account);
}
}
const accounts = await helper.getAccounts<ExpectedAccountType>();
await Promise.all(accounts.map(({ userId, account }) => rollbackUser(userId, account)));
}
}