mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
fix: Properly serialize metadata objects in Chat UI (#17963)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
126
packages/frontend/@n8n/chat/src/__tests__/api/generic.spec.ts
Normal file
126
packages/frontend/@n8n/chat/src/__tests__/api/generic.spec.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { postWithFiles } from '@n8n/chat/api/generic';
|
||||||
|
|
||||||
|
describe('postWithFiles', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly serialize object metadata to JSON string in FormData', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => await Promise.resolve({ success: true }),
|
||||||
|
text: async () => await Promise.resolve('success'),
|
||||||
|
clone: () => mockResponse,
|
||||||
|
} as Response;
|
||||||
|
|
||||||
|
const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const testFile = new File(['test content'], 'test.txt', { type: 'text/plain' });
|
||||||
|
const metadata = {
|
||||||
|
userId: 'user-123',
|
||||||
|
token: 'abc-def-ghi',
|
||||||
|
nested: {
|
||||||
|
prop: 'value',
|
||||||
|
num: 42,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await postWithFiles(
|
||||||
|
'https://example.com/webhook',
|
||||||
|
{
|
||||||
|
action: 'sendMessage',
|
||||||
|
sessionId: 'test-session',
|
||||||
|
chatInput: 'test message',
|
||||||
|
metadata,
|
||||||
|
},
|
||||||
|
[testFile],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(fetchSpy).toHaveBeenCalledWith('https://example.com/webhook', {
|
||||||
|
method: 'POST',
|
||||||
|
body: expect.any(FormData),
|
||||||
|
mode: 'cors',
|
||||||
|
cache: 'no-cache',
|
||||||
|
headers: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the FormData from the call
|
||||||
|
const formData = fetchSpy.mock.calls[0][1]?.body as FormData;
|
||||||
|
expect(formData).toBeInstanceOf(FormData);
|
||||||
|
|
||||||
|
// Verify that metadata was properly serialized as JSON, not "[object Object]"
|
||||||
|
const metadataValue = formData.get('metadata');
|
||||||
|
expect(metadataValue).toBe(JSON.stringify(metadata));
|
||||||
|
|
||||||
|
// Verify other fields are still strings
|
||||||
|
expect(formData.get('action')).toBe('sendMessage');
|
||||||
|
expect(formData.get('sessionId')).toBe('test-session');
|
||||||
|
expect(formData.get('chatInput')).toBe('test message');
|
||||||
|
|
||||||
|
// Verify file was included
|
||||||
|
expect(formData.get('files')).toBe(testFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle primitive values correctly', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => await Promise.resolve({ success: true }),
|
||||||
|
text: async () => await Promise.resolve('success'),
|
||||||
|
clone: () => mockResponse,
|
||||||
|
} as Response;
|
||||||
|
|
||||||
|
const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
await postWithFiles('https://example.com/webhook', {
|
||||||
|
stringValue: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
const formData = fetchSpy.mock.calls[0][1]?.body as FormData;
|
||||||
|
|
||||||
|
expect(formData.get('stringValue')).toBe('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle arrays as JSON strings', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => await Promise.resolve({ success: true }),
|
||||||
|
text: async () => await Promise.resolve('success'),
|
||||||
|
clone: () => mockResponse,
|
||||||
|
} as Response;
|
||||||
|
|
||||||
|
const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const arrayValue = ['item1', 'item2', { nested: 'object' }];
|
||||||
|
|
||||||
|
await postWithFiles('https://example.com/webhook', {
|
||||||
|
arrayValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
const formData = fetchSpy.mock.calls[0][1]?.body as FormData;
|
||||||
|
expect(formData.get('arrayValue')).toBe(JSON.stringify(arrayValue));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty objects correctly', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => await Promise.resolve({ success: true }),
|
||||||
|
text: async () => await Promise.resolve('success'),
|
||||||
|
clone: () => mockResponse,
|
||||||
|
} as Response;
|
||||||
|
|
||||||
|
const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
await postWithFiles('https://example.com/webhook', {
|
||||||
|
emptyObject: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const formData = fetchSpy.mock.calls[0][1]?.body as FormData;
|
||||||
|
expect(formData.get('emptyObject')).toBe('{}');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -55,14 +55,19 @@ export async function post<T>(url: string, body: object = {}, options: RequestIn
|
|||||||
}
|
}
|
||||||
export async function postWithFiles<T>(
|
export async function postWithFiles<T>(
|
||||||
url: string,
|
url: string,
|
||||||
body: Record<string, unknown> = {},
|
body: Record<string, string | object> = {},
|
||||||
files: File[] = [],
|
files: File[] = [],
|
||||||
options: RequestInit = {},
|
options: RequestInit = {},
|
||||||
) {
|
) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
for (const key in body) {
|
for (const key in body) {
|
||||||
formData.append(key, body[key] as string);
|
const value = body[key];
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
formData.append(key, JSON.stringify(value));
|
||||||
|
} else {
|
||||||
|
formData.append(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
|||||||
Reference in New Issue
Block a user