mirror of
https://github.com/labring/FastGPT.git
synced 2025-12-25 20:02:47 +00:00
add savechat test (#6118)
Some checks are pending
Document deploy / sync-images (push) Waiting to run
Document deploy / generate-timestamp (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.cn suffix:cn]) (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.io suffix:io]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.cn kube_config:KUBE_CONFIG_CN suffix:cn]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.io kube_config:KUBE_CONFIG_IO suffix:io]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / get-vars (push) Waiting to run
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:amd64 runs-on:ubuntu-24.04]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:arm64 runs-on:ubuntu-24.04-arm]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / release-fastgpt-images (push) Blocked by required conditions
Some checks are pending
Document deploy / sync-images (push) Waiting to run
Document deploy / generate-timestamp (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.cn suffix:cn]) (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.io suffix:io]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.cn kube_config:KUBE_CONFIG_CN suffix:cn]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.io kube_config:KUBE_CONFIG_IO suffix:io]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / get-vars (push) Waiting to run
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:amd64 runs-on:ubuntu-24.04]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:arm64 runs-on:ubuntu-24.04-arm]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / release-fastgpt-images (push) Blocked by required conditions
This commit is contained in:
parent
c6fe6f18da
commit
ea7c37745a
|
|
@ -0,0 +1,893 @@
|
|||
import { describe, expect, it, beforeEach } from 'vitest';
|
||||
import { saveChat, updateInteractiveChat } from '@fastgpt/service/core/chat/saveChat';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { MongoAppChatLog } from '@fastgpt/service/core/app/logs/chatLogsSchema';
|
||||
import { MongoChatItemResponse } from '@fastgpt/service/core/chat/chatItemResponseSchema';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import type { Props } from '@fastgpt/service/core/chat/saveChat';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
|
||||
const createMockProps = (
|
||||
overrides?: Partial<Props>,
|
||||
ids?: { appId?: string; teamId?: string; tmbId?: string }
|
||||
): Props => ({
|
||||
chatId: 'test-chat-id',
|
||||
appId: ids?.appId || '67e0d5535c02d1d5cdede71f',
|
||||
teamId: ids?.teamId || '654a4107c32f3bf5f998452f',
|
||||
tmbId: ids?.tmbId || '65ab7007462ada7dbb899948',
|
||||
nodes: [
|
||||
{
|
||||
nodeId: 'node-1',
|
||||
name: 'test-node',
|
||||
flowNodeType: FlowNodeTypeEnum.systemConfig,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
}
|
||||
],
|
||||
isUpdateUseTime: true,
|
||||
newTitle: 'Test Chat',
|
||||
source: 'online' as any,
|
||||
userContent: {
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: 'Hello, how are you?'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
aiContent: {
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: 'I am doing well, thank you!'
|
||||
}
|
||||
}
|
||||
],
|
||||
responseData: []
|
||||
},
|
||||
durationSeconds: 2.5,
|
||||
...overrides
|
||||
});
|
||||
|
||||
describe('saveChat', () => {
|
||||
let testAppId: string;
|
||||
let testTeamId: string;
|
||||
let testTmbId: string;
|
||||
let testUserId: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create test user
|
||||
const user = await MongoUser.create({
|
||||
username: 'test-user',
|
||||
password: 'test-password'
|
||||
});
|
||||
testUserId = String(user._id);
|
||||
|
||||
// Create test team
|
||||
const team = await MongoTeam.create({
|
||||
name: 'Test Team',
|
||||
ownerId: user._id,
|
||||
avatar: 'test-avatar',
|
||||
createTime: new Date(),
|
||||
balance: 0,
|
||||
teamDomain: 'test-domain'
|
||||
});
|
||||
testTeamId = String(team._id);
|
||||
|
||||
// Create team member
|
||||
const teamMember = await MongoTeamMember.create({
|
||||
teamId: team._id,
|
||||
userId: user._id,
|
||||
name: 'Test Member',
|
||||
role: TeamMemberRoleEnum.owner,
|
||||
status: 'active',
|
||||
createTime: new Date(),
|
||||
defaultTeam: true
|
||||
});
|
||||
testTmbId = String(teamMember._id);
|
||||
|
||||
// Create a test app for use in tests
|
||||
const app = await MongoApp.create({
|
||||
name: 'Test App',
|
||||
type: AppTypeEnum.simple,
|
||||
teamId: team._id,
|
||||
tmbId: teamMember._id,
|
||||
avatar: 'test-avatar',
|
||||
intro: 'Test intro'
|
||||
});
|
||||
testAppId = String(app._id);
|
||||
});
|
||||
|
||||
describe('saveChat function', () => {
|
||||
it('should skip saving if chatId is empty', async () => {
|
||||
const props = createMockProps(
|
||||
{ chatId: '' },
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
await saveChat(props);
|
||||
|
||||
const chatItems = await MongoChatItem.find({ appId: testAppId });
|
||||
expect(chatItems).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should skip saving if chatId is NO_RECORD_HISTORIES', async () => {
|
||||
const props = createMockProps(
|
||||
{ chatId: 'NO_RECORD_HISTORIES' },
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
await saveChat(props);
|
||||
|
||||
const chatItems = await MongoChatItem.find({ appId: testAppId });
|
||||
expect(chatItems).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should remove file URLs from user content before processing', async () => {
|
||||
const props = createMockProps({
|
||||
userContent: {
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.file,
|
||||
file: {
|
||||
type: 'image',
|
||||
name: 'test.jpg',
|
||||
url: 'https://example.com/test.jpg',
|
||||
key: 'file-key-123'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
await saveChat(props);
|
||||
|
||||
// Verify that the URL was removed
|
||||
expect(props.userContent.value[0].file?.url).toBe('');
|
||||
});
|
||||
|
||||
it('should create chat items and update chat record', async () => {
|
||||
const props = createMockProps({}, { appId: testAppId, teamId: testTeamId, tmbId: testTmbId });
|
||||
|
||||
await saveChat(props);
|
||||
|
||||
// Check chat items were created
|
||||
const chatItems = await MongoChatItem.find({ appId: testAppId, chatId: props.chatId });
|
||||
expect(chatItems).toHaveLength(2);
|
||||
|
||||
// Check human message
|
||||
const humanItem = chatItems.find((item) => item.obj === ChatRoleEnum.Human);
|
||||
expect(humanItem).toBeDefined();
|
||||
expect(humanItem?.value[0].text?.content).toBe('Hello, how are you?');
|
||||
|
||||
// Check AI message
|
||||
const aiItem = chatItems.find((item) => item.obj === ChatRoleEnum.AI);
|
||||
expect(aiItem).toBeDefined();
|
||||
expect(aiItem?.value[0].text?.content).toBe('I am doing well, thank you!');
|
||||
|
||||
// Check chat record
|
||||
const chat = await MongoChat.findOne({ appId: testAppId, chatId: props.chatId });
|
||||
expect(chat).toBeDefined();
|
||||
expect(chat?.title).toBe('Test Chat');
|
||||
expect(String(chat?.teamId)).toBe(props.teamId);
|
||||
});
|
||||
|
||||
it('should create chat item responses when responseData is provided', async () => {
|
||||
const props = createMockProps({
|
||||
aiContent: {
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Response' }
|
||||
}
|
||||
],
|
||||
responseData: [
|
||||
{
|
||||
nodeId: 'xx',
|
||||
id: 'xx',
|
||||
moduleType: FlowNodeTypeEnum.chatNode,
|
||||
moduleName: 'Chat',
|
||||
runningTime: 1.5,
|
||||
totalPoints: 10
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
await saveChat(props);
|
||||
|
||||
const responses = await MongoChatItemResponse.find({
|
||||
appId: testAppId,
|
||||
chatId: props.chatId
|
||||
});
|
||||
// ResponseData is only created when dataId exists on the AI chat item
|
||||
// Since we're using real database, check if responses were created
|
||||
if (responses.length > 0) {
|
||||
expect(responses[0].data.moduleType).toBe(FlowNodeTypeEnum.chatNode);
|
||||
expect(responses[0].data.totalPoints).toBe(10);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle dataset search node with quoteList', async () => {
|
||||
const quote = {
|
||||
id: 'quote-1',
|
||||
chunkIndex: 0,
|
||||
datasetId: 'dataset-1',
|
||||
collectionId: 'collection-1',
|
||||
sourceId: 'source-1',
|
||||
sourceName: 'doc.pdf',
|
||||
score: [{ type: 'embedding', value: 0.95, index: 0 }],
|
||||
q: 'What is AI?',
|
||||
a: 'AI stands for Artificial Intelligence...',
|
||||
updateTime: new Date()
|
||||
};
|
||||
const props = createMockProps(
|
||||
{
|
||||
aiContent: {
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Based on the search results...' }
|
||||
}
|
||||
],
|
||||
responseData: [
|
||||
{
|
||||
nodeId: 'xx',
|
||||
id: 'xx',
|
||||
moduleType: FlowNodeTypeEnum.datasetSearchNode,
|
||||
moduleName: 'Dataset Search',
|
||||
runningTime: 0.5,
|
||||
totalPoints: 5,
|
||||
quoteList: [quote]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await saveChat(props);
|
||||
|
||||
const responses = await MongoChatItemResponse.find({
|
||||
appId: testAppId,
|
||||
chatId: props.chatId
|
||||
});
|
||||
// ResponseData is only created when dataId exists on the AI chat item
|
||||
if (responses.length > 0) {
|
||||
expect(responses[0].data.quoteList).toBeDefined();
|
||||
expect(responses[0].data.quoteList?.[0]).toMatchObject({
|
||||
id: quote.id,
|
||||
chunkIndex: quote.chunkIndex,
|
||||
datasetId: quote.datasetId,
|
||||
collectionId: quote.collectionId,
|
||||
sourceId: quote.sourceId,
|
||||
sourceName: quote.sourceName,
|
||||
score: quote.score
|
||||
});
|
||||
// q and a should be removed
|
||||
expect(responses[0].data.quoteList?.[0]?.q).toBeUndefined();
|
||||
expect(responses[0].data.quoteList?.[0]?.a).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should update app use time when isUpdateUseTime is true', async () => {
|
||||
const beforeTime = new Date();
|
||||
|
||||
const props = createMockProps(
|
||||
{ isUpdateUseTime: true },
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await saveChat(props);
|
||||
|
||||
const app = await MongoApp.findById(testAppId);
|
||||
expect(app?.updateTime).toBeDefined();
|
||||
expect(app!.updateTime.getTime()).toBeGreaterThanOrEqual(beforeTime.getTime());
|
||||
});
|
||||
|
||||
it('should not update app use time when isUpdateUseTime is false', async () => {
|
||||
const app = await MongoApp.findById(testAppId);
|
||||
const originalUpdateTime = app!.updateTime;
|
||||
|
||||
const props = createMockProps(
|
||||
{ isUpdateUseTime: false },
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await saveChat(props);
|
||||
|
||||
const updatedApp = await MongoApp.findById(testAppId);
|
||||
expect(updatedApp!.updateTime.getTime()).toBe(originalUpdateTime.getTime());
|
||||
});
|
||||
|
||||
it('should create chat data log with error count when response has error', async () => {
|
||||
const props = createMockProps(
|
||||
{
|
||||
aiContent: {
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [],
|
||||
responseData: [
|
||||
{
|
||||
nodeId: 'xx',
|
||||
id: 'xx',
|
||||
moduleType: FlowNodeTypeEnum.chatNode,
|
||||
moduleName: 'Chat',
|
||||
runningTime: 1.0,
|
||||
totalPoints: 5,
|
||||
errorText: 'API rate limit exceeded'
|
||||
}
|
||||
]
|
||||
},
|
||||
errorMsg: 'API rate limit exceeded'
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await saveChat(props);
|
||||
|
||||
const logs = await MongoAppChatLog.find({ appId: testAppId, chatId: props.chatId });
|
||||
expect(logs).toHaveLength(1);
|
||||
expect(logs[0].errorCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should calculate total points from response data', async () => {
|
||||
const props = createMockProps(
|
||||
{
|
||||
aiContent: {
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [],
|
||||
responseData: [
|
||||
{
|
||||
nodeId: 'xx',
|
||||
id: 'xx',
|
||||
moduleType: FlowNodeTypeEnum.chatNode,
|
||||
moduleName: 'Chat',
|
||||
runningTime: 1.0,
|
||||
totalPoints: 10
|
||||
},
|
||||
{
|
||||
nodeId: '22',
|
||||
id: '33',
|
||||
moduleType: FlowNodeTypeEnum.datasetSearchNode,
|
||||
moduleName: 'Dataset Search',
|
||||
runningTime: 0.5,
|
||||
totalPoints: 5
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await saveChat(props);
|
||||
|
||||
const logs = await MongoAppChatLog.find({ appId: testAppId, chatId: props.chatId });
|
||||
expect(logs).toHaveLength(1);
|
||||
expect(logs[0].totalPoints).toBe(15);
|
||||
});
|
||||
|
||||
it('should merge metadata from existing chat', async () => {
|
||||
const props1 = createMockProps(
|
||||
{
|
||||
metadata: { source: 'web', version: '1.0' }
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await saveChat(props1);
|
||||
|
||||
const props2 = createMockProps(
|
||||
{
|
||||
chatId: props1.chatId,
|
||||
metadata: { user: 'test-user' }
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await saveChat(props2);
|
||||
|
||||
const chat = await MongoChat.findOne({ appId: testAppId, chatId: props1.chatId });
|
||||
expect(chat?.metadata).toMatchObject({
|
||||
source: 'web',
|
||||
version: '1.0',
|
||||
user: 'test-user'
|
||||
});
|
||||
});
|
||||
|
||||
it('should track duration seconds', async () => {
|
||||
const props = createMockProps(
|
||||
{
|
||||
durationSeconds: 3.75
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await saveChat(props);
|
||||
|
||||
const aiItem = await MongoChatItem.findOne({
|
||||
appId: testAppId,
|
||||
chatId: props.chatId,
|
||||
obj: ChatRoleEnum.AI
|
||||
});
|
||||
if (aiItem) {
|
||||
if ('durationSeconds' in aiItem) {
|
||||
expect(aiItem?.durationSeconds).toBe(3.75);
|
||||
} else {
|
||||
throw new Error('aiItem does not have durationSeconds');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should store citeCollectionIds from dataset search', async () => {
|
||||
const props = createMockProps(
|
||||
{
|
||||
aiContent: {
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [],
|
||||
responseData: [
|
||||
{
|
||||
nodeId: 'xx',
|
||||
id: 'xx',
|
||||
moduleType: FlowNodeTypeEnum.datasetSearchNode,
|
||||
moduleName: 'Dataset Search',
|
||||
runningTime: 0.5,
|
||||
totalPoints: 5,
|
||||
quoteList: [
|
||||
{
|
||||
id: 'quote-1',
|
||||
chunkIndex: 0,
|
||||
datasetId: 'dataset-1',
|
||||
collectionId: 'collection-1',
|
||||
sourceId: 'source-1',
|
||||
sourceName: 'doc1.pdf',
|
||||
score: [{ type: 'embedding', value: 0.95, index: 0 }],
|
||||
q: 'What is AI?',
|
||||
a: 'AI stands for Artificial Intelligence...',
|
||||
updateTime: new Date()
|
||||
},
|
||||
{
|
||||
id: 'quote-2',
|
||||
chunkIndex: 1,
|
||||
datasetId: 'dataset-1',
|
||||
collectionId: 'collection-2',
|
||||
sourceId: 'source-2',
|
||||
sourceName: 'doc2.pdf',
|
||||
score: [{ type: 'embedding', value: 0.85, index: 0 }],
|
||||
q: 'What is AI?',
|
||||
a: 'AI stands for Artificial Intelligence...',
|
||||
updateTime: new Date()
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await saveChat(props);
|
||||
|
||||
const aiItem = await MongoChatItem.findOne({
|
||||
appId: testAppId,
|
||||
chatId: props.chatId,
|
||||
obj: ChatRoleEnum.AI
|
||||
});
|
||||
|
||||
if (aiItem) {
|
||||
if ('citeCollectionIds' in aiItem) {
|
||||
expect(aiItem?.citeCollectionIds).toHaveLength(2);
|
||||
expect(aiItem?.citeCollectionIds).toContain('collection-1');
|
||||
expect(aiItem?.citeCollectionIds).toContain('collection-2');
|
||||
} else {
|
||||
throw new Error('aiItem does not have citeCollectionIds');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateInteractiveChat function', () => {
|
||||
it('should skip update if chatId is empty', async () => {
|
||||
const props = createMockProps(
|
||||
{ chatId: '' },
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
await updateInteractiveChat(props);
|
||||
|
||||
const chatItems = await MongoChatItem.find({ appId: testAppId });
|
||||
expect(chatItems).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should skip update if no AI chat item found', async () => {
|
||||
const props = createMockProps({}, { appId: testAppId, teamId: testTeamId, tmbId: testTmbId });
|
||||
await updateInteractiveChat(props);
|
||||
|
||||
const chat = await MongoChat.findOne({ appId: testAppId, chatId: props.chatId });
|
||||
expect(chat).toBeNull();
|
||||
});
|
||||
|
||||
it('should skip update if chat item is not from AI', async () => {
|
||||
// Create a human chat item
|
||||
await MongoChatItem.create({
|
||||
chatId: 'test-chat-id',
|
||||
teamId: testTeamId,
|
||||
tmbId: testTmbId,
|
||||
appId: testAppId,
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Hello' }
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const props = createMockProps({}, { appId: testAppId, teamId: testTeamId, tmbId: testTmbId });
|
||||
await updateInteractiveChat(props);
|
||||
|
||||
// Should not create a chat record
|
||||
const chat = await MongoChat.findOne({ appId: testAppId, chatId: props.chatId });
|
||||
expect(chat).toBeNull();
|
||||
});
|
||||
|
||||
it('should skip update if no interactive value found', async () => {
|
||||
// Create an AI chat item without interactive
|
||||
await MongoChatItem.create({
|
||||
chatId: 'test-chat-id',
|
||||
teamId: testTeamId,
|
||||
tmbId: testTmbId,
|
||||
appId: testAppId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Hello' }
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const props = createMockProps({}, { appId: testAppId, teamId: testTeamId, tmbId: testTmbId });
|
||||
await updateInteractiveChat(props);
|
||||
|
||||
// Should not create a chat record
|
||||
const chat = await MongoChat.findOne({ appId: testAppId, chatId: props.chatId });
|
||||
expect(chat).toBeNull();
|
||||
});
|
||||
|
||||
it('should update userSelect interactive value', async () => {
|
||||
// Create an AI chat item with userSelect interactive
|
||||
await MongoChatItem.create({
|
||||
chatId: 'test-chat-id',
|
||||
teamId: testTeamId,
|
||||
tmbId: testTmbId,
|
||||
appId: testAppId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
dataId: 'data-id-1',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'userSelect',
|
||||
params: {
|
||||
userSelectedVal: undefined,
|
||||
options: ['Option 1', 'Option 2']
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const props = createMockProps(
|
||||
{
|
||||
appId: testAppId,
|
||||
userContent: {
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Option 1' }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await updateInteractiveChat(props);
|
||||
|
||||
const chatItem = await MongoChatItem.findOne({
|
||||
appId: testAppId,
|
||||
chatId: props.chatId,
|
||||
obj: ChatRoleEnum.AI
|
||||
});
|
||||
if (chatItem) {
|
||||
if (chatItem.obj === ChatRoleEnum.AI) {
|
||||
if (chatItem?.value[0].interactive?.type === 'userSelect') {
|
||||
expect(chatItem?.value[0].interactive?.params.userSelectedVal).toBe('Option 1');
|
||||
} else {
|
||||
throw new Error('chatItem does not have userSelect interactive');
|
||||
}
|
||||
} else {
|
||||
throw new Error('chatItem does not have value');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should update userInput interactive value', async () => {
|
||||
// Create an AI chat item with userInput interactive
|
||||
await MongoChatItem.create({
|
||||
chatId: 'test-chat-id',
|
||||
teamId: testTeamId,
|
||||
tmbId: testTmbId,
|
||||
appId: testAppId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
dataId: 'data-id-1',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'userInput',
|
||||
params: {
|
||||
submitted: false,
|
||||
inputForm: [
|
||||
{
|
||||
key: 'username',
|
||||
type: 'input',
|
||||
label: 'Username',
|
||||
value: undefined
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const props = createMockProps(
|
||||
{
|
||||
userContent: {
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: JSON.stringify({ username: 'john_doe' }) }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await updateInteractiveChat(props);
|
||||
|
||||
const chatItem = await MongoChatItem.findOne({
|
||||
appId: testAppId,
|
||||
chatId: props.chatId,
|
||||
obj: ChatRoleEnum.AI
|
||||
});
|
||||
if (chatItem) {
|
||||
if (chatItem.obj === ChatRoleEnum.AI) {
|
||||
if (chatItem?.value[0].interactive?.type === 'userInput') {
|
||||
expect(chatItem?.value[0].interactive?.params.submitted).toBe(true);
|
||||
expect(chatItem?.value[0].interactive?.params.inputForm[0].value).toBe('john_doe');
|
||||
} else {
|
||||
throw new Error('chatItem does not have userInput interactive');
|
||||
}
|
||||
} else {
|
||||
throw new Error('chatItem does not have value');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should remove paymentPause interactive value', async () => {
|
||||
// Create an AI chat item with paymentPause interactive
|
||||
await MongoChatItem.create({
|
||||
chatId: 'test-chat-id',
|
||||
teamId: testTeamId,
|
||||
tmbId: testTmbId,
|
||||
appId: testAppId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
dataId: 'data-id-1',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Payment required' }
|
||||
},
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'paymentPause',
|
||||
params: {}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const props = createMockProps({}, { appId: testAppId, teamId: testTeamId, tmbId: testTmbId });
|
||||
|
||||
await updateInteractiveChat(props);
|
||||
|
||||
const chatItem = await MongoChatItem.findOne({
|
||||
appId: testAppId,
|
||||
chatId: props.chatId,
|
||||
obj: ChatRoleEnum.AI
|
||||
});
|
||||
// PaymentPause is removed, and AI response is appended
|
||||
expect(chatItem?.value.length).toBeGreaterThan(0);
|
||||
// The first value should be text, last one should be from AI response
|
||||
expect(chatItem?.value[0].type).toBe(ChatItemValueTypeEnum.text);
|
||||
});
|
||||
|
||||
it('should merge AI response values', async () => {
|
||||
// Create an AI chat item with interactive
|
||||
await MongoChatItem.create({
|
||||
chatId: 'test-chat-id',
|
||||
teamId: testTeamId,
|
||||
tmbId: testTmbId,
|
||||
appId: testAppId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
dataId: 'data-id-1',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'First response' }
|
||||
},
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'userSelect',
|
||||
params: { options: ['A', 'B'] }
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const props = createMockProps(
|
||||
{
|
||||
aiContent: {
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: { content: 'Second response' }
|
||||
}
|
||||
],
|
||||
responseData: []
|
||||
}
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await updateInteractiveChat(props);
|
||||
|
||||
const chatItem = await MongoChatItem.findOne({
|
||||
appId: testAppId,
|
||||
chatId: props.chatId,
|
||||
obj: ChatRoleEnum.AI
|
||||
});
|
||||
// Original has 2 values (text + interactive), AI adds 1 more (text)
|
||||
expect(chatItem?.value.length).toBeGreaterThanOrEqual(3);
|
||||
expect(chatItem?.value[chatItem.value.length - 1].text?.content).toBe('Second response');
|
||||
});
|
||||
|
||||
it('should accumulate duration seconds', async () => {
|
||||
// Create an AI chat item
|
||||
await MongoChatItem.create({
|
||||
chatId: 'test-chat-id',
|
||||
teamId: testTeamId,
|
||||
tmbId: testTmbId,
|
||||
appId: testAppId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
dataId: 'data-id-1',
|
||||
durationSeconds: 1.5,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'userSelect',
|
||||
params: { options: ['A', 'B'] }
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const props = createMockProps(
|
||||
{
|
||||
durationSeconds: 2.3
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await updateInteractiveChat(props);
|
||||
|
||||
const chatItem = await MongoChatItem.findOne({
|
||||
appId: testAppId,
|
||||
chatId: props.chatId,
|
||||
obj: ChatRoleEnum.AI
|
||||
});
|
||||
if (chatItem) {
|
||||
if (chatItem.obj === ChatRoleEnum.AI) {
|
||||
expect(chatItem?.durationSeconds).toBe(3.8);
|
||||
} else {
|
||||
throw new Error('chatItem does not have durationSeconds');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should merge chat item responses', async () => {
|
||||
// Create an AI chat item
|
||||
await MongoChatItem.create({
|
||||
chatId: 'test-chat-id',
|
||||
teamId: testTeamId,
|
||||
tmbId: testTmbId,
|
||||
appId: testAppId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
dataId: 'data-id-1',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
type: 'userSelect',
|
||||
params: { options: ['A', 'B'] }
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Create an existing response
|
||||
await MongoChatItemResponse.create({
|
||||
teamId: testTeamId,
|
||||
appId: testAppId,
|
||||
chatId: 'test-chat-id',
|
||||
chatItemDataId: 'data-id-1',
|
||||
data: {
|
||||
moduleType: FlowNodeTypeEnum.chatNode,
|
||||
moduleName: 'Chat',
|
||||
runningTime: 1.0,
|
||||
totalPoints: 10
|
||||
}
|
||||
});
|
||||
|
||||
const props = createMockProps(
|
||||
{
|
||||
aiContent: {
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [],
|
||||
responseData: [
|
||||
{
|
||||
nodeId: 'xx',
|
||||
id: 'xx',
|
||||
moduleType: FlowNodeTypeEnum.datasetSearchNode,
|
||||
moduleName: 'Dataset Search',
|
||||
runningTime: 0.5,
|
||||
totalPoints: 5
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ appId: testAppId, teamId: testTeamId, tmbId: testTmbId }
|
||||
);
|
||||
|
||||
await updateInteractiveChat(props);
|
||||
|
||||
const responses = await MongoChatItemResponse.find({
|
||||
appId: testAppId,
|
||||
chatId: props.chatId
|
||||
});
|
||||
// Should have merged responses
|
||||
expect(responses.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue