@remit/drizzle-service 0.0.81 → 0.0.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { after, describe, test } from "node:test";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import Database from "better-sqlite3";
|
|
8
|
+
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
9
|
+
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A release's `schemaVersion` and an instance's `currentSchemaVersion` have to
|
|
13
|
+
* be the same quantity, because the self-update consent screen subtracts one
|
|
14
|
+
* from the other: `schemaVersion > currentSchemaVersion` is the whole of
|
|
15
|
+
* "installing this release runs a migration" (#279).
|
|
16
|
+
*
|
|
17
|
+
* They are computed by two different programs from two different sources. The
|
|
18
|
+
* manifest sums the drizzle journals (npm-scripts/lib/update-manifest.mjs); the
|
|
19
|
+
* wrapper counts rows in the `__drizzle_migrations_*` tables on the live
|
|
20
|
+
* database (deploy/vps/remit). Nothing tied the two together, and a run that
|
|
21
|
+
* reported ten against a manifest's nine inverted the derivation with no test
|
|
22
|
+
* to catch it.
|
|
23
|
+
*
|
|
24
|
+
* This applies the shipped migration sets with the shipped migrator and asserts
|
|
25
|
+
* the two quantities agree: one applied row per journal entry, in exactly the
|
|
26
|
+
* tables the wrapper counts.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const REPO_ROOT = new URL("../../../", import.meta.url);
|
|
30
|
+
|
|
31
|
+
const MIGRATIONS_ROOT = new URL("deploy/vps/migrations-sqlite/", REPO_ROOT);
|
|
32
|
+
|
|
33
|
+
const read = (path: string): string =>
|
|
34
|
+
readFileSync(new URL(path, REPO_ROOT), "utf8");
|
|
35
|
+
|
|
36
|
+
interface MigrationSet {
|
|
37
|
+
set: string;
|
|
38
|
+
table: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The sets the migrate one-shot applies, taken from the entrypoint itself
|
|
43
|
+
* rather than restated here: a set added, renamed or dropped there is one this
|
|
44
|
+
* test then migrates and counts, instead of one it silently stops covering.
|
|
45
|
+
* The folder is the path inside the image, where the migrations are staged at
|
|
46
|
+
* the working directory; in this tree they are under deploy/vps.
|
|
47
|
+
*/
|
|
48
|
+
const migrationSets = (): MigrationSet[] => {
|
|
49
|
+
const source = read("packages/migrate/src/run-migrate.ts");
|
|
50
|
+
const sets = [
|
|
51
|
+
...source.matchAll(
|
|
52
|
+
/migrationsFolder:\s*"migrations-sqlite\/(\w+)",\s*migrationsTable:\s*"(\w+)",/g,
|
|
53
|
+
),
|
|
54
|
+
].map(([, set, table]) => ({ set, table }));
|
|
55
|
+
assert.ok(
|
|
56
|
+
sets.length > 0,
|
|
57
|
+
"no sqlite migration sets found in the migrate entrypoint",
|
|
58
|
+
);
|
|
59
|
+
return sets;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const journalEntries = (set: string): unknown[] => {
|
|
63
|
+
const journal = JSON.parse(
|
|
64
|
+
readFileSync(new URL(`${set}/meta/_journal.json`, MIGRATIONS_ROOT), "utf8"),
|
|
65
|
+
) as { entries: unknown[] };
|
|
66
|
+
return journal.entries;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** The tables `read_schema_version` in the wrapper sums on the live database. */
|
|
70
|
+
const wrapperCountedTables = (): string[] => {
|
|
71
|
+
const match = read("deploy/vps/remit").match(
|
|
72
|
+
/for t in ((?:__drizzle_migrations_\w+ ?)+); do/,
|
|
73
|
+
);
|
|
74
|
+
assert.ok(match, "the wrapper no longer sums any __drizzle_migrations table");
|
|
75
|
+
return match[1].trim().split(/\s+/);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
describe("schema version accounting", () => {
|
|
79
|
+
const dir = mkdtempSync(join(tmpdir(), "remit-schema-version-"));
|
|
80
|
+
const sets = migrationSets();
|
|
81
|
+
const sqlite = new Database(join(dir, "remit.db"));
|
|
82
|
+
sqlite.pragma("journal_mode = WAL");
|
|
83
|
+
sqlite.pragma("foreign_keys = ON");
|
|
84
|
+
const db = drizzle(sqlite);
|
|
85
|
+
for (const { set, table } of sets) {
|
|
86
|
+
migrate(db, {
|
|
87
|
+
migrationsFolder: fileURLToPath(new URL(set, MIGRATIONS_ROOT)),
|
|
88
|
+
migrationsTable: table,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
after(() => {
|
|
93
|
+
sqlite.close();
|
|
94
|
+
rmSync(dir, { recursive: true, force: true });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const rows = (table: string): number =>
|
|
98
|
+
(
|
|
99
|
+
sqlite.prepare(`SELECT count(*) AS n FROM ${table}`).get() as {
|
|
100
|
+
n: number;
|
|
101
|
+
}
|
|
102
|
+
).n;
|
|
103
|
+
|
|
104
|
+
test("a fully migrated database holds one row per journal entry", () => {
|
|
105
|
+
for (const { set, table } of sets) {
|
|
106
|
+
assert.equal(
|
|
107
|
+
rows(table),
|
|
108
|
+
journalEntries(set).length,
|
|
109
|
+
`${table} does not hold one row per entry in the ${set} journal`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// The sum is the quantity both sides publish, and the manifest derives it
|
|
115
|
+
// from these same journals — so this is `deriveSchemaVersion` against the
|
|
116
|
+
// count the wrapper reads back off a migrated instance.
|
|
117
|
+
test("the total equals the schema version the manifest derives", () => {
|
|
118
|
+
const applied = sets.reduce((total, { table }) => total + rows(table), 0);
|
|
119
|
+
const derived = sets.reduce(
|
|
120
|
+
(total, { set }) => total + journalEntries(set).length,
|
|
121
|
+
0,
|
|
122
|
+
);
|
|
123
|
+
assert.equal(applied, derived);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Same number, same tables. A set the migrator writes and the wrapper does
|
|
127
|
+
// not count is a version that reads low forever, which inverts the consent
|
|
128
|
+
// screen's comparison rather than failing it.
|
|
129
|
+
test("the wrapper counts exactly the tables the migrator writes", () => {
|
|
130
|
+
assert.deepEqual(
|
|
131
|
+
wrapperCountedTables().sort(),
|
|
132
|
+
sets.map(({ table }) => table).sort(),
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Forward-only: the migrator is run on every boot, and a second pass that
|
|
137
|
+
// re-recorded an applied migration would lift the count above the journal
|
|
138
|
+
// sum — the shape of the drift #279 reported.
|
|
139
|
+
test("a second migrate run records nothing further", () => {
|
|
140
|
+
const before = sets.reduce((total, { table }) => total + rows(table), 0);
|
|
141
|
+
for (const { set, table } of sets) {
|
|
142
|
+
migrate(db, {
|
|
143
|
+
migrationsFolder: fileURLToPath(new URL(set, MIGRATIONS_ROOT)),
|
|
144
|
+
migrationsTable: table,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
assert.equal(
|
|
148
|
+
sets.reduce((total, { table }) => total + rows(table), 0),
|
|
149
|
+
before,
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
});
|