1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

[PM-5266] Create Avatar Service (#7905)

* rename file, move, and update imports

* refactor and implement StateProvider

* remove comments

* add migration

* use 'disk-local' for web

* add JSDoc comments

* move AvatarService before SyncService

* create factory

* replace old method with observable in story

* fix tests

* add tests for migration

* receive most recent avatarColor emission

* move logic to component

* fix CLI dependency

* remove BehaviorSubject

* cleanup

* use UserKeyDefinition

* avoid extra write

* convert to observable

* fix tests
This commit is contained in:
rr-bw
2024-03-14 09:56:48 -07:00
committed by GitHub
parent 10d503c15f
commit 65b7ca7177
25 changed files with 403 additions and 165 deletions

View File

@@ -0,0 +1,143 @@
import { MockProxy } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper, runMigrator } from "../migration-helper.spec";
import { AvatarColorMigrator } from "./37-move-avatar-color-to-state-providers";
function rollbackJSON() {
return {
authenticatedAccounts: ["user-1", "user-2"],
"user_user-1_avatar_avatarColor": "#ff0000",
"user_user-2_avatar_avatarColor": "#cccccc",
"user-1": {
settings: {
extra: "data",
},
extra: "data",
},
"user-2": {
settings: {
extra: "data",
},
extra: "data",
},
};
}
describe("AvatarColorMigrator", () => {
const migrator = new AvatarColorMigrator(36, 37);
it("should migrate the avatarColor property from the account settings object to a user StorageKey", async () => {
const output = await runMigrator(migrator, {
authenticatedAccounts: ["user-1", "user-2"] as const,
"user-1": {
settings: {
avatarColor: "#ff0000",
extra: "data",
},
extra: "data",
},
"user-2": {
settings: {
avatarColor: "#cccccc",
extra: "data",
},
extra: "data",
},
});
expect(output).toEqual({
authenticatedAccounts: ["user-1", "user-2"],
"user_user-1_avatar_avatarColor": "#ff0000",
"user_user-2_avatar_avatarColor": "#cccccc",
"user-1": {
settings: {
extra: "data",
},
extra: "data",
},
"user-2": {
settings: {
extra: "data",
},
extra: "data",
},
});
});
it("should handle missing parts", async () => {
const output = await runMigrator(migrator, {
authenticatedAccounts: ["user-1", "user-2"],
global: {
extra: "data",
},
"user-1": {
extra: "data",
settings: {
extra: "data",
},
},
"user-2": null,
});
expect(output).toEqual({
authenticatedAccounts: ["user-1", "user-2"],
global: {
extra: "data",
},
"user-1": {
extra: "data",
settings: {
extra: "data",
},
},
"user-2": null,
});
});
describe("rollback", () => {
let helper: MockProxy<MigrationHelper>;
let sut: AvatarColorMigrator;
const keyDefinitionLike = {
key: "avatarColor",
stateDefinition: {
name: "avatar",
},
};
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 37);
sut = new AvatarColorMigrator(36, 37);
});
it("should null out the avatarColor user StorageKey for each account", async () => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledTimes(2);
expect(helper.setToUser).toHaveBeenCalledWith("user-1", keyDefinitionLike, null);
expect(helper.setToUser).toHaveBeenCalledWith("user-2", keyDefinitionLike, null);
});
it("should add the avatarColor property back to the account settings object", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
avatarColor: "#ff0000",
extra: "data",
},
extra: "data",
});
expect(helper.set).toHaveBeenCalledWith("user-2", {
settings: {
avatarColor: "#cccccc",
extra: "data",
},
extra: "data",
});
});
});
});

View File

@@ -0,0 +1,57 @@
import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedAccountState = {
settings?: { avatarColor?: string };
};
const AVATAR_COLOR_STATE: StateDefinitionLike = { name: "avatar" };
const AVATAR_COLOR_KEY: KeyDefinitionLike = {
key: "avatarColor",
stateDefinition: AVATAR_COLOR_STATE,
};
export class AvatarColorMigrator extends Migrator<36, 37> {
async migrate(helper: MigrationHelper): Promise<void> {
const legacyAccounts = await helper.getAccounts<ExpectedAccountState>();
await Promise.all(
legacyAccounts.map(async ({ userId, account }) => {
// Move account avatarColor
if (account?.settings?.avatarColor != null) {
await helper.setToUser(userId, AVATAR_COLOR_KEY, account.settings.avatarColor);
// Delete old account avatarColor property
delete account?.settings?.avatarColor;
await helper.set(userId, account);
}
}),
);
}
async rollback(helper: MigrationHelper): Promise<void> {
async function rollbackUser(userId: string, account: ExpectedAccountState) {
let updatedAccount = false;
const userAvatarColor = await helper.getFromUser<string>(userId, AVATAR_COLOR_KEY);
if (userAvatarColor) {
if (!account) {
account = {};
}
updatedAccount = true;
account.settings.avatarColor = userAvatarColor;
await helper.setToUser(userId, AVATAR_COLOR_KEY, null);
}
if (updatedAccount) {
await helper.set(userId, account);
}
}
const accounts = await helper.getAccounts<ExpectedAccountState>();
await Promise.all(accounts.map(({ userId, account }) => rollbackUser(userId, account)));
}
}