diff --git a/packages/graph-explorer-proxy-server/src/app.test.ts b/packages/graph-explorer-proxy-server/src/app.test.ts index 411f25f27..9d44cdd95 100644 --- a/packages/graph-explorer-proxy-server/src/app.test.ts +++ b/packages/graph-explorer-proxy-server/src/app.test.ts @@ -498,6 +498,13 @@ describe("createApp", () => { .send({ gremlin: "test" }); expect(response.status).toBe(400); + expect(response.body.error.message).toContain( + "Must not include a username or password", + ); + expect(mockFetch).not.toHaveBeenCalledWith( + expect.stringContaining("my-graph-db.example.com"), + expect.anything(), + ); }); it("does not echo the rejected value back to the client", async () => { diff --git a/packages/graph-explorer/src/connections/defaultConnection.test.ts b/packages/graph-explorer/src/connections/defaultConnection.test.ts index 0908d6a34..411cc7450 100644 --- a/packages/graph-explorer/src/connections/defaultConnection.test.ts +++ b/packages/graph-explorer/src/connections/defaultConnection.test.ts @@ -257,6 +257,20 @@ describe("backward compatibility: main-era defaultConnection.json", () => { }); }); + test("keeps the full path of a direct public endpoint", () => { + const connection = readFile({ + GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT: + "http://blazegraph:9999/blazegraph/namespace/kb", + GRAPH_EXP_USING_PROXY_SERVER: false, + GRAPH_EXP_GRAPH_TYPE: "sparql", + }); + + expect(connection?.graphDbUrl).toBe( + "http://blazegraph:9999/blazegraph/namespace/kb", + ); + expect(connection?.proxyConnection).toBe(false); + }); + // Earlier versions defaulted a missing flag to false for this file test("connects directly to the public endpoint when the flag is absent and both URLs are set", () => { const connection = readFile(createMainEraFile()); diff --git a/packages/graph-explorer/src/core/StateProvider/activeConnectionStorage.test.ts b/packages/graph-explorer/src/core/StateProvider/activeConnectionStorage.test.ts index e26689ebc..e696e2eda 100644 --- a/packages/graph-explorer/src/core/StateProvider/activeConnectionStorage.test.ts +++ b/packages/graph-explorer/src/core/StateProvider/activeConnectionStorage.test.ts @@ -26,6 +26,11 @@ async function openTab() { let atom = await createActiveConnectionIdAtom({ sessionStorage }); return { read: () => store.get(atom), + /** + * Mounts the atom the way a rendered component does, so a cross-tab sync + * wired through `onMount` would reach this tab. + */ + subscribe: () => store.sub(atom, () => {}), /** Activates a connection; resolves once the breadcrumb has landed. */ activate: (id: ReturnType | null) => { store.set(atom, id); @@ -144,6 +149,7 @@ describe("activeConnectionStorage across tabs", () => { test("activating a connection in one tab does not change an already-open tab", async () => { const tabB = await openTab(); + tabB.subscribe(); const tabBConnection = createConnectionId(); await tabB.activate(tabBConnection); diff --git a/packages/graph-explorer/src/core/StateProvider/sessionScopedStorage.test.ts b/packages/graph-explorer/src/core/StateProvider/sessionScopedStorage.test.ts index 36ae0bf31..96eae2ce8 100644 --- a/packages/graph-explorer/src/core/StateProvider/sessionScopedStorage.test.ts +++ b/packages/graph-explorer/src/core/StateProvider/sessionScopedStorage.test.ts @@ -58,6 +58,11 @@ function tabOpener( let atom = await createSessionScopedAtom({ ...options, sessionStorage }); return { read: () => store.get(atom), + /** + * Mounts the atom the way a rendered component does, so a cross-tab sync + * wired through `onMount` would reach this tab. + */ + subscribe: () => store.sub(atom, () => {}), write: (value: T) => { store.set(atom, value); return persistenceStatusStore.waitForIdle(); @@ -319,6 +324,7 @@ describe("createSessionScopedAtom", () => { describe("createSessionScopedAtom across tabs", () => { test("writing in one tab does not change an already-open tab", async () => { const tabB = await openTab(); + tabB.subscribe(); await tabB.write({ count: 2 }); const tabA = await openTab(); @@ -471,6 +477,7 @@ describe("backward compatibility: graph view layout breadcrumb", () => { describe("graph view layout across tabs", () => { test("changing the view layout in one tab does not change an already-open tab", async () => { const tabB = await openGraphViewTab(); + tabB.subscribe(); const tabBLayout: GraphViewLayout = { ...defaultGraphViewLayout, activeSidebarItem: "filters", @@ -517,6 +524,7 @@ describe("schema view layout across tabs", () => { test("changing the view layout in one tab does not change an already-open tab", async () => { const tabB = await openSchemaViewTab(); + tabB.subscribe(); const tabBLayout: SchemaViewLayout = { ...defaultSchemaViewLayout, activeSidebarItem: "styles", diff --git a/packages/graph-explorer/src/core/connectionLink/validation.test.ts b/packages/graph-explorer/src/core/connectionLink/validation.test.ts index 5a230f09c..b408577bd 100644 --- a/packages/graph-explorer/src/core/connectionLink/validation.test.ts +++ b/packages/graph-explorer/src/core/connectionLink/validation.test.ts @@ -294,6 +294,10 @@ describe("reading a connection link", () => { test("reports every offending parameter, not just the first", () => { expect( problemsOf("?graphDbUrl=not-a-url&queryEngine=sql&serviceType=bogus"), - ).toHaveLength(3); + ).toStrictEqual([ + "graphDbUrl must be a valid http or https URL", + 'queryEngine must be one of "gremlin", "openCypher", "sparql"', + 'serviceType must be one of "neptune-db", "neptune-graph"', + ]); }); }); diff --git a/packages/graph-explorer/src/utils/mapWithConcurrency.ts b/packages/graph-explorer/src/utils/mapWithConcurrency.ts index 288743926..3176c84ef 100644 --- a/packages/graph-explorer/src/utils/mapWithConcurrency.ts +++ b/packages/graph-explorer/src/utils/mapWithConcurrency.ts @@ -10,10 +10,6 @@ * leave the remaining lanes issuing requests nobody is waiting for. Callbacks * already running are not cancelled. * - * That stop applies to every caller, including the openCypher and SPARQL schema - * and edge connection fetches. They previously drained the whole queue after a - * failure their caller had already given up on, so the change is theirs too. - * * @param items The items to process * @param concurrency The maximum number of callbacks running at once * @param callback The async operation to run for each item