1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 15:23:33 +00:00

PM-3585 Improve state migrations (#5009)

* WIP: safer state migrations

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

* Add min version check and remove old migrations

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* Add rollback and version checking

* Add state version move migration

* Expand tests and improve typing for Migrations

* Remove StateMigration Service

* Rewrite version 5 and 6 migrations

* Add all but initial migration to supported migrations

* Handle stateVersion location in migrator update versions

* Move to unique migrations directory

* Disallow imports outside of state-migrations

* Lint and test fixes

* Do not run migrations if we cannot determine state

* Fix desktop background StateService build

* Document Migration builder class

* Add debug logging to migrations

* Comment on migrator overrides

* Use specific property names

* `npm run prettier` 🤖

* Insert new migration

* Set stateVersion when creating new globals object

* PR comments

* Fix migrate imports

* Move migration building into `migrate` function

* Export current version from migration definitions

* Move file version concerns to migrator

* Update migrate spec to reflect new version requirements

* Fix import paths

* Prefer unique state data

* Remove unnecessary async

* Prefer to not use `any`

---------

Co-authored-by: Justin Baur <justindbaur@users.noreply.github.com>
Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
Matt Gibson
2023-08-30 12:57:20 -05:00
committed by GitHub
parent b444eed0b5
commit 3340af8084
51 changed files with 1538 additions and 980 deletions

View File

@@ -0,0 +1,117 @@
import { mock } from "jest-mock-extended";
import { MigrationBuilder } from "./migration-builder";
import { MigrationHelper } from "./migration-helper";
import { Migrator } from "./migrator";
describe("MigrationBuilder", () => {
class TestMigrator extends Migrator<0, 1> {
async migrate(helper: MigrationHelper): Promise<void> {
await helper.set("test", "test");
return;
}
async rollback(helper: MigrationHelper): Promise<void> {
await helper.set("test", "rollback");
return;
}
}
let sut: MigrationBuilder<number>;
beforeEach(() => {
sut = MigrationBuilder.create();
});
class TestBadMigrator extends Migrator<1, 0> {
async migrate(helper: MigrationHelper): Promise<void> {
await helper.set("test", "test");
}
async rollback(helper: MigrationHelper): Promise<void> {
await helper.set("test", "rollback");
}
}
it("should throw if instantiated incorrectly", () => {
expect(() => MigrationBuilder.create().with(TestMigrator, null, null)).toThrow();
expect(() =>
MigrationBuilder.create().with(TestMigrator, 0, 1).with(TestBadMigrator, 1, 0)
).toThrow();
});
it("should be able to create a new MigrationBuilder", () => {
expect(sut).toBeInstanceOf(MigrationBuilder);
});
it("should be able to add a migrator", () => {
const newBuilder = sut.with(TestMigrator, 0, 1);
const migrations = newBuilder["migrations"];
expect(migrations.length).toBe(1);
expect(migrations[0]).toMatchObject({ migrator: expect.any(TestMigrator), direction: "up" });
});
it("should be able to add a rollback", () => {
const newBuilder = sut.with(TestMigrator, 0, 1).rollback(TestMigrator, 1, 0);
const migrations = newBuilder["migrations"];
expect(migrations.length).toBe(2);
expect(migrations[1]).toMatchObject({ migrator: expect.any(TestMigrator), direction: "down" });
});
describe("migrate", () => {
let migrator: TestMigrator;
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());
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());
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());
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());
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());
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());
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();
});
});
});