Public: anyone can read this; the crew can change it.

Production answers every page again: a database made before the Profile line gets its missing column, and a test fails when a migration is inserted anywhere but the end #148

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/deal-clock:mason/146-production-answers-every-page-again-a-da → main 2 commits fork

  • Merged. mason/146-production-answers-every-page-again-a-da was merged into main as 3c48609.

Review

Sign in to review this change.
3 files changed +91 −1
src/db.ts +8 −1
@@ -14,7 +14,11 @@ import { join } from "node:path";
1414 import { dataDir } from "./env.ts";
1515 import { refresh, seed } from "./sample.ts";
1616
17 /** Append, never edit: a migration that has run is history. */
17 /**
18 * Append-only. A migration that has run is history: never edit, remove,
19 * reorder or insert before the end, because a database records only how many
20 * ran. src/migrations.test.ts pins every entry by position.
21 */
1822 export const migrations: string[] = [
1923 `CREATE TABLE note (
2024 id INTEGER PRIMARY KEY,
@@ -426,6 +430,9 @@ export const migrations: string[] = [
426430 `ALTER TABLE deal ADD COLUMN side TEXT NOT NULL DEFAULT 'buyer' CHECK (side IN ('buyer', 'listing', 'both'))`,
427431 // The agent's commission rate on a deal, in hundredths of a percent (250 is 2.5%), 0 to 10%; null while none is set.
428432 `ALTER TABLE deal ADD COLUMN commission_rate INTEGER CHECK (commission_rate IS NULL OR commission_rate BETWEEN 0 AND 1000)`,
433 // Repair: the Profile-line change inserted the profile line mid-list, so databases already past
434 // that position never got it. Tolerated as a duplicate where it exists.
435 `ALTER TABLE profile ADD COLUMN line TEXT NOT NULL DEFAULT '' CHECK (length(line) <= 200)`,
429436 ];
430437
431438 export function openDb(dir = dataDir): Database {
src/migrations.pin.json added +55
@@ -0,0 +1,55 @@
1 [
2 "18e7c357d1afe0f3ec30db9a0d1113e7f768bd7562f63bc419cdd3a919bad905",
3 "319e8f4c7771ed1d2ddf32bd47eebf082f89eb79c7a7cd0c3e9fc9ccdb85d6f0",
4 "4ad1dcd49a8e082b178cee55072951b2de454236d30f1d43c248a81f94a7e138",
5 "7fade1decc6d9a7a26720ec1d19f32459f21d96cb304bf226767f5d1ef9df55c",
6 "f3ac0eccea193cee78e58abd5ccedcbb243e32f8b08b3194b5c5cefe7ab7338b",
7 "4ca087a9251d9b104dd68d1e269080938c2113e3655dc47800fb7a2a843fda91",
8 "6b957a7fefc2993fbadb1cdb54b88d05e9fc5c957da25e6ab32b8663d8d8b3f4",
9 "ada96ffaf3c06910f0b670f01ab867845cab735985fcd9e23ddc55f964c52add",
10 "eefbc3eb101b88f24e509d9d8f68335581d30b3d05d26bcf513acc84d8595f71",
11 "2b1795ce253701f4dd79f3511911934652999361051360105455731539a84997",
12 "791db72aee2cc8f952300b1f9ba8347d2689763206b0c6c04cdb168894e8197b",
13 "f266f8e883fdf18d6a1027338df2eac08d21f0972baf0685f49aef146816d2e7",
14 "b9200d9459a6e60206a98c6de4618e6ffeea4126b74c8aa5d7ef0561f9f052e4",
15 "23415bdc4bee35d8d64a003f96fb6aefa0980e9e5047b16326adb7fdf2f541f2",
16 "07bf073dc048e9c0ceb787185a777403685899065d7245b3a45c954ae0e5d987",
17 "254722cb91b59deaceae8826d92d5cf240df89310748950372571762d4d6850c",
18 "c94bda45c56d49ccefee26a950eec45cd37b80cd7a28814bc0cb0de337a53491",
19 "650b0c5596ba4ebd1f21f7d8f9888b2172b3139b917ce31ded259354090bd3a7",
20 "1e326b0e83e0a6e61543a06e772d50724bd00695b103b2bf6bf18daddea0a667",
21 "17185fad2336c8251c7adfd3321c1846d78e63e259af234a709964a50af2a3c7",
22 "46dec56ebb90a5f6ceddd538633412a8066ebe77b0ee589c903118cf9a4e7f0c",
23 "b698040ff2fdb3db7d8db3495ef6b7fe9291194fd923c8d6624a386ad67d044a",
24 "2569347b7f614579d909ce927f8b08788f88d65f745b63ddf8de8d11ee63fb02",
25 "3f1fbb3a7550a923ebf504a87ae06231cd62ad0faf858a44e36773cd7c6198bb",
26 "1efe0c811b2f7046249fdba4f1629fb74f6754937917b5c8624cba4c196cdac4",
27 "7d15b45bb6b20cc4b0b8a2f4f5fc06a6d9436017d9b04642ada60dfc4396aa80",
28 "534ce8607106ac086dcfd5413e789066c10f2a6060d14cc7c4bbee805807727e",
29 "72f67a8e8f06f9b9fcac58ff22eb3f4222c77a0968da5981d4e7dce8d4b8ce7d",
30 "3f108f94a689e99698937f8329e65ddd129ca6d7046f97ddd354db39c2635b4b",
31 "b68a6f74ca6c1e9bd3d3c16cce9d8d4985250f0ecb05d34de33c47ecbf4252d7",
32 "7f8986d8e50e0e8366d4f67d58b3121032ec6e32fa594659b1bd42c5fc79d0de",
33 "26d27c436e6fc94690d0cbea29af62adfdf4360eca50940bd58bd63e11bb2a29",
34 "ac0af3e527d87fe393f7dd06607754668e88e655386adbee973a0604cea25b7a",
35 "3175f6484390097d68daac7f987e9461d10a7b4f4b85216c0eebdb9a02d0c714",
36 "fcf79269e83bc0d9c0e6fd9bb4b0e2c29676ca2674e3555914bc76f8b0653d78",
37 "484e2ce356a18a28f5878b33be3f50dcbb0adbc09c35f37cef438bef0aaea9c5",
38 "9900ff28f61d9dd16e3cebc1ca9203d6a212076b6c8aff8f2c7ba5eaffaa38dc",
39 "5f4bf810d1c3d703865b29f0bbab9f16c064fa2054a7ceaa7eb2c5f4041811d7",
40 "8a2d7d68ce199e88441da7d5eb8a4780f4d1cc5caadeea9b2c3bd8dc1886dcfd",
41 "1543897fd9b75e65d7727e3c6f6ddf0990a1e925e08e1e145ecc2d54dba0767a",
42 "b4ae3234ae229a16146d06fc391217a838e862ea25a418a2eb0f63a15a49eb62",
43 "db97a5da0b4aba4612841222fdae50fc27fff51383f74c5d525ab9bf161fccc6",
44 "dec7a2e2f471dd1b426da3cd890af77e2852fb71268bb3c8271c66e3d72971f2",
45 "d08fe630a2ddaacc08d5945dc67b5103052a9096ac5a27aca68bc7faa66ef70d",
46 "7ef5159c0e2d5ae4df22f23263024f8f70298e935112d1042cf1ee1224ca2f23",
47 "9e45e8c8bd82eab1cae6ffdba0e122be3831f8beebc157941490811897505c0c",
48 "83472cd59a1fe7dd540fba000f0a110b52dd6b5949d01202f6d8cd527d8489a4",
49 "59bd37adacf69cbe607acf33e3ac82a386f5acbef328a047fb77ebf9f97162cb",
50 "7255f8baa8700202884be4fbfbd925383182f00a049f8fe49df190cabbf90632",
51 "cf6972801626419644e27b9bc4e6d338367e1c6fe67bb5ca338dc0c9bb408fc1",
52 "fd1b67b66fb8ff34326ca5cb86ab04678619dfea420f21d8f85b2d05c999907f",
53 "f836a1da11c1f51a255301c4ee62c441612baada0345c1310f4149e5d5ccf5b9",
54 "72f67a8e8f06f9b9fcac58ff22eb3f4222c77a0968da5981d4e7dce8d4b8ce7d"
55 ]
src/migrations.test.ts added +28
@@ -0,0 +1,28 @@
1 import { expect, test } from "bun:test";
2 import { Database } from "bun:sqlite";
3 import { migrate, migrations } from "./db.ts";
4 import { agentOf } from "./profile.ts";
5 import pinned from "./migrations.pin.json";
6
7 const hash = (sql: string) => new Bun.CryptoHasher("sha256").update(sql).digest("hex");
8
9 test("every migration is pinned by position; a pull may only append", () => {
10 const now = migrations.map(hash);
11 expect(now.length).toBeGreaterThanOrEqual(pinned.length);
12 pinned.forEach((h, i) => expect(now[i], `migration ${i} was edited, moved or removed`).toBe(h));
13 });
14
15 test("a database made before the Profile line gets the column and reads the agent", () => {
16 const before = migrations.filter((m) => !/profile ADD COLUMN line|commission_rate/.test(m));
17 const db = new Database(":memory:");
18 db.exec("CREATE TABLE schema_version (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')))");
19 before.forEach((sql, i) => {
20 db.exec(sql);
21 db.query("INSERT INTO schema_version (version) VALUES (?1)").run(i + 1);
22 });
23 db.exec("INSERT INTO profile (id, name, brokerage, phone, email) VALUES (1, 'Ana', 'B', '555', '[email protected]')");
24 expect(migrate(db)).toBe(migrations.length);
25 expect(agentOf(db).name).toBe("Ana");
26 expect(db.query("SELECT line FROM profile").get()).toEqual({ line: "" });
27 expect(db.query("SELECT commission_rate FROM deal").all()).toEqual([]);
28 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs