Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/JAVA/Openclaw/extensions/google/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 26 kB image not shown  

Quelle  oauth.test.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { join, parse } from "node:path";
import { describe, expect, it, vi, beforeAll, beforeEach, afterEach } from "vitest";

vi.mock("../../src/infra/wsl.js", () => ({
  isWSL2Sync: () => false,
}));

vi.mock("../../src/infra/net/fetch-guard.js", () => ({
  fetchWithSsrFGuard: async (params: {
    url: string;
    init?: RequestInit;
    fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
  }) => {
    const fetchImpl = params.fetchImpl ?? globalThis.fetch;
    const response = await fetchImpl(params.url, params.init);
    return {
      response,
      finalUrl: params.url,
      release: async () => {},
    };
  },
}));

const mockExistsSync = vi.fn();
const mockReadFileSync = vi.fn();
const mockRealpathSync = vi.fn();
const mockReaddirSync = vi.fn();
const mockSettingsExistsSync = vi.fn();
const mockSettingsReadFileSync = vi.fn();

describe("resolveGeminiCliSelectedAuthType", () => {
  const ENV_KEYS = ["GOOGLE_GENAI_USE_GCA"] as const;

  let envSnapshot: Partial<Record<(typeof ENV_KEYS)[number], string>>;
  let resolveGeminiCliSelectedAuthType: typeof import("./oauth.settings.js").resolveGeminiCliSelectedAuthType;
  let setOAuthSettingsFsForTest: typeof import("./oauth.settings.js").setOAuthSettingsFsForTest;

  beforeAll(async () => {
    ({ resolveGeminiCliSelectedAuthType, setOAuthSettingsFsForTest } =
      await import("./oauth.settings.js"));
  });

  beforeEach(() => {
    envSnapshot = Object.fromEntries(ENV_KEYS.map((key) => [key, process.env[key]]));
    delete process.env.GOOGLE_GENAI_USE_GCA;
    mockSettingsExistsSync.mockReset();
    mockSettingsReadFileSync.mockReset();
    setOAuthSettingsFsForTest({
      existsSync: (...args) => mockSettingsExistsSync(...args),
      readFileSync: (...args) => mockSettingsReadFileSync(...args),
      homedir: () => "/mock/home",
    });
  });

  afterEach(() => {
    for (const key of ENV_KEYS) {
      const value = envSnapshot[key];
      if (value === undefined) {
        delete process.env[key];
      } else {
        process.env[key] = value;
      }
    }
    setOAuthSettingsFsForTest();
  });

  it("uses GOOGLE_GENAI_USE_GCA as an oauth-personal fallback when settings are absent", () => {
    process.env.GOOGLE_GENAI_USE_GCA = "true";
    mockSettingsExistsSync.mockReturnValue(false);

    expect(resolveGeminiCliSelectedAuthType()).toBe("oauth-personal");
  });

  it("prefers settings auth selection over the GOOGLE_GENAI_USE_GCA fallback", () => {
    process.env.GOOGLE_GENAI_USE_GCA = "true";
    mockSettingsExistsSync.mockReturnValue(true);
    mockSettingsReadFileSync.mockReturnValue(
      JSON.stringify({
        security: {
          auth: {
            selectedType: "oauth-code-assist",
          },
        },
      }),
    );

    expect(resolveGeminiCliSelectedAuthType()).toBe("oauth-code-assist");
  });

  it("reads the nested security auth selection from ~/.gemini/settings.json", () => {
    mockSettingsExistsSync.mockReturnValue(true);
    mockSettingsReadFileSync.mockReturnValue(
      JSON.stringify({
        security: {
          auth: {
            selectedType: "oauth-personal",
          },
        },
      }),
    );

    expect(resolveGeminiCliSelectedAuthType()).toBe("oauth-personal");
  });

  it("falls back to legacy top-level selectedAuthType keys", () => {
    mockSettingsExistsSync.mockReturnValue(true);
    mockSettingsReadFileSync.mockReturnValue(
      JSON.stringify({ selectedAuthType: "oauth-personal" }),
    );

    expect(resolveGeminiCliSelectedAuthType()).toBe("oauth-personal");
  });
});

