mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 19:50:09 +00:00
🐛 fix(database): prevent empty array insertion in aiModel batch operations (#9491)
* 🐛 fix(database): prevent empty array insertion in aiModel batch operations - Add validation to batchUpdateAiModels to return early if models array is empty - Add validation to batchToggleAiModels to return early if models array is empty - Add validation to updateModelsOrder to return early if sortMap array is empty - Fixes 'values() must be called with at least one value' error when OpenRouter returns empty model list Fixes #9429 Co-authored-by: Arvin Xu <arvinxx@users.noreply.github.com> * ✅ test(database): add tests for empty array validation in aiModel batch operations - Add test for batchUpdateAiModels with empty array returning empty result - Add test for batchToggleAiModels with empty array returning early - Add test for updateModelsOrder with empty sortMap returning early These tests verify the fix for issue #9429 where empty arrays caused "values() must be called with at least one value" database errors. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Arvin Xu <arvinxx@users.noreply.github.com> * 🐛 fix(test): remove invalid sort property access in aiModel test - Remove test assertion accessing sort property on AiProviderModelListItem - AiProviderModelListItem interface doesn't include sort property - Fix TypeScript error: Property 'sort' does not exist on type 'AiProviderModelListItem' 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Arvin Xu <arvinxx@users.noreply.github.com> * ♻️ refactor(database): extract shared validation helper for empty arrays - Add private isEmptyArray() helper method to AiModelModel class - Replace duplicate empty array checks in batch methods with shared helper - Improve code maintainability and reduce duplication - Address Sourcery AI feedback for better code organization 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Arvin Xu <arvinxx@users.noreply.github.com> * 🐛 fix(database): respect enabled parameter in aiModel create method The create method was forcing enabled: true regardless of input. This fix allows explicit enabled: false while maintaining true as default. Fixes failing test: batchToggleAiModels empty array validation. Co-authored-by: Arvin Xu <arvinxx@users.noreply.github.com> --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Arvin Xu <arvinxx@users.noreply.github.com>
This commit is contained in:
@@ -254,6 +254,15 @@ describe('AiModelModel', () => {
|
||||
expect(allModels.find((m) => m.id === 'existing-model')?.displayName).toBe('Old Name');
|
||||
expect(allModels.find((m) => m.id === 'new-model')?.displayName).toBe('New Model');
|
||||
});
|
||||
|
||||
it('should return empty array when models array is empty', async () => {
|
||||
const result = await aiProviderModel.batchUpdateAiModels('openai', []);
|
||||
expect(result).toEqual([]);
|
||||
|
||||
// Verify no models were created
|
||||
const allModels = await aiProviderModel.query();
|
||||
expect(allModels).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('batchToggleAiModels', () => {
|
||||
@@ -274,6 +283,23 @@ describe('AiModelModel', () => {
|
||||
const models = await aiProviderModel.query();
|
||||
expect(models.every((m) => m.enabled)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return early when models array is empty', async () => {
|
||||
// Create an initial model to verify it's not affected
|
||||
await aiProviderModel.create({
|
||||
id: 'model1',
|
||||
providerId: 'openai',
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
const result = await aiProviderModel.batchToggleAiModels('openai', [], true);
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
// Verify existing models were not affected
|
||||
const models = await aiProviderModel.query();
|
||||
expect(models).toHaveLength(1);
|
||||
expect(models[0].enabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearRemoteModels', () => {
|
||||
@@ -324,5 +350,22 @@ describe('AiModelModel', () => {
|
||||
const model = await aiProviderModel.findById('image-model');
|
||||
expect(model?.type).toBe('image');
|
||||
});
|
||||
|
||||
it('should return early when sortMap array is empty', async () => {
|
||||
// Create an initial model to verify it's not affected
|
||||
await aiProviderModel.create({
|
||||
id: 'model1',
|
||||
providerId: 'openai',
|
||||
sort: 1,
|
||||
});
|
||||
|
||||
const result = await aiProviderModel.updateModelsOrder('openai', []);
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
// Verify existing models were not affected (check by querying the created model directly)
|
||||
const models = await aiProviderModel.getModelListByProviderId('openai');
|
||||
expect(models).toHaveLength(1);
|
||||
expect(models[0].id).toBe('model1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,12 +19,21 @@ export class AiModelModel {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to validate if array is empty and return early if needed
|
||||
* @param array - Array to validate
|
||||
* @returns true if array is empty, false otherwise
|
||||
*/
|
||||
private isEmptyArray(array: unknown[]): boolean {
|
||||
return array.length === 0;
|
||||
}
|
||||
|
||||
create = async (params: NewAiModelItem) => {
|
||||
const [result] = await this.db
|
||||
.insert(aiModels)
|
||||
.values({
|
||||
...params,
|
||||
enabled: true, // enabled by default
|
||||
enabled: params.enabled ?? true, // enabled by default, but respect explicit value
|
||||
source: AiModelSourceEnum.Custom,
|
||||
userId: this.userId,
|
||||
})
|
||||
@@ -148,6 +157,11 @@ export class AiModelModel {
|
||||
};
|
||||
|
||||
batchUpdateAiModels = async (providerId: string, models: AiProviderModelListItem[]) => {
|
||||
// Early return if models array is empty to prevent database insertion error
|
||||
if (this.isEmptyArray(models)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const records = models.map(({ id, ...model }) => ({
|
||||
...model,
|
||||
id,
|
||||
@@ -166,6 +180,11 @@ export class AiModelModel {
|
||||
};
|
||||
|
||||
batchToggleAiModels = async (providerId: string, models: string[], enabled: boolean) => {
|
||||
// Early return if models array is empty to prevent database insertion error
|
||||
if (this.isEmptyArray(models)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.db.transaction(async (trx) => {
|
||||
// 1. insert models that are not in the db
|
||||
const insertedRecords = await trx
|
||||
@@ -222,6 +241,11 @@ export class AiModelModel {
|
||||
}
|
||||
|
||||
updateModelsOrder = async (providerId: string, sortMap: AiModelSortMap[]) => {
|
||||
// Early return if sortMap array is empty
|
||||
if (this.isEmptyArray(sortMap)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.db.transaction(async (tx) => {
|
||||
const updates = sortMap.map(({ id, sort, type }) => {
|
||||
const now = new Date();
|
||||
|
||||
Reference in New Issue
Block a user