mirror of https://github.com/buster-so/buster.git
Add comprehensive unit tests for server-shared package
Co-authored-by: natemkelley <natemkelley@gmail.com>
This commit is contained in:
parent
0d7db25a15
commit
17b4263a15
|
@ -0,0 +1,156 @@
|
|||
# Server-Shared Package - Comprehensive Unit Tests Summary
|
||||
|
||||
## Overview
|
||||
I have successfully created comprehensive unit tests for **every single file** in the `packages/server-shared` folder. All tests are now passing with **115 total tests** across **7 test files**.
|
||||
|
||||
## Test Coverage Summary
|
||||
|
||||
### 🎯 Files Tested
|
||||
- ✅ `src/index.ts` - Main entry point exports
|
||||
- ✅ `src/chats/index.ts` - Chat module exports
|
||||
- ✅ `src/chats/chat.types.ts` - Chat type schemas
|
||||
- ✅ `src/chats/chat-errors.types.ts` - Error handling types
|
||||
- ✅ `src/chats/chat-message.types.ts` - Message type schemas
|
||||
- ✅ `src/currency/index.ts` - Currency module exports
|
||||
- ✅ `src/currency/currency.types.ts` - Currency type schemas
|
||||
|
||||
### 📊 Test Statistics
|
||||
- **Test Files**: 7 passed (7)
|
||||
- **Total Tests**: 115 passed (115)
|
||||
- **Duration**: 428ms
|
||||
- **Pass Rate**: 100% ✅
|
||||
|
||||
## Detailed Test Coverage
|
||||
|
||||
### 1. Chat Types (`chat.types.test.ts`) - 27 tests
|
||||
**Comprehensive testing of chat-related Zod schemas:**
|
||||
- ✅ `AssetPermissionRoleSchema` validation (enum values: viewer, editor, owner)
|
||||
- ✅ `BusterShareIndividualSchema` validation (email, role, optional name)
|
||||
- ✅ `ChatWithMessagesSchema` validation (complex nested objects)
|
||||
- ✅ `ChatCreateRequestSchema` validation (request validation with refinements)
|
||||
- ✅ `ChatCreateHandlerRequestSchema` validation (internal handler requests)
|
||||
|
||||
**Key Test Cases:**
|
||||
- Valid enum values and rejection of invalid ones
|
||||
- Email validation and required field validation
|
||||
- Complex nested object validation with optional fields
|
||||
- UUID format validation
|
||||
- Schema refinement rules (asset_id requires asset_type)
|
||||
- Type inference verification
|
||||
|
||||
### 2. Chat Errors (`chat-errors.types.test.ts`) - 22 tests
|
||||
**Complete testing of error handling system:**
|
||||
- ✅ `ChatErrorCode` constant validation (10 error codes)
|
||||
- ✅ `ChatErrorResponseSchema` validation
|
||||
- ✅ `ChatError` class functionality
|
||||
|
||||
**Key Test Cases:**
|
||||
- All error code constants exist and have correct values
|
||||
- Error response schema validation with and without details
|
||||
- ChatError class constructor, methods, and inheritance
|
||||
- Error serialization with `toResponse()` method
|
||||
- Custom status codes and error details handling
|
||||
- Schema compatibility between ChatError output and response schema
|
||||
|
||||
### 3. Chat Messages (`chat-message.types.test.ts`) - 28 tests
|
||||
**Extensive testing of complex discriminated union schemas:**
|
||||
- ✅ `ResponseMessageSchema` (discriminated union: text vs file)
|
||||
- ✅ `ReasoningMessageSchema` (discriminated union: text vs files vs pills)
|
||||
- ✅ `ChatMessageSchema` (complete message structure)
|
||||
|
||||
**Key Test Cases:**
|
||||
- Text response messages with optional fields
|
||||
- File response messages with all file types (metric, dashboard, reasoning)
|
||||
- File metadata and version handling
|
||||
- Text reasoning messages with status validation
|
||||
- Files reasoning messages with nested file objects
|
||||
- Pills reasoning messages with pill containers and types
|
||||
- Complex nested chat message validation
|
||||
- Discriminated union type validation
|
||||
- Status enum validation (loading, completed, failed)
|
||||
- All file and pill type enums
|
||||
|
||||
### 4. Currency Types (`currency.types.test.ts`) - 14 tests
|
||||
**Thorough testing of simple schema:**
|
||||
- ✅ `CurrencySchema` validation and edge cases
|
||||
|
||||
**Key Test Cases:**
|
||||
- Valid currency objects (code, description, flag)
|
||||
- Multiple real-world currency examples
|
||||
- Empty string handling
|
||||
- Long descriptions and special characters
|
||||
- Unicode character support (Arabic, Chinese, complex emojis)
|
||||
- Missing field validation
|
||||
- Non-string value rejection
|
||||
- Extra property handling (schema stripping)
|
||||
- SafeParse functionality for error handling
|
||||
|
||||
### 5. Index Files (`index.test.ts`, `chats/index.test.ts`, `currency/index.test.ts`) - 24 tests
|
||||
**Export verification and integration testing:**
|
||||
- ✅ Main package exports work correctly
|
||||
- ✅ Chat module exports work correctly
|
||||
- ✅ Currency module exports work correctly
|
||||
|
||||
**Key Test Cases:**
|
||||
- All expected schemas are exported and functional
|
||||
- Schema validation works through exports
|
||||
- Type inference works correctly
|
||||
- Export isolation (currency not in main index)
|
||||
- Integration testing of exported functionality
|
||||
|
||||
## 🔧 Testing Infrastructure Setup
|
||||
|
||||
### Dependencies Added
|
||||
- ✅ Added `vitest` and `@buster/vitest-config` to devDependencies
|
||||
- ✅ Added test scripts to package.json (`test`, `test:watch`)
|
||||
- ✅ Created `vitest.config.ts` using workspace base configuration
|
||||
|
||||
### Test File Organization
|
||||
- ✅ Co-located tests with source files (`.test.ts` alongside `.ts`)
|
||||
- ✅ Follows project testing conventions
|
||||
- ✅ Uses vitest framework as specified in project rules
|
||||
|
||||
## 🧪 Test Quality Features
|
||||
|
||||
### Comprehensive Validation Testing
|
||||
- **Schema Defaults**: Tested optional fields and default behavior
|
||||
- **Edge Cases**: Empty strings, null values, undefined, special characters
|
||||
- **Type Safety**: Verified TypeScript type inference works correctly
|
||||
- **Error Scenarios**: Invalid inputs, missing fields, wrong types
|
||||
- **Real-World Data**: Used realistic examples (currencies, UUIDs, etc.)
|
||||
|
||||
### Advanced Schema Testing
|
||||
- **Discriminated Unions**: Thoroughly tested complex type discrimination
|
||||
- **Nested Objects**: Deep validation of complex object structures
|
||||
- **Array Validation**: Tested array fields with various lengths and contents
|
||||
- **Enum Validation**: Tested all enum values and rejection of invalid ones
|
||||
- **Custom Validation**: Tested Zod refinements and custom rules
|
||||
|
||||
### Integration Testing
|
||||
- **Export Verification**: Ensured all modules export correctly
|
||||
- **Cross-Module**: Tested schemas work across module boundaries
|
||||
- **Type Inference**: Verified TypeScript types work as expected
|
||||
|
||||
## 🚀 Benefits Achieved
|
||||
|
||||
1. **100% File Coverage**: Every TypeScript file in the package has corresponding tests
|
||||
2. **Schema Validation**: Comprehensive testing of all Zod schemas ensures data integrity
|
||||
3. **Type Safety**: Verified TypeScript integration and type inference
|
||||
4. **Error Handling**: Complete coverage of error scenarios and edge cases
|
||||
5. **Real-World Testing**: Used realistic data examples and use cases
|
||||
6. **Maintenance**: Tests will catch breaking changes and regressions
|
||||
7. **Documentation**: Tests serve as living examples of how to use the schemas
|
||||
|
||||
## 🎯 Conclusion
|
||||
|
||||
The `packages/server-shared` package now has **comprehensive, battle-tested unit tests** covering every schema, type, and function. With **115 passing tests**, this provides excellent confidence in the reliability and correctness of the shared type definitions used across the Buster application.
|
||||
|
||||
The tests follow best practices including:
|
||||
- ✅ Co-located test files
|
||||
- ✅ Descriptive test names and organization
|
||||
- ✅ Edge case coverage
|
||||
- ✅ Type inference verification
|
||||
- ✅ Real-world example usage
|
||||
- ✅ Error scenario testing
|
||||
|
||||
**All tests pass successfully!** 🎉
|
|
@ -8,7 +8,9 @@
|
|||
"build": "tsc --build",
|
||||
"dev": "tsc --watch",
|
||||
"lint": "biome check",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
|
@ -27,5 +29,9 @@
|
|||
"dependencies": {
|
||||
"@buster/typescript-config": "workspace:*",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@buster/vitest-config": "workspace:*",
|
||||
"vitest": "catalog:"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
ChatError,
|
||||
ChatErrorCode,
|
||||
type ChatErrorResponse,
|
||||
ChatErrorResponseSchema,
|
||||
} from './chat-errors.types';
|
||||
|
||||
describe('ChatErrorCode', () => {
|
||||
it('should have all expected error codes', () => {
|
||||
const expectedCodes = [
|
||||
'INVALID_REQUEST',
|
||||
'MISSING_ORGANIZATION',
|
||||
'UNAUTHORIZED',
|
||||
'PERMISSION_DENIED',
|
||||
'CHAT_NOT_FOUND',
|
||||
'ASSET_NOT_FOUND',
|
||||
'USER_NOT_FOUND',
|
||||
'DATABASE_ERROR',
|
||||
'TRIGGER_ERROR',
|
||||
'INTERNAL_ERROR',
|
||||
];
|
||||
|
||||
for (const code of expectedCodes) {
|
||||
expect(ChatErrorCode[code as keyof typeof ChatErrorCode]).toBe(code);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have validation error codes', () => {
|
||||
expect(ChatErrorCode.INVALID_REQUEST).toBe('INVALID_REQUEST');
|
||||
expect(ChatErrorCode.MISSING_ORGANIZATION).toBe('MISSING_ORGANIZATION');
|
||||
});
|
||||
|
||||
it('should have permission error codes', () => {
|
||||
expect(ChatErrorCode.UNAUTHORIZED).toBe('UNAUTHORIZED');
|
||||
expect(ChatErrorCode.PERMISSION_DENIED).toBe('PERMISSION_DENIED');
|
||||
});
|
||||
|
||||
it('should have resource error codes', () => {
|
||||
expect(ChatErrorCode.CHAT_NOT_FOUND).toBe('CHAT_NOT_FOUND');
|
||||
expect(ChatErrorCode.ASSET_NOT_FOUND).toBe('ASSET_NOT_FOUND');
|
||||
expect(ChatErrorCode.USER_NOT_FOUND).toBe('USER_NOT_FOUND');
|
||||
});
|
||||
|
||||
it('should have service error codes', () => {
|
||||
expect(ChatErrorCode.DATABASE_ERROR).toBe('DATABASE_ERROR');
|
||||
expect(ChatErrorCode.TRIGGER_ERROR).toBe('TRIGGER_ERROR');
|
||||
expect(ChatErrorCode.INTERNAL_ERROR).toBe('INTERNAL_ERROR');
|
||||
});
|
||||
|
||||
it('should be readonly (const assertion)', () => {
|
||||
// TypeScript should prevent this at compile time, but we can test the values
|
||||
expect(typeof ChatErrorCode).toBe('object');
|
||||
expect(Object.isFrozen(ChatErrorCode)).toBe(false); // const assertion doesn't freeze
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChatErrorResponseSchema', () => {
|
||||
it('should validate valid error response', () => {
|
||||
const validResponse = {
|
||||
code: 'INVALID_REQUEST',
|
||||
message: 'The request is invalid',
|
||||
};
|
||||
|
||||
const result = ChatErrorResponseSchema.parse(validResponse);
|
||||
expect(result).toEqual(validResponse);
|
||||
});
|
||||
|
||||
it('should validate error response with details', () => {
|
||||
const responseWithDetails = {
|
||||
code: 'PERMISSION_DENIED',
|
||||
message: 'You do not have permission to access this resource',
|
||||
details: {
|
||||
resource: 'chat',
|
||||
resourceId: '123',
|
||||
requiredPermission: 'read',
|
||||
},
|
||||
};
|
||||
|
||||
const result = ChatErrorResponseSchema.parse(responseWithDetails);
|
||||
expect(result).toEqual(responseWithDetails);
|
||||
});
|
||||
|
||||
it('should validate error response with complex details', () => {
|
||||
const responseWithComplexDetails = {
|
||||
code: 'DATABASE_ERROR',
|
||||
message: 'Database operation failed',
|
||||
details: {
|
||||
query: 'SELECT * FROM chats',
|
||||
error: { message: 'Connection timeout' },
|
||||
retries: 3,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
|
||||
const result = ChatErrorResponseSchema.parse(responseWithComplexDetails);
|
||||
expect(result.details).toEqual(responseWithComplexDetails.details);
|
||||
});
|
||||
|
||||
it('should handle optional details field', () => {
|
||||
const responseWithoutDetails = {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: 'An internal error occurred',
|
||||
};
|
||||
|
||||
const result = ChatErrorResponseSchema.parse(responseWithoutDetails);
|
||||
expect(result.details).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should require code and message fields', () => {
|
||||
expect(() => ChatErrorResponseSchema.parse({})).toThrow();
|
||||
expect(() => ChatErrorResponseSchema.parse({ code: 'INVALID_REQUEST' })).toThrow();
|
||||
expect(() => ChatErrorResponseSchema.parse({ message: 'Error message' })).toThrow();
|
||||
});
|
||||
|
||||
it('should reject non-string code and message', () => {
|
||||
expect(() =>
|
||||
ChatErrorResponseSchema.parse({
|
||||
code: 123,
|
||||
message: 'Error message',
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
ChatErrorResponseSchema.parse({
|
||||
code: 'INVALID_REQUEST',
|
||||
message: 123,
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should have correct type inference', () => {
|
||||
const response: ChatErrorResponse = {
|
||||
code: 'UNAUTHORIZED',
|
||||
message: 'Authentication required',
|
||||
details: { endpoint: '/api/chat' },
|
||||
};
|
||||
expect(response.code).toBe('UNAUTHORIZED');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChatError', () => {
|
||||
it('should create error with required parameters', () => {
|
||||
const error = new ChatError('INVALID_REQUEST', 'The request is invalid');
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error).toBeInstanceOf(ChatError);
|
||||
expect(error.name).toBe('ChatError');
|
||||
expect(error.code).toBe('INVALID_REQUEST');
|
||||
expect(error.message).toBe('The request is invalid');
|
||||
expect(error.statusCode).toBe(500); // default
|
||||
expect(error.details).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should create error with custom status code', () => {
|
||||
const error = new ChatError('UNAUTHORIZED', 'Authentication required', 401);
|
||||
|
||||
expect(error.code).toBe('UNAUTHORIZED');
|
||||
expect(error.message).toBe('Authentication required');
|
||||
expect(error.statusCode).toBe(401);
|
||||
});
|
||||
|
||||
it('should create error with details', () => {
|
||||
const details = { userId: '123', resource: 'chat' };
|
||||
const error = new ChatError('PERMISSION_DENIED', 'Access denied', 403, details);
|
||||
|
||||
expect(error.code).toBe('PERMISSION_DENIED');
|
||||
expect(error.message).toBe('Access denied');
|
||||
expect(error.statusCode).toBe(403);
|
||||
expect(error.details).toEqual(details);
|
||||
});
|
||||
|
||||
it('should support all valid status codes', () => {
|
||||
const statusCodes = [400, 401, 403, 404, 409, 500] as const;
|
||||
|
||||
for (const statusCode of statusCodes) {
|
||||
const error = new ChatError('INTERNAL_ERROR', 'Test error', statusCode);
|
||||
expect(error.statusCode).toBe(statusCode);
|
||||
}
|
||||
});
|
||||
|
||||
it('should convert to response format', () => {
|
||||
const error = new ChatError('DATABASE_ERROR', 'Database connection failed', 500, {
|
||||
query: 'SELECT * FROM users',
|
||||
});
|
||||
|
||||
const response = error.toResponse();
|
||||
|
||||
expect(response).toEqual({
|
||||
code: 'DATABASE_ERROR',
|
||||
message: 'Database connection failed',
|
||||
details: { query: 'SELECT * FROM users' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert to response format without details', () => {
|
||||
const error = new ChatError('CHAT_NOT_FOUND', 'Chat not found', 404);
|
||||
|
||||
const response = error.toResponse();
|
||||
|
||||
expect(response).toEqual({
|
||||
code: 'CHAT_NOT_FOUND',
|
||||
message: 'Chat not found',
|
||||
});
|
||||
expect(response.details).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should maintain error stack trace', () => {
|
||||
const error = new ChatError('INTERNAL_ERROR', 'Test error');
|
||||
expect(error.stack).toBeDefined();
|
||||
expect(error.stack).toContain('ChatError');
|
||||
});
|
||||
|
||||
it('should be throwable and catchable', () => {
|
||||
expect(() => {
|
||||
throw new ChatError('INVALID_REQUEST', 'Test error', 400);
|
||||
}).toThrow(ChatError);
|
||||
|
||||
try {
|
||||
throw new ChatError('UNAUTHORIZED', 'Auth error', 401);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ChatError);
|
||||
expect((error as ChatError).code).toBe('UNAUTHORIZED');
|
||||
expect((error as ChatError).statusCode).toBe(401);
|
||||
}
|
||||
});
|
||||
|
||||
it('should validate response format against schema', () => {
|
||||
const error = new ChatError('TRIGGER_ERROR', 'External service error', 500, {
|
||||
service: 'trigger',
|
||||
endpoint: '/api/chat/create',
|
||||
});
|
||||
|
||||
const response = error.toResponse();
|
||||
|
||||
// The response should be valid according to our schema
|
||||
expect(() => ChatErrorResponseSchema.parse(response)).not.toThrow();
|
||||
|
||||
const validatedResponse = ChatErrorResponseSchema.parse(response);
|
||||
expect(validatedResponse).toEqual(response);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,587 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
type ChatMessage,
|
||||
type ChatMessageReasoningMessage,
|
||||
type ChatMessageReasoningMessage_Files,
|
||||
type ChatMessageReasoningMessage_Pills,
|
||||
type ChatMessageReasoningMessage_Text,
|
||||
type ChatMessageResponseMessage,
|
||||
type ChatMessageResponseMessage_File,
|
||||
type ChatMessageResponseMessage_Text,
|
||||
ChatMessageSchema,
|
||||
ReasoningMessageSchema,
|
||||
ResponseMessageSchema,
|
||||
} from './chat-message.types';
|
||||
|
||||
describe('ResponseMessageSchema', () => {
|
||||
describe('Text Response Message', () => {
|
||||
it('should validate text response message', () => {
|
||||
const textMessage = {
|
||||
id: 'msg-1',
|
||||
type: 'text' as const,
|
||||
message: 'This is a text response',
|
||||
};
|
||||
|
||||
const result = ResponseMessageSchema.parse(textMessage);
|
||||
expect(result).toEqual(textMessage);
|
||||
expect(result.type).toBe('text');
|
||||
});
|
||||
|
||||
it('should validate text message with optional is_final_message', () => {
|
||||
const textMessage = {
|
||||
id: 'msg-1',
|
||||
type: 'text' as const,
|
||||
message: 'Final response',
|
||||
is_final_message: true,
|
||||
};
|
||||
|
||||
const result = ResponseMessageSchema.parse(textMessage);
|
||||
expect(result.is_final_message).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle missing optional fields', () => {
|
||||
const textMessage = {
|
||||
id: 'msg-1',
|
||||
type: 'text' as const,
|
||||
message: 'Response without optional fields',
|
||||
};
|
||||
|
||||
const result = ResponseMessageSchema.parse(textMessage);
|
||||
expect(result.is_final_message).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('File Response Message', () => {
|
||||
it('should validate file response message', () => {
|
||||
const fileMessage = {
|
||||
id: 'file-1',
|
||||
type: 'file' as const,
|
||||
file_type: 'metric' as const,
|
||||
file_name: 'sales_metrics.yaml',
|
||||
version_number: 1,
|
||||
};
|
||||
|
||||
const result = ResponseMessageSchema.parse(fileMessage);
|
||||
expect(result).toEqual(fileMessage);
|
||||
expect(result.type).toBe('file');
|
||||
});
|
||||
|
||||
it('should validate all file types', () => {
|
||||
const fileTypes = ['metric', 'dashboard', 'reasoning'] as const;
|
||||
|
||||
for (const fileType of fileTypes) {
|
||||
const fileMessage = {
|
||||
id: `file-${fileType}`,
|
||||
type: 'file' as const,
|
||||
file_type: fileType,
|
||||
file_name: `test_${fileType}.yaml`,
|
||||
version_number: 1,
|
||||
};
|
||||
|
||||
const result = ResponseMessageSchema.parse(fileMessage);
|
||||
expect(result.file_type).toBe(fileType);
|
||||
}
|
||||
});
|
||||
|
||||
it('should validate file message with optional fields', () => {
|
||||
const fileMessage = {
|
||||
id: 'file-1',
|
||||
type: 'file' as const,
|
||||
file_type: 'dashboard' as const,
|
||||
file_name: 'dashboard.yaml',
|
||||
version_number: 2,
|
||||
filter_version_id: 'filter-123',
|
||||
metadata: [
|
||||
{
|
||||
status: 'completed' as const,
|
||||
message: 'File generated successfully',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = ResponseMessageSchema.parse(fileMessage);
|
||||
expect(result.filter_version_id).toBe('filter-123');
|
||||
expect(result.metadata).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should handle null filter_version_id', () => {
|
||||
const fileMessage = {
|
||||
id: 'file-1',
|
||||
type: 'file' as const,
|
||||
file_type: 'metric' as const,
|
||||
file_name: 'metric.yaml',
|
||||
version_number: 1,
|
||||
filter_version_id: null,
|
||||
};
|
||||
|
||||
const result = ResponseMessageSchema.parse(fileMessage);
|
||||
expect(result.filter_version_id).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject invalid discriminated union types', () => {
|
||||
const invalidMessage = {
|
||||
id: 'invalid-1',
|
||||
type: 'invalid_type',
|
||||
message: 'This should fail',
|
||||
};
|
||||
|
||||
expect(() => ResponseMessageSchema.parse(invalidMessage)).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReasoningMessageSchema', () => {
|
||||
describe('Text Reasoning Message', () => {
|
||||
it('should validate text reasoning message', () => {
|
||||
const textReasoning = {
|
||||
id: 'reasoning-1',
|
||||
type: 'text' as const,
|
||||
title: 'Analyzing Data',
|
||||
status: 'loading' as const,
|
||||
};
|
||||
|
||||
const result = ReasoningMessageSchema.parse(textReasoning);
|
||||
expect(result).toEqual({ ...textReasoning, finished_reasoning: undefined });
|
||||
expect(result.type).toBe('text');
|
||||
});
|
||||
|
||||
it('should validate text reasoning with all optional fields', () => {
|
||||
const textReasoning = {
|
||||
id: 'reasoning-1',
|
||||
type: 'text' as const,
|
||||
title: 'Data Analysis Complete',
|
||||
secondary_title: 'Summary of findings',
|
||||
message: 'Analysis completed successfully',
|
||||
message_chunk: 'Processing chunk 1/5',
|
||||
status: 'completed' as const,
|
||||
finished_reasoning: true,
|
||||
};
|
||||
|
||||
const result = ReasoningMessageSchema.parse(textReasoning);
|
||||
expect(result.secondary_title).toBe('Summary of findings');
|
||||
expect(result.finished_reasoning).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate all status values', () => {
|
||||
const statusValues = ['loading', 'completed', 'failed'] as const;
|
||||
|
||||
for (const status of statusValues) {
|
||||
const textReasoning = {
|
||||
id: 'reasoning-1',
|
||||
type: 'text' as const,
|
||||
title: 'Test',
|
||||
status,
|
||||
};
|
||||
|
||||
const result = ReasoningMessageSchema.parse(textReasoning);
|
||||
expect(result.status).toBe(status);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle null message fields', () => {
|
||||
const textReasoning = {
|
||||
id: 'reasoning-1',
|
||||
type: 'text' as const,
|
||||
title: 'Test',
|
||||
message: null,
|
||||
message_chunk: null,
|
||||
status: 'loading' as const,
|
||||
};
|
||||
|
||||
const result = ReasoningMessageSchema.parse(textReasoning);
|
||||
expect(result.message).toBeNull();
|
||||
expect(result.message_chunk).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Files Reasoning Message', () => {
|
||||
it('should validate files reasoning message', () => {
|
||||
const filesReasoning = {
|
||||
id: 'reasoning-files-1',
|
||||
type: 'files' as const,
|
||||
title: 'Generated Files',
|
||||
status: 'completed' as const,
|
||||
file_ids: ['file-1', 'file-2'],
|
||||
files: {
|
||||
'file-1': {
|
||||
id: 'file-1',
|
||||
file_type: 'metric' as const,
|
||||
file_name: 'metrics.yaml',
|
||||
status: 'completed' as const,
|
||||
file: { text: 'metric content' },
|
||||
},
|
||||
'file-2': {
|
||||
id: 'file-2',
|
||||
file_type: 'dashboard' as const,
|
||||
file_name: 'dashboard.yaml',
|
||||
status: 'loading' as const,
|
||||
file: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = ReasoningMessageSchema.parse(filesReasoning);
|
||||
expect(result.type).toBe('files');
|
||||
expect(result.file_ids).toHaveLength(2);
|
||||
expect(result.files['file-1'].file_type).toBe('metric');
|
||||
});
|
||||
|
||||
it('should validate all reasoning file types', () => {
|
||||
const fileTypes = ['metric', 'dashboard', 'reasoning', 'agent-action', 'todo'] as const;
|
||||
|
||||
for (const fileType of fileTypes) {
|
||||
const filesReasoning = {
|
||||
id: 'reasoning-files-1',
|
||||
type: 'files' as const,
|
||||
title: 'Test Files',
|
||||
status: 'completed' as const,
|
||||
file_ids: [`file-${fileType}`],
|
||||
files: {
|
||||
[`file-${fileType}`]: {
|
||||
id: `file-${fileType}`,
|
||||
file_type: fileType,
|
||||
file_name: `test.${fileType}`,
|
||||
status: 'completed' as const,
|
||||
file: { text: 'content' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = ReasoningMessageSchema.parse(filesReasoning);
|
||||
expect(result.files[`file-${fileType}`].file_type).toBe(fileType);
|
||||
}
|
||||
});
|
||||
|
||||
it('should validate file with modifications', () => {
|
||||
const filesReasoning = {
|
||||
id: 'reasoning-files-1',
|
||||
type: 'files' as const,
|
||||
title: 'Modified Files',
|
||||
status: 'completed' as const,
|
||||
file_ids: ['file-1'],
|
||||
files: {
|
||||
'file-1': {
|
||||
id: 'file-1',
|
||||
file_type: 'metric' as const,
|
||||
file_name: 'modified_metrics.yaml',
|
||||
status: 'completed' as const,
|
||||
file: {
|
||||
text: 'updated content',
|
||||
modified: [
|
||||
[0, 10],
|
||||
[20, 30],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = ReasoningMessageSchema.parse(filesReasoning);
|
||||
expect(result.files['file-1'].file.modified).toEqual([
|
||||
[0, 10],
|
||||
[20, 30],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pills Reasoning Message', () => {
|
||||
it('should validate pills reasoning message', () => {
|
||||
const pillsReasoning = {
|
||||
id: 'reasoning-pills-1',
|
||||
type: 'pills' as const,
|
||||
title: 'Related Items',
|
||||
status: 'completed' as const,
|
||||
pill_containers: [
|
||||
{
|
||||
title: 'Metrics',
|
||||
pills: [
|
||||
{
|
||||
text: 'Revenue',
|
||||
type: 'metric' as const,
|
||||
id: 'metric-1',
|
||||
},
|
||||
{
|
||||
text: 'Users',
|
||||
type: 'metric' as const,
|
||||
id: 'metric-2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = ReasoningMessageSchema.parse(pillsReasoning);
|
||||
expect(result.type).toBe('pills');
|
||||
expect(result.pill_containers).toHaveLength(1);
|
||||
expect(result.pill_containers[0].pills).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should validate all pill types', () => {
|
||||
const pillTypes = [
|
||||
'metric',
|
||||
'dashboard',
|
||||
'collection',
|
||||
'dataset',
|
||||
'term',
|
||||
'topic',
|
||||
'value',
|
||||
'empty',
|
||||
] as const;
|
||||
|
||||
for (const pillType of pillTypes) {
|
||||
const pillsReasoning = {
|
||||
id: 'reasoning-pills-1',
|
||||
type: 'pills' as const,
|
||||
title: 'Test Pills',
|
||||
status: 'completed' as const,
|
||||
pill_containers: [
|
||||
{
|
||||
title: 'Test Container',
|
||||
pills: [
|
||||
{
|
||||
text: `Test ${pillType}`,
|
||||
type: pillType,
|
||||
id: `${pillType}-1`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = ReasoningMessageSchema.parse(pillsReasoning);
|
||||
expect(result.pill_containers[0].pills[0].type).toBe(pillType);
|
||||
}
|
||||
});
|
||||
|
||||
it('should validate multiple pill containers', () => {
|
||||
const pillsReasoning = {
|
||||
id: 'reasoning-pills-1',
|
||||
type: 'pills' as const,
|
||||
title: 'Multiple Containers',
|
||||
status: 'completed' as const,
|
||||
pill_containers: [
|
||||
{
|
||||
title: 'Metrics',
|
||||
pills: [{ text: 'Revenue', type: 'metric' as const, id: 'metric-1' }],
|
||||
},
|
||||
{
|
||||
title: 'Dashboards',
|
||||
pills: [{ text: 'Sales Dashboard', type: 'dashboard' as const, id: 'dash-1' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = ReasoningMessageSchema.parse(pillsReasoning);
|
||||
expect(result.pill_containers).toHaveLength(2);
|
||||
expect(result.pill_containers[0].title).toBe('Metrics');
|
||||
expect(result.pill_containers[1].title).toBe('Dashboards');
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate finished_reasoning field across all types', () => {
|
||||
const textWithFinished = {
|
||||
id: 'reasoning-1',
|
||||
type: 'text' as const,
|
||||
title: 'Test',
|
||||
status: 'completed' as const,
|
||||
finished_reasoning: true,
|
||||
};
|
||||
|
||||
const result = ReasoningMessageSchema.parse(textWithFinished);
|
||||
expect(result.finished_reasoning).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChatMessageSchema', () => {
|
||||
const baseChatMessage = {
|
||||
id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
request_message: {
|
||||
request: 'Show me revenue metrics',
|
||||
sender_id: 'user-123',
|
||||
sender_name: 'John Doe',
|
||||
sender_avatar: 'https://example.com/avatar.jpg',
|
||||
},
|
||||
response_messages: {
|
||||
'resp-1': {
|
||||
id: 'resp-1',
|
||||
type: 'text' as const,
|
||||
message: 'Here are your revenue metrics',
|
||||
},
|
||||
},
|
||||
response_message_ids: ['resp-1'],
|
||||
reasoning_message_ids: ['reason-1'],
|
||||
reasoning_messages: {
|
||||
'reason-1': {
|
||||
id: 'reason-1',
|
||||
type: 'text' as const,
|
||||
title: 'Analyzing request',
|
||||
status: 'completed' as const,
|
||||
},
|
||||
},
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
final_reasoning_message: 'reason-1',
|
||||
feedback: null,
|
||||
is_completed: true,
|
||||
};
|
||||
|
||||
it('should validate complete chat message', () => {
|
||||
const result = ChatMessageSchema.parse(baseChatMessage);
|
||||
expect(result).toEqual(baseChatMessage);
|
||||
});
|
||||
|
||||
it('should handle null request_message', () => {
|
||||
const messageWithNullRequest = {
|
||||
...baseChatMessage,
|
||||
request_message: null,
|
||||
};
|
||||
|
||||
const result = ChatMessageSchema.parse(messageWithNullRequest);
|
||||
expect(result.request_message).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle optional sender_avatar', () => {
|
||||
const messageWithoutAvatar = {
|
||||
...baseChatMessage,
|
||||
request_message: {
|
||||
request: 'Test request',
|
||||
sender_id: 'user-123',
|
||||
sender_name: 'John Doe',
|
||||
},
|
||||
};
|
||||
|
||||
const result = ChatMessageSchema.parse(messageWithoutAvatar);
|
||||
expect(result.request_message?.sender_avatar).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle null sender_avatar', () => {
|
||||
const messageWithNullAvatar = {
|
||||
...baseChatMessage,
|
||||
request_message: {
|
||||
request: 'Test request',
|
||||
sender_id: 'user-123',
|
||||
sender_name: 'John Doe',
|
||||
sender_avatar: null,
|
||||
},
|
||||
};
|
||||
|
||||
const result = ChatMessageSchema.parse(messageWithNullAvatar);
|
||||
expect(result.request_message?.sender_avatar).toBeNull();
|
||||
});
|
||||
|
||||
it('should validate negative feedback', () => {
|
||||
const messageWithFeedback = {
|
||||
...baseChatMessage,
|
||||
feedback: 'negative' as const,
|
||||
};
|
||||
|
||||
const result = ChatMessageSchema.parse(messageWithFeedback);
|
||||
expect(result.feedback).toBe('negative');
|
||||
});
|
||||
|
||||
it('should validate complex nested structures', () => {
|
||||
const complexMessage = {
|
||||
...baseChatMessage,
|
||||
response_messages: {
|
||||
'text-1': {
|
||||
id: 'text-1',
|
||||
type: 'text' as const,
|
||||
message: 'Analysis complete',
|
||||
is_final_message: true,
|
||||
},
|
||||
'file-1': {
|
||||
id: 'file-1',
|
||||
type: 'file' as const,
|
||||
file_type: 'metric' as const,
|
||||
file_name: 'revenue_metrics.yaml',
|
||||
version_number: 1,
|
||||
metadata: [
|
||||
{
|
||||
status: 'completed' as const,
|
||||
message: 'File generated',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
response_message_ids: ['text-1', 'file-1'],
|
||||
reasoning_messages: {
|
||||
'reason-1': {
|
||||
id: 'reason-1',
|
||||
type: 'text' as const,
|
||||
title: 'Processing request',
|
||||
status: 'completed' as const,
|
||||
},
|
||||
'reason-2': {
|
||||
id: 'reason-2',
|
||||
type: 'files' as const,
|
||||
title: 'Generated files',
|
||||
status: 'completed' as const,
|
||||
file_ids: ['gen-file-1'],
|
||||
files: {
|
||||
'gen-file-1': {
|
||||
id: 'gen-file-1',
|
||||
file_type: 'metric' as const,
|
||||
file_name: 'temp_metric.yaml',
|
||||
status: 'completed' as const,
|
||||
file: { text: 'temp content' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
reasoning_message_ids: ['reason-1', 'reason-2'],
|
||||
};
|
||||
|
||||
const result = ChatMessageSchema.parse(complexMessage);
|
||||
expect(result.response_message_ids).toHaveLength(2);
|
||||
expect(result.reasoning_message_ids).toHaveLength(2);
|
||||
expect(result.response_messages['file-1'].type).toBe('file');
|
||||
expect(result.reasoning_messages['reason-2'].type).toBe('files');
|
||||
});
|
||||
|
||||
it('should require UUID format for id', () => {
|
||||
const invalidMessage = {
|
||||
...baseChatMessage,
|
||||
id: 'invalid-uuid',
|
||||
};
|
||||
|
||||
expect(() => ChatMessageSchema.parse(invalidMessage)).toThrow();
|
||||
});
|
||||
|
||||
it('should validate empty collections', () => {
|
||||
const messageWithEmptyCollections = {
|
||||
...baseChatMessage,
|
||||
response_messages: {},
|
||||
response_message_ids: [],
|
||||
reasoning_messages: {},
|
||||
reasoning_message_ids: [],
|
||||
};
|
||||
|
||||
const result = ChatMessageSchema.parse(messageWithEmptyCollections);
|
||||
expect(result.response_message_ids).toEqual([]);
|
||||
expect(result.reasoning_message_ids).toEqual([]);
|
||||
});
|
||||
|
||||
it('should have correct type inference', () => {
|
||||
const message: ChatMessage = baseChatMessage;
|
||||
expect(message.id).toBe(baseChatMessage.id);
|
||||
|
||||
// Test discriminated union type inference
|
||||
const textResponse: ChatMessageResponseMessage_Text = {
|
||||
id: 'text-1',
|
||||
type: 'text',
|
||||
message: 'Text response',
|
||||
};
|
||||
expect(textResponse.type).toBe('text');
|
||||
|
||||
const fileResponse: ChatMessageResponseMessage_File = {
|
||||
id: 'file-1',
|
||||
type: 'file',
|
||||
file_type: 'metric',
|
||||
file_name: 'test.yaml',
|
||||
version_number: 1,
|
||||
};
|
||||
expect(fileResponse.type).toBe('file');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,309 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
type AssetPermissionRole,
|
||||
AssetPermissionRoleSchema,
|
||||
type BusterShareIndividual,
|
||||
BusterShareIndividualSchema,
|
||||
type ChatCreateHandlerRequest,
|
||||
ChatCreateHandlerRequestSchema,
|
||||
type ChatCreateRequest,
|
||||
ChatCreateRequestSchema,
|
||||
type ChatWithMessages,
|
||||
ChatWithMessagesSchema,
|
||||
} from './chat.types';
|
||||
|
||||
describe('AssetPermissionRoleSchema', () => {
|
||||
it('should validate correct permission roles', () => {
|
||||
expect(AssetPermissionRoleSchema.parse('viewer')).toBe('viewer');
|
||||
expect(AssetPermissionRoleSchema.parse('editor')).toBe('editor');
|
||||
expect(AssetPermissionRoleSchema.parse('owner')).toBe('owner');
|
||||
});
|
||||
|
||||
it('should reject invalid permission roles', () => {
|
||||
expect(() => AssetPermissionRoleSchema.parse('admin')).toThrow();
|
||||
expect(() => AssetPermissionRoleSchema.parse('invalid')).toThrow();
|
||||
expect(() => AssetPermissionRoleSchema.parse('')).toThrow();
|
||||
expect(() => AssetPermissionRoleSchema.parse(null)).toThrow();
|
||||
});
|
||||
|
||||
it('should have correct type inference', () => {
|
||||
const role: AssetPermissionRole = 'viewer';
|
||||
expect(role).toBe('viewer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('BusterShareIndividualSchema', () => {
|
||||
it('should validate valid individual permission objects', () => {
|
||||
const validIndividual = {
|
||||
email: 'test@example.com',
|
||||
role: 'viewer' as const,
|
||||
name: 'Test User',
|
||||
};
|
||||
|
||||
const result = BusterShareIndividualSchema.parse(validIndividual);
|
||||
expect(result).toEqual(validIndividual);
|
||||
});
|
||||
|
||||
it('should validate without optional name field', () => {
|
||||
const validIndividual = {
|
||||
email: 'test@example.com',
|
||||
role: 'editor' as const,
|
||||
};
|
||||
|
||||
const result = BusterShareIndividualSchema.parse(validIndividual);
|
||||
expect(result).toEqual(validIndividual);
|
||||
});
|
||||
|
||||
it('should reject invalid email addresses', () => {
|
||||
const invalidIndividual = {
|
||||
email: 'invalid-email',
|
||||
role: 'viewer' as const,
|
||||
};
|
||||
|
||||
expect(() => BusterShareIndividualSchema.parse(invalidIndividual)).toThrow();
|
||||
});
|
||||
|
||||
it('should reject invalid roles', () => {
|
||||
const invalidIndividual = {
|
||||
email: 'test@example.com',
|
||||
role: 'invalid',
|
||||
};
|
||||
|
||||
expect(() => BusterShareIndividualSchema.parse(invalidIndividual)).toThrow();
|
||||
});
|
||||
|
||||
it('should require email and role fields', () => {
|
||||
expect(() => BusterShareIndividualSchema.parse({})).toThrow();
|
||||
expect(() => BusterShareIndividualSchema.parse({ email: 'test@example.com' })).toThrow();
|
||||
expect(() => BusterShareIndividualSchema.parse({ role: 'viewer' })).toThrow();
|
||||
});
|
||||
|
||||
it('should have correct type inference', () => {
|
||||
const individual: BusterShareIndividual = {
|
||||
email: 'test@example.com',
|
||||
role: 'owner',
|
||||
name: 'Test User',
|
||||
};
|
||||
expect(individual.email).toBe('test@example.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChatWithMessagesSchema', () => {
|
||||
const baseChatData = {
|
||||
id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
title: 'Test Chat',
|
||||
is_favorited: false,
|
||||
message_ids: ['msg1', 'msg2'],
|
||||
messages: {
|
||||
msg1: {
|
||||
id: '123e4567-e89b-12d3-a456-426614174001',
|
||||
request_message: null,
|
||||
response_messages: {},
|
||||
response_message_ids: [],
|
||||
reasoning_message_ids: [],
|
||||
reasoning_messages: {},
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
final_reasoning_message: null,
|
||||
feedback: null,
|
||||
is_completed: true,
|
||||
},
|
||||
},
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
created_by: 'test-user',
|
||||
created_by_id: 'user123',
|
||||
created_by_name: 'Test User',
|
||||
created_by_avatar: 'https://example.com/avatar.png',
|
||||
publicly_accessible: false,
|
||||
};
|
||||
|
||||
it('should validate valid chat with messages', () => {
|
||||
const result = ChatWithMessagesSchema.parse(baseChatData);
|
||||
expect(result).toEqual(baseChatData);
|
||||
});
|
||||
|
||||
it('should handle optional fields correctly', () => {
|
||||
const chatWithOptionals = {
|
||||
...baseChatData,
|
||||
individual_permissions: [
|
||||
{
|
||||
email: 'user@example.com',
|
||||
role: 'viewer' as const,
|
||||
name: 'Shared User',
|
||||
},
|
||||
],
|
||||
public_expiry_date: '2024-12-31T23:59:59Z',
|
||||
public_enabled_by: 'admin-user',
|
||||
public_password: 'secret123',
|
||||
permission: 'editor' as const,
|
||||
};
|
||||
|
||||
const result = ChatWithMessagesSchema.parse(chatWithOptionals);
|
||||
expect(result.individual_permissions).toHaveLength(1);
|
||||
expect(result.public_expiry_date).toBe('2024-12-31T23:59:59Z');
|
||||
});
|
||||
|
||||
it('should handle null avatar', () => {
|
||||
const chatWithNullAvatar = {
|
||||
...baseChatData,
|
||||
created_by_avatar: null,
|
||||
};
|
||||
|
||||
const result = ChatWithMessagesSchema.parse(chatWithNullAvatar);
|
||||
expect(result.created_by_avatar).toBeNull();
|
||||
});
|
||||
|
||||
it('should reject invalid UUID formats', () => {
|
||||
const invalidChat = {
|
||||
...baseChatData,
|
||||
id: 'invalid-uuid',
|
||||
};
|
||||
|
||||
expect(() => ChatWithMessagesSchema.parse(invalidChat)).toThrow();
|
||||
});
|
||||
|
||||
it('should validate nested individual permissions', () => {
|
||||
const chatWithPermissions = {
|
||||
...baseChatData,
|
||||
individual_permissions: [
|
||||
{
|
||||
email: 'user1@example.com',
|
||||
role: 'viewer' as const,
|
||||
},
|
||||
{
|
||||
email: 'user2@example.com',
|
||||
role: 'editor' as const,
|
||||
name: 'User Two',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = ChatWithMessagesSchema.parse(chatWithPermissions);
|
||||
expect(result.individual_permissions).toHaveLength(2);
|
||||
expect(result.individual_permissions![0].email).toBe('user1@example.com');
|
||||
});
|
||||
|
||||
it('should have correct type inference', () => {
|
||||
const chat: ChatWithMessages = baseChatData;
|
||||
expect(chat.id).toBe(baseChatData.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChatCreateRequestSchema', () => {
|
||||
it('should validate minimal request', () => {
|
||||
const minimalRequest = {};
|
||||
const result = ChatCreateRequestSchema.parse(minimalRequest);
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should validate complete request with new asset fields', () => {
|
||||
const request = {
|
||||
prompt: 'Test prompt',
|
||||
chat_id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
message_id: '123e4567-e89b-12d3-a456-426614174001',
|
||||
asset_id: '123e4567-e89b-12d3-a456-426614174002',
|
||||
asset_type: 'metric_file' as const,
|
||||
};
|
||||
|
||||
const result = ChatCreateRequestSchema.parse(request);
|
||||
expect(result).toEqual(request);
|
||||
});
|
||||
|
||||
it('should validate with legacy fields', () => {
|
||||
const legacyRequest = {
|
||||
prompt: 'Test prompt',
|
||||
metric_id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
dashboard_id: '123e4567-e89b-12d3-a456-426614174001',
|
||||
};
|
||||
|
||||
const result = ChatCreateRequestSchema.parse(legacyRequest);
|
||||
expect(result).toEqual(legacyRequest);
|
||||
});
|
||||
|
||||
it('should require asset_type when asset_id is provided', () => {
|
||||
const invalidRequest = {
|
||||
asset_id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
// Missing asset_type
|
||||
};
|
||||
|
||||
expect(() => ChatCreateRequestSchema.parse(invalidRequest)).toThrow();
|
||||
});
|
||||
|
||||
it('should allow asset_type without asset_id', () => {
|
||||
const validRequest = {
|
||||
asset_type: 'dashboard_file' as const,
|
||||
// No asset_id
|
||||
};
|
||||
|
||||
const result = ChatCreateRequestSchema.parse(validRequest);
|
||||
expect(result.asset_type).toBe('dashboard_file');
|
||||
});
|
||||
|
||||
it('should reject invalid UUID formats', () => {
|
||||
const invalidRequest = {
|
||||
chat_id: 'invalid-uuid',
|
||||
};
|
||||
|
||||
expect(() => ChatCreateRequestSchema.parse(invalidRequest)).toThrow();
|
||||
});
|
||||
|
||||
it('should reject invalid asset types', () => {
|
||||
const invalidRequest = {
|
||||
asset_id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
asset_type: 'invalid_type',
|
||||
};
|
||||
|
||||
expect(() => ChatCreateRequestSchema.parse(invalidRequest)).toThrow();
|
||||
});
|
||||
|
||||
it('should have correct type inference', () => {
|
||||
const request: ChatCreateRequest = {
|
||||
prompt: 'Test',
|
||||
asset_type: 'metric_file',
|
||||
};
|
||||
expect(request.prompt).toBe('Test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChatCreateHandlerRequestSchema', () => {
|
||||
it('should validate handler request without legacy fields', () => {
|
||||
const handlerRequest = {
|
||||
prompt: 'Test prompt',
|
||||
chat_id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
message_id: '123e4567-e89b-12d3-a456-426614174001',
|
||||
asset_id: '123e4567-e89b-12d3-a456-426614174002',
|
||||
asset_type: 'dashboard_file' as const,
|
||||
};
|
||||
|
||||
const result = ChatCreateHandlerRequestSchema.parse(handlerRequest);
|
||||
expect(result).toEqual(handlerRequest);
|
||||
});
|
||||
|
||||
it('should validate minimal handler request', () => {
|
||||
const minimalRequest = {};
|
||||
const result = ChatCreateHandlerRequestSchema.parse(minimalRequest);
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should reject legacy fields (metric_id, dashboard_id)', () => {
|
||||
const requestWithLegacy = {
|
||||
prompt: 'Test prompt',
|
||||
metric_id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
};
|
||||
|
||||
// This should still parse since extra fields are typically ignored in zod objects
|
||||
// unless .strict() is used, but let's verify the schema behavior
|
||||
const result = ChatCreateHandlerRequestSchema.parse(requestWithLegacy);
|
||||
expect(result.prompt).toBe('Test prompt');
|
||||
expect((result as any).metric_id).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should have correct type inference', () => {
|
||||
const request: ChatCreateHandlerRequest = {
|
||||
prompt: 'Test',
|
||||
asset_type: 'metric_file',
|
||||
};
|
||||
expect(request.prompt).toBe('Test');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,217 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
type AssetPermissionRole,
|
||||
AssetPermissionRoleSchema,
|
||||
type BusterShareIndividual,
|
||||
BusterShareIndividualSchema,
|
||||
type ChatCreateHandlerRequest,
|
||||
ChatCreateHandlerRequestSchema,
|
||||
type ChatCreateRequest,
|
||||
ChatCreateRequestSchema,
|
||||
ChatError,
|
||||
ChatErrorCode,
|
||||
type ChatErrorResponse,
|
||||
ChatErrorResponseSchema,
|
||||
type ChatMessage,
|
||||
type ChatMessageReasoningMessage,
|
||||
type ChatMessageResponseMessage,
|
||||
ChatMessageSchema,
|
||||
type ChatWithMessages,
|
||||
ChatWithMessagesSchema,
|
||||
ReasoningMessageSchema,
|
||||
ResponseMessageSchema,
|
||||
} from './index';
|
||||
|
||||
describe('chats index exports', () => {
|
||||
it('should export all chat.types schemas', () => {
|
||||
expect(AssetPermissionRoleSchema).toBeDefined();
|
||||
expect(BusterShareIndividualSchema).toBeDefined();
|
||||
expect(ChatWithMessagesSchema).toBeDefined();
|
||||
expect(ChatCreateRequestSchema).toBeDefined();
|
||||
expect(ChatCreateHandlerRequestSchema).toBeDefined();
|
||||
});
|
||||
|
||||
it('should export all chat-errors.types', () => {
|
||||
expect(ChatErrorCode).toBeDefined();
|
||||
expect(ChatErrorResponseSchema).toBeDefined();
|
||||
expect(ChatError).toBeDefined();
|
||||
});
|
||||
|
||||
it('should export all chat-message.types schemas', () => {
|
||||
expect(ChatMessageSchema).toBeDefined();
|
||||
expect(ResponseMessageSchema).toBeDefined();
|
||||
expect(ReasoningMessageSchema).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have working type inference', () => {
|
||||
// Test that types are properly exported
|
||||
const role: AssetPermissionRole = 'viewer';
|
||||
expect(role).toBe('viewer');
|
||||
|
||||
const individual: BusterShareIndividual = {
|
||||
email: 'test@example.com',
|
||||
role: 'editor',
|
||||
};
|
||||
expect(individual.email).toBe('test@example.com');
|
||||
});
|
||||
|
||||
it('should validate schemas work from chats index', () => {
|
||||
// Test AssetPermissionRoleSchema
|
||||
expect(AssetPermissionRoleSchema.parse('viewer')).toBe('viewer');
|
||||
expect(() => AssetPermissionRoleSchema.parse('invalid')).toThrow();
|
||||
|
||||
// Test BusterShareIndividualSchema
|
||||
const validIndividual = {
|
||||
email: 'test@example.com',
|
||||
role: 'owner' as const,
|
||||
name: 'Test User',
|
||||
};
|
||||
expect(() => BusterShareIndividualSchema.parse(validIndividual)).not.toThrow();
|
||||
|
||||
// Test ChatErrorResponseSchema
|
||||
const validErrorResponse = {
|
||||
code: 'INVALID_REQUEST',
|
||||
message: 'Test error message',
|
||||
};
|
||||
expect(() => ChatErrorResponseSchema.parse(validErrorResponse)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should have working ChatError class', () => {
|
||||
const error = new ChatError(ChatErrorCode.PERMISSION_DENIED, 'Access denied', 403, {
|
||||
resource: 'chat',
|
||||
});
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.code).toBe('PERMISSION_DENIED');
|
||||
expect(error.statusCode).toBe(403);
|
||||
expect(error.details).toEqual({ resource: 'chat' });
|
||||
|
||||
const response = error.toResponse();
|
||||
expect(response.code).toBe('PERMISSION_DENIED');
|
||||
expect(response.message).toBe('Access denied');
|
||||
expect(response.details).toEqual({ resource: 'chat' });
|
||||
});
|
||||
|
||||
it('should validate response message discriminated union', () => {
|
||||
const textMessage = {
|
||||
id: 'text-1',
|
||||
type: 'text' as const,
|
||||
message: 'Hello world',
|
||||
};
|
||||
|
||||
const fileMessage = {
|
||||
id: 'file-1',
|
||||
type: 'file' as const,
|
||||
file_type: 'metric' as const,
|
||||
file_name: 'test.yaml',
|
||||
version_number: 1,
|
||||
};
|
||||
|
||||
expect(() => ResponseMessageSchema.parse(textMessage)).not.toThrow();
|
||||
expect(() => ResponseMessageSchema.parse(fileMessage)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate reasoning message discriminated union', () => {
|
||||
const textReasoning = {
|
||||
id: 'reason-1',
|
||||
type: 'text' as const,
|
||||
title: 'Processing',
|
||||
status: 'loading' as const,
|
||||
};
|
||||
|
||||
const filesReasoning = {
|
||||
id: 'reason-2',
|
||||
type: 'files' as const,
|
||||
title: 'Generated Files',
|
||||
status: 'completed' as const,
|
||||
file_ids: ['file-1'],
|
||||
files: {
|
||||
'file-1': {
|
||||
id: 'file-1',
|
||||
file_type: 'metric' as const,
|
||||
file_name: 'test.yaml',
|
||||
status: 'completed' as const,
|
||||
file: { text: 'content' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const pillsReasoning = {
|
||||
id: 'reason-3',
|
||||
type: 'pills' as const,
|
||||
title: 'Related Items',
|
||||
status: 'completed' as const,
|
||||
pill_containers: [
|
||||
{
|
||||
title: 'Metrics',
|
||||
pills: [
|
||||
{
|
||||
text: 'Revenue',
|
||||
type: 'metric' as const,
|
||||
id: 'metric-1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(() => ReasoningMessageSchema.parse(textReasoning)).not.toThrow();
|
||||
expect(() => ReasoningMessageSchema.parse(filesReasoning)).not.toThrow();
|
||||
expect(() => ReasoningMessageSchema.parse(pillsReasoning)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate complete chat message', () => {
|
||||
const chatMessage = {
|
||||
id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
request_message: {
|
||||
request: 'Show me metrics',
|
||||
sender_id: 'user-123',
|
||||
sender_name: 'John Doe',
|
||||
},
|
||||
response_messages: {
|
||||
'resp-1': {
|
||||
id: 'resp-1',
|
||||
type: 'text' as const,
|
||||
message: 'Here are your metrics',
|
||||
},
|
||||
},
|
||||
response_message_ids: ['resp-1'],
|
||||
reasoning_message_ids: ['reason-1'],
|
||||
reasoning_messages: {
|
||||
'reason-1': {
|
||||
id: 'reason-1',
|
||||
type: 'text' as const,
|
||||
title: 'Processing request',
|
||||
status: 'completed' as const,
|
||||
},
|
||||
},
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
final_reasoning_message: null,
|
||||
feedback: null,
|
||||
is_completed: true,
|
||||
};
|
||||
|
||||
expect(() => ChatMessageSchema.parse(chatMessage)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate chat creation requests', () => {
|
||||
const minimalRequest = {};
|
||||
expect(() => ChatCreateRequestSchema.parse(minimalRequest)).not.toThrow();
|
||||
|
||||
const fullRequest = {
|
||||
prompt: 'Create a dashboard',
|
||||
chat_id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
message_id: '123e4567-e89b-12d3-a456-426614174001',
|
||||
asset_id: '123e4567-e89b-12d3-a456-426614174002',
|
||||
asset_type: 'dashboard_file' as const,
|
||||
};
|
||||
expect(() => ChatCreateRequestSchema.parse(fullRequest)).not.toThrow();
|
||||
|
||||
const handlerRequest = {
|
||||
prompt: 'Create a metric',
|
||||
asset_type: 'metric_file' as const,
|
||||
};
|
||||
expect(() => ChatCreateHandlerRequestSchema.parse(handlerRequest)).not.toThrow();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,249 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { type Currency, CurrencySchema } from './currency.types';
|
||||
|
||||
describe('CurrencySchema', () => {
|
||||
it('should validate valid currency object', () => {
|
||||
const validCurrency = {
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
flag: '🇺🇸',
|
||||
};
|
||||
|
||||
const result = CurrencySchema.parse(validCurrency);
|
||||
expect(result).toEqual(validCurrency);
|
||||
});
|
||||
|
||||
it('should validate different currency codes', () => {
|
||||
const currencies = [
|
||||
{ code: 'EUR', description: 'Euro', flag: '🇪🇺' },
|
||||
{ code: 'GBP', description: 'British Pound Sterling', flag: '🇬🇧' },
|
||||
{ code: 'JPY', description: 'Japanese Yen', flag: '🇯🇵' },
|
||||
{ code: 'CAD', description: 'Canadian Dollar', flag: '🇨🇦' },
|
||||
{ code: 'AUD', description: 'Australian Dollar', flag: '🇦🇺' },
|
||||
];
|
||||
|
||||
for (const currency of currencies) {
|
||||
const result = CurrencySchema.parse(currency);
|
||||
expect(result).toEqual(currency);
|
||||
expect(result.code).toBe(currency.code);
|
||||
}
|
||||
});
|
||||
|
||||
it('should accept empty strings for fields', () => {
|
||||
const currencyWithEmptyStrings = {
|
||||
code: '',
|
||||
description: '',
|
||||
flag: '',
|
||||
};
|
||||
|
||||
const result = CurrencySchema.parse(currencyWithEmptyStrings);
|
||||
expect(result).toEqual(currencyWithEmptyStrings);
|
||||
});
|
||||
|
||||
it('should accept long descriptions', () => {
|
||||
const currencyWithLongDescription = {
|
||||
code: 'BTC',
|
||||
description:
|
||||
'Bitcoin - A decentralized digital currency that can be transferred on the peer-to-peer bitcoin network',
|
||||
flag: '₿',
|
||||
};
|
||||
|
||||
const result = CurrencySchema.parse(currencyWithLongDescription);
|
||||
expect(result.description).toBe(currencyWithLongDescription.description);
|
||||
});
|
||||
|
||||
it('should accept special characters in flag', () => {
|
||||
const currencyWithSpecialFlag = {
|
||||
code: 'XAU',
|
||||
description: 'Gold (troy ounce)',
|
||||
flag: '🥇',
|
||||
};
|
||||
|
||||
const result = CurrencySchema.parse(currencyWithSpecialFlag);
|
||||
expect(result.flag).toBe('🥇');
|
||||
});
|
||||
|
||||
it('should reject missing required fields', () => {
|
||||
expect(() => CurrencySchema.parse({})).toThrow();
|
||||
|
||||
expect(() =>
|
||||
CurrencySchema.parse({
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
// missing flag
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
CurrencySchema.parse({
|
||||
code: 'USD',
|
||||
// missing description
|
||||
flag: '🇺🇸',
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
CurrencySchema.parse({
|
||||
// missing code
|
||||
description: 'United States Dollar',
|
||||
flag: '🇺🇸',
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should reject non-string values', () => {
|
||||
expect(() =>
|
||||
CurrencySchema.parse({
|
||||
code: 123,
|
||||
description: 'United States Dollar',
|
||||
flag: '🇺🇸',
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
CurrencySchema.parse({
|
||||
code: 'USD',
|
||||
description: null,
|
||||
flag: '🇺🇸',
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
CurrencySchema.parse({
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
flag: true,
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should reject undefined values', () => {
|
||||
expect(() =>
|
||||
CurrencySchema.parse({
|
||||
code: undefined,
|
||||
description: 'United States Dollar',
|
||||
flag: '🇺🇸',
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
CurrencySchema.parse({
|
||||
code: 'USD',
|
||||
description: undefined,
|
||||
flag: '🇺🇸',
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
CurrencySchema.parse({
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
flag: undefined,
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should handle extra properties gracefully', () => {
|
||||
const currencyWithExtra = {
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
flag: '🇺🇸',
|
||||
extraProperty: 'this should be ignored',
|
||||
anotherExtra: 123,
|
||||
};
|
||||
|
||||
const result = CurrencySchema.parse(currencyWithExtra);
|
||||
|
||||
// Zod objects by default strip unknown properties
|
||||
expect(result).toEqual({
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
flag: '🇺🇸',
|
||||
});
|
||||
expect((result as any).extraProperty).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should validate whitespace strings', () => {
|
||||
const currencyWithWhitespace = {
|
||||
code: ' ',
|
||||
description: '\t\n',
|
||||
flag: ' ',
|
||||
};
|
||||
|
||||
const result = CurrencySchema.parse(currencyWithWhitespace);
|
||||
expect(result.code).toBe(' ');
|
||||
expect(result.description).toBe('\t\n');
|
||||
expect(result.flag).toBe(' ');
|
||||
});
|
||||
|
||||
it('should validate complex Unicode characters', () => {
|
||||
const currencyWithUnicode = {
|
||||
code: 'مﻼﺰﻲ', // Arabic text
|
||||
description: '中文货币描述', // Chinese text
|
||||
flag: '🏴☠️', // Complex emoji
|
||||
};
|
||||
|
||||
const result = CurrencySchema.parse(currencyWithUnicode);
|
||||
expect(result).toEqual(currencyWithUnicode);
|
||||
});
|
||||
|
||||
it('should have correct type inference', () => {
|
||||
const currency: Currency = {
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
flag: '🇺🇸',
|
||||
};
|
||||
|
||||
expect(currency.code).toBe('USD');
|
||||
expect(currency.description).toBe('United States Dollar');
|
||||
expect(currency.flag).toBe('🇺🇸');
|
||||
});
|
||||
|
||||
it('should work with safeParse for error handling', () => {
|
||||
const validCurrency = {
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
flag: '🇺🇸',
|
||||
};
|
||||
|
||||
const validResult = CurrencySchema.safeParse(validCurrency);
|
||||
expect(validResult.success).toBe(true);
|
||||
if (validResult.success) {
|
||||
expect(validResult.data).toEqual(validCurrency);
|
||||
}
|
||||
|
||||
const invalidCurrency = {
|
||||
code: 123,
|
||||
description: 'Invalid',
|
||||
flag: '🇺🇸',
|
||||
};
|
||||
|
||||
const invalidResult = CurrencySchema.safeParse(invalidCurrency);
|
||||
expect(invalidResult.success).toBe(false);
|
||||
if (!invalidResult.success) {
|
||||
expect(invalidResult.error).toBeDefined();
|
||||
expect(invalidResult.error.issues).toHaveLength(1);
|
||||
expect(invalidResult.error.issues[0].path).toEqual(['code']);
|
||||
}
|
||||
});
|
||||
|
||||
it('should validate real-world currency examples', () => {
|
||||
const realCurrencies = [
|
||||
{ code: 'USD', description: 'United States Dollar', flag: '🇺🇸' },
|
||||
{ code: 'EUR', description: 'Euro', flag: '🇪🇺' },
|
||||
{ code: 'JPY', description: 'Japanese Yen', flag: '🇯🇵' },
|
||||
{ code: 'GBP', description: 'British Pound Sterling', flag: '🇬🇧' },
|
||||
{ code: 'CHF', description: 'Swiss Franc', flag: '🇨🇭' },
|
||||
{ code: 'CNY', description: 'Chinese Yuan', flag: '🇨🇳' },
|
||||
{ code: 'INR', description: 'Indian Rupee', flag: '🇮🇳' },
|
||||
{ code: 'BRL', description: 'Brazilian Real', flag: '🇧🇷' },
|
||||
{ code: 'KRW', description: 'South Korean Won', flag: '🇰🇷' },
|
||||
{ code: 'MXN', description: 'Mexican Peso', flag: '🇲🇽' },
|
||||
];
|
||||
|
||||
for (const currency of realCurrencies) {
|
||||
const result = CurrencySchema.parse(currency);
|
||||
expect(result).toEqual(currency);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { type Currency, CurrencySchema } from './index';
|
||||
|
||||
describe('currency index exports', () => {
|
||||
it('should export CurrencySchema', () => {
|
||||
expect(CurrencySchema).toBeDefined();
|
||||
expect(typeof CurrencySchema.parse).toBe('function');
|
||||
});
|
||||
|
||||
it('should have working Currency type', () => {
|
||||
// Test type inference
|
||||
const currency: Currency = {
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
flag: '🇺🇸',
|
||||
};
|
||||
expect(currency.code).toBe('USD');
|
||||
});
|
||||
|
||||
it('should validate currency through index export', () => {
|
||||
const validCurrency = {
|
||||
code: 'EUR',
|
||||
description: 'Euro',
|
||||
flag: '🇪🇺',
|
||||
};
|
||||
|
||||
const result = CurrencySchema.parse(validCurrency);
|
||||
expect(result).toEqual(validCurrency);
|
||||
});
|
||||
|
||||
it('should reject invalid currency through index export', () => {
|
||||
const invalidCurrency = {
|
||||
code: 123,
|
||||
description: 'Invalid',
|
||||
flag: '🇺🇸',
|
||||
};
|
||||
|
||||
expect(() => CurrencySchema.parse(invalidCurrency)).toThrow();
|
||||
});
|
||||
|
||||
it('should work with safeParse from index', () => {
|
||||
const validCurrency = {
|
||||
code: 'GBP',
|
||||
description: 'British Pound Sterling',
|
||||
flag: '🇬🇧',
|
||||
};
|
||||
|
||||
const result = CurrencySchema.safeParse(validCurrency);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data).toEqual(validCurrency);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle multiple currency validations from index', () => {
|
||||
const currencies = [
|
||||
{ code: 'USD', description: 'United States Dollar', flag: '🇺🇸' },
|
||||
{ code: 'EUR', description: 'Euro', flag: '🇪🇺' },
|
||||
{ code: 'JPY', description: 'Japanese Yen', flag: '🇯🇵' },
|
||||
];
|
||||
|
||||
for (const currency of currencies) {
|
||||
expect(() => CurrencySchema.parse(currency)).not.toThrow();
|
||||
const result = CurrencySchema.parse(currency);
|
||||
expect(result.code).toBe(currency.code);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,89 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
AssetPermissionRoleSchema,
|
||||
BusterShareIndividualSchema,
|
||||
ChatCreateHandlerRequestSchema,
|
||||
ChatCreateRequestSchema,
|
||||
ChatError,
|
||||
ChatErrorCode,
|
||||
ChatErrorResponseSchema,
|
||||
ChatMessageSchema,
|
||||
ChatWithMessagesSchema,
|
||||
ReasoningMessageSchema,
|
||||
ResponseMessageSchema,
|
||||
} from './index';
|
||||
|
||||
describe('server-shared main index exports', () => {
|
||||
it('should export all chat types', () => {
|
||||
// Check that chat-related exports are available
|
||||
expect(AssetPermissionRoleSchema).toBeDefined();
|
||||
expect(BusterShareIndividualSchema).toBeDefined();
|
||||
expect(ChatWithMessagesSchema).toBeDefined();
|
||||
expect(ChatCreateRequestSchema).toBeDefined();
|
||||
expect(ChatCreateHandlerRequestSchema).toBeDefined();
|
||||
});
|
||||
|
||||
it('should export chat error types', () => {
|
||||
expect(ChatErrorCode).toBeDefined();
|
||||
expect(ChatErrorResponseSchema).toBeDefined();
|
||||
expect(ChatError).toBeDefined();
|
||||
});
|
||||
|
||||
it('should export chat message types', () => {
|
||||
expect(ChatMessageSchema).toBeDefined();
|
||||
expect(ResponseMessageSchema).toBeDefined();
|
||||
expect(ReasoningMessageSchema).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have working schema validation from exports', () => {
|
||||
expect(AssetPermissionRoleSchema.parse('viewer')).toBe('viewer');
|
||||
expect(AssetPermissionRoleSchema.parse('editor')).toBe('editor');
|
||||
expect(AssetPermissionRoleSchema.parse('owner')).toBe('owner');
|
||||
});
|
||||
|
||||
it('should have working ChatError class from exports', () => {
|
||||
const error = new ChatError(ChatErrorCode.INVALID_REQUEST, 'Test error');
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.code).toBe('INVALID_REQUEST');
|
||||
expect(error.message).toBe('Test error');
|
||||
});
|
||||
|
||||
it('should validate chat message schema works', () => {
|
||||
const validMessage = {
|
||||
id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
request_message: null,
|
||||
response_messages: {},
|
||||
response_message_ids: [],
|
||||
reasoning_message_ids: [],
|
||||
reasoning_messages: {},
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
final_reasoning_message: null,
|
||||
feedback: null,
|
||||
is_completed: true,
|
||||
};
|
||||
|
||||
expect(() => ChatMessageSchema.parse(validMessage)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate response message schema works', () => {
|
||||
const textMessage = {
|
||||
id: 'msg-1',
|
||||
type: 'text' as const,
|
||||
message: 'Test response',
|
||||
};
|
||||
|
||||
expect(() => ResponseMessageSchema.parse(textMessage)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate reasoning message schema works', () => {
|
||||
const reasoningMessage = {
|
||||
id: 'reasoning-1',
|
||||
type: 'text' as const,
|
||||
title: 'Test reasoning',
|
||||
status: 'completed' as const,
|
||||
};
|
||||
|
||||
expect(() => ReasoningMessageSchema.parse(reasoningMessage)).not.toThrow();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
import { baseConfig } from '@buster/vitest-config';
|
||||
|
||||
export default baseConfig;
|
|
@ -763,6 +763,13 @@ importers:
|
|||
zod:
|
||||
specifier: 'catalog:'
|
||||
version: 3.25.67
|
||||
devDependencies:
|
||||
'@buster/vitest-config':
|
||||
specifier: workspace:*
|
||||
version: link:../vitest-config
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@20.19.2)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(msw@2.10.2(@types/node@20.19.2)(typescript@5.8.3))(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
|
||||
|
||||
packages/slack:
|
||||
dependencies:
|
||||
|
|
Loading…
Reference in New Issue