11import { ConfigService } from '@nestjs/config' ;
2- import { AwsBedrockService } from './aws-bedrock.service' ;
2+ import { AwsBedrockService , MedicalDocumentAnalysis } from './aws-bedrock.service' ;
33import { describe , it , expect , beforeEach , vi , beforeAll , afterAll } from 'vitest' ;
4+ import { BadRequestException } from '@nestjs/common' ;
45
56// Mock the Logger
67vi . mock ( '@nestjs/common' , async ( ) => {
@@ -17,6 +18,97 @@ vi.mock('@nestjs/common', async () => {
1718 } ;
1819} ) ;
1920
21+ // Mock AWS SDK Bedrock client
22+ vi . mock ( '@aws-sdk/client-bedrock-runtime' , ( ) => {
23+ return {
24+ BedrockRuntimeClient : vi . fn ( ) . mockImplementation ( ( ) => ( {
25+ send : vi . fn ( ) . mockImplementation ( command => {
26+ // Get the prompt from the command body
27+ const body = JSON . parse ( command . input . body ) ;
28+ const prompt = body . messages [ 0 ] . content [ 0 ] . text ;
29+
30+ // Basic mock response structure with required properties
31+ const createMockResponse = ( bodyContent : any ) => ( {
32+ body : new TextEncoder ( ) . encode ( JSON . stringify ( bodyContent ) ) ,
33+ contentType : 'application/json' ,
34+ $metadata : {
35+ httpStatusCode : 200 ,
36+ requestId : 'mock-request-id' ,
37+ attempts : 1 ,
38+ totalRetryDelay : 0 ,
39+ } ,
40+ } ) ;
41+
42+ // Return different mock responses based on the prompt content
43+ if ( prompt . includes ( 'invalid document' ) ) {
44+ return Promise . resolve (
45+ createMockResponse ( {
46+ content : [
47+ {
48+ type : 'text' ,
49+ text : 'This is not valid JSON' ,
50+ } ,
51+ ] ,
52+ } ) ,
53+ ) ;
54+ } else if ( prompt . includes ( 'empty response' ) ) {
55+ return Promise . resolve (
56+ createMockResponse ( {
57+ content : [ ] ,
58+ } ) ,
59+ ) ;
60+ } else if ( prompt . includes ( 'BLOOD TEST RESULTS' ) ) {
61+ // Only return success response for actual medical document text
62+ return Promise . resolve (
63+ createMockResponse ( {
64+ content : [
65+ {
66+ type : 'text' ,
67+ text : JSON . stringify ( {
68+ keyMedicalTerms : [
69+ { term : 'RBC' , definition : 'Red Blood Cells' } ,
70+ { term : 'WBC' , definition : 'White Blood Cells' } ,
71+ ] ,
72+ labValues : [
73+ {
74+ name : 'Hemoglobin' ,
75+ value : '14.2' ,
76+ unit : 'g/dL' ,
77+ normalRange : '13.5-17.5' ,
78+ isAbnormal : false ,
79+ } ,
80+ ] ,
81+ diagnoses : [ ] ,
82+ metadata : {
83+ isMedicalReport : true ,
84+ confidence : 0.95 ,
85+ missingInformation : [ ] ,
86+ } ,
87+ } ) ,
88+ } ,
89+ ] ,
90+ } ) ,
91+ ) ;
92+ } else {
93+ return Promise . resolve (
94+ createMockResponse ( {
95+ content : [
96+ {
97+ type : 'text' ,
98+ text : 'Default response' ,
99+ } ,
100+ ] ,
101+ } ) ,
102+ ) ;
103+ }
104+ } ) ,
105+ } ) ) ,
106+ InvokeModelCommand : vi . fn ( ) . mockImplementation ( params => ( {
107+ input : params ,
108+ } ) ) ,
109+ } ;
110+ } ) ;
111+
20112describe ( 'AwsBedrockService' , ( ) => {
21113 let service : AwsBedrockService ;
22114 let mockConfigService : ConfigService ;
@@ -62,4 +154,137 @@ describe('AwsBedrockService', () => {
62154 expect ( service [ 'defaultMaxTokens' ] ) . toBe ( 1000 ) ;
63155 } ) ;
64156 } ) ;
157+
158+ describe ( 'analyzeMedicalDocument' , ( ) => {
159+ it ( 'should successfully analyze a valid medical document' , async ( ) => {
160+ // Create a sample medical document text
161+ const sampleText = `
162+ BLOOD TEST RESULTS
163+ Patient: John Doe
164+ Date: 2023-01-15
165+
166+ Red Blood Cells (RBC): 5.1 x10^6/µL (Normal: 4.5-5.9)
167+ White Blood Cells (WBC): 7.2 x10^3/µL (Normal: 4.5-11.0)
168+ Hemoglobin: 14.2 g/dL (Normal: 13.5-17.5)
169+ ` ;
170+
171+ // Call the method
172+ const result = await service . analyzeMedicalDocument ( sampleText ) ;
173+
174+ // Assert the result
175+ expect ( result ) . toBeDefined ( ) ;
176+ expect ( result . keyMedicalTerms ) . toHaveLength ( 2 ) ;
177+ expect ( result . keyMedicalTerms [ 0 ] . term ) . toBe ( 'RBC' ) ;
178+ expect ( result . keyMedicalTerms [ 0 ] . definition ) . toBe ( 'Red Blood Cells' ) ;
179+
180+ expect ( result . labValues ) . toHaveLength ( 1 ) ;
181+ expect ( result . labValues [ 0 ] . name ) . toBe ( 'Hemoglobin' ) ;
182+ expect ( result . labValues [ 0 ] . value ) . toBe ( '14.2' ) ;
183+ expect ( result . labValues [ 0 ] . unit ) . toBe ( 'g/dL' ) ;
184+ expect ( result . labValues [ 0 ] . isAbnormal ) . toBe ( false ) ;
185+
186+ expect ( result . metadata . isMedicalReport ) . toBe ( true ) ;
187+ expect ( result . metadata . confidence ) . toBeGreaterThan ( 0.9 ) ;
188+ } ) ;
189+
190+ it ( 'should correctly format the prompt for medical document analysis' , async ( ) => {
191+ // Spy on the invokeBedrock method
192+ const invokeBedrockSpy = vi . spyOn ( service as any , 'invokeBedrock' ) ;
193+
194+ // Sample document text
195+ const sampleText = 'Sample medical document' ;
196+
197+ try {
198+ await service . analyzeMedicalDocument ( sampleText ) ;
199+ } catch ( error ) {
200+ // We don't care about the result, just the prompt format
201+ }
202+
203+ // Verify invokeBedrock was called
204+ expect ( invokeBedrockSpy ) . toHaveBeenCalled ( ) ;
205+
206+ // Verify the prompt format
207+ const prompt = invokeBedrockSpy . mock . calls [ 0 ] [ 0 ] as string ;
208+
209+ // Check key elements of the prompt
210+ expect ( prompt ) . toContain ( 'Please analyze this medical document carefully' ) ;
211+ expect ( prompt ) . toContain ( 'Format the response as a JSON object' ) ;
212+ expect ( prompt ) . toContain ( 'keyMedicalTerms' ) ;
213+ expect ( prompt ) . toContain ( 'labValues' ) ;
214+ expect ( prompt ) . toContain ( 'diagnoses' ) ;
215+ expect ( prompt ) . toContain ( 'metadata' ) ;
216+ expect ( prompt ) . toContain ( 'Sample medical document' ) ; // Document text is appended
217+ } ) ;
218+
219+ it ( 'should throw BadRequestException for invalid JSON response' , async ( ) => {
220+ // Create a sample invalid document text
221+ const invalidDocument = 'This is an invalid document that will cause an invalid response' ;
222+
223+ // Expect the method to throw BadRequestException
224+ await expect ( service . analyzeMedicalDocument ( invalidDocument ) ) . rejects . toThrow (
225+ BadRequestException ,
226+ ) ;
227+ } ) ;
228+
229+ it ( 'should throw BadRequestException for empty response' , async ( ) => {
230+ // Create a sample text that will trigger an empty response
231+ const emptyResponseText = 'This will trigger an empty response' ;
232+
233+ // Expect the method to throw BadRequestException
234+ await expect ( service . analyzeMedicalDocument ( emptyResponseText ) ) . rejects . toThrow (
235+ BadRequestException ,
236+ ) ;
237+ } ) ;
238+
239+ it ( 'should handle rate limiting correctly' , async ( ) => {
240+ // Mock the rate limiter to reject requests
241+ service [ 'rateLimiter' ] . tryRequest = vi . fn ( ) . mockReturnValue ( false ) ;
242+
243+ // Create a sample medical document text
244+ const sampleText = 'Sample medical document text' ;
245+
246+ // Expect the method to throw BadRequestException due to rate limiting
247+ await expect ( service . analyzeMedicalDocument ( sampleText ) ) . rejects . toThrow (
248+ 'Rate limit exceeded' ,
249+ ) ;
250+ } ) ;
251+
252+ it ( 'should validate response structure correctly' , ( ) => {
253+ // Create invalid response objects to test validation
254+ const invalidResponses = [
255+ null ,
256+ { } ,
257+ { keyMedicalTerms : 'not an array' } ,
258+ { keyMedicalTerms : [ ] , labValues : [ ] , diagnoses : [ ] } , // Missing metadata
259+ {
260+ keyMedicalTerms : [ ] ,
261+ labValues : [ ] ,
262+ diagnoses : [ ] ,
263+ metadata : { isMedicalReport : 'not a boolean' , confidence : 0.5 , missingInformation : [ ] } ,
264+ } ,
265+ ] ;
266+
267+ // Test each invalid response
268+ invalidResponses . forEach ( response => {
269+ expect ( ( ) => service [ 'validateMedicalAnalysisResponse' ] ( response ) ) . toThrow (
270+ BadRequestException ,
271+ ) ;
272+ } ) ;
273+
274+ // Test a valid response
275+ const validResponse : MedicalDocumentAnalysis = {
276+ keyMedicalTerms : [ ] ,
277+ labValues : [ ] ,
278+ diagnoses : [ ] ,
279+ metadata : {
280+ isMedicalReport : true ,
281+ confidence : 0.9 ,
282+ missingInformation : [ ] ,
283+ } ,
284+ } ;
285+
286+ // Should not throw for valid response
287+ expect ( ( ) => service [ 'validateMedicalAnalysisResponse' ] ( validResponse ) ) . not . toThrow ( ) ;
288+ } ) ;
289+ } ) ;
65290} ) ;
0 commit comments