mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-29 00:38:03 -05:00
* feat(deployed-form): added deployed form input * styling consolidation, finishing touches on form * updated docs * remove unused files with knip * added more form fields * consolidated more test utils * remove unused/unneeded zustand stores, refactored stores for consistency * improvement(files): uncolorized plan name * feat(emcn): button-group * feat(emcn): tag input, tooltip shortcut * improvement(emcn): modal padding, api, chat, form * fix: deleted migrations * feat(form): added migrations * fix(emcn): tag input * fix: failing tests on build * add suplementary hover and fix bg color in date picker * fix: build errors --------- Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
356 lines
10 KiB
TypeScript
356 lines
10 KiB
TypeScript
import { loggerMock } from '@sim/testing'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
StreamingResponseFormatProcessor,
|
|
streamingResponseFormatProcessor,
|
|
} from '@/executor/utils'
|
|
|
|
vi.mock('@sim/logger', () => loggerMock)
|
|
|
|
describe('StreamingResponseFormatProcessor', () => {
|
|
let processor: StreamingResponseFormatProcessor
|
|
|
|
beforeEach(() => {
|
|
processor = new StreamingResponseFormatProcessor()
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('processStream', () => {
|
|
it.concurrent('should return original stream when no response format selection', async () => {
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(new TextEncoder().encode('{"content": "test"}'))
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const result = processor.processStream(
|
|
mockStream,
|
|
'block-1',
|
|
['block-1.content'], // No underscore, not response format
|
|
{ schema: { properties: { username: { type: 'string' } } } }
|
|
)
|
|
|
|
expect(result).toBe(mockStream)
|
|
})
|
|
|
|
it.concurrent('should return original stream when no response format provided', async () => {
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(new TextEncoder().encode('{"content": "test"}'))
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const result = processor.processStream(
|
|
mockStream,
|
|
'block-1',
|
|
['block-1_username'], // Has underscore but no response format
|
|
undefined
|
|
)
|
|
|
|
expect(result).toBe(mockStream)
|
|
})
|
|
|
|
it.concurrent('should process stream and extract single selected field', async () => {
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(new TextEncoder().encode('{"username": "alice", "age": 25}'))
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const processedStream = processor.processStream(mockStream, 'block-1', ['block-1_username'], {
|
|
schema: { properties: { username: { type: 'string' }, age: { type: 'number' } } },
|
|
})
|
|
|
|
const reader = processedStream.getReader()
|
|
const decoder = new TextDecoder()
|
|
let result = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
result += decoder.decode(value)
|
|
}
|
|
|
|
expect(result).toBe('alice')
|
|
})
|
|
|
|
it.concurrent('should process stream and extract multiple selected fields', async () => {
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(
|
|
new TextEncoder().encode('{"username": "bob", "age": 30, "email": "bob@test.com"}')
|
|
)
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const processedStream = processor.processStream(
|
|
mockStream,
|
|
'block-1',
|
|
['block-1_username', 'block-1_age'],
|
|
{
|
|
schema: { properties: { username: { type: 'string' }, age: { type: 'number' } } },
|
|
}
|
|
)
|
|
|
|
const reader = processedStream.getReader()
|
|
const decoder = new TextDecoder()
|
|
let result = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
result += decoder.decode(value)
|
|
}
|
|
|
|
expect(result).toBe('bob\n30')
|
|
})
|
|
|
|
it.concurrent('should handle non-string field values by JSON stringifying them', async () => {
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(
|
|
new TextEncoder().encode(
|
|
'{"config": {"theme": "dark", "notifications": true}, "count": 42}'
|
|
)
|
|
)
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const processedStream = processor.processStream(
|
|
mockStream,
|
|
'block-1',
|
|
['block-1_config', 'block-1_count'],
|
|
{
|
|
schema: { properties: { config: { type: 'object' }, count: { type: 'number' } } },
|
|
}
|
|
)
|
|
|
|
const reader = processedStream.getReader()
|
|
const decoder = new TextDecoder()
|
|
let result = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
result += decoder.decode(value)
|
|
}
|
|
|
|
expect(result).toBe('{"theme":"dark","notifications":true}\n42')
|
|
})
|
|
|
|
it.concurrent('should handle streaming JSON that comes in chunks', async () => {
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
// Simulate streaming JSON in chunks
|
|
controller.enqueue(new TextEncoder().encode('{"username": "charlie"'))
|
|
controller.enqueue(new TextEncoder().encode(', "age": 35}'))
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const processedStream = processor.processStream(mockStream, 'block-1', ['block-1_username'], {
|
|
schema: { properties: { username: { type: 'string' }, age: { type: 'number' } } },
|
|
})
|
|
|
|
const reader = processedStream.getReader()
|
|
const decoder = new TextDecoder()
|
|
let result = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
result += decoder.decode(value)
|
|
}
|
|
|
|
expect(result).toBe('charlie')
|
|
})
|
|
|
|
it.concurrent('should handle missing fields gracefully', async () => {
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(new TextEncoder().encode('{"username": "diana"}'))
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const processedStream = processor.processStream(
|
|
mockStream,
|
|
'block-1',
|
|
['block-1_username', 'block-1_missing_field'],
|
|
{ schema: { properties: { username: { type: 'string' } } } }
|
|
)
|
|
|
|
const reader = processedStream.getReader()
|
|
const decoder = new TextDecoder()
|
|
let result = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
result += decoder.decode(value)
|
|
}
|
|
|
|
expect(result).toBe('diana')
|
|
})
|
|
|
|
it.concurrent('should handle invalid JSON gracefully', async () => {
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(new TextEncoder().encode('invalid json'))
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const processedStream = processor.processStream(mockStream, 'block-1', ['block-1_username'], {
|
|
schema: { properties: { username: { type: 'string' } } },
|
|
})
|
|
|
|
const reader = processedStream.getReader()
|
|
const decoder = new TextDecoder()
|
|
let result = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
result += decoder.decode(value)
|
|
}
|
|
|
|
expect(result).toBe('')
|
|
})
|
|
|
|
it.concurrent('should filter selected fields for correct block ID', async () => {
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(new TextEncoder().encode('{"username": "eve", "age": 28}'))
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const processedStream = processor.processStream(
|
|
mockStream,
|
|
'block-1',
|
|
['block-1_username', 'block-2_age'], // Different block ID should be filtered out
|
|
{ schema: { properties: { username: { type: 'string' }, age: { type: 'number' } } } }
|
|
)
|
|
|
|
const reader = processedStream.getReader()
|
|
const decoder = new TextDecoder()
|
|
let result = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
result += decoder.decode(value)
|
|
}
|
|
|
|
expect(result).toBe('eve')
|
|
})
|
|
|
|
it.concurrent('should handle empty result when no matching fields', async () => {
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(new TextEncoder().encode('{"other_field": "value"}'))
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const processedStream = processor.processStream(mockStream, 'block-1', ['block-1_username'], {
|
|
schema: { properties: { username: { type: 'string' } } },
|
|
})
|
|
|
|
const reader = processedStream.getReader()
|
|
const decoder = new TextDecoder()
|
|
let result = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
result += decoder.decode(value)
|
|
}
|
|
|
|
expect(result).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('singleton instance', () => {
|
|
it.concurrent('should export a singleton instance', () => {
|
|
expect(streamingResponseFormatProcessor).toBeInstanceOf(StreamingResponseFormatProcessor)
|
|
})
|
|
|
|
it.concurrent('should return the same instance on multiple imports', () => {
|
|
const instance1 = streamingResponseFormatProcessor
|
|
const instance2 = streamingResponseFormatProcessor
|
|
expect(instance1).toBe(instance2)
|
|
})
|
|
})
|
|
|
|
describe('edge cases', () => {
|
|
it.concurrent('should handle empty stream', async () => {
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const processedStream = processor.processStream(mockStream, 'block-1', ['block-1_username'], {
|
|
schema: { properties: { username: { type: 'string' } } },
|
|
})
|
|
|
|
const reader = processedStream.getReader()
|
|
const decoder = new TextDecoder()
|
|
let result = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
result += decoder.decode(value)
|
|
}
|
|
|
|
expect(result).toBe('')
|
|
})
|
|
|
|
it.concurrent('should handle very large JSON objects', async () => {
|
|
const largeObject = {
|
|
username: 'frank',
|
|
data: 'x'.repeat(10000), // Large string
|
|
nested: {
|
|
deep: {
|
|
value: 'test',
|
|
},
|
|
},
|
|
}
|
|
|
|
const mockStream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(new TextEncoder().encode(JSON.stringify(largeObject)))
|
|
controller.close()
|
|
},
|
|
})
|
|
|
|
const processedStream = processor.processStream(mockStream, 'block-1', ['block-1_username'], {
|
|
schema: { properties: { username: { type: 'string' } } },
|
|
})
|
|
|
|
const reader = processedStream.getReader()
|
|
const decoder = new TextDecoder()
|
|
let result = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
result += decoder.decode(value)
|
|
}
|
|
|
|
expect(result).toBe('frank')
|
|
})
|
|
})
|
|
})
|