mirror of
https://github.com/Kong/insomnia.git
synced 2026-11-05 20:43:52 -05:00
refactor: replace NeDB file access with database queries for improved reliability
This commit is contained in:
1 parent
1e8d768e52
commit
819e796bb1
6 files changed
+49
-69
No files matched your search
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -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()];
|
||||
}
|
||||
@@ -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()`
|
||||
|
||||
Reference in new issue
Block a user