mirror of
https://github.com/labring/FastGPT.git
synced 2026-01-15 06:04:52 +08:00
V4.14.4 features (#6036)
* feat: add query optimize and bill (#6021) * add query optimize and bill * perf: query extension * fix: embe model * remove log * remove log * fix: test --------- Co-authored-by: xxyyh <2289112474@qq> Co-authored-by: archer <545436317@qq.com> * feat: notice (#6013) * feat: record user's language * feat: notice points/dataset indexes; support count limit; update docker-compose.yml * fix: ts error * feat: send auth code i18n * chore: dataset notice limit * chore: adjust * fix: ts * fix: countLimit race condition; i18n en-prefix locale fallback to en --------- Co-authored-by: archer <545436317@qq.com> * perf: comment * perf: send inform code * fix: type error (#6029) * feat: add ip region for chat logs (#6010) * feat: add ip region for chat logs * refactor: use Geolite2.mmdb * fix: export chat logs * fix: return location directly * test: add unit test * perf: log show ip data * adjust commercial plans (#6008) * plan frontend * plan limit * coupon * discount coupon * fix * type * fix audit * type * plan name * legacy plan * track * feat: add discount coupon * fix * fix discount coupon * openapi * type * type * env * api type * fix * fix: simple agent plugin input & agent dashboard card (#6034) * refactor: remove gridfs (#6031) * fix: replace gridfs multer operations with s3 compatible ops * wip: s3 features * refactor: remove gridfs * fix * perf: mock test * doc * doc * doc * fix: test * fix: s3 * fix: mock s3 * remove invalid config * fix: init query extension * initv4144 (#6037) * chore: initv4144 * fix * version * fix: new plans (#6039) * fix: new plans * qr modal tip * fix: buffer raw text filename (#6040) * fix: initv4144 (#6041) * fix: pay refresh (#6042) * fix: migration shell * rename collection * clear timerlock * clear timerlock * perf: faq * perf: bill schema * fix: openapi * doc * fix: share var render * feat: delete dataset queue * plan usage display (#6043) * plan usage display * text * fix * fix: ts * perf: remove invalid code * perf: init shell * doc * perf: rename field * perf: avatar presign * init * custom plan text (#6045) * fix plans * fix * fixed * computed --------- Co-authored-by: archer <545436317@qq.com> * init shell * plan text & price page back button (#6046) * init * index * delete dataset * delete dataset * perf: delete dataset * init --------- Co-authored-by: YeYuheng <57035043+YYH211@users.noreply.github.com> Co-authored-by: xxyyh <2289112474@qq> Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: Roy <whoeverimf5@gmail.com> Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
23
test/mocks/common/bullmq.ts
Normal file
23
test/mocks/common/bullmq.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { vi } from 'vitest';
|
||||
|
||||
// Mock BullMQ to prevent queue connection errors
|
||||
vi.mock('@fastgpt/service/common/bullmq', async (importOriginal) => {
|
||||
const actual = (await importOriginal()) as any;
|
||||
|
||||
const mockQueue = {
|
||||
add: vi.fn().mockResolvedValue({ id: '1' }),
|
||||
close: vi.fn().mockResolvedValue(undefined),
|
||||
on: vi.fn()
|
||||
};
|
||||
|
||||
const mockWorker = {
|
||||
close: vi.fn().mockResolvedValue(undefined),
|
||||
on: vi.fn()
|
||||
};
|
||||
|
||||
return {
|
||||
...actual,
|
||||
getQueue: vi.fn(() => mockQueue),
|
||||
getWorker: vi.fn(() => mockWorker)
|
||||
};
|
||||
});
|
||||
10
test/mocks/common/geo.ts
Normal file
10
test/mocks/common/geo.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import path from 'node:path';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
vi.mock('@fastgpt/service/common/geo/constants', async (importOriginal) => {
|
||||
const actual = (await importOriginal()) as any;
|
||||
return {
|
||||
...actual,
|
||||
dbPath: path.join(process.cwd(), 'projects/app/data/GeoLite2-City.mmdb')
|
||||
};
|
||||
});
|
||||
19
test/mocks/common/mongo.ts
Normal file
19
test/mocks/common/mongo.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { vi } from 'vitest';
|
||||
import { randomUUID } from 'crypto';
|
||||
import type { Mongoose } from '@fastgpt/service/common/mongo';
|
||||
|
||||
/**
|
||||
* Mock MongoDB connection for testing
|
||||
* Creates a unique database for each test run and drops it on connection
|
||||
*/
|
||||
vi.mock(import('@fastgpt/service/common/mongo/init'), async (importOriginal: any) => {
|
||||
const mod = await importOriginal();
|
||||
return {
|
||||
...mod,
|
||||
connectMongo: async (props: { db: Mongoose; url: string; connectedCb?: () => void }) => {
|
||||
const { db, url } = props;
|
||||
await db.connect(url, { dbName: randomUUID() });
|
||||
await db.connection.db?.dropDatabase();
|
||||
}
|
||||
};
|
||||
});
|
||||
81
test/mocks/common/redis.ts
Normal file
81
test/mocks/common/redis.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { vi } from 'vitest';
|
||||
|
||||
// Create a comprehensive mock Redis client factory
|
||||
const createMockRedisClient = () => ({
|
||||
// Connection methods
|
||||
on: vi.fn().mockReturnThis(),
|
||||
connect: vi.fn().mockResolvedValue(undefined),
|
||||
disconnect: vi.fn().mockResolvedValue(undefined),
|
||||
quit: vi.fn().mockResolvedValue('OK'),
|
||||
duplicate: vi.fn(function (this: any) {
|
||||
return createMockRedisClient();
|
||||
}),
|
||||
|
||||
// Key-value operations
|
||||
get: vi.fn().mockResolvedValue(null),
|
||||
set: vi.fn().mockResolvedValue('OK'),
|
||||
del: vi.fn().mockResolvedValue(1),
|
||||
exists: vi.fn().mockResolvedValue(0),
|
||||
keys: vi.fn().mockResolvedValue([]),
|
||||
|
||||
// Hash operations
|
||||
hget: vi.fn().mockResolvedValue(null),
|
||||
hset: vi.fn().mockResolvedValue(1),
|
||||
hdel: vi.fn().mockResolvedValue(1),
|
||||
hgetall: vi.fn().mockResolvedValue({}),
|
||||
hmset: vi.fn().mockResolvedValue('OK'),
|
||||
|
||||
// Expiry operations
|
||||
expire: vi.fn().mockResolvedValue(1),
|
||||
ttl: vi.fn().mockResolvedValue(-1),
|
||||
expireat: vi.fn().mockResolvedValue(1),
|
||||
|
||||
// Increment operations
|
||||
incr: vi.fn().mockResolvedValue(1),
|
||||
decr: vi.fn().mockResolvedValue(1),
|
||||
incrby: vi.fn().mockResolvedValue(1),
|
||||
decrby: vi.fn().mockResolvedValue(1),
|
||||
incrbyfloat: vi.fn().mockResolvedValue(1),
|
||||
|
||||
// Server commands
|
||||
info: vi.fn().mockResolvedValue(''),
|
||||
ping: vi.fn().mockResolvedValue('PONG'),
|
||||
flushdb: vi.fn().mockResolvedValue('OK'),
|
||||
|
||||
// List operations
|
||||
lpush: vi.fn().mockResolvedValue(1),
|
||||
rpush: vi.fn().mockResolvedValue(1),
|
||||
lpop: vi.fn().mockResolvedValue(null),
|
||||
rpop: vi.fn().mockResolvedValue(null),
|
||||
llen: vi.fn().mockResolvedValue(0),
|
||||
|
||||
// Set operations
|
||||
sadd: vi.fn().mockResolvedValue(1),
|
||||
srem: vi.fn().mockResolvedValue(1),
|
||||
smembers: vi.fn().mockResolvedValue([]),
|
||||
sismember: vi.fn().mockResolvedValue(0)
|
||||
});
|
||||
|
||||
// Mock Redis connections to prevent connection errors in tests
|
||||
vi.mock('@fastgpt/service/common/redis', async (importOriginal) => {
|
||||
const actual = (await importOriginal()) as any;
|
||||
|
||||
return {
|
||||
...actual,
|
||||
newQueueRedisConnection: vi.fn(createMockRedisClient),
|
||||
newWorkerRedisConnection: vi.fn(createMockRedisClient),
|
||||
getGlobalRedisConnection: vi.fn(() => {
|
||||
if (!global.mockRedisClient) {
|
||||
global.mockRedisClient = createMockRedisClient();
|
||||
}
|
||||
return global.mockRedisClient;
|
||||
}),
|
||||
initRedisClient: vi.fn().mockResolvedValue(createMockRedisClient())
|
||||
};
|
||||
});
|
||||
|
||||
// Initialize global.redisClient with mock before any module imports it
|
||||
// This prevents getGlobalRedisConnection() from creating a real Redis client
|
||||
if (!global.redisClient) {
|
||||
global.redisClient = createMockRedisClient() as any;
|
||||
}
|
||||
167
test/mocks/common/s3.ts
Normal file
167
test/mocks/common/s3.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import { vi } from 'vitest';
|
||||
|
||||
// Create mock S3 bucket object for global use
|
||||
const createMockS3Bucket = () => ({
|
||||
name: 'mock-bucket',
|
||||
client: {},
|
||||
externalClient: {},
|
||||
exist: vi.fn().mockResolvedValue(true),
|
||||
delete: vi.fn().mockResolvedValue(undefined),
|
||||
putObject: vi.fn().mockResolvedValue(undefined),
|
||||
getObject: vi.fn().mockResolvedValue(null),
|
||||
statObject: vi.fn().mockResolvedValue({ size: 0, etag: 'mock-etag' }),
|
||||
move: vi.fn().mockResolvedValue(undefined),
|
||||
copy: vi.fn().mockResolvedValue(undefined),
|
||||
addDeleteJob: vi.fn().mockResolvedValue(undefined),
|
||||
createPostPresignedUrl: vi.fn().mockResolvedValue({
|
||||
url: 'http://localhost:9000/mock-bucket',
|
||||
fields: { key: 'mock-key' },
|
||||
maxSize: 100 * 1024 * 1024
|
||||
}),
|
||||
createExternalUrl: vi.fn().mockResolvedValue('http://localhost:9000/mock-bucket/mock-key'),
|
||||
createGetPresignedUrl: vi.fn().mockResolvedValue('http://localhost:9000/mock-bucket/mock-key'),
|
||||
createPublicUrl: vi.fn((key: string) => `http://localhost:9000/mock-bucket/${key}`)
|
||||
});
|
||||
|
||||
// Initialize global s3BucketMap early to prevent any real S3 connections
|
||||
const mockBucket = createMockS3Bucket();
|
||||
global.s3BucketMap = {
|
||||
'fastgpt-public': mockBucket,
|
||||
'fastgpt-private': mockBucket
|
||||
} as any;
|
||||
|
||||
// Mock minio Client to prevent real connections
|
||||
const createMockMinioClient = vi.hoisted(() => {
|
||||
return vi.fn().mockImplementation(() => ({
|
||||
bucketExists: vi.fn().mockResolvedValue(true),
|
||||
makeBucket: vi.fn().mockResolvedValue(undefined),
|
||||
setBucketPolicy: vi.fn().mockResolvedValue(undefined),
|
||||
copyObject: vi.fn().mockResolvedValue(undefined),
|
||||
removeObject: vi.fn().mockResolvedValue(undefined),
|
||||
putObject: vi.fn().mockResolvedValue({ etag: 'mock-etag' }),
|
||||
getObject: vi.fn().mockResolvedValue(null),
|
||||
statObject: vi.fn().mockResolvedValue({ size: 0, etag: 'mock-etag' }),
|
||||
presignedGetObject: vi.fn().mockResolvedValue('http://localhost:9000/mock-bucket/mock-object'),
|
||||
presignedPostPolicy: vi.fn().mockResolvedValue({
|
||||
postURL: 'http://localhost:9000/mock-bucket',
|
||||
formData: { key: 'mock-key' }
|
||||
}),
|
||||
newPostPolicy: vi.fn(() => ({
|
||||
setKey: vi.fn().mockReturnThis(),
|
||||
setBucket: vi.fn().mockReturnThis(),
|
||||
setContentType: vi.fn().mockReturnThis(),
|
||||
setContentLengthRange: vi.fn().mockReturnThis(),
|
||||
setExpires: vi.fn().mockReturnThis(),
|
||||
setUserMetaData: vi.fn().mockReturnThis()
|
||||
}))
|
||||
}));
|
||||
});
|
||||
|
||||
vi.mock('minio', () => ({
|
||||
Client: createMockMinioClient(),
|
||||
S3Error: class S3Error extends Error {},
|
||||
CopyConditions: vi.fn()
|
||||
}));
|
||||
|
||||
// Simplified S3 bucket class mock
|
||||
const createMockBucketClass = (defaultName: string) => {
|
||||
return class MockS3Bucket {
|
||||
public name: string;
|
||||
public options: any;
|
||||
public client = {};
|
||||
public externalClient = {};
|
||||
|
||||
constructor(bucket?: string, options?: any) {
|
||||
this.name = bucket || defaultName;
|
||||
this.options = options || {};
|
||||
}
|
||||
|
||||
async exist() {
|
||||
return true;
|
||||
}
|
||||
async delete() {}
|
||||
async putObject() {}
|
||||
async getObject() {
|
||||
return null;
|
||||
}
|
||||
async statObject() {
|
||||
return { size: 0, etag: 'mock-etag' };
|
||||
}
|
||||
async move() {}
|
||||
async copy() {}
|
||||
async addDeleteJob() {}
|
||||
async createPostPresignedUrl(params: any, options?: any) {
|
||||
return {
|
||||
url: 'http://localhost:9000/mock-bucket',
|
||||
fields: { key: `mock/${params.teamId || 'test'}/${params.filename}` },
|
||||
maxSize: (options?.maxFileSize || 100) * 1024 * 1024
|
||||
};
|
||||
}
|
||||
async createExternalUrl(params: any) {
|
||||
return `http://localhost:9000/mock-bucket/${params.key}`;
|
||||
}
|
||||
async createGetPresignedUrl(params: any) {
|
||||
return `http://localhost:9000/mock-bucket/${params.key}`;
|
||||
}
|
||||
createPublicUrl(objectKey: string) {
|
||||
return `http://localhost:9000/mock-bucket/${objectKey}`;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
vi.mock('@fastgpt/service/common/s3/buckets/base', () => ({
|
||||
S3BaseBucket: createMockBucketClass('fastgpt-bucket')
|
||||
}));
|
||||
|
||||
vi.mock('@fastgpt/service/common/s3/buckets/public', () => ({
|
||||
S3PublicBucket: createMockBucketClass('fastgpt-public')
|
||||
}));
|
||||
|
||||
vi.mock('@fastgpt/service/common/s3/buckets/private', () => ({
|
||||
S3PrivateBucket: createMockBucketClass('fastgpt-private')
|
||||
}));
|
||||
|
||||
// Mock S3 source modules
|
||||
vi.mock('@fastgpt/service/common/s3/sources/avatar', () => ({
|
||||
getS3AvatarSource: vi.fn(() => ({
|
||||
prefix: '/avatar/',
|
||||
createUploadAvatarURL: vi.fn().mockResolvedValue({
|
||||
url: 'http://localhost:9000/mock-bucket',
|
||||
fields: { key: 'mock-key' },
|
||||
maxSize: 5 * 1024 * 1024
|
||||
}),
|
||||
createPublicUrl: vi.fn((key: string) => `http://localhost:9000/mock-bucket/${key}`),
|
||||
removeAvatarTTL: vi.fn().mockResolvedValue(undefined),
|
||||
deleteAvatar: vi.fn().mockResolvedValue(undefined),
|
||||
refreshAvatar: vi.fn().mockResolvedValue(undefined),
|
||||
copyAvatar: vi.fn().mockResolvedValue('http://localhost:9000/mock-bucket/mock-avatar')
|
||||
}))
|
||||
}));
|
||||
|
||||
vi.mock('@fastgpt/service/common/s3/sources/dataset/index', () => ({
|
||||
getS3DatasetSource: vi.fn(() => ({
|
||||
createUploadDatasetFileURL: vi.fn().mockResolvedValue({
|
||||
url: 'http://localhost:9000/mock-bucket',
|
||||
fields: { key: 'mock-key' },
|
||||
maxSize: 500 * 1024 * 1024
|
||||
}),
|
||||
deleteDatasetFile: vi.fn().mockResolvedValue(undefined)
|
||||
})),
|
||||
S3DatasetSource: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('@fastgpt/service/common/s3/sources/chat/index', () => ({
|
||||
S3ChatSource: vi.fn()
|
||||
}));
|
||||
|
||||
// Mock S3 initialization
|
||||
vi.mock('@fastgpt/service/common/s3', () => ({
|
||||
initS3Buckets: vi.fn(() => {
|
||||
const mockBucket = createMockS3Bucket();
|
||||
global.s3BucketMap = {
|
||||
'fastgpt-public': mockBucket,
|
||||
'fastgpt-private': mockBucket
|
||||
} as any;
|
||||
}),
|
||||
initS3MQWorker: vi.fn().mockResolvedValue(undefined)
|
||||
}));
|
||||
33
test/mocks/common/system.ts
Normal file
33
test/mocks/common/system.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { vi } from 'vitest';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
/**
|
||||
* Mock system configuration for testing
|
||||
*/
|
||||
vi.mock(import('@/service/common/system'), async (importOriginal) => {
|
||||
const mod = await importOriginal();
|
||||
return {
|
||||
...mod,
|
||||
getSystemVersion: async () => {
|
||||
return '0.0.0';
|
||||
},
|
||||
readConfigData: async () => {
|
||||
return readFileSync('projects/app/data/config.json', 'utf-8');
|
||||
},
|
||||
initSystemConfig: async () => {
|
||||
// read env from projects/app/.env
|
||||
const str = readFileSync('projects/app/.env.local', 'utf-8');
|
||||
const lines = str.split('\n');
|
||||
const systemEnv: Record<string, string> = {};
|
||||
for (const line of lines) {
|
||||
const [key, value] = line.split('=');
|
||||
if (key && value) {
|
||||
systemEnv[key] = value;
|
||||
}
|
||||
}
|
||||
global.systemEnv = systemEnv as any;
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
});
|
||||
19
test/mocks/common/tracks.ts
Normal file
19
test/mocks/common/tracks.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { vi } from 'vitest';
|
||||
|
||||
// Mock tracking utilities - automatically mock all methods
|
||||
vi.mock('@fastgpt/service/common/middle/tracks/utils', async (importOriginal) => {
|
||||
const actual = (await importOriginal()) as any;
|
||||
|
||||
// Get all methods from original pushTrack and mock them
|
||||
const mockedPushTrack: Record<string, any> = {};
|
||||
if (actual.pushTrack) {
|
||||
Object.keys(actual.pushTrack).forEach((key) => {
|
||||
mockedPushTrack[key] = vi.fn();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...actual,
|
||||
pushTrack: mockedPushTrack
|
||||
};
|
||||
});
|
||||
79
test/mocks/common/vector.ts
Normal file
79
test/mocks/common/vector.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { vi } from 'vitest';
|
||||
|
||||
/**
|
||||
* Mock Vector Controller for testing
|
||||
*/
|
||||
|
||||
export const mockVectorInsert = vi.fn().mockResolvedValue({
|
||||
insertIds: ['id_1', 'id_2', 'id_3']
|
||||
});
|
||||
|
||||
export const mockVectorDelete = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
export const mockVectorEmbRecall = vi.fn().mockResolvedValue({
|
||||
results: [
|
||||
{ id: '1', collectionId: 'col_1', score: 0.95 },
|
||||
{ id: '2', collectionId: 'col_2', score: 0.85 }
|
||||
]
|
||||
});
|
||||
|
||||
export const mockVectorInit = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
export const mockGetVectorDataByTime = vi.fn().mockResolvedValue([
|
||||
{ id: '1', teamId: 'team_1', datasetId: 'dataset_1' },
|
||||
{ id: '2', teamId: 'team_1', datasetId: 'dataset_2' }
|
||||
]);
|
||||
|
||||
export const mockGetVectorCountByTeamId = vi.fn().mockResolvedValue(100);
|
||||
|
||||
export const mockGetVectorCountByDatasetId = vi.fn().mockResolvedValue(50);
|
||||
|
||||
export const mockGetVectorCountByCollectionId = vi.fn().mockResolvedValue(25);
|
||||
|
||||
const MockVectorCtrl = vi.fn().mockImplementation(() => ({
|
||||
init: mockVectorInit,
|
||||
insert: mockVectorInsert,
|
||||
delete: mockVectorDelete,
|
||||
embRecall: mockVectorEmbRecall,
|
||||
getVectorDataByTime: mockGetVectorDataByTime,
|
||||
getVectorCountByTeamId: mockGetVectorCountByTeamId,
|
||||
getVectorCountByDatasetId: mockGetVectorCountByDatasetId,
|
||||
getVectorCountByCollectionId: mockGetVectorCountByCollectionId
|
||||
}));
|
||||
|
||||
// Mock PgVectorCtrl
|
||||
vi.mock('@fastgpt/service/common/vectorDB/pg', () => ({
|
||||
PgVectorCtrl: MockVectorCtrl
|
||||
}));
|
||||
|
||||
// Mock ObVectorCtrl
|
||||
vi.mock('@fastgpt/service/common/vectorDB/oceanbase', () => ({
|
||||
ObVectorCtrl: MockVectorCtrl
|
||||
}));
|
||||
|
||||
// Mock MilvusCtrl
|
||||
vi.mock('@fastgpt/service/common/vectorDB/milvus', () => ({
|
||||
MilvusCtrl: MockVectorCtrl
|
||||
}));
|
||||
|
||||
// Mock constants - use PG_ADDRESS to ensure PgVectorCtrl is used
|
||||
vi.mock('@fastgpt/service/common/vectorDB/constants', () => ({
|
||||
DatasetVectorDbName: 'fastgpt',
|
||||
DatasetVectorTableName: 'modeldata',
|
||||
PG_ADDRESS: 'mock://pg',
|
||||
OCEANBASE_ADDRESS: undefined,
|
||||
MILVUS_ADDRESS: undefined,
|
||||
MILVUS_TOKEN: undefined
|
||||
}));
|
||||
|
||||
// Export mocks for test assertions
|
||||
export const resetVectorMocks = () => {
|
||||
mockVectorInsert.mockClear();
|
||||
mockVectorDelete.mockClear();
|
||||
mockVectorEmbRecall.mockClear();
|
||||
mockVectorInit.mockClear();
|
||||
mockGetVectorDataByTime.mockClear();
|
||||
mockGetVectorCountByTeamId.mockClear();
|
||||
mockGetVectorCountByDatasetId.mockClear();
|
||||
mockGetVectorCountByCollectionId.mockClear();
|
||||
};
|
||||
Reference in New Issue
Block a user