mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 00:03:56 +00:00
[AC-2156] Billing State Provider Migration (#8133)
* Added billing account profile state service
* Update usages after removing state service functions
* Added migrator
* Updated bw.ts and main.background.ts
* Removed comment
* Updated state service dependencies to include billing service
* Added missing mv3 factory and updated MainContextMenuHandler
* updated autofill service and tests
* Updated the remaining extensions usages
* Updated desktop
* Removed subjects where they weren't needed
* Refactored billing service to have a single setter to avoid unecessary emissions
* Refactored has premium guard to return an observable
* Renamed services to match ADR
f633f2cdd8/docs/architecture/clients/presentation/angular.md (abstract--default-implementations)
* Updated property names to be a smidgen more descriptive and added jsdocs
* Updated setting of canAccessPremium to automatically update when the underlying observable emits
* Fixed build error after merge conflicts
* Another build error from conflict
* Removed autofill unit test changes from conflict
* Updated login strategy to not set premium field using state service
* Updated CLI to use billing state provider
* Shortened names a bit
* Fixed build
This commit is contained in:
@@ -33,6 +33,7 @@ import { MoveThemeToStateProviderMigrator } from "./migrations/35-move-theme-to-
|
||||
import { VaultSettingsKeyMigrator } from "./migrations/36-move-show-card-and-identity-to-state-provider";
|
||||
import { AvatarColorMigrator } from "./migrations/37-move-avatar-color-to-state-providers";
|
||||
import { TokenServiceStateProviderMigrator } from "./migrations/38-migrate-token-svc-to-state-provider";
|
||||
import { MoveBillingAccountProfileMigrator } from "./migrations/39-move-billing-account-profile-to-state-providers";
|
||||
import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked";
|
||||
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
|
||||
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
|
||||
@@ -42,7 +43,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
|
||||
import { MinVersionMigrator } from "./migrations/min-version";
|
||||
|
||||
export const MIN_VERSION = 3;
|
||||
export const CURRENT_VERSION = 38;
|
||||
export const CURRENT_VERSION = 39;
|
||||
export type MinVersion = typeof MIN_VERSION;
|
||||
|
||||
export function createMigrationBuilder() {
|
||||
@@ -82,7 +83,8 @@ export function createMigrationBuilder() {
|
||||
.with(MoveThemeToStateProviderMigrator, 34, 35)
|
||||
.with(VaultSettingsKeyMigrator, 35, 36)
|
||||
.with(AvatarColorMigrator, 36, 37)
|
||||
.with(TokenServiceStateProviderMigrator, 37, CURRENT_VERSION);
|
||||
.with(TokenServiceStateProviderMigrator, 37, 38)
|
||||
.with(MoveBillingAccountProfileMigrator, 38, CURRENT_VERSION);
|
||||
}
|
||||
|
||||
export async function currentVersion(
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import { MockProxy, any } from "jest-mock-extended";
|
||||
|
||||
import { MigrationHelper } from "../migration-helper";
|
||||
import { mockMigrationHelper } from "../migration-helper.spec";
|
||||
|
||||
import {
|
||||
BILLING_ACCOUNT_PROFILE_KEY_DEFINITION,
|
||||
MoveBillingAccountProfileMigrator,
|
||||
} from "./39-move-billing-account-profile-to-state-providers";
|
||||
|
||||
const exampleJSON = () => ({
|
||||
global: {
|
||||
otherStuff: "otherStuff1",
|
||||
},
|
||||
authenticatedAccounts: ["user-1", "user-2", "user-3"],
|
||||
"user-1": {
|
||||
profile: {
|
||||
hasPremiumPersonally: true,
|
||||
hasPremiumFromOrganization: false,
|
||||
otherStuff: "otherStuff2",
|
||||
},
|
||||
otherStuff: "otherStuff3",
|
||||
},
|
||||
"user-2": {
|
||||
otherStuff: "otherStuff4",
|
||||
},
|
||||
});
|
||||
|
||||
const rollbackJSON = () => ({
|
||||
"user_user-1_billing_accountProfile": {
|
||||
hasPremiumPersonally: true,
|
||||
hasPremiumFromOrganization: false,
|
||||
},
|
||||
global: {
|
||||
otherStuff: "otherStuff1",
|
||||
},
|
||||
authenticatedAccounts: ["user-1", "user-2", "user-3"],
|
||||
"user-1": {
|
||||
profile: {
|
||||
otherStuff: "otherStuff2",
|
||||
},
|
||||
otherStuff: "otherStuff3",
|
||||
},
|
||||
"user-2": {
|
||||
otherStuff: "otherStuff4",
|
||||
},
|
||||
});
|
||||
|
||||
describe("MoveBillingAccountProfileToStateProviders migrator", () => {
|
||||
let helper: MockProxy<MigrationHelper>;
|
||||
let sut: MoveBillingAccountProfileMigrator;
|
||||
|
||||
describe("migrate", () => {
|
||||
beforeEach(() => {
|
||||
helper = mockMigrationHelper(exampleJSON(), 39);
|
||||
sut = new MoveBillingAccountProfileMigrator(38, 39);
|
||||
});
|
||||
|
||||
it("removes from all accounts", async () => {
|
||||
await sut.migrate(helper);
|
||||
expect(helper.set).toHaveBeenCalledTimes(1);
|
||||
expect(helper.set).toHaveBeenCalledWith("user-1", {
|
||||
profile: {
|
||||
otherStuff: "otherStuff2",
|
||||
},
|
||||
otherStuff: "otherStuff3",
|
||||
});
|
||||
});
|
||||
|
||||
it("sets hasPremiumPersonally value for account that have it", async () => {
|
||||
await sut.migrate(helper);
|
||||
|
||||
expect(helper.setToUser).toHaveBeenCalledWith(
|
||||
"user-1",
|
||||
BILLING_ACCOUNT_PROFILE_KEY_DEFINITION,
|
||||
{ hasPremiumFromOrganization: false, hasPremiumPersonally: true },
|
||||
);
|
||||
});
|
||||
|
||||
it("should not call extra setToUser", async () => {
|
||||
await sut.migrate(helper);
|
||||
|
||||
expect(helper.setToUser).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("rollback", () => {
|
||||
beforeEach(() => {
|
||||
helper = mockMigrationHelper(rollbackJSON(), 39);
|
||||
sut = new MoveBillingAccountProfileMigrator(38, 39);
|
||||
});
|
||||
|
||||
it("nulls out new values", async () => {
|
||||
await sut.rollback(helper);
|
||||
|
||||
expect(helper.setToUser).toHaveBeenCalledWith(
|
||||
"user-1",
|
||||
BILLING_ACCOUNT_PROFILE_KEY_DEFINITION,
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
it("adds explicit value back to accounts", async () => {
|
||||
await sut.rollback(helper);
|
||||
|
||||
expect(helper.set).toHaveBeenCalledTimes(1);
|
||||
expect(helper.set).toHaveBeenCalledWith("user-1", {
|
||||
profile: {
|
||||
hasPremiumPersonally: true,
|
||||
hasPremiumFromOrganization: false,
|
||||
otherStuff: "otherStuff2",
|
||||
},
|
||||
otherStuff: "otherStuff3",
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["user-2", "user-3"])(
|
||||
"does not restore values when accounts are not present",
|
||||
async (userId) => {
|
||||
await sut.rollback(helper);
|
||||
|
||||
expect(helper.set).not.toHaveBeenCalledWith(userId, any());
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
|
||||
import { Migrator } from "../migrator";
|
||||
|
||||
type ExpectedAccountType = {
|
||||
profile?: {
|
||||
hasPremiumPersonally?: boolean;
|
||||
hasPremiumFromOrganization?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type ExpectedBillingAccountProfileType = {
|
||||
hasPremiumPersonally: boolean;
|
||||
hasPremiumFromOrganization: boolean;
|
||||
};
|
||||
|
||||
export const BILLING_ACCOUNT_PROFILE_KEY_DEFINITION: KeyDefinitionLike = {
|
||||
key: "accountProfile",
|
||||
stateDefinition: {
|
||||
name: "billing",
|
||||
},
|
||||
};
|
||||
|
||||
export class MoveBillingAccountProfileMigrator extends Migrator<38, 39> {
|
||||
async migrate(helper: MigrationHelper): Promise<void> {
|
||||
const accounts = await helper.getAccounts<ExpectedAccountType>();
|
||||
|
||||
const migrateAccount = async (userId: string, account: ExpectedAccountType): Promise<void> => {
|
||||
const hasPremiumPersonally = account?.profile?.hasPremiumPersonally;
|
||||
const hasPremiumFromOrganization = account?.profile?.hasPremiumFromOrganization;
|
||||
|
||||
if (hasPremiumPersonally != null || hasPremiumFromOrganization != null) {
|
||||
await helper.setToUser(userId, BILLING_ACCOUNT_PROFILE_KEY_DEFINITION, {
|
||||
hasPremiumPersonally: hasPremiumPersonally,
|
||||
hasPremiumFromOrganization: hasPremiumFromOrganization,
|
||||
});
|
||||
|
||||
delete account?.profile?.hasPremiumPersonally;
|
||||
delete account?.profile?.hasPremiumFromOrganization;
|
||||
await helper.set(userId, account);
|
||||
}
|
||||
};
|
||||
|
||||
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
|
||||
}
|
||||
|
||||
async rollback(helper: MigrationHelper): Promise<void> {
|
||||
const accounts = await helper.getAccounts<ExpectedAccountType>();
|
||||
const rollbackAccount = async (userId: string, account: ExpectedAccountType): Promise<void> => {
|
||||
const value = await helper.getFromUser<ExpectedBillingAccountProfileType>(
|
||||
userId,
|
||||
BILLING_ACCOUNT_PROFILE_KEY_DEFINITION,
|
||||
);
|
||||
|
||||
if (account && value) {
|
||||
account.profile = Object.assign(account.profile ?? {}, {
|
||||
hasPremiumPersonally: value?.hasPremiumPersonally,
|
||||
hasPremiumFromOrganization: value?.hasPremiumFromOrganization,
|
||||
});
|
||||
await helper.set(userId, account);
|
||||
}
|
||||
|
||||
await helper.setToUser(userId, BILLING_ACCOUNT_PROFILE_KEY_DEFINITION, null);
|
||||
};
|
||||
|
||||
await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user