diff --git a/sdk/typescript/_bundled_plugin/scripts/workbench_native_indexes.py b/sdk/typescript/_bundled_plugin/scripts/workbench_native_indexes.py index 8ce2492bc..39f844383 100644 --- a/sdk/typescript/_bundled_plugin/scripts/workbench_native_indexes.py +++ b/sdk/typescript/_bundled_plugin/scripts/workbench_native_indexes.py @@ -202,23 +202,29 @@ def list_repositories( for row in connection.execute( "SELECT id, target_id FROM scans ORDER BY started_at DESC, id DESC" ): - latest_scan_by_target.setdefault(row["target_id"], scans_by_id[row["id"]]) + scan = scans_by_id.get(row["id"]) + if scan is not None: + latest_scan_by_target.setdefault(row["target_id"], scan) open_findings_by_target = Counter( row["target_id"] for row in _indexed_findings(connection) if row["status"] == "open" ) targets = {row["id"]: row for row in connection.execute("SELECT * FROM security_targets")} + target_ids = [ + *latest_scan_by_target, + *(target_id for target_id in targets if target_id not in latest_scan_by_target), + ] repositories = [ { "checkoutAvailable": Path(target["current_path"]).is_dir(), "displayName": target["display_name"], - "latestScan": latest_scan, + "latestScan": latest_scan_by_target.get(target_id), "openFindingsCount": open_findings_by_target.get(target_id, 0), - "scanCount": scan_count_by_target[target_id], + "scanCount": scan_count_by_target.get(target_id, 0), "targetId": target_id, "targetPath": target["current_path"], } - for target_id, latest_scan in latest_scan_by_target.items() + for target_id in target_ids if (target := targets.get(target_id)) is not None ] if args is None: @@ -229,7 +235,8 @@ def list_repositories( repository for repository in repositories if (args.target_id is None or repository["targetId"] == args.target_id) - and args.status != "not_scanned" + and (args.status != "scanned" or repository["scanCount"] > 0) + and (args.status != "not_scanned" or repository["scanCount"] == 0) and (args.status != "open_findings" or repository["openFindingsCount"] > 0) and ( not query diff --git a/sdk/typescript/tests-ts/native-repository-index.test.ts b/sdk/typescript/tests-ts/native-repository-index.test.ts new file mode 100644 index 000000000..05cb7f31a --- /dev/null +++ b/sdk/typescript/tests-ts/native-repository-index.test.ts @@ -0,0 +1,108 @@ +import { spawnSync } from "node:child_process"; +import { join } from "node:path"; +import { expect, test } from "bun:test"; +import { PLUGIN_ROOT } from "./plugin-root.js"; + +test("repository index includes and filters persisted unscanned targets", () => { + const python = Bun.which("python3") ?? Bun.which("python") ?? Bun.which("py"); + expect(python).not.toBeNull(); + const scripts = join(PLUGIN_ROOT, "scripts"); + const probe = ` +import argparse +import json +import sqlite3 +import sys +sys.path.insert(0, sys.argv[1]) +import workbench_native_indexes as indexes + +connection = sqlite3.connect(":memory:") +connection.row_factory = sqlite3.Row +connection.executescript(""" +CREATE TABLE security_targets ( + id TEXT PRIMARY KEY, + current_path TEXT NOT NULL, + display_name TEXT NOT NULL +); +CREATE TABLE scans ( + id TEXT PRIMARY KEY, + target_id TEXT NOT NULL, + started_at TEXT NOT NULL +); +""") +connection.executemany( + "INSERT INTO security_targets VALUES (?, ?, ?)", + [ + ("target-scanned", "/repo/scanned", "Scanned"), + ("target-unscanned", "/repo/unscanned", "Unscanned"), + ], +) +connection.execute( + "INSERT INTO scans VALUES (?, ?, ?)", + ("scan-1", "target-scanned", "2026-08-18T00:00:00Z"), +) +connection.commit() + +indexes.scan_history.list_scans = lambda _connection: { + "scans": [{"scanId": "scan-1", "targetId": "target-scanned"}] +} +indexes._indexed_findings = lambda _connection: iter(()) + +def arguments(status): + return argparse.Namespace( + query=None, + target_id=None, + status=status, + offset=0, + limit=None, + ) + +def compact(result): + return [ + { + "targetId": row["targetId"], + "scanCount": row["scanCount"], + "latestScan": row["latestScan"], + } + for row in result["repositories"] + ] + +print(json.dumps({ + "all": compact(indexes.list_repositories(connection)), + "scanned": compact(indexes.list_repositories(connection, arguments("scanned"))), + "notScanned": compact(indexes.list_repositories(connection, arguments("not_scanned"))), +})) +`; + + const result = spawnSync(python!, ["-I", "-B", "-c", probe, scripts], { + encoding: "utf8", + }); + expect(result.status, result.stderr).toBe(0); + expect(JSON.parse(result.stdout)).toEqual({ + all: [ + { + targetId: "target-scanned", + scanCount: 1, + latestScan: { scanId: "scan-1", targetId: "target-scanned" }, + }, + { + targetId: "target-unscanned", + scanCount: 0, + latestScan: null, + }, + ], + scanned: [ + { + targetId: "target-scanned", + scanCount: 1, + latestScan: { scanId: "scan-1", targetId: "target-scanned" }, + }, + ], + notScanned: [ + { + targetId: "target-unscanned", + scanCount: 0, + latestScan: null, + }, + ], + }); +});