155 lines
3.8 KiB
JavaScript
155 lines
3.8 KiB
JavaScript
import { strict as assert } from "node:assert";
|
|
import { readFileSync } from "node:fs";
|
|
import { globSync } from "node:fs";
|
|
import { Database } from "bun:sqlite";
|
|
import worker from "../apps/api/src/index.ts";
|
|
|
|
class SqliteD1PreparedStatement {
|
|
constructor(db, sql, bindings = []) {
|
|
this.db = db;
|
|
this.sql = sql;
|
|
this.bindings = bindings;
|
|
}
|
|
|
|
bind(...bindings) {
|
|
return new SqliteD1PreparedStatement(this.db, this.sql, bindings);
|
|
}
|
|
|
|
async all() {
|
|
return {
|
|
results: this.db.query(this.sql).all(...this.bindings),
|
|
success: true,
|
|
meta: {},
|
|
};
|
|
}
|
|
|
|
async first() {
|
|
return this.db.query(this.sql).get(...this.bindings) ?? null;
|
|
}
|
|
|
|
async run() {
|
|
this.db.query(this.sql).run(...this.bindings);
|
|
return {
|
|
success: true,
|
|
meta: {},
|
|
};
|
|
}
|
|
}
|
|
|
|
class SqliteD1Database {
|
|
constructor(db) {
|
|
this.db = db;
|
|
}
|
|
|
|
prepare(sql) {
|
|
return new SqliteD1PreparedStatement(this.db, sql);
|
|
}
|
|
|
|
async batch(statements) {
|
|
const results = [];
|
|
this.db.transaction(() => {
|
|
for (const statement of statements) {
|
|
results.push(this.db.query(statement.sql).run(...statement.bindings));
|
|
}
|
|
})();
|
|
return results;
|
|
}
|
|
}
|
|
|
|
const applyMigrations = (db) => {
|
|
for (const file of globSync("migrations/*.sql").sort()) {
|
|
db.exec(readFileSync(file, "utf8"));
|
|
}
|
|
};
|
|
|
|
const createEnv = () => {
|
|
const sqlite = new Database(":memory:");
|
|
sqlite.exec("PRAGMA foreign_keys = ON");
|
|
applyMigrations(sqlite);
|
|
|
|
return {
|
|
sqlite,
|
|
env: {
|
|
DB: new SqliteD1Database(sqlite),
|
|
RESOURCES: {
|
|
delete: async () => undefined,
|
|
get: async () => null,
|
|
put: async () => undefined,
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
const fetchWorker = async (env, path, init = {}) =>
|
|
worker.fetch(
|
|
new Request(`https://edgeever.test${path}`, init),
|
|
env,
|
|
{
|
|
waitUntil: () => undefined,
|
|
passThroughOnException: () => undefined,
|
|
}
|
|
);
|
|
|
|
const assertStatus = async (response, status) => {
|
|
if (response.status !== status) {
|
|
assert.fail(`Expected status ${status}, got ${response.status}: ${await response.text()}`);
|
|
}
|
|
};
|
|
|
|
const { sqlite, env } = createEnv();
|
|
|
|
sqlite.run(
|
|
`INSERT INTO notebooks (id, parent_id, name, slug, icon, color, sort_order)
|
|
VALUES ('nb_bug', NULL, 'Bug Notebook', 'bug-notebook', 'notebook', '#000000', 99)`
|
|
);
|
|
sqlite.run(
|
|
`INSERT INTO memos (id, notebook_id, title, excerpt, tags_json, created_by, updated_by)
|
|
VALUES ('memo_bug', 'nb_bug', 'Bug Memo', 'Bug', '[]', 'test', 'test')`
|
|
);
|
|
sqlite.run(
|
|
`INSERT INTO memo_contents (memo_id, content_json, content_markdown, content_text, content_hash, revision)
|
|
VALUES (
|
|
'memo_bug',
|
|
'{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Bug"}]}]}',
|
|
'Bug',
|
|
'Bug',
|
|
'hash',
|
|
0
|
|
)`
|
|
);
|
|
sqlite.run(
|
|
`INSERT INTO memos_fts (memo_id, title, content_text, tags)
|
|
VALUES ('memo_bug', 'Bug Memo', 'Bug', '')`
|
|
);
|
|
|
|
let response = await fetchWorker(env, "/api/v1/memos/memo_bug", { method: "DELETE" });
|
|
await assertStatus(response, 200);
|
|
|
|
response = await fetchWorker(env, "/api/v1/notebooks/nb_bug", { method: "DELETE" });
|
|
await assertStatus(response, 200);
|
|
|
|
response = await fetchWorker(env, "/api/v1/memos/memo_bug/restore", { method: "POST" });
|
|
await assertStatus(response, 200);
|
|
|
|
const body = await response.json();
|
|
assert.equal(body.memo.id, "memo_bug");
|
|
assert.equal(body.memo.isDeleted, false);
|
|
assert.equal(body.memo.notebookId, "nb_inbox");
|
|
|
|
const restored = sqlite
|
|
.query(
|
|
`SELECT m.notebook_id, m.is_deleted, deleted_notebook.is_deleted AS old_notebook_deleted
|
|
FROM memos m
|
|
INNER JOIN notebooks deleted_notebook ON deleted_notebook.id = 'nb_bug'
|
|
WHERE m.id = 'memo_bug'`
|
|
)
|
|
.get();
|
|
|
|
assert.deepEqual(restored, {
|
|
notebook_id: "nb_inbox",
|
|
is_deleted: 0,
|
|
old_notebook_deleted: 1,
|
|
});
|
|
|
|
console.log("restore-deleted-notebook regression passed");
|