mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
185 lines
5.6 KiB
TypeScript
185 lines
5.6 KiB
TypeScript
import { mkdtempSync } from "node:fs";
|
|
import { writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { resolveOAuthApiKeyMarker } from "openclaw/plugin-sdk/provider-auth";
|
|
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { resolveImplicitProvidersForTest } from "../../src/agents/models-config.e2e-harness.js";
|
|
import { CHUTES_BASE_URL } from "./models.js";
|
|
|
|
const CHUTES_OAUTH_MARKER = resolveOAuthApiKeyMarker("chutes");
|
|
const ORIGINAL_VITEST_ENV = process.env.VITEST;
|
|
const ORIGINAL_NODE_ENV = process.env.NODE_ENV;
|
|
|
|
function createTempAgentDir() {
|
|
return mkdtempSync(join(tmpdir(), "openclaw-test-"));
|
|
}
|
|
|
|
type ChutesAuthProfiles = {
|
|
[profileId: string]:
|
|
| {
|
|
type: "api_key";
|
|
provider: "chutes";
|
|
key: string;
|
|
}
|
|
| {
|
|
type: "oauth";
|
|
provider: "chutes";
|
|
access: string;
|
|
refresh: string;
|
|
expires: number;
|
|
};
|
|
};
|
|
|
|
function createChutesApiKeyProfile(key = "chutes-live-api-key") {
|
|
return {
|
|
type: "api_key" as const,
|
|
provider: "chutes" as const,
|
|
key,
|
|
};
|
|
}
|
|
|
|
function createChutesOAuthProfile(access = "oauth-access-token") {
|
|
return {
|
|
type: "oauth" as const,
|
|
provider: "chutes" as const,
|
|
access,
|
|
refresh: "oauth-refresh-token",
|
|
expires: Date.now() + 60_000,
|
|
};
|
|
}
|
|
|
|
async function writeChutesAuthProfiles(agentDir: string, profiles: ChutesAuthProfiles) {
|
|
await writeFile(
|
|
join(agentDir, "auth-profiles.json"),
|
|
JSON.stringify(
|
|
{
|
|
version: 1,
|
|
profiles,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
"utf8",
|
|
);
|
|
}
|
|
|
|
async function resolveChutesProvidersForProfiles(
|
|
profiles: ChutesAuthProfiles,
|
|
env: NodeJS.ProcessEnv = {},
|
|
) {
|
|
const agentDir = createTempAgentDir();
|
|
await writeChutesAuthProfiles(agentDir, profiles);
|
|
return resolveImplicitProvidersForTest({ agentDir, env });
|
|
}
|
|
|
|
function expectChutesApiKeyProvider(
|
|
providers: Awaited<ReturnType<typeof resolveImplicitProvidersForTest>>,
|
|
apiKey = "chutes-live-api-key",
|
|
) {
|
|
expect(providers?.chutes?.baseUrl).toBe(CHUTES_BASE_URL);
|
|
expect(providers?.chutes?.apiKey).toBe(apiKey);
|
|
expect(providers?.chutes?.apiKey).not.toBe(CHUTES_OAUTH_MARKER);
|
|
}
|
|
|
|
function expectChutesOAuthMarkerProvider(
|
|
providers: Awaited<ReturnType<typeof resolveImplicitProvidersForTest>>,
|
|
) {
|
|
expect(providers?.chutes?.baseUrl).toBe(CHUTES_BASE_URL);
|
|
expect(providers?.chutes?.apiKey).toBe(CHUTES_OAUTH_MARKER);
|
|
}
|
|
|
|
async function withRealChutesDiscovery<T>(
|
|
run: (fetchMock: ReturnType<typeof vi.fn>) => Promise<T>,
|
|
) {
|
|
const originalVitest = process.env.VITEST;
|
|
const originalNodeEnv = process.env.NODE_ENV;
|
|
const originalFetch = globalThis.fetch;
|
|
delete process.env.VITEST;
|
|
delete process.env.NODE_ENV;
|
|
|
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ data: [{ id: "chutes/private-model" }] }),
|
|
});
|
|
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
|
|
|
try {
|
|
return await run(fetchMock);
|
|
} finally {
|
|
process.env.VITEST = originalVitest;
|
|
process.env.NODE_ENV = originalNodeEnv;
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
}
|
|
|
|
describe("chutes implicit provider auth mode", () => {
|
|
beforeEach(() => {
|
|
process.env.VITEST = "true";
|
|
process.env.NODE_ENV = "test";
|
|
});
|
|
|
|
afterAll(() => {
|
|
process.env.VITEST = ORIGINAL_VITEST_ENV;
|
|
process.env.NODE_ENV = ORIGINAL_NODE_ENV;
|
|
});
|
|
|
|
it("auto-loads bundled chutes discovery for env api keys", async () => {
|
|
const agentDir = createTempAgentDir();
|
|
const providers = await resolveImplicitProvidersForTest({
|
|
agentDir,
|
|
env: {
|
|
VITEST: "true",
|
|
NODE_ENV: "test",
|
|
CHUTES_API_KEY: "env-chutes-api-key",
|
|
} as NodeJS.ProcessEnv,
|
|
});
|
|
|
|
expect(providers?.chutes?.baseUrl).toBe(CHUTES_BASE_URL);
|
|
expect(providers?.chutes?.apiKey).toBe("CHUTES_API_KEY");
|
|
});
|
|
|
|
it("keeps api_key-backed chutes profiles on the api-key loader path", async () => {
|
|
const providers = await resolveChutesProvidersForProfiles({
|
|
"chutes:default": createChutesApiKeyProfile(),
|
|
});
|
|
expectChutesApiKeyProvider(providers);
|
|
});
|
|
|
|
it("keeps api_key precedence when oauth profile is inserted first", async () => {
|
|
const providers = await resolveChutesProvidersForProfiles({
|
|
"chutes:oauth": createChutesOAuthProfile(),
|
|
"chutes:default": createChutesApiKeyProfile(),
|
|
});
|
|
expectChutesApiKeyProvider(providers);
|
|
});
|
|
|
|
it("keeps api_key precedence when api_key profile is inserted first", async () => {
|
|
const providers = await resolveChutesProvidersForProfiles({
|
|
"chutes:default": createChutesApiKeyProfile(),
|
|
"chutes:oauth": createChutesOAuthProfile(),
|
|
});
|
|
expectChutesApiKeyProvider(providers);
|
|
});
|
|
|
|
it("forwards oauth access token to chutes model discovery", async () => {
|
|
await withRealChutesDiscovery(async (fetchMock) => {
|
|
const providers = await resolveChutesProvidersForProfiles({
|
|
"chutes:default": createChutesOAuthProfile("my-chutes-access-token"),
|
|
});
|
|
expectChutesOAuthMarkerProvider(providers);
|
|
const chutesCalls = fetchMock.mock.calls.filter(([url]) => String(url).includes("chutes.ai"));
|
|
expect(chutesCalls.length).toBeGreaterThan(0);
|
|
const request = chutesCalls[0]?.[1] as { headers?: Record<string, string> } | undefined;
|
|
expect(request?.headers?.Authorization).toBe("Bearer my-chutes-access-token");
|
|
});
|
|
});
|
|
|
|
it("uses CHUTES_OAUTH_MARKER only for oauth-backed chutes profiles", async () => {
|
|
const providers = await resolveChutesProvidersForProfiles({
|
|
"chutes:default": createChutesOAuthProfile(),
|
|
});
|
|
expectChutesOAuthMarkerProvider(providers);
|
|
});
|
|
});
|