@@ -2,155 +2,55 @@ const should = require('should');
22const sinon = require ( 'sinon' ) ;
33const sharp = require ( 'sharp' ) ;
44const FileService = require ( '../../../api/services/FileService' ) ;
5- const ThumbnailService = require ( '../../../api/services/ThumbnailService' ) ;
65
76describe ( 'FileService.document - Thumbnail Integration' , ( ) => {
8- // Store original methods for restoration
9- const originalCreate = FileService . document . create ;
10- const originalDelete = FileService . document . delete ;
11- const originalIsCredentials = FileService . isCredentials ;
12-
13- // Mock blob client
147 let mockUploadData ;
158 let mockDeleteBlob ;
16- let uploadedBlobs ;
179 let mockContainerClient ;
10+ let originalIsCredentials ;
11+ let originalCredentials ;
1812
1913 before ( ( ) => {
14+ // Save original state to restore later
15+ originalIsCredentials = FileService . isCredentials ;
16+ originalCredentials = FileService . getCredentialsForTest ( ) ;
17+
2018 mockUploadData = sinon . stub ( ) . resolves ( ) ;
2119 mockDeleteBlob = sinon . stub ( ) . resolves ( ) ;
22- uploadedBlobs = { } ;
2320
2421 mockContainerClient = {
2522 getBlockBlobClient : ( blobPath ) => ( {
26- uploadData : async ( buffer , options ) => {
27- uploadedBlobs [ blobPath ] = { buffer, options } ;
28- return mockUploadData ( buffer , options ) ;
29- } ,
30- delete : async ( options ) => {
31- mockDeleteBlob ( blobPath , options ) ;
32- } ,
23+ uploadData : async ( buffer , options ) =>
24+ mockUploadData ( blobPath , buffer , options ) ,
25+ delete : async ( options ) => mockDeleteBlob ( blobPath , options ) ,
3326 } ) ,
3427 } ;
3528
36- // Patch create to use mock credentials
37- FileService . document . create = async function (
38- file ,
39- idDocument ,
40- fetchResult = false ,
41- isValidated = true
42- ) {
43- const name = file . originalname ;
44- const pathName = `${ Math . random ( )
45- . toString ( )
46- . replace ( / 0 \. / , '' ) } -${ name . replace ( / / , '_' ) } `;
47- const lastDot = name . lastIndexOf ( '.' ) ;
48- if ( lastDot <= 0 || lastDot === name . length - 1 ) {
49- const err = new Error ( FileService . INVALID_NAME ) ;
50- err . fileName = name ;
51- throw err ;
52- }
53- const extension = name . slice ( lastDot + 1 ) . toLowerCase ( ) ;
54-
55- const foundFormat = await TFileFormat . find ( { extension } ) . limit ( 1 ) ;
56- if ( foundFormat . length === 0 ) {
57- const err = new Error ( FileService . INVALID_FORMAT ) ;
58- err . fileName = name ;
59- throw err ;
60- }
61-
62- // Upload original
63- const blockBlobClient = mockContainerClient . getBlockBlobClient ( pathName ) ;
64- await blockBlobClient . uploadData ( file . buffer , {
65- blobHTTPHeaders : { blobContentType : foundFormat [ 0 ] . mimeType } ,
66- } ) ;
67-
68- // Thumbnail generation (matches actual FileService logic)
69- let thumbnailPaths = { small : null , medium : null , large : null } ;
70- const fileMimeType = foundFormat [ 0 ] . mimeType ;
71- if ( ThumbnailService . isProcessable ( fileMimeType ) ) {
72- try {
73- thumbnailPaths = await ThumbnailService . generate (
74- file . buffer ,
75- pathName ,
76- mockContainerClient
77- ) ;
78- } catch ( err ) {
79- sails . log . error ( 'Thumbnail generation failed:' , err ) ;
80- }
81- }
82-
83- const param = {
84- dateInscription : new Date ( ) ,
85- fileName : name ,
86- document : idDocument ,
87- fileFormat : foundFormat [ 0 ] . id ,
88- path : pathName ,
89- isValidated,
90- thumbnailSmall : thumbnailPaths . small ,
91- thumbnailMedium : thumbnailPaths . medium ,
92- thumbnailLarge : thumbnailPaths . large ,
93- } ;
94- if ( fetchResult ) {
95- return TFile . create ( param ) . fetch ( ) ;
96- }
97- return TFile . create ( param ) ;
98- } ;
99-
100- // Patch delete to use mock credentials
101- FileService . document . delete = async function ( file ) {
102- const destroyedRecord = await TFile . destroyOne ( file . id ) ;
103- const blockBlobClient = mockContainerClient . getBlockBlobClient (
104- destroyedRecord . path
105- ) ;
106- await blockBlobClient . delete ( { deleteSnapshots : 'include' } ) ;
107-
108- // Clean up thumbnail blobs
109- const thumbnailPaths = [
110- destroyedRecord . thumbnailSmall ,
111- destroyedRecord . thumbnailMedium ,
112- destroyedRecord . thumbnailLarge ,
113- ] . filter ( Boolean ) ;
114- await Promise . all (
115- thumbnailPaths . map ( async ( thumbPath ) => {
116- try {
117- const thumbBlobClient =
118- mockContainerClient . getBlockBlobClient ( thumbPath ) ;
119- await thumbBlobClient . delete ( { deleteSnapshots : 'include' } ) ;
120- } catch ( err ) {
121- sails . log . error (
122- `Failed to delete thumbnail blob ${ thumbPath } :` ,
123- err
124- ) ;
125- }
126- } )
127- ) ;
128-
129- return destroyedRecord ;
130- } ;
131-
132- FileService . isCredentials = true ;
29+ // Inject mock credentials so the real create/delete code paths run
30+ FileService . setCredentialsForTest ( {
31+ documentsBlobClient : mockContainerClient ,
32+ } ) ;
13333 } ) ;
13434
13535 beforeEach ( ( ) => {
13636 mockUploadData . resetHistory ( ) ;
13737 mockDeleteBlob . resetHistory ( ) ;
138- uploadedBlobs = { } ;
13938 } ) ;
14039
14140 after ( ( ) => {
142- FileService . document . create = originalCreate ;
143- FileService . document . delete = originalDelete ;
41+ // Restore original credentials state
42+ FileService . setCredentialsForTest ( originalCredentials ) ;
14443 FileService . isCredentials = originalIsCredentials ;
14544 sinon . restore ( ) ;
14645 } ) ;
14746
14847 describe ( 'create() with image file' , ( ) => {
14948 let createdFile ;
15049
151- after ( async ( ) => {
50+ afterEach ( async ( ) => {
15251 if ( createdFile ) {
15352 await TFile . destroyOne ( createdFile . id ) ;
53+ createdFile = null ;
15454 }
15555 } ) ;
15656
@@ -183,8 +83,7 @@ describe('FileService.document - Thumbnail Integration', () => {
18383 should ( createdFile . thumbnailMedium ) . startWith ( 'thumbnails/medium/' ) ;
18484 should ( createdFile . thumbnailMedium ) . endWith ( '.webp' ) ;
18585
186- // Large should be null — original is only 2000px, not > 1920
187- // Actually 2000 > 1920, so large IS generated
86+ // 2000 > 1920, so large IS generated
18887 should ( createdFile . thumbnailLarge ) . be . a . String ( ) ;
18988 should ( createdFile . thumbnailLarge ) . startWith ( 'thumbnails/large/' ) ;
19089 should ( createdFile . thumbnailLarge ) . endWith ( '.webp' ) ;
@@ -208,13 +107,11 @@ describe('FileService.document - Thumbnail Integration', () => {
208107 size : imageBuffer . length ,
209108 } ;
210109
211- const result = await FileService . document . create ( file , 1 , true ) ;
212-
213- should ( result . thumbnailSmall ) . be . a . String ( ) ;
214- should ( result . thumbnailMedium ) . be . a . String ( ) ;
215- should ( result . thumbnailLarge ) . be . null ( ) ;
110+ createdFile = await FileService . document . create ( file , 1 , true ) ;
216111
217- await TFile . destroyOne ( result . id ) ;
112+ should ( createdFile . thumbnailSmall ) . be . a . String ( ) ;
113+ should ( createdFile . thumbnailMedium ) . be . a . String ( ) ;
114+ should ( createdFile . thumbnailLarge ) . be . null ( ) ;
218115 } ) ;
219116 } ) ;
220117
@@ -254,9 +151,9 @@ describe('FileService.document - Thumbnail Integration', () => {
254151
255152 describe ( 'create() with thumbnail generation failure' , ( ) => {
256153 it ( 'should still succeed with null thumbnails when sharp fails' , async ( ) => {
257- // Stub ThumbnailService.generate to throw
154+ // Stub the global ThumbnailService.generate (Sails auto-globalized)
258155 const generateStub = sinon
259- . stub ( ThumbnailService , 'generate' )
156+ . stub ( global . ThumbnailService , 'generate' )
260157 . rejects ( new Error ( 'sharp exploded' ) ) ;
261158 const logStub = sinon . stub ( sails . log , 'error' ) ;
262159
0 commit comments