describe("extractGeminiCliCredentials", () => {
  const normalizePath = (value: string) =>
    value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
  const rootDir = parse(process.cwd()).root || "/";
  const FAKE_CLIENT_ID = "123456789-abcdef.apps.googleusercontent.com";
  const FAKE_CLIENT_SECRET = "GOCSPX-FakeSecretValue123";
  const FAKE_OAUTH2_CONTENT = `
    const clientId = "${FAKE_CLIENT_ID}";
    const clientSecret = "${FAKE_CLIENT_SECRET}";
  `;

  let originalPath: string | undefined;
  let extractGeminiCliCredentials: typeof import("./oauth.credentials.js").extractGeminiCliCredentials;
  let clearCredentialsCache: typeof import("./oauth.credentials.js").clearCredentialsCache;
  let setOAuthCredentialsFsForTest: typeof import("./oauth.credentials.js").setOAuthCredentialsFsForTest;

  async function installMockFs() {
    setOAuthCredentialsFsForTest({
      existsSync: (...args) => mockExistsSync(...args),
      readFileSync: (...args) => mockReadFileSync(...args),
      realpathSync: (...args) => mockRealpathSync(...args),
      readdirSync: (...args) => mockReaddirSync(...args),
    });
  }

  function makeFakeLayout() {
    const binDir = join(rootDir, "fake", "bin");
    const geminiPath = join(binDir, "gemini");
    const resolvedPath = join(
      rootDir,
      "fake",
      "lib",
      "node_modules",
      "@google",
      "gemini-cli",
      "dist",
      "index.js",
    );
    const oauth2Path = join(
      rootDir,
      "fake",
      "lib",
      "node_modules",
      "@google",
      "gemini-cli",
      "node_modules",
      "@google",
      "gemini-cli-core",
      "dist",
      "src",
      "code_assist",
      "oauth2.js",
    );

    return { binDir, geminiPath, resolvedPath, oauth2Path };
  }

  function installGeminiLayout(params: {
    oauth2Exists?: boolean;
    oauth2Content?: string;
    readdir?: string[];
  }) {
    const layout = makeFakeLayout();
    process.env.PATH = layout.binDir;

    // resolveGeminiCliDirs checks package.json to validate candidate directories
    const geminiCliDir = join(rootDir, "fake", "lib", "node_modules", "@google", "gemini-cli");
    const packageJsonPath = normalizePath(join(geminiCliDir, "package.json"));

    mockExistsSync.mockImplementation((p: string) => {
      const normalized = normalizePath(p);
      if (normalized === normalizePath(layout.geminiPath)) {
        return true;
      }
      if (normalized === packageJsonPath) {
        return true;
      }
      if (params.oauth2Exists && normalized === normalizePath(layout.oauth2Path)) {
        return true;
      }
      return false;
    });
    mockRealpathSync.mockReturnValue(layout.resolvedPath);
    if (params.oauth2Content !== undefined) {
      mockReadFileSync.mockReturnValue(params.oauth2Content);
    }
    if (params.readdir) {
      mockReaddirSync.mockReturnValue(params.readdir);
    }

    return layout;
  }

  function installNpmShimLayout(params: { oauth2Exists?: boolean; oauth2Content?: string }) {
    const binDir = join(rootDir, "fake", "npm-bin");
    const geminiPath = join(binDir, "gemini");
    const resolvedPath = geminiPath;
    const geminiCliDir = join(binDir, "node_modules", "@google", "gemini-cli");
    const oauth2Path = join(
      geminiCliDir,
      "node_modules",
      "@google",
      "gemini-cli-core",
      "dist",
      "src",
      "code_assist",
      "oauth2.js",
    );
    const packageJsonPath = normalizePath(join(geminiCliDir, "package.json"));
    process.env.PATH = binDir;

    mockExistsSync.mockImplementation((p: string) => {
      const normalized = normalizePath(p);
      if (normalized === normalizePath(geminiPath)) {
        return true;
      }
      if (normalized === packageJsonPath) {
        return true;
      }
      if (params.oauth2Exists && normalized === normalizePath(oauth2Path)) {
        return true;
      }
      return false;
    });
    mockRealpathSync.mockReturnValue(resolvedPath);
    if (params.oauth2Content !== undefined) {
      mockReadFileSync.mockReturnValue(params.oauth2Content);
    }
  }

  function installBundledNpmLayout(params: { bundleContent: string }) {
    const binDir = join(rootDir, "fake", "npm-bundle-bin");
    const geminiPath = join(binDir, "gemini");
    const resolvedPath = geminiPath;
    const geminiCliDir = join(binDir, "node_modules", "@google", "gemini-cli");
    const packageJsonPath = normalizePath(join(geminiCliDir, "package.json"));
    const bundleDir = join(geminiCliDir, "bundle");
    const chunkPath = join(bundleDir, "chunk-ABC123.js");

    process.env.PATH = binDir;
    mockExistsSync.mockImplementation((p: string) => {
      const normalized = normalizePath(p);
      return (
        normalized === normalizePath(geminiPath) ||
        normalized === packageJsonPath ||
        normalized === normalizePath(bundleDir)
      );
    });
    mockRealpathSync.mockReturnValue(resolvedPath);
    mockReaddirSync.mockImplementation((p: string) => {
      if (normalizePath(p) === normalizePath(bundleDir)) {
        return [dirent("chunk-ABC123.js", false)];
      }
      return [];
    });
    mockReadFileSync.mockImplementation((p: string) => {
      if (normalizePath(p) === normalizePath(chunkPath)) {
        return params.bundleContent;
      }
      throw new Error(`Unexpected read for ${p}`);
    });
  }

  function installHomebrewLibexecLayout(params: { oauth2Content: string }) {
    const brewPrefix = join(rootDir, "opt", "homebrew");
    const cellarRoot = join(brewPrefix, "Cellar", "gemini-cli", "1.2.3");
    const binDir = join(brewPrefix, "bin");
    const geminiPath = join(binDir, "gemini");
    const resolvedPath = join(cellarRoot, "libexec", "bin", "gemini");
    const geminiCliDir = join(
      cellarRoot,
      "libexec",
      "lib",
      "node_modules",
      "@google",
      "gemini-cli",
    );
    const packageJsonPath = normalizePath(join(geminiCliDir, "package.json"));
    const oauth2Path = join(
      geminiCliDir,
      "node_modules",
      "@google",
      "gemini-cli-core",
      "dist",
      "src",
      "code_assist",
      "oauth2.js",
    );

    process.env.PATH = binDir;
    mockExistsSync.mockImplementation((p: string) => {
      const normalized = normalizePath(p);
      return (
        normalized === normalizePath(geminiPath) ||
        normalized === packageJsonPath ||
        normalized === normalizePath(oauth2Path)
      );
    });
    mockRealpathSync.mockReturnValue(resolvedPath);
    mockReadFileSync.mockImplementation((p: string) => {
      if (normalizePath(p) === normalizePath(oauth2Path)) {
        return params.oauth2Content;
      }
      throw new Error(`Unexpected read for ${p}`);
    });
  }

  function installWindowsNvmLayoutWithUnrelatedOauth(params: {
    oauth2Content: string;
    unrelatedOauth2Content: string;
  }) {
    const nvmRoot = join(rootDir, "fake", "Users", "lobster", "AppData", "Local", "nvm");
    const versionDir = join(nvmRoot, "v24.1.0");
    const geminiPath = join(versionDir, process.platform === "win32" ? "gemini.cmd" : "gemini");
    const resolvedPath = geminiPath;
    const geminiCliDir = join(versionDir, "node_modules", "@google", "gemini-cli");
    const packageJsonPath = normalizePath(join(geminiCliDir, "package.json"));
    const oauth2Path = join(
      geminiCliDir,
      "node_modules",
      "@google",
      "gemini-cli-core",
      "dist",
      "src",
      "code_assist",
      "oauth2.js",
    );
    const unrelatedOauth2Path = join(
      nvmRoot,
      "node_modules",
      "discord-api-types",
      "payloads",
      "v10",
      "oauth2.js",
    );

    process.env.PATH = versionDir;
    mockExistsSync.mockImplementation((p: string) => {
      const normalized = normalizePath(p);
      return (
        normalized === normalizePath(geminiPath) ||
        normalized === packageJsonPath ||
        normalized === normalizePath(oauth2Path)
      );
    });
    mockRealpathSync.mockReturnValue(resolvedPath);
    mockReadFileSync.mockImplementation((p: string) => {
      const normalized = normalizePath(p);
      if (normalized === normalizePath(oauth2Path)) {
        return params.oauth2Content;
      }
      if (normalized === normalizePath(unrelatedOauth2Path)) {
        return params.unrelatedOauth2Content;
      }
      throw new Error(`Unexpected read for ${p}`);
    });
    mockReaddirSync.mockImplementation((p: string) => {
      const normalized = normalizePath(p);
      if (normalized === normalizePath(nvmRoot)) {
        return [dirent("node_modules", true)];
      }
      if (normalized === normalizePath(join(nvmRoot, "node_modules"))) {
        return [dirent("discord-api-types", true)];
      }
      if (normalized === normalizePath(join(nvmRoot, "node_modules", "discord-api-types"))) {
        return [dirent("payloads", true)];
      }
      if (
        normalized === normalizePath(join(nvmRoot, "node_modules", "discord-api-types", "payloads"))
      ) {
        return [dirent("v10", true)];
      }
      if (
        normalized ===
        normalizePath(join(nvmRoot, "node_modules", "discord-api-types", "payloads", "v10"))
      ) {
        return [dirent("oauth2.js", false)];
      }
      return [];
    });

    return { unrelatedOauth2Path };
  }

  function dirent(name: string, isDirectory: boolean) {
    return {
      name,
      isBlockDevice: () => false,
      isCharacterDevice: () => false,
      isDirectory: () => isDirectory,
      isFIFO: () => false,
      isFile: () => !isDirectory,
      isSocket: () => false,
      isSymbolicLink: () => false,
    };
  }

  function expectFakeCliCredentials(result: unknown) {
    expect(result).toEqual({
      clientId: FAKE_CLIENT_ID,
      clientSecret: FAKE_CLIENT_SECRET,
    });
  }

  beforeAll(async () => {
    ({ extractGeminiCliCredentials, clearCredentialsCache, setOAuthCredentialsFsForTest } =
      await import("./oauth.credentials.js"));
  });

  beforeEach(async () => {
    vi.clearAllMocks();
    originalPath = process.env.PATH;
    await installMockFs();
  });

  afterEach(async () => {
    process.env.PATH = originalPath;
    setOAuthCredentialsFsForTest();
  });

  it("returns null when gemini binary is not in PATH", async () => {
    process.env.PATH = "/nonexistent";
    mockExistsSync.mockReturnValue(false);

    clearCredentialsCache();
    expect(extractGeminiCliCredentials()).toBeNull();
  });

  it("extracts credentials from oauth2.js in known path", async () => {
    installGeminiLayout({ oauth2Exists: true, oauth2Content: FAKE_OAUTH2_CONTENT });

    clearCredentialsCache();
    const result = extractGeminiCliCredentials();

    expectFakeCliCredentials(result);
  });

  it("extracts credentials when PATH entry is an npm global shim", async () => {
    installNpmShimLayout({ oauth2Exists: true, oauth2Content: FAKE_OAUTH2_CONTENT });

    clearCredentialsCache();
    const result = extractGeminiCliCredentials();

    expectFakeCliCredentials(result);
  });

  it("extracts credentials from bundled npm installs", async () => {
    installBundledNpmLayout({
      bundleContent: `
        const OAUTH_CLIENT_ID = "${FAKE_CLIENT_ID}";
        const OAUTH_CLIENT_SECRET = "${FAKE_CLIENT_SECRET}";
      `,
    });

    clearCredentialsCache();
    const result = extractGeminiCliCredentials();

    expectFakeCliCredentials(result);
  });

  it("extracts credentials from Homebrew libexec installs", async () => {
    installHomebrewLibexecLayout({ oauth2Content: FAKE_OAUTH2_CONTENT });

    clearCredentialsCache();
    const result = extractGeminiCliCredentials();

    expectFakeCliCredentials(result);
  });

  it("returns null when oauth2.js cannot be found", async () => {
    installGeminiLayout({ oauth2Exists: false, readdir: [] });

    clearCredentialsCache();
    expect(extractGeminiCliCredentials()).toBeNull();
  });

  it("returns null when oauth2.js lacks credentials", async () => {
    installGeminiLayout({ oauth2Exists: true, oauth2Content: "// no credentials here" });

    clearCredentialsCache();
    expect(extractGeminiCliCredentials()).toBeNull();
  });

  it("caches credentials after first extraction", async () => {
    installGeminiLayout({ oauth2Exists: true, oauth2Content: FAKE_OAUTH2_CONTENT });

    clearCredentialsCache();

    // First call
    const result1 = extractGeminiCliCredentials();
    expect(result1).not.toBeNull();

    // Second call should use cache (readFileSync not called again)
    const readCount = mockReadFileSync.mock.calls.length;
    const result2 = extractGeminiCliCredentials();
    expect(result2).toEqual(result1);
    expect(mockReadFileSync.mock.calls.length).toBe(readCount);
  });

  it("skips unrelated oauth2.js files when gemini resolves inside a Windows nvm root", async () => {
    const { unrelatedOauth2Path } = installWindowsNvmLayoutWithUnrelatedOauth({
      oauth2Content: FAKE_OAUTH2_CONTENT,
      unrelatedOauth2Content: "// unrelated oauth file",
    });

    clearCredentialsCache();
    const result = extractGeminiCliCredentials();

    expectFakeCliCredentials(result);
    expect(
      mockReadFileSync.mock.calls.some(
        ([path]) => normalizePath(String(path)) === normalizePath(unrelatedOauth2Path),
      ),
    ).toBe(false);
  });
});

