66 BLOB_STORAGE ,
77 BlobStorageInterface ,
88} from "../blob-storage/blob-storage.interface" ;
9+ import { UploadNormalizationLimiter } from "../upload/upload-normalization-limiter" ;
910import { DocumentService } from "./document.service" ;
1011import { DocumentDbService } from "./document-db.service" ;
1112import { PdfNormalizationService } from "./pdf-normalization.service" ;
@@ -15,7 +16,13 @@ describe("DocumentService", () => {
1516 let documentDbService : DocumentDbService ;
1617 let blobStorage : BlobStorageInterface ;
1718 let pdfNormalization : jest . Mocked <
18- Pick < PdfNormalizationService , "validateForUpload" | "normalizeToPdf" >
19+ Pick <
20+ PdfNormalizationService ,
21+ "validateForUpload" | "normalizeToPdf" | "generateThumbnailWebp"
22+ >
23+ > ;
24+ let uploadNormalizationLimiter : jest . Mocked <
25+ Pick < UploadNormalizationLimiter , "run" >
1926 > ;
2027
2128 beforeEach ( async ( ) => {
@@ -24,7 +31,11 @@ describe("DocumentService", () => {
2431 normalizeToPdf : jest
2532 . fn ( )
2633 . mockImplementation ( ( buf : Buffer ) => Promise . resolve ( Buffer . from ( buf ) ) ) ,
34+ generateThumbnailWebp : jest . fn ( ) . mockResolvedValue ( Buffer . from ( "webp" ) ) ,
2735 } ;
36+ uploadNormalizationLimiter = {
37+ run : jest . fn ( ( task : ( ) => Promise < unknown > ) => task ( ) ) ,
38+ } as jest . Mocked < Pick < UploadNormalizationLimiter , "run" > > ;
2839 documentDbService = {
2940 createDocument : jest . fn ( ) ,
3041 findDocument : jest . fn ( ) ,
@@ -35,7 +46,7 @@ describe("DocumentService", () => {
3546 upsertOcrResult : jest . fn ( ) ,
3647 } as any ;
3748 blobStorage = {
38- write : jest . fn ( ) ,
49+ write : jest . fn ( ) . mockResolvedValue ( undefined ) ,
3950 read : jest . fn ( ) ,
4051 exists : jest . fn ( ) ,
4152 delete : jest . fn ( ) ,
@@ -49,6 +60,10 @@ describe("DocumentService", () => {
4960 { provide : DocumentDbService , useValue : documentDbService } ,
5061 { provide : BLOB_STORAGE , useValue : blobStorage } ,
5162 { provide : PdfNormalizationService , useValue : pdfNormalization } ,
63+ {
64+ provide : UploadNormalizationLimiter ,
65+ useValue : uploadNormalizationLimiter ,
66+ } ,
5267 ] ,
5368 } ) . compile ( ) ;
5469 service = module . get < DocumentService > ( DocumentService ) ;
@@ -103,6 +118,121 @@ describe("DocumentService", () => {
103118 expect . stringMatching ( / ^ g r o u p - 1 \/ o c r \/ .+ \/ n o r m a l i z e d \. p d f $ / ) ,
104119 expect . any ( Buffer ) ,
105120 ) ;
121+ expect ( uploadNormalizationLimiter . run ) . toHaveBeenCalledTimes ( 1 ) ;
122+ expect ( pdfNormalization . generateThumbnailWebp ) . toHaveBeenCalledWith (
123+ expect . any ( Buffer ) ,
124+ "pdf" ,
125+ ) ;
126+ } ) ;
127+
128+ it ( "writes the normalized blob only after the original write completes" , async ( ) => {
129+ const pdfBytes = Buffer . from ( "%PDF-1.4\n%\xe2\xe3\xcf\xd3\n" ) ;
130+ const base64 = pdfBytes . toString ( "base64" ) ;
131+ const mockDoc = {
132+ id : "1" ,
133+ title : "Test" ,
134+ original_filename : "file.pdf" ,
135+ file_path : "documents/1/original.pdf" ,
136+ normalized_file_path : "documents/1/normalized.pdf" ,
137+ file_type : "pdf" ,
138+ file_size : pdfBytes . length ,
139+ metadata : { } ,
140+ source : "api" ,
141+ status : DocumentStatus . ongoing_ocr ,
142+ created_at : new Date ( ) ,
143+ updated_at : new Date ( ) ,
144+ model_id : "test-model-id" ,
145+ group_id : "group-1" ,
146+ } ;
147+ const writeOrder : string [ ] = [ ] ;
148+ ( documentDbService . createDocument as jest . Mock ) . mockResolvedValue (
149+ mockDoc ,
150+ ) ;
151+ ( blobStorage . write as jest . Mock ) . mockImplementation ( ( key : string ) => {
152+ if ( key . endsWith ( "/original.pdf" ) ) {
153+ writeOrder . push ( "original" ) ;
154+ return new Promise < void > ( ( resolve ) => {
155+ setTimeout ( resolve , 10 ) ;
156+ } ) ;
157+ }
158+ if ( key . endsWith ( "/normalized.pdf" ) ) {
159+ writeOrder . push ( "normalized" ) ;
160+ }
161+ return Promise . resolve ( ) ;
162+ } ) ;
163+
164+ await service . uploadDocument (
165+ "Test" ,
166+ base64 ,
167+ "pdf" ,
168+ "file.pdf" ,
169+ "test-model-id" ,
170+ "group-1" ,
171+ { } ,
172+ ) ;
173+
174+ expect ( writeOrder ) . toEqual ( [ "original" , "normalized" ] ) ;
175+ } ) ;
176+
177+ it ( "starts PDF normalization before the original blob write finishes" , async ( ) => {
178+ const pdfBytes = Buffer . from ( "%PDF-1.4\n%\xe2\xe3\xcf\xd3\n" ) ;
179+ const base64 = pdfBytes . toString ( "base64" ) ;
180+ const mockDoc = {
181+ id : "1" ,
182+ title : "Test" ,
183+ original_filename : "file.pdf" ,
184+ file_path : "documents/1/original.pdf" ,
185+ normalized_file_path : "documents/1/normalized.pdf" ,
186+ file_type : "pdf" ,
187+ file_size : pdfBytes . length ,
188+ metadata : { } ,
189+ source : "api" ,
190+ status : DocumentStatus . ongoing_ocr ,
191+ created_at : new Date ( ) ,
192+ updated_at : new Date ( ) ,
193+ model_id : "test-model-id" ,
194+ group_id : "group-1" ,
195+ } ;
196+ let resolveOriginalWrite : ( ( ) => void ) | undefined ;
197+ let normalizationStarted = false ;
198+ ( documentDbService . createDocument as jest . Mock ) . mockResolvedValue (
199+ mockDoc ,
200+ ) ;
201+ ( blobStorage . write as jest . Mock ) . mockImplementation ( ( key : string ) => {
202+ if ( key . endsWith ( "/original.pdf" ) ) {
203+ return new Promise < void > ( ( resolve ) => {
204+ resolveOriginalWrite = resolve ;
205+ } ) ;
206+ }
207+ return Promise . resolve ( ) ;
208+ } ) ;
209+ pdfNormalization . normalizeToPdf . mockImplementation (
210+ async ( buf : Buffer ) => {
211+ normalizationStarted = true ;
212+ return Buffer . from ( buf ) ;
213+ } ,
214+ ) ;
215+
216+ const uploadPromise = service . uploadDocument (
217+ "Test" ,
218+ base64 ,
219+ "pdf" ,
220+ "file.pdf" ,
221+ "test-model-id" ,
222+ "group-1" ,
223+ { } ,
224+ ) ;
225+
226+ await Promise . resolve ( ) ;
227+
228+ expect ( normalizationStarted ) . toBe ( true ) ;
229+ expect ( documentDbService . createDocument ) . not . toHaveBeenCalled ( ) ;
230+
231+ resolveOriginalWrite ?.( ) ;
232+ const result = await uploadPromise ;
233+
234+ expect ( result . kind ) . toBe ( "success" ) ;
235+ expect ( documentDbService . createDocument ) . toHaveBeenCalled ( ) ;
106236 } ) ;
107237
108238 it ( "should throw on invalid base64" , async ( ) => {
0 commit comments