mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
fix(qa): expose credential fingerprints in admin list
This commit is contained in:
@@ -521,21 +521,26 @@ function printQaCredentialListTable(credentials: QaCredentialRecord[]) {
|
||||
}
|
||||
const rows = credentials.map((credential) => ({
|
||||
credentialId: credential.credentialId,
|
||||
fingerprint: credential.credentialFingerprint ?? "",
|
||||
kind: credential.kind,
|
||||
status: credential.status,
|
||||
leased: formatQaCredentialLeaseState(credential),
|
||||
note: credential.note ?? "",
|
||||
}));
|
||||
const idWidth = Math.max("credentialId".length, ...rows.map((row) => row.credentialId.length));
|
||||
const fingerprintWidth = Math.max(
|
||||
"fingerprint".length,
|
||||
...rows.map((row) => row.fingerprint.length),
|
||||
);
|
||||
const kindWidth = Math.max("kind".length, ...rows.map((row) => row.kind.length));
|
||||
const statusWidth = Math.max("status".length, ...rows.map((row) => row.status.length));
|
||||
const leaseWidth = Math.max("leased".length, ...rows.map((row) => row.leased.length));
|
||||
process.stdout.write(
|
||||
`${"credentialId".padEnd(idWidth)} ${"kind".padEnd(kindWidth)} ${"status".padEnd(statusWidth)} ${"leased".padEnd(leaseWidth)} note\n`,
|
||||
`${"credentialId".padEnd(idWidth)} ${"fingerprint".padEnd(fingerprintWidth)} ${"kind".padEnd(kindWidth)} ${"status".padEnd(statusWidth)} ${"leased".padEnd(leaseWidth)} note\n`,
|
||||
);
|
||||
for (const row of rows) {
|
||||
process.stdout.write(
|
||||
`${row.credentialId.padEnd(idWidth)} ${row.kind.padEnd(kindWidth)} ${row.status.padEnd(statusWidth)} ${row.leased.padEnd(leaseWidth)} ${row.note}\n`,
|
||||
`${row.credentialId.padEnd(idWidth)} ${row.fingerprint.padEnd(fingerprintWidth)} ${row.kind.padEnd(kindWidth)} ${row.status.padEnd(statusWidth)} ${row.leased.padEnd(leaseWidth)} ${row.note}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
@@ -15,6 +15,7 @@ import { normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/strin
|
||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
import { z } from "zod";
|
||||
import { startQaGatewayChild } from "../../gateway-child.js";
|
||||
import { fingerprintQaCredentialId } from "../../qa-credentials-fingerprint.runtime.js";
|
||||
import { DEFAULT_QA_LIVE_PROVIDER_MODE } from "../../providers/index.js";
|
||||
import {
|
||||
defaultQaModelForMode,
|
||||
@@ -736,14 +737,6 @@ function renderWhatsAppQaMarkdown(params: {
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function fingerprintWhatsAppCredentialId(credentialId: string | undefined) {
|
||||
if (!credentialId) {
|
||||
return undefined;
|
||||
}
|
||||
const digest = createHash("sha256").update(credentialId).digest("hex").slice(0, 16);
|
||||
return `sha256:${digest}`;
|
||||
}
|
||||
|
||||
function createMissingGroupJidScenarioResult(params: {
|
||||
explicitScenarioSelection: boolean;
|
||||
scenario: WhatsAppQaScenarioDefinition;
|
||||
@@ -983,7 +976,7 @@ export async function runWhatsAppQaLive(params: {
|
||||
const passed = scenarioResults.filter((entry) => entry.status === "pass").length;
|
||||
const failed = scenarioResults.filter((entry) => entry.status === "fail").length;
|
||||
const skipped = scenarioResults.filter((entry) => entry.status === "skip").length;
|
||||
const credentialFingerprint = fingerprintWhatsAppCredentialId(credentialLease?.credentialId);
|
||||
const credentialFingerprint = fingerprintQaCredentialId(credentialLease?.credentialId);
|
||||
const summary: WhatsAppQaSummary = {
|
||||
credentials: credentialLease
|
||||
? {
|
||||
@@ -1057,7 +1050,7 @@ export const testing = {
|
||||
buildWhatsAppQaConfig,
|
||||
createMissingGroupJidScenarioResult,
|
||||
findScenarios,
|
||||
fingerprintWhatsAppCredentialId,
|
||||
fingerprintWhatsAppCredentialId: fingerprintQaCredentialId,
|
||||
isTransientWhatsAppQaDriverError,
|
||||
parseWhatsAppQaCredentialPayload,
|
||||
renderWhatsAppQaMarkdown,
|
||||
|
||||
@@ -85,6 +85,7 @@ describe("qa credential admin runtime", () => {
|
||||
});
|
||||
|
||||
expect(result.credential.credentialId).toBe("cred-1");
|
||||
expect(result.credential.credentialFingerprint).toMatch(/^sha256:[0-9a-f]{16}$/);
|
||||
expect(requireFirstFetchInput(fetchImpl)).toBe(
|
||||
"https://first-schnauzer-821.convex.site/qa-credentials/v1/admin/add",
|
||||
);
|
||||
@@ -225,6 +226,7 @@ describe("qa credential admin runtime", () => {
|
||||
});
|
||||
|
||||
expect(result.credentials).toHaveLength(1);
|
||||
expect(result.credentials[0]?.credentialFingerprint).toMatch(/^sha256:[0-9a-f]{16}$/);
|
||||
const bodyText = requireFirstFetchInit(fetchImpl).body;
|
||||
expect(typeof bodyText).toBe("string");
|
||||
const body = JSON.parse(bodyText as string) as Record<string, unknown>;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
parseQaCredentialPositiveIntegerEnv,
|
||||
QA_CREDENTIALS_DEFAULT_ENDPOINT_PREFIX,
|
||||
} from "./qa-credentials-common.runtime.js";
|
||||
import { fingerprintQaCredentialId } from "./qa-credentials-fingerprint.runtime.js";
|
||||
|
||||
const DEFAULT_ENDPOINT_PREFIX = QA_CREDENTIALS_DEFAULT_ENDPOINT_PREFIX;
|
||||
const DEFAULT_HTTP_TIMEOUT_MS = 15_000;
|
||||
@@ -32,6 +33,7 @@ const credentialLeaseSchema = z.object({
|
||||
|
||||
const credentialRecordSchema = z.object({
|
||||
credentialId: z.string().min(1),
|
||||
credentialFingerprint: z.string().optional(),
|
||||
kind: z.string().min(1),
|
||||
status: credentialStatusSchema,
|
||||
createdAtMs: z.number().int(),
|
||||
@@ -442,10 +444,17 @@ function normalizeLimit(value: number | undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function withQaCredentialFingerprint(credential: QaCredentialRecord): QaCredentialRecord {
|
||||
return {
|
||||
...credential,
|
||||
credentialFingerprint: fingerprintQaCredentialId(credential.credentialId),
|
||||
};
|
||||
}
|
||||
|
||||
export async function addQaCredentialSet(options: AddQaCredentialSetOptions) {
|
||||
const config = resolveAdminConfig(options);
|
||||
const fetchImpl = options.fetchImpl ?? fetch;
|
||||
return await postJson({
|
||||
const result = await postJson({
|
||||
fetchImpl,
|
||||
authToken: config.authToken,
|
||||
httpTimeoutMs: config.httpTimeoutMs,
|
||||
@@ -459,12 +468,16 @@ export async function addQaCredentialSet(options: AddQaCredentialSetOptions) {
|
||||
actorId: config.actorId,
|
||||
},
|
||||
});
|
||||
return {
|
||||
...result,
|
||||
credential: withQaCredentialFingerprint(result.credential),
|
||||
};
|
||||
}
|
||||
|
||||
export async function removeQaCredentialSet(options: RemoveQaCredentialSetOptions) {
|
||||
const config = resolveAdminConfig(options);
|
||||
const fetchImpl = options.fetchImpl ?? fetch;
|
||||
return await postJson({
|
||||
const result = await postJson({
|
||||
fetchImpl,
|
||||
authToken: config.authToken,
|
||||
httpTimeoutMs: config.httpTimeoutMs,
|
||||
@@ -475,6 +488,10 @@ export async function removeQaCredentialSet(options: RemoveQaCredentialSetOption
|
||||
actorId: config.actorId,
|
||||
},
|
||||
});
|
||||
return {
|
||||
...result,
|
||||
credential: withQaCredentialFingerprint(result.credential),
|
||||
};
|
||||
}
|
||||
|
||||
export async function listQaCredentialSets(options: ListQaCredentialSetsOptions) {
|
||||
@@ -482,7 +499,7 @@ export async function listQaCredentialSets(options: ListQaCredentialSetsOptions)
|
||||
const fetchImpl = options.fetchImpl ?? fetch;
|
||||
const status = normalizeStatus(options.status);
|
||||
const limit = normalizeLimit(options.limit);
|
||||
return await postJson({
|
||||
const result = await postJson({
|
||||
fetchImpl,
|
||||
authToken: config.authToken,
|
||||
httpTimeoutMs: config.httpTimeoutMs,
|
||||
@@ -495,4 +512,8 @@ export async function listQaCredentialSets(options: ListQaCredentialSetsOptions)
|
||||
...(limit !== undefined ? { limit } : {}),
|
||||
},
|
||||
});
|
||||
return {
|
||||
...result,
|
||||
credentials: result.credentials.map((credential) => withQaCredentialFingerprint(credential)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
export function fingerprintQaCredentialId(credentialId: string | undefined) {
|
||||
if (!credentialId) {
|
||||
return undefined;
|
||||
}
|
||||
const digest = createHash("sha256").update(credentialId).digest("hex").slice(0, 16);
|
||||
return `sha256:${digest}`;
|
||||
}
|
||||
Reference in New Issue
Block a user