describe("loginGeminiCliOAuth", () => {
  const TOKEN_URL = "https://oauth2.googleapis.com/token";
  const USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json";
  const LOAD_PROD = "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist";
  const LOAD_DAILY = "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:loadCodeAssist";
  const LOAD_AUTOPUSH =
    "https://autopush-cloudcode-pa.sandbox.googleapis.com/v1internal:loadCodeAssist";

  const ENV_KEYS = [
    "OPENCLAW_GEMINI_OAUTH_CLIENT_ID",
    "OPENCLAW_GEMINI_OAUTH_CLIENT_SECRET",
    "GEMINI_CLI_OAUTH_CLIENT_ID",
    "GEMINI_CLI_OAUTH_CLIENT_SECRET",
    "GOOGLE_CLOUD_PROJECT",
    "GOOGLE_CLOUD_PROJECT_ID",
    "GOOGLE_GENAI_USE_GCA",
  ] as const;

  const EXPECTED_LOAD_CODE_ASSIST_METADATA = {
    ideType: "IDE_UNSPECIFIED",
    platform: "PLATFORM_UNSPECIFIED",
    pluginType: "GEMINI",
  } as const;

  function getRequestUrl(input: string | URL | Request): string {
    if (typeof input === "string") {
      return input;
    }
    if (input instanceof URL) {
      return input.toString();
    }
    return input.url;
  }

  function getHeaderValue(headers: HeadersInit | undefined, name: string): string | undefined {
    if (!headers) {
      return undefined;
    }
    if (headers instanceof Headers) {
      return headers.get(name) ?? undefined;
    }
    if (Array.isArray(headers)) {
      return headers.find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
    }
    return headers[name];
  }

  function responseJson(body: unknown, status = 200): Response {
    return new Response(JSON.stringify(body), {
      status,
      headers: { "Content-Type": "application/json" },
    });
  }

  function tokenResponse(): Response {
    return responseJson({
      access_token: "access-token",
      refresh_token: "refresh-token",
      expires_in: 3600,
    });
  }

  function userInfoResponse(): Response {
    return responseJson({ email: "lobster@openclaw.ai" });
  }

  type RecordedFetchRequest = {
    url: string;
    init?: RequestInit;
  };

  function installGeminiOAuthFetchMock(
    handleRequest: (request: RecordedFetchRequest) => Response | undefined,
  ) {
    const requests: RecordedFetchRequest[] = [];
    const fetchMock = vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
      const request = { url: getRequestUrl(input), init };
      requests.push(request);

      if (request.url === TOKEN_URL) {
        return tokenResponse();
      }
      if (request.url === USERINFO_URL) {
        return userInfoResponse();
      }

      const response = handleRequest(request);
      if (response) {
        return response;
      }
      throw new Error(`Unexpected request: ${request.url}`);
    });
    vi.stubGlobal("fetch", fetchMock);
    return { fetchMock, requests };
  }

  function getFormField(body: RequestInit["body"], name: string): string | null {
    if (!(body instanceof URLSearchParams)) {
      throw new Error("Expected URLSearchParams body");
    }
    return body.get(name);
  }

  function parseJsonString(value: unknown, label: string): unknown {
    if (typeof value !== "string") {
      throw new Error(`Expected ${label} JSON string`);
    }
    return JSON.parse(value);
  }

  type LoginGeminiCliOAuthFn = (options: {
    isRemote: boolean;
    openUrl: () => Promise<void>;
    log: (msg: string) => void;
    note: () => Promise<void>;
    prompt: () => Promise<string>;
    progress: { update: () => void; stop: () => void };
  }) => Promise<{ projectId?: string }>;

  async function runRemoteLoginWithCapturedAuthUrl(loginGeminiCliOAuth: LoginGeminiCliOAuthFn) {
    let authUrl = "";
    const result = await loginGeminiCliOAuth({
      isRemote: true,
      openUrl: async () => {},
      log: (msg) => {
        const found = msg.match(/https:\/\/accounts\.google\.com\/o\/oauth2\/v2\/auth\?[^\s]+/);
        if (found?.[0]) {
          authUrl = found[0];
        }
      },
      note: async () => {},
      prompt: async () => {
        const state = new URL(authUrl).searchParams.get("state");
        return `http://localhost:8085/oauth2callback?code=oauth-code&state=${state}`;
      },
      progress: { update: () => {}, stop: () => {} },
    });
    return { result, authUrl };
  }

  async function runProjectDiscoveryExpectingProjectId(projectId: string) {
    const { resolveGoogleOAuthIdentity } = await import("./oauth.project.js");
    const result = await resolveGoogleOAuthIdentity("access-token");
    expect(result.projectId).toBe(projectId);
  }

  let envSnapshot: Partial<Record<(typeof ENV_KEYS)[number], string>>;
  let setOAuthSettingsFsForTest: typeof import("./oauth.settings.js").setOAuthSettingsFsForTest;

  beforeAll(async () => {
    ({ setOAuthSettingsFsForTest } = await import("./oauth.settings.js"));
  });

  beforeEach(() => {
    envSnapshot = Object.fromEntries(ENV_KEYS.map((key) => [key, process.env[key]]));
    process.env.OPENCLAW_GEMINI_OAUTH_CLIENT_ID = "test-client-id.apps.googleusercontent.com";
    process.env.OPENCLAW_GEMINI_OAUTH_CLIENT_SECRET = "GOCSPX-test-client-secret"; // pragma: allowlist secret
    delete process.env.GEMINI_CLI_OAUTH_CLIENT_ID;
    delete process.env.GEMINI_CLI_OAUTH_CLIENT_SECRET;
    delete process.env.GOOGLE_CLOUD_PROJECT;
    delete process.env.GOOGLE_CLOUD_PROJECT_ID;
    delete process.env.GOOGLE_GENAI_USE_GCA;
    mockSettingsExistsSync.mockReset();
    mockSettingsReadFileSync.mockReset();
    setOAuthSettingsFsForTest({
      existsSync: (...args) => mockSettingsExistsSync(...args),
      readFileSync: (...args) => mockSettingsReadFileSync(...args),
      homedir: () => "/mock/home",
    });
    mockSettingsExistsSync.mockReturnValue(false);
  });

  afterEach(() => {
    for (const key of ENV_KEYS) {
      const value = envSnapshot[key];
      if (value === undefined) {
        delete process.env[key];
      } else {
        process.env[key] = value;
      }
    }
    setOAuthSettingsFsForTest();
    vi.unstubAllGlobals();
  });

  it("falls back across loadCodeAssist endpoints with aligned headers and metadata", async () => {
    const { requests } = installGeminiOAuthFetchMock(({ url }) => {
      if (url === LOAD_PROD) {
        return responseJson({ error: { message: "temporary failure" } }, 503);
      }
      if (url === LOAD_DAILY) {
        return responseJson({
          currentTier: { id: "standard-tier" },
          cloudaicompanionProject: { id: "daily-project" },
        });
      }
      return undefined;
    });

    await runProjectDiscoveryExpectingProjectId("daily-project");
    const loadRequests = requests.filter((request) =>
      request.url.includes("v1internal:loadCodeAssist"),
    );
    expect(loadRequests.map((request) => request.url)).toEqual([LOAD_PROD, LOAD_DAILY]);

    const firstHeaders = loadRequests[0]?.init?.headers;
    expect(getHeaderValue(firstHeaders, "X-Goog-Api-Client")).toBe(
      `gl-node/${process.versions.node}`,
    );

    const clientMetadata = getHeaderValue(firstHeaders, "Client-Metadata");
    expect(clientMetadata).toBeDefined();
    expect(parseJsonString(clientMetadata, "Client-Metadata")).toEqual(
      EXPECTED_LOAD_CODE_ASSIST_METADATA,
    );

    const loadBody = loadRequests[0]?.init?.body;
    const body = parseJsonString(loadBody, "loadCodeAssist body");
    expect(body).toEqual({
      metadata: EXPECTED_LOAD_CODE_ASSIST_METADATA,
    });
  });

  it("keeps OAuth state separate from the PKCE verifier during manual login", async () => {
    const { requests } = installGeminiOAuthFetchMock(({ url }) => {
      if (url === LOAD_PROD) {
        return responseJson({
          currentTier: { id: "standard-tier" },
          cloudaicompanionProject: { id: "prod-project" },
        });
      }
      return undefined;
    });

    const { loginGeminiCliOAuth } = await import("./oauth.js");
    const { authUrl } = await runRemoteLoginWithCapturedAuthUrl(loginGeminiCliOAuth);

    const authState = new URL(authUrl).searchParams.get("state");
    expect(authState).toBeTruthy();

    const tokenRequest = requests.find((request) => request.url === TOKEN_URL);
    expect(tokenRequest).toBeDefined();
    const codeVerifier = getFormField(tokenRequest?.init?.body, "code_verifier");
    expect(codeVerifier).toBeTruthy();
    expect(codeVerifier).not.toBe(authState);
  });

  it("rejects manual callback input when the returned state does not match", async () => {
    const { loginGeminiCliOAuth } = await import("./oauth.js");

    await expect(
      loginGeminiCliOAuth({
        isRemote: true,
        openUrl: async () => {},
        log: () => {},
        note: async () => {},
        prompt: async () =>
          "http://localhost:8085/oauth2callback?code=oauth-code&state=wrong-state",
        progress: { update: () => {}, stop: () => {} },
      }),
    ).rejects.toThrow("OAuth state mismatch - please try again");
  });

  it("falls back to GOOGLE_CLOUD_PROJECT when all loadCodeAssist endpoints fail", async () => {
    process.env.GOOGLE_CLOUD_PROJECT = "env-project";

    const { requests } = installGeminiOAuthFetchMock(({ url }) => {
      if ([LOAD_PROD, LOAD_DAILY, LOAD_AUTOPUSH].includes(url)) {
        return responseJson({ error: { message: "unavailable" } }, 503);
      }
      return undefined;
    });

    await runProjectDiscoveryExpectingProjectId("env-project");
    expect(requests.filter(({ url }) => url.includes("v1internal:loadCodeAssist"))).toHaveLength(3);
    expect(requests.some(({ url }) => url.includes("v1internal:onboardUser"))).toBe(false);
  });

  it("skips loadCodeAssist entirely when Gemini CLI is configured for personal OAuth", async () => {
    mockSettingsExistsSync.mockReturnValue(true);
    mockSettingsReadFileSync.mockReturnValue(
      JSON.stringify({
        security: {
          auth: {
            selectedType: "oauth-personal",
          },
        },
      }),
    );

    const { requests } = installGeminiOAuthFetchMock(() => undefined);
    const { exchangeCodeForTokens } = await import("./oauth.token.js");
    const result = await exchangeCodeForTokens("oauth-code", "pkce-verifier");

    expect(result.projectId).toBeUndefined();
    expect(requests.map(({ url }) => url)).toEqual([TOKEN_URL, USERINFO_URL]);
  });
});

¤ Dauer der Verarbeitung: 0.2 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.