Files
edgeever/tests/notebook-appearance-upgrade.integration.mjs
T
edgeever-updater[bot] 05e53dcba3 chore: update EdgeEver from v2.7.0
EdgeEver-Upstream-Commit: 30fcb3fc65c419000a3e6f8cfb70eca47ae53914
2026-10-26 07:48:43 +00:00

90 lines
4.4 KiB
JavaScript

import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { createInterface } from "node:readline";
// Supply the binary and migrations from the PR's base revision, never a real profile.
const oldBinary = process.env.EDGE_EVER_LEGACY_SIDECAR_PATH;
const oldMigrations = process.env.EDGE_EVER_LEGACY_MIGRATIONS_PATH;
assert.ok(oldBinary && oldMigrations, "Set EDGE_EVER_LEGACY_SIDECAR_PATH and EDGE_EVER_LEGACY_MIGRATIONS_PATH");
const directory = await mkdtemp(join(tmpdir(), "edgeever-appearance-upgrade-"));
let active;
const bounded = async (promise, label) => {
let timer;
try {
return await Promise.race([promise, new Promise((_, reject) => {
timer = setTimeout(() => reject(new Error(`Timed out: ${label}`)), 10000);
})]);
} finally { clearTimeout(timer); }
};
const start = async (binary, migrations) => {
const child = spawn(binary, ["--data-dir", directory, "--migrations-dir", migrations], { stdio: ["pipe", "pipe", "inherit"] });
const pending = new Map();
let nextId = 0;
const lines = createInterface({ input: child.stdout });
let ready;
const booted = new Promise((resolveReady, reject) => {
ready = resolveReady;
child.once("error", reject);
});
lines.on("line", line => {
const message = JSON.parse(line);
if (message.event === "ready") ready();
const resolveResponse = pending.get(message.id);
if (resolveResponse) { pending.delete(message.id); resolveResponse(message); }
});
const close = async () => {
if (child.exitCode !== null) return;
const closed = new Promise(resolveClosed => child.once("close", resolveClosed));
child.stdin.end();
await bounded(closed, "sidecar shutdown").catch(() => { child.kill(); return closed; });
};
active = { close };
await bounded(booted, "sidecar startup");
return {
close,
request: async (method, params = {}) => {
const id = ++nextId;
const response = new Promise(resolveResponse => pending.set(id, resolveResponse));
child.stdin.write(`${JSON.stringify({ id, method, params })}\n`);
const message = await bounded(response, method);
if (message.error) throw new Error(message.error.message);
return message.result;
},
};
};
try {
let host = await start(oldBinary, oldMigrations);
const book = (await host.request("notebook.create", { name: "Keep notebook" })).notebook;
const memo = (await host.request("memo.create", { notebookId: book.id, title: "Keep note", contentMarkdown: "Keep **content**", tags: ["keep"] })).memo;
const oldQueue = (await host.request("sync.outbox.list", { limit: 1000 })).items;
await host.close();
const currentBinary = resolve("crates/desktop-sidecar/target/debug/edgeever-sidecar");
host = await start(currentBinary, resolve("migrations"));
const readBook = async () => (await host.request("notebook.list")).notebooks.find(item => item.id === book.id);
const upgraded = await readBook();
assert.equal(upgraded.name, book.name);
for (const key of ["isBold", "iconInverted", "rowInverted"]) assert.equal(upgraded[key], false);
assert.equal(upgraded.textColor, null);
assert.deepEqual((await host.request("memo.get", { memoId: memo.id })).memo, memo);
assert.deepEqual((await host.request("sync.outbox.list", { limit: 1000 })).items, oldQueue);
const appearance = { icon: "alarm-clock", color: "#2563eb", textColor: "#dc2626", isBold: true, iconInverted: true, rowInverted: true };
await host.request("notebook.update", { notebookId: book.id, ...appearance });
await host.close();
// Additive migrations also allow reopening the database with the old client.
host = await start(oldBinary, oldMigrations);
assert.equal((await host.request("memo.get", { memoId: memo.id })).memo.contentMarkdown, memo.contentMarkdown);
await host.request("notebook.update", { notebookId: book.id, name: "Renamed with old client" });
await host.close();
host = await start(currentBinary, resolve("migrations"));
const reopened = await readBook();
assert.equal(reopened.name, "Renamed with old client");
for (const [key, value] of Object.entries(appearance)) assert.equal(reopened[key], value);
console.log("Notebook appearance upgrade, restart and rollback: passed; notes and pending sync preserved.");
} finally {
await active?.close();
await rm(directory, { recursive: true, force: true });
}