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:
Peter Steinberger
2026-08-11 03:59:58 -07:00
committed by GitHub
parent 26d0158e50
commit 63bba283a6
6 changed files with 46 additions and 212 deletions
@@ -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);
},
};
+20 -61
View File
@@ -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",
},
},
);
-12
View File
@@ -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,
};