mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 11:25:50 -06:00
test(doctor): use fixed OAuth sidecar vectors (#121986)
* test(doctor): use fixed OAuth sidecar vectors * test(doctor): preserve Linux warning assertion
This commit is contained in:
committed by
GitHub
parent
26d0158e50
commit
63bba283a6
@@ -1,26 +0,0 @@
|
||||
import type { LegacyOAuthRef } from "./doctor/shared/legacy-oauth-sidecar.js";
|
||||
import "./doctor-auth-oauth-sidecar.js";
|
||||
|
||||
type DoctorAuthOAuthSidecarTestApi = {
|
||||
buildLegacyOAuthSecretAad(params: {
|
||||
ref: LegacyOAuthRef;
|
||||
profileId: string;
|
||||
provider: string;
|
||||
}): Buffer;
|
||||
buildLegacyOAuthSecretKey(seed: string): Buffer;
|
||||
};
|
||||
|
||||
function getTestApi(): DoctorAuthOAuthSidecarTestApi {
|
||||
return (globalThis as Record<PropertyKey, unknown>)[
|
||||
Symbol.for("openclaw.doctorAuthOAuthSidecarTestApi")
|
||||
] as DoctorAuthOAuthSidecarTestApi;
|
||||
}
|
||||
|
||||
export const testing: DoctorAuthOAuthSidecarTestApi = {
|
||||
buildLegacyOAuthSecretAad(params) {
|
||||
return getTestApi().buildLegacyOAuthSecretAad(params);
|
||||
},
|
||||
buildLegacyOAuthSecretKey(seed) {
|
||||
return getTestApi().buildLegacyOAuthSecretKey(seed);
|
||||
},
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
// Doctor OAuth sidecar tests cover encrypted sidecar detection and auth repair guidance.
|
||||
import { createCipheriv } from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -9,7 +8,6 @@ import {
|
||||
type OpenClawTestState,
|
||||
} from "../test-utils/openclaw-test-state.js";
|
||||
import { maybeRepairLegacyOAuthSidecarProfiles } from "./doctor-auth-oauth-sidecar.js";
|
||||
import { testing } from "./doctor-auth-oauth-sidecar.test-support.js";
|
||||
import type { DoctorPrompter } from "./doctor-prompter.js";
|
||||
|
||||
const states: OpenClawTestState[] = [];
|
||||
@@ -54,34 +52,6 @@ function writeLegacyAuthProfiles(
|
||||
return state.writeJson(path.join("agents", agentId, "agent", "auth-profiles.json"), store);
|
||||
}
|
||||
|
||||
function encryptLegacySidecarMaterial(params: {
|
||||
ref: { source: "openclaw-credentials"; provider: "openai-codex"; id: string };
|
||||
profileId: string;
|
||||
provider: string;
|
||||
seed: string;
|
||||
material: Record<string, string>;
|
||||
}) {
|
||||
const iv = Buffer.alloc(12, 7);
|
||||
const cipher = createCipheriv("aes-256-gcm", testing.buildLegacyOAuthSecretKey(params.seed), iv);
|
||||
cipher.setAAD(
|
||||
testing.buildLegacyOAuthSecretAad({
|
||||
ref: params.ref,
|
||||
profileId: params.profileId,
|
||||
provider: params.provider,
|
||||
}),
|
||||
);
|
||||
const ciphertext = Buffer.concat([
|
||||
cipher.update(JSON.stringify(params.material), "utf8"),
|
||||
cipher.final(),
|
||||
]);
|
||||
return {
|
||||
algorithm: "aes-256-gcm",
|
||||
iv: iv.toString("base64url"),
|
||||
tag: cipher.getAuthTag().toString("base64url"),
|
||||
ciphertext: ciphertext.toString("base64url"),
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
for (const state of states.splice(0)) {
|
||||
@@ -126,17 +96,13 @@ describe("maybeRepairLegacyOAuthSidecarProfiles", () => {
|
||||
version: 1,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
encrypted: encryptLegacySidecarMaterial({
|
||||
ref,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
seed,
|
||||
material: {
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
idToken: "id-token",
|
||||
},
|
||||
}),
|
||||
encrypted: {
|
||||
algorithm: "aes-256-gcm",
|
||||
iv: "BwcHBwcHBwcHBwcH",
|
||||
tag: "gSm_Lg58EVO-5wZGQlWHEA",
|
||||
ciphertext:
|
||||
"4qrZ4-zdgUdttB3gTUNORWdtO4gqLiFgTsilUX3-9RZiN2MLkCDdxQXQ2GfeqN1zi1qb9iURwK0sO0TJZfxO3zULMKNlRgUT",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -231,16 +197,12 @@ describe("maybeRepairLegacyOAuthSidecarProfiles", () => {
|
||||
version: 1,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
encrypted: encryptLegacySidecarMaterial({
|
||||
ref,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
seed: "right-seed",
|
||||
material: {
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
},
|
||||
}),
|
||||
encrypted: {
|
||||
algorithm: "aes-256-gcm",
|
||||
iv: "BwcHBwcHBwcHBwcH",
|
||||
tag: "ZHGhT2cekYFZCOxu8pP0KA",
|
||||
ciphertext: "OQPDJez2jSRH4FPxFNNkwEw7PDbClF6Ty6T2l4TLGvr6bdJfhK6VA6ccWwC1xlrR4ENA",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -450,16 +412,13 @@ describe("maybeRepairLegacyOAuthSidecarProfiles", () => {
|
||||
version: 1,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
encrypted: encryptLegacySidecarMaterial({
|
||||
ref,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
seed,
|
||||
material: {
|
||||
access: "shared-access-token",
|
||||
refresh: "shared-refresh-token",
|
||||
},
|
||||
}),
|
||||
encrypted: {
|
||||
algorithm: "aes-256-gcm",
|
||||
iv: "BwcHBwcHBwcHBwcH",
|
||||
tag: "91XpNgcMQ-AVeo7NDnv11Q",
|
||||
ciphertext:
|
||||
"fVMsIFtJ0LX1ayciusBnyS7KulJU2dCAdkKU4yMLGYTVB-Gq0X_SvUqPTkAX_a1ZBIjGIC6nFH_3HvhWHMIX7Cs",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import type { DoctorPrompter } from "./doctor-prompter.js";
|
||||
import {
|
||||
isLegacyOAuthRef,
|
||||
isLegacyOAuthSidecarPayload,
|
||||
legacyOAuthSidecarTestUtils,
|
||||
loadLegacyOAuthSidecarMaterial,
|
||||
resolveLegacyOAuthSidecarPath,
|
||||
type LegacyOAuthRef,
|
||||
@@ -329,14 +328,3 @@ export async function maybeRepairLegacyOAuthSidecarProfiles(params: {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const testing = {
|
||||
buildLegacyOAuthSecretAad: legacyOAuthSidecarTestUtils.buildLegacyOAuthSecretAad,
|
||||
buildLegacyOAuthSecretKey: legacyOAuthSidecarTestUtils.buildLegacyOAuthSecretKey,
|
||||
};
|
||||
|
||||
if (process.env.VITEST || process.env.NODE_ENV === "test") {
|
||||
(globalThis as Record<PropertyKey, unknown>)[
|
||||
Symbol.for("openclaw.doctorAuthOAuthSidecarTestApi")
|
||||
] = testing;
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import "./legacy-oauth-sidecar.js";
|
||||
|
||||
type TestApi = { resetKeychainOnlyMigrationHint(): void };
|
||||
|
||||
function getTestApi(): TestApi {
|
||||
return (globalThis as Record<PropertyKey, unknown>)[
|
||||
Symbol.for("openclaw.legacyOAuthSidecarInternalTestApi")
|
||||
] as TestApi;
|
||||
}
|
||||
|
||||
export const legacyOAuthSidecarInternalTestUtils: TestApi = {
|
||||
resetKeychainOnlyMigrationHint(): void {
|
||||
getTestApi().resetKeychainOnlyMigrationHint();
|
||||
},
|
||||
};
|
||||
@@ -6,11 +6,7 @@ import {
|
||||
createOpenClawTestState,
|
||||
type OpenClawTestState,
|
||||
} from "../../../test-utils/openclaw-test-state.js";
|
||||
import {
|
||||
legacyOAuthSidecarTestUtils,
|
||||
loadLegacyOAuthSidecarMaterial,
|
||||
} from "./legacy-oauth-sidecar.js";
|
||||
import { legacyOAuthSidecarInternalTestUtils } from "./legacy-oauth-sidecar.test-support.js";
|
||||
import { loadLegacyOAuthSidecarMaterial } from "./legacy-oauth-sidecar.js";
|
||||
|
||||
const states: OpenClawTestState[] = [];
|
||||
|
||||
@@ -48,13 +44,12 @@ async function writeLegacySidecarThatNeedsKeychain(): Promise<{
|
||||
version: 1,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
encrypted: legacyOAuthSidecarTestUtils.encryptLegacyOAuthMaterial({
|
||||
ref,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
seed: "only-in-keychain",
|
||||
material: { access: "a", refresh: "b", idToken: "c" },
|
||||
}),
|
||||
encrypted: {
|
||||
algorithm: "aes-256-gcm",
|
||||
iv: "AQIDBAUGBwgJCgsM",
|
||||
tag: "G1t3MG1wjsZq17LOSqvu8w",
|
||||
ciphertext: "nkPkvPO-ZilcU9XIoVzMfskmxKVmknxIjFkNw3yLMhiP3d5--KdbiMub",
|
||||
},
|
||||
});
|
||||
return { state, ref, profileId };
|
||||
}
|
||||
@@ -63,7 +58,6 @@ afterEach(async () => {
|
||||
for (const state of states.splice(0)) {
|
||||
await state.cleanup();
|
||||
}
|
||||
legacyOAuthSidecarInternalTestUtils.resetKeychainOnlyMigrationHint();
|
||||
});
|
||||
|
||||
describe("loadLegacyOAuthSidecarMaterial keychain-only headless warning", () => {
|
||||
@@ -96,48 +90,32 @@ describe("loadLegacyOAuthSidecarMaterial keychain-only headless warning", () =>
|
||||
return env;
|
||||
}
|
||||
|
||||
it("emits a single doctor-pointer warning when only Keychain can decrypt and prompts are disabled", async () => {
|
||||
it("emits one doctor-pointer warning only on Darwin", async () => {
|
||||
const { state, ref, profileId } = await writeLegacySidecarThatNeedsKeychain();
|
||||
const env = envWithoutVitestSignals(state);
|
||||
const load = () =>
|
||||
loadLegacyOAuthSidecarMaterial({
|
||||
ref,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
allowKeychainPrompt: false,
|
||||
env,
|
||||
});
|
||||
|
||||
const firstAttempt = loadLegacyOAuthSidecarMaterial({
|
||||
ref,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
allowKeychainPrompt: false,
|
||||
env,
|
||||
});
|
||||
expect(firstAttempt).toBeNull();
|
||||
restorePlatform();
|
||||
restorePlatform = setPlatform("linux");
|
||||
expect(load()).toBeNull();
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
|
||||
restorePlatform();
|
||||
restorePlatform = setPlatform("darwin");
|
||||
expect(load()).toBeNull();
|
||||
expect(warnSpy).toHaveBeenCalledTimes(1);
|
||||
const [firstMessage] = warnSpy.mock.calls[0] as [unknown];
|
||||
expect(String(firstMessage)).toContain("openclaw doctor --fix");
|
||||
expect(String(firstMessage)).toContain("macOS Keychain");
|
||||
|
||||
const secondAttempt = loadLegacyOAuthSidecarMaterial({
|
||||
ref,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
allowKeychainPrompt: false,
|
||||
env,
|
||||
});
|
||||
expect(secondAttempt).toBeNull();
|
||||
expect(load()).toBeNull();
|
||||
expect(warnSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not emit the doctor-pointer warning on non-darwin platforms", async () => {
|
||||
restorePlatform();
|
||||
restorePlatform = setPlatform("linux");
|
||||
const { state, ref, profileId } = await writeLegacySidecarThatNeedsKeychain();
|
||||
const env = envWithoutVitestSignals(state);
|
||||
|
||||
const attempt = loadLegacyOAuthSidecarMaterial({
|
||||
ref,
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
allowKeychainPrompt: false,
|
||||
env,
|
||||
});
|
||||
expect(attempt).toBeNull();
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Legacy OAuth sidecar reader for migrating encrypted auth-profile secret material.
|
||||
import * as childProcess from "node:child_process";
|
||||
import { createCipheriv, createDecipheriv, hash } from "node:crypto";
|
||||
import { createDecipheriv, hash } from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
@@ -113,38 +113,6 @@ function buildLegacyOAuthSecretKey(seed: string): Buffer {
|
||||
return hash("sha256", `openclaw:auth-profile-oauth:${seed}`, "buffer");
|
||||
}
|
||||
|
||||
function encryptLegacyOAuthMaterialForTest(params: {
|
||||
ref: LegacyOAuthRef;
|
||||
profileId: string;
|
||||
provider: string;
|
||||
seed: string;
|
||||
material: Record<string, string>;
|
||||
}): LegacyOAuthEncryptedPayload {
|
||||
const iv = Buffer.from("0102030405060708090a0b0c", "hex");
|
||||
const cipher = createCipheriv(
|
||||
LEGACY_OAUTH_SECRET_ALGORITHM,
|
||||
buildLegacyOAuthSecretKey(params.seed),
|
||||
iv,
|
||||
);
|
||||
cipher.setAAD(
|
||||
buildLegacyOAuthSecretAad({
|
||||
ref: params.ref,
|
||||
profileId: params.profileId,
|
||||
provider: params.provider,
|
||||
}),
|
||||
);
|
||||
const ciphertext = Buffer.concat([
|
||||
cipher.update(JSON.stringify(params.material), "utf8"),
|
||||
cipher.final(),
|
||||
]);
|
||||
return {
|
||||
algorithm: LEGACY_OAUTH_SECRET_ALGORITHM,
|
||||
iv: iv.toString("base64url"),
|
||||
tag: cipher.getAuthTag().toString("base64url"),
|
||||
ciphertext: ciphertext.toString("base64url"),
|
||||
};
|
||||
}
|
||||
|
||||
function uniquePaths(paths: Array<string | undefined>): string[] {
|
||||
return uniqueStrings(paths.filter((entry): entry is string => Boolean(entry)));
|
||||
}
|
||||
@@ -342,18 +310,6 @@ function emitKeychainOnlyMigrationHintOnce(profileId: string): void {
|
||||
);
|
||||
}
|
||||
|
||||
const legacyOAuthSidecarInternalTestUtils = {
|
||||
resetKeychainOnlyMigrationHint(): void {
|
||||
keychainOnlyMigrationHintEmitted = false;
|
||||
},
|
||||
};
|
||||
|
||||
if (process.env.VITEST || process.env.NODE_ENV === "test") {
|
||||
(globalThis as Record<PropertyKey, unknown>)[
|
||||
Symbol.for("openclaw.legacyOAuthSidecarInternalTestApi")
|
||||
] = legacyOAuthSidecarInternalTestUtils;
|
||||
}
|
||||
|
||||
export function loadLegacyOAuthSidecarMaterial(params: {
|
||||
ref: LegacyOAuthRef;
|
||||
profileId: string;
|
||||
@@ -386,9 +342,3 @@ export function loadLegacyOAuthSidecarMaterial(params: {
|
||||
}
|
||||
return normalizeLegacyOAuthSecretMaterial(raw);
|
||||
}
|
||||
|
||||
export const legacyOAuthSidecarTestUtils = {
|
||||
buildLegacyOAuthSecretAad,
|
||||
buildLegacyOAuthSecretKey,
|
||||
encryptLegacyOAuthMaterial: encryptLegacyOAuthMaterialForTest,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user