test: Add Voice service tests (546 lines, 22 tests)
Created comprehensive test suite for voice/speech recognition service: - OpenAI Whisper transcription integration - Azure OpenAI configuration support - Audio transcription with language detection - Activity extraction from natural language (GPT-4o-mini) - Support for 6 activity types (feeding, sleep, diaper, medicine, activity, milestone) - Multi-language support (en, es, fr, pt, zh) - Process voice input (transcribe + extract) - Generate clarification questions for ambiguous input - Save user feedback on voice command accuracy - Error handling and fallbacks Tests cover: - Standard OpenAI and Azure OpenAI configurations - Transcription with language parameter - Activity extraction for all types (feeding, sleep, diaper, medicine) - Unknown activity detection - Child name inclusion in prompts - Clarification question generation - Feedback persistence - Error scenarios and service unavailability Total: 546 lines, 22 test cases Coverage: Whisper transcription, GPT extraction, feedback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,546 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { BadRequestException } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
VoiceService,
|
||||||
|
TranscriptionResult,
|
||||||
|
ActivityExtractionResult,
|
||||||
|
} from './voice.service';
|
||||||
|
import { VoiceFeedback } from '../../database/entities';
|
||||||
|
import { SaveVoiceFeedbackDto } from './dto/save-voice-feedback.dto';
|
||||||
|
|
||||||
|
describe('VoiceService', () => {
|
||||||
|
let service: VoiceService;
|
||||||
|
let configService: ConfigService;
|
||||||
|
let voiceFeedbackRepository: Repository<VoiceFeedback>;
|
||||||
|
|
||||||
|
const mockConfigService = {
|
||||||
|
get: jest.fn((key: string) => {
|
||||||
|
const config: Record<string, any> = {
|
||||||
|
AZURE_OPENAI_ENABLED: false,
|
||||||
|
OPENAI_API_KEY: 'sk-test-key',
|
||||||
|
};
|
||||||
|
return config[key];
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockVoiceFeedbackRepository = {
|
||||||
|
create: jest.fn(),
|
||||||
|
save: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockOpenAI = {
|
||||||
|
audio: {
|
||||||
|
transcriptions: {
|
||||||
|
create: jest.fn().mockResolvedValue({
|
||||||
|
text: 'Baby ate 120 milliliters',
|
||||||
|
language: 'en',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
chat: {
|
||||||
|
completions: {
|
||||||
|
create: jest.fn().mockResolvedValue({
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
content: JSON.stringify({
|
||||||
|
type: 'feeding',
|
||||||
|
timestamp: null,
|
||||||
|
details: {
|
||||||
|
feedingType: 'bottle',
|
||||||
|
amount: 120,
|
||||||
|
unit: 'ml',
|
||||||
|
},
|
||||||
|
confidence: 0.95,
|
||||||
|
action: 'create_activity',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
VoiceService,
|
||||||
|
{
|
||||||
|
provide: ConfigService,
|
||||||
|
useValue: mockConfigService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(VoiceFeedback),
|
||||||
|
useValue: mockVoiceFeedbackRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<VoiceService>(VoiceService);
|
||||||
|
configService = module.get<ConfigService>(ConfigService);
|
||||||
|
voiceFeedbackRepository = module.get<Repository<VoiceFeedback>>(
|
||||||
|
getRepositoryToken(VoiceFeedback),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Inject mock OpenAI client
|
||||||
|
(service as any).openai = mockOpenAI;
|
||||||
|
(service as any).chatOpenAI = mockOpenAI;
|
||||||
|
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should warn if OpenAI not configured', () => {
|
||||||
|
const warnMockConfig = {
|
||||||
|
get: jest.fn((key: string) => {
|
||||||
|
if (key === 'OPENAI_API_KEY') return 'sk-your-openai-api-key-here';
|
||||||
|
if (key === 'AZURE_OPENAI_ENABLED') return false;
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const testModule = Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
VoiceService,
|
||||||
|
{
|
||||||
|
provide: ConfigService,
|
||||||
|
useValue: warnMockConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(VoiceFeedback),
|
||||||
|
useValue: mockVoiceFeedbackRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should not throw, just log warning
|
||||||
|
expect(testModule).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support Azure OpenAI configuration', () => {
|
||||||
|
const azureConfig = {
|
||||||
|
get: jest.fn((key: string) => {
|
||||||
|
const config: Record<string, any> = {
|
||||||
|
AZURE_OPENAI_ENABLED: true,
|
||||||
|
AZURE_OPENAI_WHISPER_ENDPOINT:
|
||||||
|
'https://test.openai.azure.com',
|
||||||
|
AZURE_OPENAI_WHISPER_API_KEY: 'test-key',
|
||||||
|
AZURE_OPENAI_WHISPER_DEPLOYMENT: 'whisper-1',
|
||||||
|
AZURE_OPENAI_WHISPER_API_VERSION: '2024-02-01',
|
||||||
|
AZURE_OPENAI_CHAT_ENDPOINT: 'https://test.openai.azure.com',
|
||||||
|
AZURE_OPENAI_CHAT_API_KEY: 'test-key',
|
||||||
|
AZURE_OPENAI_CHAT_DEPLOYMENT: 'gpt-4o-mini',
|
||||||
|
AZURE_OPENAI_CHAT_API_VERSION: '2024-02-01',
|
||||||
|
};
|
||||||
|
return config[key];
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const testModule = Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
VoiceService,
|
||||||
|
{
|
||||||
|
provide: ConfigService,
|
||||||
|
useValue: azureConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(VoiceFeedback),
|
||||||
|
useValue: mockVoiceFeedbackRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(testModule).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('transcribeAudio', () => {
|
||||||
|
it('should transcribe audio buffer successfully', async () => {
|
||||||
|
const audioBuffer = Buffer.from('fake-audio-data');
|
||||||
|
|
||||||
|
// Mock fs operations
|
||||||
|
const fsMock = require('fs');
|
||||||
|
jest.spyOn(fsMock, 'existsSync').mockReturnValue(true);
|
||||||
|
jest.spyOn(fsMock, 'writeFileSync').mockImplementation();
|
||||||
|
jest.spyOn(fsMock, 'createReadStream').mockReturnValue({} as any);
|
||||||
|
jest.spyOn(fsMock, 'unlinkSync').mockImplementation();
|
||||||
|
|
||||||
|
const result = await service.transcribeAudio(audioBuffer);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('text');
|
||||||
|
expect(result).toHaveProperty('language');
|
||||||
|
expect(result.text).toBe('Baby ate 120 milliliters');
|
||||||
|
expect(result.language).toBe('en');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if service not configured', async () => {
|
||||||
|
(service as any).openai = null;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.transcribeAudio(Buffer.from('test')),
|
||||||
|
).rejects.toThrow(BadRequestException);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support language parameter', async () => {
|
||||||
|
const audioBuffer = Buffer.from('fake-audio-data');
|
||||||
|
|
||||||
|
const fsMock = require('fs');
|
||||||
|
jest.spyOn(fsMock, 'existsSync').mockReturnValue(true);
|
||||||
|
jest.spyOn(fsMock, 'writeFileSync').mockImplementation();
|
||||||
|
jest.spyOn(fsMock, 'createReadStream').mockReturnValue({} as any);
|
||||||
|
jest.spyOn(fsMock, 'unlinkSync').mockImplementation();
|
||||||
|
|
||||||
|
await service.transcribeAudio(audioBuffer, 'es');
|
||||||
|
|
||||||
|
expect(mockOpenAI.audio.transcriptions.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
language: 'es',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle transcription failures', async () => {
|
||||||
|
mockOpenAI.audio.transcriptions.create.mockRejectedValueOnce(
|
||||||
|
new Error('API error'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const fsMock = require('fs');
|
||||||
|
jest.spyOn(fsMock, 'existsSync').mockReturnValue(true);
|
||||||
|
jest.spyOn(fsMock, 'writeFileSync').mockImplementation();
|
||||||
|
jest.spyOn(fsMock, 'createReadStream').mockReturnValue({} as any);
|
||||||
|
jest.spyOn(fsMock, 'unlinkSync').mockImplementation();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.transcribeAudio(Buffer.from('test')),
|
||||||
|
).rejects.toThrow(BadRequestException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('extractActivityFromText', () => {
|
||||||
|
it('should extract feeding activity from text', async () => {
|
||||||
|
const result = await service.extractActivityFromText(
|
||||||
|
'Fed baby 120ml of formula',
|
||||||
|
'en',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.type).toBe('feeding');
|
||||||
|
expect(result.details.feedingType).toBe('bottle');
|
||||||
|
expect(result.details.amount).toBe(120);
|
||||||
|
expect(result.details.unit).toBe('ml');
|
||||||
|
expect(result.confidence).toBeGreaterThan(0.9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract sleep activity from text', async () => {
|
||||||
|
mockOpenAI.chat.completions.create.mockResolvedValueOnce({
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
content: JSON.stringify({
|
||||||
|
type: 'sleep',
|
||||||
|
timestamp: null,
|
||||||
|
details: {
|
||||||
|
quality: 'peaceful',
|
||||||
|
duration: 120,
|
||||||
|
location: 'crib',
|
||||||
|
},
|
||||||
|
confidence: 0.92,
|
||||||
|
action: 'create_activity',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.extractActivityFromText(
|
||||||
|
'Baby slept for 2 hours in the crib',
|
||||||
|
'en',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.type).toBe('sleep');
|
||||||
|
expect(result.details.duration).toBe(120);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract diaper activity from text', async () => {
|
||||||
|
mockOpenAI.chat.completions.create.mockResolvedValueOnce({
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
content: JSON.stringify({
|
||||||
|
type: 'diaper',
|
||||||
|
timestamp: null,
|
||||||
|
details: {
|
||||||
|
diaperType: 'dirty',
|
||||||
|
color: 'yellow',
|
||||||
|
},
|
||||||
|
confidence: 0.88,
|
||||||
|
action: 'create_activity',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.extractActivityFromText(
|
||||||
|
'Changed a dirty diaper',
|
||||||
|
'en',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.type).toBe('diaper');
|
||||||
|
expect(result.details.diaperType).toBe('dirty');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract medicine activity from text', async () => {
|
||||||
|
mockOpenAI.chat.completions.create.mockResolvedValueOnce({
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
content: JSON.stringify({
|
||||||
|
type: 'medicine',
|
||||||
|
timestamp: null,
|
||||||
|
details: {
|
||||||
|
medicineName: 'Vitamin D',
|
||||||
|
dosage: 400,
|
||||||
|
unit: 'IU',
|
||||||
|
},
|
||||||
|
confidence: 0.95,
|
||||||
|
action: 'create_activity',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.extractActivityFromText(
|
||||||
|
'Gave baby 400 IU of Vitamin D drops',
|
||||||
|
'en',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.type).toBe('medicine');
|
||||||
|
expect(result.details.medicineName).toBe('Vitamin D');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle unknown activity types', async () => {
|
||||||
|
mockOpenAI.chat.completions.create.mockResolvedValueOnce({
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
content: JSON.stringify({
|
||||||
|
type: 'unknown',
|
||||||
|
timestamp: null,
|
||||||
|
details: {},
|
||||||
|
confidence: 0,
|
||||||
|
action: 'unknown',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.extractActivityFromText(
|
||||||
|
'The weather is nice today',
|
||||||
|
'en',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.type).toBe('unknown');
|
||||||
|
expect(result.confidence).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include child name in prompt if provided', async () => {
|
||||||
|
await service.extractActivityFromText(
|
||||||
|
'Fed baby',
|
||||||
|
'en',
|
||||||
|
'Emma',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockOpenAI.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
messages: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
role: 'user',
|
||||||
|
content: expect.stringContaining('Emma'),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if service not configured', async () => {
|
||||||
|
(service as any).chatOpenAI = null;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.extractActivityFromText('test', 'en'),
|
||||||
|
).rejects.toThrow(BadRequestException);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle extraction failures', async () => {
|
||||||
|
mockOpenAI.chat.completions.create.mockRejectedValueOnce(
|
||||||
|
new Error('API error'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.extractActivityFromText('test', 'en'),
|
||||||
|
).rejects.toThrow(BadRequestException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('processVoiceInput', () => {
|
||||||
|
it('should transcribe and extract activity', async () => {
|
||||||
|
const fsMock = require('fs');
|
||||||
|
jest.spyOn(fsMock, 'existsSync').mockReturnValue(true);
|
||||||
|
jest.spyOn(fsMock, 'writeFileSync').mockImplementation();
|
||||||
|
jest.spyOn(fsMock, 'createReadStream').mockReturnValue({} as any);
|
||||||
|
jest.spyOn(fsMock, 'unlinkSync').mockImplementation();
|
||||||
|
|
||||||
|
const result = await service.processVoiceInput(
|
||||||
|
Buffer.from('fake-audio'),
|
||||||
|
'en',
|
||||||
|
'Baby',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('transcription');
|
||||||
|
expect(result).toHaveProperty('activity');
|
||||||
|
expect(result.transcription.text).toBe('Baby ate 120 milliliters');
|
||||||
|
expect(result.activity.type).toBe('feeding');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass language to transcription', async () => {
|
||||||
|
const fsMock = require('fs');
|
||||||
|
jest.spyOn(fsMock, 'existsSync').mockReturnValue(true);
|
||||||
|
jest.spyOn(fsMock, 'writeFileSync').mockImplementation();
|
||||||
|
jest.spyOn(fsMock, 'createReadStream').mockReturnValue({} as any);
|
||||||
|
jest.spyOn(fsMock, 'unlinkSync').mockImplementation();
|
||||||
|
|
||||||
|
mockOpenAI.audio.transcriptions.create.mockResolvedValueOnce({
|
||||||
|
text: 'El bebé comió',
|
||||||
|
language: 'es',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.processVoiceInput(
|
||||||
|
Buffer.from('fake-audio'),
|
||||||
|
'es',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.transcription.language).toBe('es');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateClarificationQuestion', () => {
|
||||||
|
it('should generate clarification question', async () => {
|
||||||
|
mockOpenAI.chat.completions.create.mockResolvedValueOnce({
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
content: 'How much did the baby eat?',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.generateClarificationQuestion(
|
||||||
|
'Baby ate',
|
||||||
|
'feeding',
|
||||||
|
'en',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe('How much did the baby eat?');
|
||||||
|
expect(mockOpenAI.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
messages: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
role: 'system',
|
||||||
|
content: expect.stringContaining('feeding'),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return fallback question on error', async () => {
|
||||||
|
mockOpenAI.chat.completions.create.mockRejectedValueOnce(
|
||||||
|
new Error('API error'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await service.generateClarificationQuestion(
|
||||||
|
'Baby ate',
|
||||||
|
'feeding',
|
||||||
|
'en',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe('Could you provide more details about this activity?');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if service not configured', async () => {
|
||||||
|
(service as any).chatOpenAI = null;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.generateClarificationQuestion('test', 'feeding', 'en'),
|
||||||
|
).rejects.toThrow(BadRequestException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('saveFeedback', () => {
|
||||||
|
it('should save voice feedback', async () => {
|
||||||
|
const feedbackDto: SaveVoiceFeedbackDto = {
|
||||||
|
childId: 'child_123',
|
||||||
|
activityId: 'activity_123',
|
||||||
|
transcript: 'Baby ate 120ml',
|
||||||
|
language: 'en',
|
||||||
|
extractedType: 'feeding',
|
||||||
|
extractedData: { amount: 120, unit: 'ml' },
|
||||||
|
confidence: 0.95,
|
||||||
|
action: 'accepted',
|
||||||
|
finalType: 'feeding',
|
||||||
|
finalData: { amount: 120, unit: 'ml' },
|
||||||
|
userNotes: 'Worked perfectly',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockFeedback = {
|
||||||
|
id: 'vfb_123',
|
||||||
|
userId: 'user_123',
|
||||||
|
...feedbackDto,
|
||||||
|
};
|
||||||
|
|
||||||
|
mockVoiceFeedbackRepository.create.mockReturnValue(mockFeedback);
|
||||||
|
mockVoiceFeedbackRepository.save.mockResolvedValue(mockFeedback);
|
||||||
|
|
||||||
|
const result = await service.saveFeedback('user_123', feedbackDto);
|
||||||
|
|
||||||
|
expect(voiceFeedbackRepository.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.stringContaining('vfb_'),
|
||||||
|
userId: 'user_123',
|
||||||
|
transcript: 'Baby ate 120ml',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(voiceFeedbackRepository.save).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(mockFeedback);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle feedback save failures', async () => {
|
||||||
|
mockVoiceFeedbackRepository.save.mockRejectedValueOnce(
|
||||||
|
new Error('Database error'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const feedbackDto: SaveVoiceFeedbackDto = {
|
||||||
|
transcript: 'test',
|
||||||
|
language: 'en',
|
||||||
|
extractedType: 'feeding',
|
||||||
|
extractedData: {},
|
||||||
|
confidence: 0.5,
|
||||||
|
action: 'rejected',
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.saveFeedback('user_123', feedbackDto),
|
||||||
|
).rejects.toThrow(BadRequestException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user