Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ out-test/
/bin
/lib
/venv

scripts/mock-insights-server/certs/
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1229,7 +1229,8 @@
"ui-test-cmd": "extest setup-and-run --code_version 1.103.0 --code_settings ./test/ui/fixtures/settings.json --extensions_dir ./.test-extensions --storage ./.test-folder -m ./test/ui/fixtures/mocha.json",
"preui-test": "rimraf out-test .test-extensions",
"ui-test": "npm run ui-test-cmd -- ./out-test/test/ui/**/*.test.js",
"q-test": "q qcumber.q -color -src ./test/q/main.q -test ./test/q/tests"
"q-test": "q qcumber.q -color -src ./test/q/main.q -test ./test/q/tests",
"mock-insights-server": "node --experimental-strip-types scripts/mock-insights-server/server.ts"
},
"devDependencies": {
"@eslint/js": "9.39.4",
Expand Down
71 changes: 71 additions & 0 deletions scripts/mock-insights-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## Test instructions: self-signed mock Insights connection

Verifies the fix in `src/services/kdbInsights/codeFlowLogin.ts` (`getAuthPrefix`
now honors the connection's "insecure" flag) against
`scripts/mock-insights-server/`.

### 1. Start the mock server

```
npm run mock-insights-server
```

Leave it running in its own terminal. First run generates a self-signed cert
into `scripts/mock-insights-server/certs/` (gitignored). Confirm the log line:

```
Mock Insights API listening on https://localhost:8443
```

### 2. Launch the extension

- In VS Code: Run and Debug → **Run Extension** (F5).
- No `NODE_EXTRA_CA_CERTS` env var needed anymore — that was the workaround
for the bug this fix removes.

### 3. Add the mock connection

In the Extension Development Host:

- Open the **KX** view → click **New Connection** (`kdb.connections.add`), or
run it from the Command Palette.
- Choose "KDB Insights connection".
- Fill in:
- **Server**: `https://localhost:8443/` (trailing slash matters — it's
resolved as a relative base URL)
- **Alias**: `MOCK`
- Check **"Accept insecure SSL certifcates"**
- Save.

### 4. Connect and verify

- Click the `MOCK` connection to connect.
- Expected: connects successfully, no `self signed certificate` error in the
**Output → KX** channel.
- Confirm the full handshake happened: `Output → KX` should show REST debug
lines for `/kxicontroller/config`, `/api/config`, and
`/servicegateway/api/v3/meta`, then `Connection established successfully to: MOCK`.

### 5. Regression check (pre-fix behavior)

To confirm the failure this fixes, temporarily revert
`src/services/kdbInsights/codeFlowLogin.ts` (or `git stash` the fix), repeat
steps 2-4 with "insecure" checked, and confirm the original error reproduces:

```
self signed certificate; if the root CA is installed locally, try running Node.js with --use-system-ca
```

Then restore the fix and re-run step 4 to confirm it's resolved.

### 6. Optional: exercise a query

Once connected, open a `.q` or scratchpad file, run a trivial query (e.g.
`1+1`). The mock server's `/scratchpadmanager/scratchpad/display` route
returns `mock result for: <expression>`, so the results panel should show
that string rather than an error.

### Cleanup

Stop the mock server (Ctrl+C). `scripts/mock-insights-server/certs/` can be
deleted to force cert regeneration on the next run.
50 changes: 50 additions & 0 deletions scripts/mock-insights-server/cert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 1998-2026 KX Systems Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

import { execFileSync } from "child_process";
import { existsSync, mkdirSync, readFileSync } from "fs";
import { dirname, join } from "path";
import { fileURLToPath } from "url";

const here = dirname(fileURLToPath(import.meta.url));
const certDir = join(here, "certs");
const keyPath = join(certDir, "key.pem");
const certPath = join(certDir, "cert.pem");

export function loadOrCreateSelfSignedCert(): { key: string; cert: string } {
if (!existsSync(keyPath) || !existsSync(certPath)) {
mkdirSync(certDir, { recursive: true });
execFileSync("openssl", [
"req",
"-x509",
"-newkey",
"rsa:2048",
"-nodes",
"-keyout",
keyPath,
"-out",
certPath,
"-days",
"365",
"-subj",
"/CN=localhost",
"-addext",
"subjectAltName=DNS:localhost,IP:127.0.0.1",
]);
}

return {
key: readFileSync(keyPath, "utf8"),
cert: readFileSync(certPath, "utf8"),
};
}
181 changes: 181 additions & 0 deletions scripts/mock-insights-server/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* Copyright (c) 1998-2026 KX Systems Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

// Static fixture data returned by the mock Insights server. Kept separate
// from server.ts so the wire/plumbing code and the canned data are easy to
// tell apart and edit independently.

export const mockUsername = "mock.user";

// Matched by InsightsConnection.getInsightsVersion()'s /-\d+(\.\d+){2}(-|$)/
// regex to resolve to insightsVersion = 1.14.
export const configResponse = {
description: "Mock KDB Insights instance",
encryptionInFlight: false,
restricted: false,
storage: {},
version: "insights-1.14.0-mock",
};

export const apiConfigResponse = {
encryptionDatabase: false,
encryptionInTransit: false,
queryEnvironmentsEnabled: false,
version: "1.14.0",
};

export const metaResponse = {
header: {
ac: "0",
agg: ":127.0.0.1:5070",
ai: "",
api: ".kxi.getMeta",
client: ":127.0.0.1:5050",
corr: "CorrHash",
http: "json",
logCorr: "logCorrHash",
protocol: "gw",
rc: "0",
rcvTS: new Date().toISOString(),
retryCount: "0",
to: new Date().toISOString(),
userID: "mockID",
userName: mockUsername,
},
payload: {
rc: [
{
api: 3,
agg: 1,
assembly: 1,
schema: 1,
rc: "mock-rc",
labels: [{ kxname: "mock-assembly" }],
started: new Date().toISOString(),
},
],
dap: [
{
dap: "mock-assembly-tp",
assembly: "mock-assembly",
startTS: "2000-01-01T00:00:00.000000000",
endTS: "2099-01-01T00:00:00.000000000",
labels: ["mock-assembly"],
instance: "tp",
},
],
api: [
{
api: ".kxi.getData",
kxname: ["mock-assembly"],
aggFn: ".sgagg.aggFnDflt",
custom: false,
uda: false,
full: true,
procs: [],
},
],
agg: [
{
aggFn: ".sgagg.aggFnDflt",
custom: false,
full: true,
metadata: {
description: "mock aggregator",
params: [{ description: "mock param" }],
return: { description: "mock return" },
misc: {},
},
procs: [],
},
],
assembly: [
{
assembly: "mock-assembly",
kxname: "mock-assembly",
tbls: ["trade"],
},
],
schema: [
{
table: "trade",
assembly: "mock-assembly",
type: "partitioned",
columns: [
{
column: "time",
typ: 19,
anymap: false,
attrDisk: "",
attrIDisk: "",
attrMem: "",
foreign: "",
isSerialized: false,
},
{
column: "sym",
typ: 11,
anymap: false,
attrDisk: "",
attrIDisk: "",
attrMem: "",
foreign: "",
isSerialized: false,
},
{
column: "price",
typ: 9,
anymap: false,
attrDisk: "",
attrIDisk: "",
attrMem: "",
foreign: "",
isSerialized: false,
},
{
column: "size",
typ: 7,
anymap: false,
attrDisk: "",
attrIDisk: "",
attrMem: "",
foreign: "",
isSerialized: false,
},
],
},
],
},
};

// A few sample rows shared by every data/sql/qsql/uda endpoint so all query
// paths in the extension have something plausible to render.
export const sampleRows = [
{ time: "2026-07-03T09:30:00.000000000", sym: "AAPL", price: 213.5, size: 100 },
{ time: "2026-07-03T09:30:01.000000000", sym: "MSFT", price: 452.1, size: 250 },
{ time: "2026-07-03T09:30:02.000000000", sym: "GOOG", price: 178.25, size: 75 },
];

export function structuredTextFromRows(rows: Record<string, unknown>[]) {
const columns =
rows.length === 0
? []
: Object.keys(rows[0]).map((name) => ({
name,
type: typeof rows[0][name] === "number" ? "f" : "s",
values: rows.map((row) => String(row[name])),
order: rows.map((_row, i) => i),
}));

return { columns, count: rows.length };
}
Loading
Loading