🧯 fix: Suppress Google Service Key Noise (#13322)

* fix: suppress unused Google service key noise

* test: stabilize Google endpoint config mocks

* fix: normalize Google service key endpoint config
This commit is contained in:
Danny Avila
2026-05-26 15:33:38 -07:00
committed by GitHub
parent a00800161c
commit cb23d6b993
4 changed files with 229 additions and 10 deletions

View File

@@ -1,10 +1,34 @@
const path = require('path');
const fs = require('fs/promises');
const { logger } = require('@librechat/data-schemas');
const { loadServiceKey, isUserProvided } = require('@librechat/api');
const { config } = require('./EndpointService');
const defaultServiceKeyPath = path.join(__dirname, '../../..', 'data', 'auth.json');
async function getServiceKeyPath() {
const serviceKeyPath = process.env.GOOGLE_SERVICE_KEY_FILE?.trim();
if (serviceKeyPath) {
return serviceKeyPath;
}
try {
await fs.access(defaultServiceKeyPath);
return defaultServiceKeyPath;
} catch (error) {
if (error?.code !== 'ENOENT') {
logger.warn(
`Unable to access default Google service key file: ${defaultServiceKeyPath}`,
error,
);
}
return null;
}
}
async function loadAsyncEndpoints() {
let serviceKey, googleUserProvides;
let serviceKey;
let googleUserProvides = false;
const { googleKey } = config;
/** Check if GOOGLE_KEY is provided at all(including 'user_provided') */
@@ -14,15 +38,15 @@ async function loadAsyncEndpoints() {
/** If GOOGLE_KEY is provided, check if it's user_provided */
googleUserProvides = isUserProvided(googleKey);
} else {
/** Only attempt to load service key if GOOGLE_KEY is not provided */
const serviceKeyPath =
process.env.GOOGLE_SERVICE_KEY_FILE || path.join(__dirname, '../../..', 'data', 'auth.json');
const serviceKeyPath = await getServiceKeyPath();
try {
serviceKey = await loadServiceKey(serviceKeyPath);
} catch (error) {
logger.error('Error loading service key', error);
serviceKey = null;
if (serviceKeyPath) {
try {
serviceKey = await loadServiceKey(serviceKeyPath);
} catch (error) {
logger.warn('Error loading Google service key', error);
serviceKey = null;
}
}
}

View File

@@ -0,0 +1,119 @@
const mockAccess = jest.fn();
const mockLoadServiceKey = jest.fn();
const mockIsUserProvided = jest.fn((value) => value === 'user_provided');
const mockLogger = {
debug: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
};
function mockOptionalModule(moduleName, factory) {
try {
require.resolve(moduleName);
jest.doMock(moduleName, factory);
} catch {
jest.doMock(moduleName, factory, { virtual: true });
}
}
function mockDependencies() {
jest.doMock('fs/promises', () => ({
access: mockAccess,
}));
mockOptionalModule('@librechat/api', () => ({
isEnabled: (value) => value === true || value === 'true' || value === '1',
isUserProvided: mockIsUserProvided,
loadServiceKey: mockLoadServiceKey,
}));
mockOptionalModule('@librechat/data-schemas', () => ({
logger: mockLogger,
}));
mockOptionalModule('librechat-data-provider', () => ({
EModelEndpoint: {
agents: 'agents',
anthropic: 'anthropic',
assistants: 'assistants',
azureAssistants: 'azureAssistants',
azureOpenAI: 'azureOpenAI',
bedrock: 'bedrock',
google: 'google',
openAI: 'openAI',
},
}));
jest.doMock('~/server/utils/handleText', () => ({
generateConfig: (key) => (key ? { userProvide: key === 'user_provided' } : false),
}));
}
describe('loadAsyncEndpoints', () => {
const originalEnv = process.env;
beforeEach(() => {
jest.clearAllMocks();
jest.resetModules();
mockDependencies();
process.env = { ...originalEnv };
delete process.env.GOOGLE_KEY;
delete process.env.GOOGLE_SERVICE_KEY_FILE;
});
afterAll(() => {
process.env = originalEnv;
});
function loadModule(env = {}) {
process.env = { ...process.env, ...env };
return require('./loadAsyncEndpoints');
}
it('does not load the default Google service key when the default file is missing', async () => {
mockAccess.mockRejectedValue(Object.assign(new Error('missing'), { code: 'ENOENT' }));
const loadAsyncEndpoints = loadModule();
const result = await loadAsyncEndpoints();
expect(result).toEqual({ google: false });
expect(mockLoadServiceKey).not.toHaveBeenCalled();
expect(mockLogger.error).not.toHaveBeenCalled();
});
it('loads the default Google service key when the default file exists', async () => {
const serviceKey = { project_id: 'test-project' };
mockAccess.mockResolvedValue();
mockLoadServiceKey.mockResolvedValue(serviceKey);
const loadAsyncEndpoints = loadModule();
const result = await loadAsyncEndpoints();
expect(result).toEqual({ google: { userProvide: false } });
expect(mockLoadServiceKey).toHaveBeenCalledWith(expect.stringContaining('api/data/auth.json'));
});
it('loads an explicitly configured Google service key path without probing the default file', async () => {
const serviceKey = { project_id: 'test-project' };
mockLoadServiceKey.mockResolvedValue(serviceKey);
const loadAsyncEndpoints = loadModule({
GOOGLE_SERVICE_KEY_FILE: '/secrets/google-service-account.json',
});
const result = await loadAsyncEndpoints();
expect(result).toEqual({ google: { userProvide: false } });
expect(mockAccess).not.toHaveBeenCalled();
expect(mockLoadServiceKey).toHaveBeenCalledWith('/secrets/google-service-account.json');
});
it('uses GOOGLE_KEY without probing for a service key', async () => {
const loadAsyncEndpoints = loadModule({ GOOGLE_KEY: 'user_provided' });
const result = await loadAsyncEndpoints();
expect(result).toEqual({ google: { userProvide: true } });
expect(mockAccess).not.toHaveBeenCalled();
expect(mockLoadServiceKey).not.toHaveBeenCalled();
});
});

View File

@@ -8,10 +8,12 @@ const { config } = require('./EndpointService');
* @returns {Promise<Object.<string, EndpointWithOrder>>} An object whose keys are endpoint names and values are objects that contain the endpoint configuration and an order.
*/
async function loadDefaultEndpointsConfig(appConfig) {
const { google } = await loadAsyncEndpoints(appConfig);
const { assistants, azureAssistants, azureOpenAI } = config;
const enabledEndpoints = getEnabledEndpoints();
const { google } = enabledEndpoints.includes(EModelEndpoint.google)
? await loadAsyncEndpoints(appConfig)
: { google: false };
const endpointConfig = {
[EModelEndpoint.openAI]: config[EModelEndpoint.openAI],

View File

@@ -0,0 +1,74 @@
const mockGetEnabledEndpoints = jest.fn();
const mockLoadAsyncEndpoints = jest.fn();
function mockOptionalModule(moduleName, factory) {
try {
require.resolve(moduleName);
jest.doMock(moduleName, factory);
} catch {
jest.doMock(moduleName, factory, { virtual: true });
}
}
function mockDependencies() {
mockOptionalModule('librechat-data-provider', () => ({
EModelEndpoint: {
agents: 'agents',
anthropic: 'anthropic',
assistants: 'assistants',
azureAssistants: 'azureAssistants',
azureOpenAI: 'azureOpenAI',
bedrock: 'bedrock',
google: 'google',
openAI: 'openAI',
},
getEnabledEndpoints: mockGetEnabledEndpoints,
}));
jest.doMock('./loadAsyncEndpoints', () => mockLoadAsyncEndpoints);
jest.doMock('./EndpointService', () => ({
config: {
agents: { userProvide: false },
anthropic: false,
assistants: false,
azureAssistants: false,
azureOpenAI: false,
bedrock: false,
openAI: { userProvide: false },
},
}));
}
describe('loadDefaultEndpointsConfig', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.resetModules();
mockDependencies();
});
it('does not probe async Google credentials when Google is excluded from enabled endpoints', async () => {
mockGetEnabledEndpoints.mockReturnValue(['openAI']);
const loadDefaultEndpointsConfig = require('./loadDefaultEConfig');
const result = await loadDefaultEndpointsConfig();
expect(mockLoadAsyncEndpoints).not.toHaveBeenCalled();
expect(result).toEqual({
openAI: { userProvide: false, order: 0 },
});
});
it('loads async Google credentials when Google is enabled', async () => {
mockGetEnabledEndpoints.mockReturnValue(['google']);
mockLoadAsyncEndpoints.mockResolvedValue({ google: { userProvide: false } });
const loadDefaultEndpointsConfig = require('./loadDefaultEConfig');
const result = await loadDefaultEndpointsConfig();
expect(mockLoadAsyncEndpoints).toHaveBeenCalledTimes(1);
expect(result).toEqual({
google: { userProvide: false, order: 0 },
});
});
});