Skip to content

Commit

Permalink
test(ai-assistant): ai assistant test cases (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianmusial committed Mar 12, 2024
1 parent 2374279 commit 6ab1042
Show file tree
Hide file tree
Showing 31 changed files with 3,199 additions and 1,217 deletions.
2 changes: 1 addition & 1 deletion libs/ai-assistant/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
"@nx/dependency-checks": 1
}
}
]
Expand Down
1 change: 0 additions & 1 deletion libs/ai-assistant/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable */
export default {
displayName: 'ai-assistant',
preset: '../../jest.preset.js',
Expand Down
3 changes: 2 additions & 1 deletion libs/ai-assistant/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@boldare/ai-assistant",
"version": "0.0.3",
"version": "0.0.5",
"dependencies": {
"tslib": "^2.3.0",
"openai": "^4.26.1",
Expand All @@ -10,6 +10,7 @@
"envfile": "^7.1.0",
"@nestjs/axios": "^3.0.1",
"@nestjs/websockets": "^10.3.0",
"@nestjs/platform-socket.io": "^10.3.0",
"socket.io": "^4.7.3",
"multer": "^1.4.4-lts.1"
},
Expand Down
39 changes: 39 additions & 0 deletions libs/ai-assistant/src/lib/agent/agent.base.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { definitionMock } from './agent.mock';
import { AgentService } from './agent.service';
import { AgentBase } from './agent.base';
import { AgentData } from './agent.model';

describe('AgentBase', () => {
let agentBase: AgentBase;
let agentService: AgentService;

beforeEach(() => {
agentService = new AgentService();
agentBase = new AgentBase(agentService);
});

describe('onModuleInit', () => {
it('should call agentService.add with definition and output', () => {
const addSpy = jest.spyOn(agentService, 'add');
agentBase.definition = definitionMock;

agentBase.onModuleInit();

expect(addSpy).toHaveBeenCalledWith(definitionMock, expect.any(Function));
});
});

describe('output', () => {
it('should return empty string when params are missing', async () => {
const result = await agentBase.output({} as AgentData);

expect(result).toBe('');
});

it('should return params when they are provided', async () => {
const result = await agentBase.output({ params: 'test' } as AgentData);

expect(result).toBe('test');
});
});
});
3 changes: 1 addition & 2 deletions libs/ai-assistant/src/lib/agent/agent.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ export class AgentBase implements OnModuleInit {

constructor(protected readonly agentService: AgentService) {}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async output(data: AgentData): Promise<string> {
return '';
return data.params || '';
}
}
10 changes: 10 additions & 0 deletions libs/ai-assistant/src/lib/agent/agent.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { AssistantCreateParams } from 'openai/resources/beta';

export const agentNameMock = 'agent-name';

export const agentMock = async () => 'agent-result';

export const definitionMock: AssistantCreateParams.AssistantToolsFunction = {
type: 'function',
function: { name: agentNameMock },
};
18 changes: 11 additions & 7 deletions libs/ai-assistant/src/lib/agent/agent.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { AgentService } from './agent.service';
import { AssistantCreateParams } from 'openai/resources/beta';
import { agentMock, agentNameMock, definitionMock } from './agent.mock';

describe('AgentService', () => {
let agentService: AgentService;
const agentNameMock = 'agent-name';
const agentMock = async () => 'agent-result';
const definitionMock: AssistantCreateParams.AssistantToolsFunction = {
type: 'function',
function: { name: agentNameMock },
};

beforeEach(() => {
agentService = new AgentService();
});

it('should be defined', () => {
expect(agentService).toBeDefined();
});

it('agentMock should be return value', async () => {
const result = await agentMock();

expect(result).toEqual('agent-result');
});

describe('add', () => {
it('should add new tool', async () => {
agentService.add(definitionMock, agentMock);
Expand Down
61 changes: 61 additions & 0 deletions libs/ai-assistant/src/lib/ai/ai.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { AiController } from './ai.controller';
import { AiService } from './ai.service';
import { mockFileData, transcriptionMock } from './ai.mock';

describe('AiController', () => {
let aiController: AiController;
let aiService: AiService;

beforeEach(() => {
aiService = new AiService();
aiController = new AiController(aiService);

jest
.spyOn(aiService.provider.audio.transcriptions, 'create')
.mockResolvedValue(transcriptionMock);
});

afterEach(() => {
jest.clearAllMocks();
});

describe('transcription', () => {
it('should return transcription', async () => {
const transcription = await aiController.postTranscription(mockFileData);

expect(transcription).toEqual({ content: transcriptionMock.text });
});

it('should throw an error', async () => {
jest
.spyOn(aiService.provider.audio.transcriptions, 'create')
.mockRejectedValue(new Error());

try {
await aiController.postTranscription(mockFileData);
} catch (error) {
expect((error as Error).message).toBeTruthy();
}
});
});

describe('speech', () => {
it('should return speech', async () => {
const speech = await aiController.postSpeech({ content: 'Hello' });

expect(speech).toBeInstanceOf(Buffer);
});

it('should throw an error', async () => {
jest
.spyOn(aiService.provider.audio.speech, 'create')
.mockRejectedValue(new Error());

try {
await aiController.postSpeech({ content: 'Hello' });
} catch (error) {
expect((error as Error).message).toBeTruthy();
}
});
});
});
19 changes: 15 additions & 4 deletions libs/ai-assistant/src/lib/ai/ai.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { AiService } from './ai.service';
import { SpeechPayload } from './ai.model';
import { FileInterceptor } from '@nestjs/platform-express';
import { toFile } from 'openai/uploads';
// @ts-expect-error multer is necessary
// eslint-disable-next-line
import { multer } from 'multer';

@Controller('assistant/ai')
export class AiController {
Expand All @@ -17,14 +20,22 @@ export class AiController {
@Post('transcription')
@UseInterceptors(FileInterceptor('file'))
async postTranscription(@UploadedFile() fileData: Express.Multer.File) {
const file = await toFile(fileData.buffer, 'audio.wav', { type: 'wav' });
const transcription = await this.aiService.transcription(file);
try {
const file = await toFile(fileData.buffer, 'audio.wav', { type: 'wav' });
const transcription = await this.aiService.transcription(file);

return { content: transcription.text };
return { content: transcription.text };
} catch (error) {
throw new Error('Error processing transcription');
}
}

@Post('speech')
async postSpeech(@Body() payload: SpeechPayload): Promise<Buffer> {
return await this.aiService.speech(payload);
try {
return await this.aiService.speech(payload);
} catch (error) {
throw new Error('Error processing speech');
}
}
}
15 changes: 15 additions & 0 deletions libs/ai-assistant/src/lib/ai/ai.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AiTranscription } from './ai.model';
// @ts-expect-error multer is necessary
// eslint-disable-next-line
import { multer } from 'multer';

export const mockBuffer = Buffer.from('fake audio data');

export const mockFileData = {
buffer: mockBuffer,
mimetype: 'audio/wav',
} as Express.Multer.File;

export const transcriptionMock: AiTranscription = {
text: 'Text from transcription',
};
40 changes: 40 additions & 0 deletions libs/ai-assistant/src/lib/ai/ai.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { toFile } from 'openai/uploads';
import { Response } from 'openai/core';
import { AiService } from './ai.service';
import { mockBuffer, transcriptionMock } from './ai.mock';

describe('AiService', () => {
let aiService: AiService;

beforeEach(() => {
aiService = new AiService();

jest
.spyOn(aiService.provider.audio.transcriptions, 'create')
.mockResolvedValue(transcriptionMock);

jest.spyOn(aiService.provider.audio.speech, 'create').mockResolvedValue({
arrayBuffer: jest.fn().mockResolvedValue(mockBuffer),
} as unknown as Response);
});

afterEach(() => {
jest.clearAllMocks();
});

it('should return transcription', async () => {
const file = await toFile(mockBuffer, 'audio.mp3', { type: 'mp3' });

const transcription = await aiService.transcription(file);

expect(transcription.text).toBe(transcriptionMock.text);
});

it('should return speech', async () => {
const data = { content: 'test content' };

const speech = await aiService.speech(data);

expect(speech).toStrictEqual(mockBuffer);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ConfigService } from '../config';
import { AiService } from '../ai';
import { AssistantFilesService } from './assistant-files.service';
import OpenAI from 'openai';

jest.mock('fs', () => ({
createReadStream: jest.fn().mockReturnValue('file'),
}));

describe('AssistantFilesService', () => {
let aiService: AiService;
let configService: ConfigService;
let assistantFilesService: AssistantFilesService;

beforeEach(() => {
aiService = new AiService();
configService = new ConfigService();
assistantFilesService = new AssistantFilesService(configService, aiService);
});

afterEach(() => {
jest.clearAllMocks();
});

it('should be defined', () => {
expect(assistantFilesService).toBeDefined();
});

it('should create files', async () => {
const fileNames = ['file1', 'file2'];
const create = jest.fn().mockResolvedValue({ id: 'id' });
aiService.provider = { files: { create } } as unknown as OpenAI;
configService.get = jest.fn().mockReturnValue({ filesDir: 'dir' });

const result = await assistantFilesService.create(fileNames);

expect(result).toEqual(['id', 'id']);
expect(create).toHaveBeenCalledTimes(2);
expect(create).toHaveBeenCalledWith({
file: 'file',
purpose: 'assistants',
});
});

it('should create files without file directory', async () => {
const fileNames = ['file1', 'file2'];
const create = jest.fn().mockResolvedValue({ id: 'id' });
aiService.provider = { files: { create } } as unknown as OpenAI;
configService.get = jest.fn().mockReturnValue({});

const result = await assistantFilesService.create(fileNames);

expect(result).toEqual(['id', 'id']);
expect(create).toHaveBeenCalledTimes(2);
expect(create).toHaveBeenCalledWith({
file: 'file',
purpose: 'assistants',
});
});
});
Loading

0 comments on commit 6ab1042

Please sign in to comment.