- check-sf-extension-inventory.mjs: expand parseDirectRegisteredCommands()
scan to include 7 more files (guards/inturn.js, notifications/notify.js,
permissions/index.js, ui/usage-bar.js, commands/legacy/audit.js,
commands/legacy/create-extension.js, commands/legacy/create-slash-command.js)
and filter results by BASE_RUNTIME_COMMAND_NAMES to exclude doc-string false
positives ("name" in create-slash-command.js template text)
- extension-manifest.json: remove 'clear' (subcommand of logs/notifications,
never a top-level pi.registerCommand)
- packages/pi-agent-core/src/db/sf-db.ts: fix 23 noVoidTypeReturn errors
- openDatabase: void → boolean (caller uses return value at line 5625)
- claimEscalationOverride: void → boolean (caller checks at escalation.js:243)
- resolveSelfFeedbackEntry: void → boolean (caller checks at self-feedback.js:387)
- copyWorktreeDb: void → boolean (caller checks at reconcileWorktreeDb)
- compactUokMessages: void → {before,after} (caller returns value at message-bus.js:238)
- insertSessionTurn: void → bigint|null (caller uses id at session-recorder.js:104)
- expireStaleMemories: void → number (caller uses count at auto-start.js:1047)
- deleteMemorySourceRow: void → boolean (caller returns value at memory-source-store.js:107)
- deleteMemoryEmbedding: void → boolean (caller returns value at memory-embeddings.js:328)
- updateBacklogItemStatus: remove dead return expression (callers discard value)
- removeBacklogItem: remove dead return expression (callers discard value)
- updateGateCircuitBreaker: remove dead return {total,avgMs,...} (wrong-type
code accidentally merged from getGateLatencyStats, never reachable)
- markUokMessageRead: remove dead return true/false (callers discard value)
- Auto-fix formatting and organizeImports in ~30 source files (biome --write)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
250 lines
7.6 KiB
TypeScript
250 lines
7.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, test } from "vitest";
|
|
|
|
/**
|
|
* Tests for pure functions in the onboarding layer.
|
|
*
|
|
* These functions are extracted inline (most are module-private in
|
|
* onboarding-service.ts) and tested without DOM, subprocess, or I/O.
|
|
*
|
|
* See: src/web/onboarding-service.ts — resolveOnboardingLockReason,
|
|
* redactSensitiveText, extractErrorDetail, sanitizeMessage.
|
|
* web/app/api/onboarding/route.ts — isActionPayload.
|
|
*/
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Extracted pure functions (mirrors the originals — kept in sync by contract)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type OnboardingBridgeAuthRefreshPhase =
|
|
| "idle"
|
|
| "pending"
|
|
| "succeeded"
|
|
| "failed";
|
|
|
|
interface OnboardingBridgeAuthRefreshState {
|
|
phase: OnboardingBridgeAuthRefreshPhase;
|
|
strategy: "restart" | null;
|
|
startedAt: string | null;
|
|
completedAt: string | null;
|
|
error: string | null;
|
|
}
|
|
|
|
type OnboardingLockReason =
|
|
| "required_setup"
|
|
| "bridge_refresh_pending"
|
|
| "bridge_refresh_failed";
|
|
|
|
/** Mirrors resolveOnboardingLockReason from onboarding-service.ts */
|
|
function resolveOnboardingLockReason(
|
|
requiredSatisfied: boolean,
|
|
bridgeAuthRefresh: OnboardingBridgeAuthRefreshState,
|
|
): OnboardingLockReason | null {
|
|
if (!requiredSatisfied) {
|
|
return "required_setup";
|
|
}
|
|
if (bridgeAuthRefresh.phase === "pending") {
|
|
return "bridge_refresh_pending";
|
|
}
|
|
if (bridgeAuthRefresh.phase === "failed") {
|
|
return "bridge_refresh_failed";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Mirrors redactSensitiveText from onboarding-service.ts */
|
|
function redactSensitiveText(value: string): string {
|
|
return value
|
|
.replace(/sk-[A-Za-z0-9_-]{6,}/g, "[redacted]")
|
|
.replace(/xox[baprs]-[A-Za-z0-9-]+/g, "[redacted]")
|
|
.replace(/Bearer\s+[^\s]+/gi, "Bearer [redacted]")
|
|
.replace(
|
|
/([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET)["'=:\s]+)([^\s,;"']+)/gi,
|
|
"$1[redacted]",
|
|
);
|
|
}
|
|
|
|
/** Mirrors extractErrorDetail from onboarding-service.ts */
|
|
function extractErrorDetail(payload: unknown): string | null {
|
|
if (!payload) return null;
|
|
if (typeof payload === "string") return payload;
|
|
if (typeof payload !== "object") return null;
|
|
|
|
const record = payload as Record<string, unknown>;
|
|
const candidates = [
|
|
record.message,
|
|
record.error,
|
|
record.detail,
|
|
record.error_description,
|
|
];
|
|
for (const candidate of candidates) {
|
|
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
return candidate;
|
|
}
|
|
const nested = extractErrorDetail(candidate);
|
|
if (nested) return nested;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Mirrors isActionPayload from web/app/api/onboarding/route.ts */
|
|
function isActionPayload(value: unknown): boolean {
|
|
return (
|
|
typeof value === "object" &&
|
|
value !== null &&
|
|
typeof (value as { action?: unknown }).action === "string"
|
|
);
|
|
}
|
|
|
|
function idleBridgeRefresh(): OnboardingBridgeAuthRefreshState {
|
|
return {
|
|
phase: "idle",
|
|
strategy: null,
|
|
startedAt: null,
|
|
completedAt: null,
|
|
error: null,
|
|
};
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("resolveOnboardingLockReason", () => {
|
|
test("lockReason_whenRequiredNotSatisfied_returnsRequiredSetup", () => {
|
|
const result = resolveOnboardingLockReason(false, idleBridgeRefresh());
|
|
assert.equal(result, "required_setup");
|
|
});
|
|
|
|
test("lockReason_whenRequiredSatisfiedAndRefreshPending_returnsBridgeRefreshPending", () => {
|
|
const refresh: OnboardingBridgeAuthRefreshState = {
|
|
...idleBridgeRefresh(),
|
|
phase: "pending",
|
|
};
|
|
const result = resolveOnboardingLockReason(true, refresh);
|
|
assert.equal(result, "bridge_refresh_pending");
|
|
});
|
|
|
|
test("lockReason_whenRequiredSatisfiedAndRefreshFailed_returnsBridgeRefreshFailed", () => {
|
|
const refresh: OnboardingBridgeAuthRefreshState = {
|
|
...idleBridgeRefresh(),
|
|
phase: "failed",
|
|
error: "connection refused",
|
|
};
|
|
const result = resolveOnboardingLockReason(true, refresh);
|
|
assert.equal(result, "bridge_refresh_failed");
|
|
});
|
|
|
|
test("lockReason_whenRequiredSatisfiedAndRefreshIdle_returnsNull", () => {
|
|
const result = resolveOnboardingLockReason(true, idleBridgeRefresh());
|
|
assert.equal(result, null);
|
|
});
|
|
|
|
test("lockReason_whenRequiredSatisfiedAndRefreshSucceeded_returnsNull", () => {
|
|
const refresh: OnboardingBridgeAuthRefreshState = {
|
|
...idleBridgeRefresh(),
|
|
phase: "succeeded",
|
|
};
|
|
const result = resolveOnboardingLockReason(true, refresh);
|
|
assert.equal(result, null);
|
|
});
|
|
|
|
test("lockReason_requiredSetupTakesPrecedenceOverPendingRefresh", () => {
|
|
// Even if bridge refresh is pending, missing provider setup wins.
|
|
const refresh: OnboardingBridgeAuthRefreshState = {
|
|
...idleBridgeRefresh(),
|
|
phase: "pending",
|
|
};
|
|
const result = resolveOnboardingLockReason(false, refresh);
|
|
assert.equal(result, "required_setup");
|
|
});
|
|
});
|
|
|
|
describe("redactSensitiveText", () => {
|
|
test("redact_whenApiKeyInText_replacesWithPlaceholder", () => {
|
|
const result = redactSensitiveText("key: sk-abcdef1234567890");
|
|
assert.ok(
|
|
!result.includes("sk-abcdef"),
|
|
`expected redaction, got: ${result}`,
|
|
);
|
|
assert.ok(result.includes("[redacted]"));
|
|
});
|
|
|
|
test("redact_whenBearerToken_replacesWithPlaceholder", () => {
|
|
const result = redactSensitiveText(
|
|
"Authorization: Bearer eyJhbGciOiJSUzI1NiJ9",
|
|
);
|
|
assert.ok(result.includes("Bearer [redacted]"), `got: ${result}`);
|
|
});
|
|
|
|
test("redact_whenSlackToken_replacesWithPlaceholder", () => {
|
|
const result = redactSensitiveText("token=xoxb-abc123-def456");
|
|
assert.ok(!result.includes("xoxb-abc123"), `got: ${result}`);
|
|
assert.ok(result.includes("[redacted]"));
|
|
});
|
|
|
|
test("redact_whenNoSensitiveData_returnsUnchanged", () => {
|
|
const input = "validation failed: network timeout";
|
|
assert.equal(redactSensitiveText(input), input);
|
|
});
|
|
});
|
|
|
|
describe("extractErrorDetail", () => {
|
|
test("extractErrorDetail_whenString_returnsItself", () => {
|
|
assert.equal(
|
|
extractErrorDetail("something went wrong"),
|
|
"something went wrong",
|
|
);
|
|
});
|
|
|
|
test("extractErrorDetail_whenObjectWithMessage_returnsMessage", () => {
|
|
assert.equal(extractErrorDetail({ message: "auth failed" }), "auth failed");
|
|
});
|
|
|
|
test("extractErrorDetail_whenObjectWithError_returnsError", () => {
|
|
assert.equal(
|
|
extractErrorDetail({ error: "invalid token" }),
|
|
"invalid token",
|
|
);
|
|
});
|
|
|
|
test("extractErrorDetail_whenNull_returnsNull", () => {
|
|
assert.equal(extractErrorDetail(null), null);
|
|
});
|
|
|
|
test("extractErrorDetail_whenNestedMessage_returnsNestedValue", () => {
|
|
const payload = { wrapper: { message: "inner error" } };
|
|
// extractErrorDetail only recurses into known keys, not arbitrary keys.
|
|
// The 'wrapper' key is not in the candidate list, so this returns null.
|
|
assert.equal(extractErrorDetail(payload), null);
|
|
});
|
|
|
|
test("extractErrorDetail_whenObjectWithErrorDescription_returnsIt", () => {
|
|
assert.equal(
|
|
extractErrorDetail({ error_description: "token expired" }),
|
|
"token expired",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("isActionPayload", () => {
|
|
test("isActionPayload_whenObjectWithStringAction_returnsTrue", () => {
|
|
assert.equal(isActionPayload({ action: "recheck" }), true);
|
|
});
|
|
|
|
test("isActionPayload_whenNull_returnsFalse", () => {
|
|
assert.equal(isActionPayload(null), false);
|
|
});
|
|
|
|
test("isActionPayload_whenString_returnsFalse", () => {
|
|
assert.equal(isActionPayload("recheck"), false);
|
|
});
|
|
|
|
test("isActionPayload_whenObjectWithNumericAction_returnsFalse", () => {
|
|
assert.equal(isActionPayload({ action: 42 }), false);
|
|
});
|
|
|
|
test("isActionPayload_whenObjectMissingAction_returnsFalse", () => {
|
|
assert.equal(isActionPayload({ provider: "anthropic" }), false);
|
|
});
|
|
});
|