1
+ import { vi , describe , test , expect , beforeEach , afterEach } from 'vitest' ;
2
+ import { uploadReport , ReportError , fetchLatestReports , fetchAllReports , markReportAsRead } from '../reportService' ;
3
+ import { ReportCategory , ReportStatus } from '../../models/medicalReport' ;
4
+ import axios from 'axios' ;
5
+
6
+ // Mock axios
7
+ vi . mock ( 'axios' , ( ) => ( {
8
+ default : {
9
+ post : vi . fn ( ) ,
10
+ get : vi . fn ( ) ,
11
+ patch : vi . fn ( ) ,
12
+ isAxiosError : vi . fn ( ( ) => true )
13
+ }
14
+ } ) ) ;
15
+
16
+ // Mock response data
17
+ const mockReports = [
18
+ {
19
+ id : '1' ,
20
+ title : 'heart-report' ,
21
+ status : ReportStatus . UNREAD ,
22
+ category : ReportCategory . HEART ,
23
+ documentUrl : 'http://example.com/heart-report.pdf' ,
24
+ date : '2024-03-24' ,
25
+ } ,
26
+ {
27
+ id : '2' ,
28
+ title : 'brain-scan' ,
29
+ status : ReportStatus . UNREAD ,
30
+ category : ReportCategory . NEUROLOGICAL ,
31
+ documentUrl : 'http://example.com/brain-scan.pdf' ,
32
+ date : '2024-03-24' ,
33
+ }
34
+ ] ;
35
+
36
+ describe ( 'reportService' , ( ) => {
37
+ const mockFile = new File ( [ 'test content' ] , 'test-report.pdf' , { type : 'application/pdf' } ) ;
38
+ let progressCallback : ( progress : number ) => void ;
39
+
40
+ beforeEach ( ( ) => {
41
+ vi . resetAllMocks ( ) ;
42
+ progressCallback = vi . fn ( ) ;
43
+ } ) ;
44
+
45
+ describe ( 'uploadReport' , ( ) => {
46
+ // Create a mock implementation for FormData
47
+ let mockFormData : { append : ReturnType < typeof vi . fn > } ;
48
+
49
+ beforeEach ( ( ) => {
50
+ // Mock the internal timers used in uploadReport
51
+ vi . spyOn ( global , 'setTimeout' ) . mockImplementation ( ( fn ) => {
52
+ if ( typeof fn === 'function' ) fn ( ) ;
53
+ return 123 as unknown as NodeJS . Timeout ;
54
+ } ) ;
55
+
56
+ vi . spyOn ( global , 'setInterval' ) . mockImplementation ( ( ) => {
57
+ return 456 as unknown as NodeJS . Timeout ;
58
+ } ) ;
59
+
60
+ vi . spyOn ( global , 'clearInterval' ) . mockImplementation ( ( ) => { } ) ;
61
+
62
+ // Setup mock FormData
63
+ mockFormData = {
64
+ append : vi . fn ( )
65
+ } ;
66
+
67
+ // Mock FormData constructor
68
+ global . FormData = vi . fn ( ( ) => mockFormData as unknown as FormData ) ;
69
+ } ) ;
70
+
71
+ test ( 'should upload file successfully' , async ( ) => {
72
+ const report = await uploadReport ( mockFile , progressCallback ) ;
73
+
74
+ // Check the returned data matches our expectations
75
+ expect ( report ) . toBeDefined ( ) ;
76
+ expect ( report . title ) . toBe ( 'test-report' ) ;
77
+ expect ( report . status ) . toBe ( ReportStatus . UNREAD ) ;
78
+
79
+ // Verify form data was created with the correct file
80
+ expect ( FormData ) . toHaveBeenCalled ( ) ;
81
+ expect ( mockFormData . append ) . toHaveBeenCalledWith ( 'file' , mockFile ) ;
82
+
83
+ // Check the progress callback was called
84
+ expect ( progressCallback ) . toHaveBeenCalled ( ) ;
85
+ } ) ;
86
+
87
+ test ( 'should determine category based on filename' , async ( ) => {
88
+ const heartFile = new File ( [ 'test' ] , 'heart-report.pdf' , { type : 'application/pdf' } ) ;
89
+ const heartReport = await uploadReport ( heartFile ) ;
90
+ expect ( heartReport . category ) . toBe ( ReportCategory . HEART ) ;
91
+
92
+ // Reset mocks for the second file
93
+ vi . resetAllMocks ( ) ;
94
+ mockFormData = { append : vi . fn ( ) } ;
95
+ global . FormData = vi . fn ( ( ) => mockFormData as unknown as FormData ) ;
96
+
97
+ // Recreate timer mocks for the second upload
98
+ vi . spyOn ( global , 'setTimeout' ) . mockImplementation ( ( fn ) => {
99
+ if ( typeof fn === 'function' ) fn ( ) ;
100
+ return 123 as unknown as NodeJS . Timeout ;
101
+ } ) ;
102
+
103
+ vi . spyOn ( global , 'setInterval' ) . mockImplementation ( ( ) => {
104
+ return 456 as unknown as NodeJS . Timeout ;
105
+ } ) ;
106
+
107
+ vi . spyOn ( global , 'clearInterval' ) . mockImplementation ( ( ) => { } ) ;
108
+
109
+ const neuroFile = new File ( [ 'test' ] , 'brain-scan.pdf' , { type : 'application/pdf' } ) ;
110
+ const neuroReport = await uploadReport ( neuroFile ) ;
111
+ expect ( neuroReport . category ) . toBe ( ReportCategory . NEUROLOGICAL ) ;
112
+ } ) ;
113
+
114
+ test ( 'should handle upload without progress callback' , async ( ) => {
115
+ const report = await uploadReport ( mockFile ) ;
116
+ expect ( report ) . toBeDefined ( ) ;
117
+ expect ( report . title ) . toBe ( 'test-report' ) ;
118
+ } ) ;
119
+
120
+ test ( 'should throw ReportError on upload failure' , async ( ) => {
121
+ // Restore the original FormData
122
+ const originalFormData = global . FormData ;
123
+
124
+ // Mock FormData to throw an error
125
+ global . FormData = vi . fn ( ( ) => {
126
+ throw new Error ( 'FormData construction failed' ) ;
127
+ } ) ;
128
+
129
+ await expect ( uploadReport ( mockFile , progressCallback ) )
130
+ . rejects
131
+ . toThrow ( ReportError ) ;
132
+
133
+ // Restore the previous mock
134
+ global . FormData = originalFormData ;
135
+ } ) ;
136
+
137
+ afterEach ( ( ) => {
138
+ vi . restoreAllMocks ( ) ;
139
+ } ) ;
140
+ } ) ;
141
+
142
+ describe ( 'fetchLatestReports' , ( ) => {
143
+ beforeEach ( ( ) => {
144
+ // Setup axios mock response
145
+ ( axios . get as ReturnType < typeof vi . fn > ) . mockResolvedValue ( {
146
+ data : mockReports . slice ( 0 , 2 )
147
+ } ) ;
148
+ } ) ;
149
+
150
+ test ( 'should fetch latest reports with default limit' , async ( ) => {
151
+ const reports = await fetchLatestReports ( ) ;
152
+
153
+ expect ( axios . get ) . toHaveBeenCalled ( ) ;
154
+ expect ( reports ) . toHaveLength ( 2 ) ;
155
+ expect ( reports [ 0 ] ) . toEqual ( expect . objectContaining ( {
156
+ id : expect . any ( String ) ,
157
+ title : expect . any ( String )
158
+ } ) ) ;
159
+ } ) ;
160
+
161
+ test ( 'should fetch latest reports with custom limit' , async ( ) => {
162
+ const limit = 1 ;
163
+ ( axios . get as ReturnType < typeof vi . fn > ) . mockResolvedValue ( {
164
+ data : mockReports . slice ( 0 , 1 )
165
+ } ) ;
166
+
167
+ const reports = await fetchLatestReports ( limit ) ;
168
+
169
+ expect ( axios . get ) . toHaveBeenCalled ( ) ;
170
+ expect ( reports ) . toHaveLength ( 1 ) ;
171
+ } ) ;
172
+
173
+ test ( 'should throw ReportError on fetch failure' , async ( ) => {
174
+ ( axios . get as ReturnType < typeof vi . fn > ) . mockRejectedValue ( new Error ( 'Network error' ) ) ;
175
+
176
+ await expect ( fetchLatestReports ( ) )
177
+ . rejects
178
+ . toThrow ( ReportError ) ;
179
+ } ) ;
180
+ } ) ;
181
+
182
+ describe ( 'fetchAllReports' , ( ) => {
183
+ beforeEach ( ( ) => {
184
+ ( axios . get as ReturnType < typeof vi . fn > ) . mockResolvedValue ( {
185
+ data : mockReports
186
+ } ) ;
187
+ } ) ;
188
+
189
+ test ( 'should fetch all reports' , async ( ) => {
190
+ const reports = await fetchAllReports ( ) ;
191
+
192
+ expect ( axios . get ) . toHaveBeenCalled ( ) ;
193
+ expect ( reports ) . toEqual ( mockReports ) ;
194
+ } ) ;
195
+
196
+ test ( 'should throw ReportError on fetch failure' , async ( ) => {
197
+ ( axios . get as ReturnType < typeof vi . fn > ) . mockRejectedValue ( new Error ( 'Network error' ) ) ;
198
+
199
+ await expect ( fetchAllReports ( ) )
200
+ . rejects
201
+ . toThrow ( ReportError ) ;
202
+ } ) ;
203
+ } ) ;
204
+
205
+ describe ( 'markReportAsRead' , ( ) => {
206
+ beforeEach ( ( ) => {
207
+ const updatedReport = {
208
+ ...mockReports [ 0 ] ,
209
+ status : ReportStatus . READ
210
+ } ;
211
+
212
+ ( axios . patch as ReturnType < typeof vi . fn > ) . mockResolvedValue ( {
213
+ data : updatedReport
214
+ } ) ;
215
+ } ) ;
216
+
217
+ test ( 'should mark a report as read' , async ( ) => {
218
+ const updatedReport = await markReportAsRead ( '1' ) ;
219
+
220
+ expect ( axios . patch ) . toHaveBeenCalled ( ) ;
221
+ expect ( updatedReport . status ) . toBe ( ReportStatus . READ ) ;
222
+ } ) ;
223
+
224
+ test ( 'should throw error when report not found' , async ( ) => {
225
+ ( axios . patch as ReturnType < typeof vi . fn > ) . mockRejectedValue ( new Error ( 'Report not found' ) ) ;
226
+
227
+ await expect ( markReportAsRead ( 'non-existent-id' ) )
228
+ . rejects
229
+ . toThrow ( ReportError ) ;
230
+ } ) ;
231
+ } ) ;
232
+ } ) ;
0 commit comments