refactor: replace NeDB file access with database queries for improved reliability

This commit is contained in:
Bob Qiu authored and GitHub committed 2026-11-02 16:34:00 +08:00
1 parent 1e8d768e52
commit 819e796bb1
6 files changed
+49 -69

No files matched your search

+8 -21
View File
@@ -1,7 +1,6 @@
import { expect } from "@playwright/test";
import { DEFAULT_TIMEOUT } from "../misc/constants";
import { readNedbLines } from "../misc/nedb";
import type { RequestAuthentication } from "../models/authentication";
import type { AuthTabComponent } from "../pages/auth-tab.page";
import type { PageManager } from "../pages/page-manager";
@@ -62,13 +61,13 @@ export abstract class BaseFlow {
}
/**
* Polls the on-disk `dbFile` NeDB file directly until `id`'s persisted
* Polls the app's `dbFile` model until `id`'s persisted
* document settles on `expected` for `field`, optionally re-running
* `onMismatch` first whenever a read still disagrees. The app can patch
* a just-written field into a document's on-disk state in the
* background rather than synchronously, so a UI read-back right after a
* setter call can observe a value that a later background write then
* reverts — reading the same on-disk store the app itself acts on is
* reverts — reading the same database the app itself acts on is
* the only way to know a value has actually landed.
*
* Most callers only need to wait (`onMismatch` omitted) for that
@@ -106,7 +105,7 @@ export abstract class BaseFlow {
* "URL is required" error or hits the previous address. Passive waiting
* never recovers from that — the value only lands once the write is
* repeated after the initial patch has landed — so this re-sends the
* same write once the on-disk store shows it was lost.
* same write once the database shows it was lost.
*/
protected async waitForUrlPersisted(
dbFile: string,
@@ -124,7 +123,7 @@ export abstract class BaseFlow {
}
/**
* Polls the on-disk `dbFile` NeDB file until a document whose `parentId`
* Polls the app's `dbFile` model until a document whose `parentId`
* is `parentId` exists, and returns its `_id`. For models created by the
* app as a hidden child of a node the UI exposes (e.g. an MCP Client's
* McpRequest under its workspace), so the tree node's id is the parent's
@@ -134,29 +133,17 @@ export abstract class BaseFlow {
dbFile: string,
parentId: string,
): Promise<string> {
const dataPath = await this.context.getDataPath();
let id: string | undefined;
await expect(async () => {
for (const doc of readNedbLines(dataPath, dbFile)) {
if (doc.parentId === parentId) id = doc._id;
}
const docs = await this.pageManager.findDocs(dbFile, { parentId });
id = docs.at(-1)?._id;
expect(id).toBeDefined();
}).toPass({ timeout: DEFAULT_TIMEOUT });
return id!;
}
/**
* Reads the on-disk `dbFile` NeDB file directly for `id`'s
* currently-persisted document, keeping only its latest revision.
*/
/** Reads `id`'s currently-persisted document from the app's database. */
private async readPersistedDoc(dbFile: string, id: string): Promise<any> {
const dataPath = await this.context.getDataPath();
let latest: any;
for (const doc of readNedbLines(dataPath, dbFile)) {
if (doc._id === id) latest = doc;
}
return latest;
return (await this.pageManager.findDocs(dbFile, { _id: id }))[0];
}
}
+6 -9
View File
@@ -1,7 +1,6 @@
import { expect } from "@playwright/test";
import { DEFAULT_TIMEOUT } from "../misc/constants";
import { latestLiveDocs, readNedbLines } from "../misc/nedb";
import type { Collection } from "../models/collection";
import type { Cookie } from "../models/cookie";
import type { EventStreamRequest } from "../models/event-stream-request";
@@ -66,7 +65,7 @@ export class CookieFlow extends BaseFlow {
}
/**
* Polls the on-disk `CookieJar` NeDB file directly until it contains
* Polls the app's `CookieJar` documents until it contains
* every one of `cookies` by key/value, re-running the whole
* open→setCookies→close write once per mismatch. The dialog's unmount
* can cancel the app's debounced save, so a lost cookie write has to be
@@ -76,11 +75,9 @@ export class CookieFlow extends BaseFlow {
item: CookieTarget,
cookies: Cookie[],
): Promise<void> {
const dataPath = await this.context.getDataPath();
const isPersisted = (): boolean => {
const persisted = latestLiveDocs(
readNedbLines(dataPath, "insomnia.CookieJar.db"),
const isPersisted = async (): Promise<boolean> => {
const persisted = (
await this.pageManager.findDocs("insomnia.CookieJar.db")
).flatMap((doc) => doc.cookies ?? []);
return cookies.every((cookie) =>
@@ -91,13 +88,13 @@ export class CookieFlow extends BaseFlow {
};
await expect(async () => {
if (!isPersisted()) {
if (!(await isPersisted())) {
await this.openTarget(item);
await this.pageManager.cookiePage.open();
await this.pageManager.cookiePage.setCookies(cookies);
await this.pageManager.cookiePage.close();
}
expect(isPersisted()).toBe(true);
expect(await isPersisted()).toBe(true);
}).toPass({ timeout: DEFAULT_TIMEOUT });
}
+21
View File
@@ -0,0 +1,21 @@
import type { Page } from "@playwright/test";
/**
* Queries the app's database through the same `window.database` bridge the
* renderer uses, so tests read the documents the app acts on rather than
* parsing its on-disk NeDB files. `dbFile` is the model's file name
* (`insomnia.Request.db`), kept so callers still name the model they mean.
*/
export async function findDocs(
page: Page,
dbFile: string,
query: Record<string, unknown> = {},
): Promise<any[]> {
const type = /^insomnia\.(.+)\.db$/.exec(dbFile)?.[1];
if (!type) throw new Error(`Not a model db file name: ${dbFile}`);
return page.evaluate(
([t, q]) =>
(window as any).database.invoke("find", t, q) as Promise<any[]>,
[type, query] as const,
);
}
-28
View File
@@ -1,28 +0,0 @@
import * as fs from "node:fs";
import path from "node:path";
/**
* Parses the app's on-disk NeDB append-log `dbFile` under `dataPath` into
* its raw lines, oldest first. A document appears once per revision, and a
* deletion is a later line carrying `$$deleted`.
*/
export function readNedbLines(dataPath: string, dbFile: string): any[] {
return fs
.readFileSync(path.join(dataPath, dbFile), "utf8")
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line));
}
/**
* Collapses `lines` to each document's latest revision, dropping the ones
* whose latest revision is a deletion.
*/
export function latestLiveDocs(lines: any[]): any[] {
const latest = new Map<string, any>();
for (const doc of lines) {
if (doc.$$deleted) latest.delete(doc._id);
else latest.set(doc._id, doc);
}
return [...latest.values()];
}
+8 -11
View File
@@ -4,7 +4,7 @@ import type { Page } from "playwright-core";
import { CodeEditorComponent } from "../components/code-editor.component";
import { DEFAULT_TIMEOUT } from "../misc/constants";
import { latestLiveDocs, readNedbLines } from "../misc/nedb";
import { findDocs } from "../misc/db";
import { waitForRendererIdle, waitForStableValue } from "../misc/wait";
export abstract class BasePage {
@@ -74,23 +74,20 @@ export abstract class BasePage {
}
/**
* Reads the app's on-disk NeDB file `dbFile`, keeping only each
* document's latest revision and dropping deleted ones.
* Reads every live document of the model stored in `dbFile` from the
* app's database.
*/
protected async readPersistedDocs(dbFile: string): Promise<any[]> {
const dataPath = await this.insomnia!.evaluate(({ app }) =>
app.getPath("userData"),
);
return latestLiveDocs(readNedbLines(dataPath, dbFile));
return findDocs(this.page, dbFile);
}
/**
* Polls the app's on-disk NeDB files until any live document in one of
* Polls the app's database until any live document in one of
* `dbFiles` satisfies `predicate`. Use in place of a fixed sleep after
* an edit the app saves through a debounce: the UI already shows the
* new value, but a caller that navigates away or unmounts the editor
* before the debounce fires can silently lose the write, and only the
* on-disk store the app itself reads from shows it has landed.
* app's database shows it has landed.
*/
protected async waitForPersisted(
dbFiles: string[],
@@ -107,7 +104,7 @@ export abstract class BasePage {
}
/**
* Polls the on-disk NeDB files until any live document in one of
* Polls the app's database until any live document in one of
* `dbFiles` holds `expected` at the value `read` extracts from it — the
* single-field form of `waitForPersisted()`. Use after an edit the app
* autosaves through a debounce (a script, an auth field, docs, a spec).
@@ -121,7 +118,7 @@ export abstract class BasePage {
}
/**
* Polls the on-disk NeDB files until any live document in one of
* Polls the app's database until any live document in one of
* `dbFiles` lists a row named `name` in one of the arrays `rowsOf`
* returns — confirming a name/value row's debounced save has landed
* (and the row has settled onto its persisted key) before the next edit
@@ -1,6 +1,7 @@
import type { ElectronApplication } from "@playwright/test";
import type { Page } from "playwright-core";
import { findDocs } from "../misc/db";
import type { BasePage } from "./base.page";
import { CertificatesPage } from "./certificates.page";
import { CloudSyncPage } from "./cloud-sync.page";
@@ -138,6 +139,11 @@ export class PageManager {
private insomnia?: ElectronApplication,
) {}
/** Queries the app's database through the current window. See `findDocs`. */
findDocs(dbFile: string, query: Record<string, unknown> = {}): Promise<any[]> {
return findDocs(this.page, dbFile, query);
}
/**
* Rewires every already-created Page instance (and future ones) onto a
* fresh window/ElectronApplication — called by `AppFlow.restart()`