mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 09:50:15 +01:00
🗄️ feat: Allow Skipping Transactions When Balance is Disabled (#9419)
* Disable transaction creation when balance is disabled * Add configuration to disable transactions creation * chore: remove comments --------- Co-authored-by: Danny Avila <danacordially@gmail.com>
This commit is contained in:
parent
0ecafcd38e
commit
e95e0052da
10 changed files with 544 additions and 7 deletions
285
packages/api/src/app/config.test.ts
Normal file
285
packages/api/src/app/config.test.ts
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
import { getTransactionsConfig, getBalanceConfig } from './config';
|
||||
import { logger } from '@librechat/data-schemas';
|
||||
import { FileSources } from 'librechat-data-provider';
|
||||
import type { AppConfig } from '~/types';
|
||||
import type { TCustomConfig } from 'librechat-data-provider';
|
||||
|
||||
// Helper function to create a minimal AppConfig for testing
|
||||
const createTestAppConfig = (overrides: Partial<AppConfig> = {}): AppConfig => {
|
||||
const minimalConfig: TCustomConfig = {
|
||||
version: '1.0.0',
|
||||
cache: true,
|
||||
interface: {
|
||||
endpointsMenu: true,
|
||||
},
|
||||
registration: {
|
||||
socialLogins: [],
|
||||
},
|
||||
endpoints: {},
|
||||
};
|
||||
|
||||
return {
|
||||
config: minimalConfig,
|
||||
paths: {
|
||||
uploads: '',
|
||||
imageOutput: '',
|
||||
publicPath: '',
|
||||
},
|
||||
fileStrategy: FileSources.local,
|
||||
fileStrategies: {},
|
||||
imageOutputType: 'png',
|
||||
...overrides,
|
||||
};
|
||||
};
|
||||
|
||||
jest.mock('@librechat/data-schemas', () => ({
|
||||
logger: {
|
||||
warn: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('~/utils', () => ({
|
||||
isEnabled: jest.fn((value) => value === 'true'),
|
||||
normalizeEndpointName: jest.fn((name) => name),
|
||||
}));
|
||||
|
||||
describe('getTransactionsConfig', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
delete process.env.CHECK_BALANCE;
|
||||
delete process.env.START_BALANCE;
|
||||
});
|
||||
|
||||
describe('when appConfig is not provided', () => {
|
||||
it('should return default config with enabled: true', () => {
|
||||
const result = getTransactionsConfig();
|
||||
expect(result).toEqual({ enabled: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when appConfig is provided', () => {
|
||||
it('should return transactions config when explicitly set to false', () => {
|
||||
const appConfig = createTestAppConfig({
|
||||
transactions: { enabled: false },
|
||||
balance: { enabled: false },
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: false });
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return transactions config when explicitly set to true', () => {
|
||||
const appConfig = createTestAppConfig({
|
||||
transactions: { enabled: true },
|
||||
balance: { enabled: false },
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: true });
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return default config when transactions is not defined', () => {
|
||||
const appConfig = createTestAppConfig({
|
||||
balance: { enabled: false },
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: true });
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('balance and transactions interaction', () => {
|
||||
it('should force transactions to be enabled when balance is enabled but transactions is disabled', () => {
|
||||
const appConfig = createTestAppConfig({
|
||||
transactions: { enabled: false },
|
||||
balance: { enabled: true },
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: true });
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
'Configuration warning: transactions.enabled=false is incompatible with balance.enabled=true. ' +
|
||||
'Transactions will be enabled to ensure balance tracking works correctly.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not override transactions when balance is enabled and transactions is enabled', () => {
|
||||
const appConfig = createTestAppConfig({
|
||||
transactions: { enabled: true },
|
||||
balance: { enabled: true },
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: true });
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should allow transactions to be disabled when balance is disabled', () => {
|
||||
const appConfig = createTestAppConfig({
|
||||
transactions: { enabled: false },
|
||||
balance: { enabled: false },
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: false });
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use default when balance is enabled but transactions is not defined', () => {
|
||||
const appConfig = createTestAppConfig({
|
||||
balance: { enabled: true },
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: true });
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with environment variables for balance', () => {
|
||||
it('should force transactions enabled when CHECK_BALANCE env is true and transactions is false', () => {
|
||||
process.env.CHECK_BALANCE = 'true';
|
||||
const appConfig = createTestAppConfig({
|
||||
transactions: { enabled: false },
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: true });
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
'Configuration warning: transactions.enabled=false is incompatible with balance.enabled=true. ' +
|
||||
'Transactions will be enabled to ensure balance tracking works correctly.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow transactions disabled when CHECK_BALANCE env is false', () => {
|
||||
process.env.CHECK_BALANCE = 'false';
|
||||
const appConfig = createTestAppConfig({
|
||||
transactions: { enabled: false },
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: false });
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle empty appConfig object', () => {
|
||||
const appConfig = createTestAppConfig();
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: true });
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle appConfig with null balance', () => {
|
||||
const appConfig = createTestAppConfig({
|
||||
transactions: { enabled: false },
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
balance: null as any,
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: false });
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle appConfig with undefined balance', () => {
|
||||
const appConfig = createTestAppConfig({
|
||||
transactions: { enabled: false },
|
||||
balance: undefined,
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: false });
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle appConfig with balance enabled undefined', () => {
|
||||
const appConfig = createTestAppConfig({
|
||||
transactions: { enabled: false },
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
balance: { enabled: undefined as any },
|
||||
});
|
||||
const result = getTransactionsConfig(appConfig);
|
||||
expect(result).toEqual({ enabled: false });
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBalanceConfig', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
delete process.env.CHECK_BALANCE;
|
||||
delete process.env.START_BALANCE;
|
||||
});
|
||||
|
||||
describe('when appConfig is not provided', () => {
|
||||
it('should return config based on environment variables', () => {
|
||||
process.env.CHECK_BALANCE = 'true';
|
||||
process.env.START_BALANCE = '1000';
|
||||
const result = getBalanceConfig();
|
||||
expect(result).toEqual({
|
||||
enabled: true,
|
||||
startBalance: 1000,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty config when no env vars are set', () => {
|
||||
const result = getBalanceConfig();
|
||||
expect(result).toEqual({ enabled: false });
|
||||
});
|
||||
|
||||
it('should handle CHECK_BALANCE true without START_BALANCE', () => {
|
||||
process.env.CHECK_BALANCE = 'true';
|
||||
const result = getBalanceConfig();
|
||||
expect(result).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle START_BALANCE without CHECK_BALANCE', () => {
|
||||
process.env.START_BALANCE = '5000';
|
||||
const result = getBalanceConfig();
|
||||
expect(result).toEqual({
|
||||
enabled: false,
|
||||
startBalance: 5000,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when appConfig is provided', () => {
|
||||
it('should merge appConfig balance with env config', () => {
|
||||
process.env.CHECK_BALANCE = 'true';
|
||||
process.env.START_BALANCE = '1000';
|
||||
const appConfig = createTestAppConfig({
|
||||
balance: {
|
||||
enabled: false,
|
||||
startBalance: 2000,
|
||||
autoRefillEnabled: true,
|
||||
},
|
||||
});
|
||||
const result = getBalanceConfig(appConfig);
|
||||
expect(result).toEqual({
|
||||
enabled: false,
|
||||
startBalance: 2000,
|
||||
autoRefillEnabled: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use env config when appConfig balance is not provided', () => {
|
||||
process.env.CHECK_BALANCE = 'true';
|
||||
process.env.START_BALANCE = '3000';
|
||||
const appConfig = createTestAppConfig();
|
||||
const result = getBalanceConfig(appConfig);
|
||||
expect(result).toEqual({
|
||||
enabled: true,
|
||||
startBalance: 3000,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle appConfig with null balance', () => {
|
||||
process.env.CHECK_BALANCE = 'true';
|
||||
const appConfig = createTestAppConfig({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
balance: null as any,
|
||||
});
|
||||
const result = getBalanceConfig(appConfig);
|
||||
expect(result).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import { EModelEndpoint, removeNullishValues } from 'librechat-data-provider';
|
||||
import type { TCustomConfig, TEndpoint } from 'librechat-data-provider';
|
||||
import type { TCustomConfig, TEndpoint, TTransactionsConfig } from 'librechat-data-provider';
|
||||
import type { AppConfig } from '~/types';
|
||||
import { isEnabled, normalizeEndpointName } from '~/utils';
|
||||
import { logger } from '@librechat/data-schemas';
|
||||
|
||||
/**
|
||||
* Retrieves the balance configuration object
|
||||
|
|
@ -20,6 +21,32 @@ export function getBalanceConfig(appConfig?: AppConfig): Partial<TCustomConfig['
|
|||
return { ...config, ...(appConfig?.['balance'] ?? {}) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the transactions configuration object
|
||||
* */
|
||||
export function getTransactionsConfig(appConfig?: AppConfig): TTransactionsConfig {
|
||||
const defaultConfig: TTransactionsConfig = { enabled: true };
|
||||
|
||||
if (!appConfig) {
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
const transactionsConfig = appConfig?.['transactions'] ?? defaultConfig;
|
||||
const balanceConfig = getBalanceConfig(appConfig);
|
||||
|
||||
// If balance is enabled but transactions are disabled, force transactions to be enabled
|
||||
// and log a warning
|
||||
if (balanceConfig?.enabled && !transactionsConfig.enabled) {
|
||||
logger.warn(
|
||||
'Configuration warning: transactions.enabled=false is incompatible with balance.enabled=true. ' +
|
||||
'Transactions will be enabled to ensure balance tracking works correctly.',
|
||||
);
|
||||
return { ...transactionsConfig, enabled: true };
|
||||
}
|
||||
|
||||
return transactionsConfig;
|
||||
}
|
||||
|
||||
export const getCustomEndpointConfig = ({
|
||||
endpoint,
|
||||
appConfig,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ export interface AppConfig {
|
|||
turnstileConfig?: TCustomConfig['turnstile'];
|
||||
/** Balance configuration */
|
||||
balance?: TCustomConfig['balance'];
|
||||
/** Transactions configuration */
|
||||
transactions?: TCustomConfig['transactions'];
|
||||
/** Speech configuration */
|
||||
speech?: TCustomConfig['speech'];
|
||||
/** MCP server configuration */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue