1
0
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:
Conner Turnbull
2024-03-15 15:53:05 -04:00
committed by GitHub
parent 65534a1323
commit b99153a016
85 changed files with 942 additions and 261 deletions

View File

@@ -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(

View File

@@ -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());
},
);
});
});

View File

@@ -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))]);
}
}