Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

utils.spec.ts 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. import mime from 'mime'
  2. import { upload } from '@/service/base'
  3. import {
  4. downloadFile,
  5. fileIsUploaded,
  6. fileUpload,
  7. getFileAppearanceType,
  8. getFileExtension,
  9. getFileNameFromUrl,
  10. getFilesInLogs,
  11. getProcessedFiles,
  12. getProcessedFilesFromResponse,
  13. getSupportFileExtensionList,
  14. getSupportFileType,
  15. isAllowedFileExtension,
  16. } from './utils'
  17. import { FileAppearanceTypeEnum } from './types'
  18. import { SupportUploadFileTypes } from '@/app/components/workflow/types'
  19. import { TransferMethod } from '@/types/app'
  20. import { FILE_EXTS } from '../prompt-editor/constants'
  21. jest.mock('mime', () => ({
  22. __esModule: true,
  23. default: {
  24. getAllExtensions: jest.fn(),
  25. },
  26. }))
  27. jest.mock('@/service/base', () => ({
  28. upload: jest.fn(),
  29. }))
  30. describe('file-uploader utils', () => {
  31. beforeEach(() => {
  32. jest.clearAllMocks()
  33. })
  34. describe('fileUpload', () => {
  35. it('should handle successful file upload', () => {
  36. const mockFile = new File(['test'], 'test.txt')
  37. const mockCallbacks = {
  38. onProgressCallback: jest.fn(),
  39. onSuccessCallback: jest.fn(),
  40. onErrorCallback: jest.fn(),
  41. }
  42. jest.mocked(upload).mockResolvedValue({ id: '123' })
  43. fileUpload({
  44. file: mockFile,
  45. ...mockCallbacks,
  46. })
  47. expect(upload).toHaveBeenCalled()
  48. })
  49. })
  50. describe('getFileExtension', () => {
  51. it('should get extension from mimetype', () => {
  52. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
  53. expect(getFileExtension('file', 'application/pdf')).toBe('pdf')
  54. })
  55. it('should get extension from mimetype and file name 1', () => {
  56. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
  57. expect(getFileExtension('file.pdf', 'application/pdf')).toBe('pdf')
  58. })
  59. it('should get extension from mimetype with multiple ext candidates with filename hint', () => {
  60. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['der', 'crt', 'pem']))
  61. expect(getFileExtension('file.pem', 'application/x-x509-ca-cert')).toBe('pem')
  62. })
  63. it('should get extension from mimetype with multiple ext candidates without filename hint', () => {
  64. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['der', 'crt', 'pem']))
  65. expect(getFileExtension('file', 'application/x-x509-ca-cert')).toBe('der')
  66. })
  67. it('should get extension from filename if mimetype fails', () => {
  68. jest.mocked(mime.getAllExtensions).mockReturnValue(null)
  69. expect(getFileExtension('file.txt', '')).toBe('txt')
  70. expect(getFileExtension('file.txt.docx', '')).toBe('docx')
  71. expect(getFileExtension('file', '')).toBe('')
  72. })
  73. it('should return empty string for remote files', () => {
  74. expect(getFileExtension('file.txt', '', true)).toBe('')
  75. })
  76. })
  77. describe('getFileAppearanceType', () => {
  78. it('should identify gif files', () => {
  79. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['gif']))
  80. expect(getFileAppearanceType('image.gif', 'image/gif'))
  81. .toBe(FileAppearanceTypeEnum.gif)
  82. })
  83. it('should identify image files', () => {
  84. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['jpg']))
  85. expect(getFileAppearanceType('image.jpg', 'image/jpeg'))
  86. .toBe(FileAppearanceTypeEnum.image)
  87. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['jpeg']))
  88. expect(getFileAppearanceType('image.jpeg', 'image/jpeg'))
  89. .toBe(FileAppearanceTypeEnum.image)
  90. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['png']))
  91. expect(getFileAppearanceType('image.png', 'image/png'))
  92. .toBe(FileAppearanceTypeEnum.image)
  93. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['webp']))
  94. expect(getFileAppearanceType('image.webp', 'image/webp'))
  95. .toBe(FileAppearanceTypeEnum.image)
  96. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['svg']))
  97. expect(getFileAppearanceType('image.svg', 'image/svgxml'))
  98. .toBe(FileAppearanceTypeEnum.image)
  99. })
  100. it('should identify video files', () => {
  101. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mp4']))
  102. expect(getFileAppearanceType('video.mp4', 'video/mp4'))
  103. .toBe(FileAppearanceTypeEnum.video)
  104. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mov']))
  105. expect(getFileAppearanceType('video.mov', 'video/quicktime'))
  106. .toBe(FileAppearanceTypeEnum.video)
  107. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mpeg']))
  108. expect(getFileAppearanceType('video.mpeg', 'video/mpeg'))
  109. .toBe(FileAppearanceTypeEnum.video)
  110. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['webm']))
  111. expect(getFileAppearanceType('video.web', 'video/webm'))
  112. .toBe(FileAppearanceTypeEnum.video)
  113. })
  114. it('should identify audio files', () => {
  115. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mp3']))
  116. expect(getFileAppearanceType('audio.mp3', 'audio/mpeg'))
  117. .toBe(FileAppearanceTypeEnum.audio)
  118. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['m4a']))
  119. expect(getFileAppearanceType('audio.m4a', 'audio/mp4'))
  120. .toBe(FileAppearanceTypeEnum.audio)
  121. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['wav']))
  122. expect(getFileAppearanceType('audio.wav', 'audio/vnd.wav'))
  123. .toBe(FileAppearanceTypeEnum.audio)
  124. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['amr']))
  125. expect(getFileAppearanceType('audio.amr', 'audio/AMR'))
  126. .toBe(FileAppearanceTypeEnum.audio)
  127. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mpga']))
  128. expect(getFileAppearanceType('audio.mpga', 'audio/mpeg'))
  129. .toBe(FileAppearanceTypeEnum.audio)
  130. })
  131. it('should identify code files', () => {
  132. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['html']))
  133. expect(getFileAppearanceType('index.html', 'text/html'))
  134. .toBe(FileAppearanceTypeEnum.code)
  135. })
  136. it('should identify PDF files', () => {
  137. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
  138. expect(getFileAppearanceType('doc.pdf', 'application/pdf'))
  139. .toBe(FileAppearanceTypeEnum.pdf)
  140. })
  141. it('should identify markdown files', () => {
  142. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['md']))
  143. expect(getFileAppearanceType('file.md', 'text/markdown'))
  144. .toBe(FileAppearanceTypeEnum.markdown)
  145. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['markdown']))
  146. expect(getFileAppearanceType('file.markdown', 'text/markdown'))
  147. .toBe(FileAppearanceTypeEnum.markdown)
  148. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mdx']))
  149. expect(getFileAppearanceType('file.mdx', 'text/mdx'))
  150. .toBe(FileAppearanceTypeEnum.markdown)
  151. })
  152. it('should identify excel files', () => {
  153. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xlsx']))
  154. expect(getFileAppearanceType('doc.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'))
  155. .toBe(FileAppearanceTypeEnum.excel)
  156. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xls']))
  157. expect(getFileAppearanceType('doc.xls', 'application/vnd.ms-excel'))
  158. .toBe(FileAppearanceTypeEnum.excel)
  159. })
  160. it('should identify word files', () => {
  161. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['doc']))
  162. expect(getFileAppearanceType('doc.doc', 'application/msword'))
  163. .toBe(FileAppearanceTypeEnum.word)
  164. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['docx']))
  165. expect(getFileAppearanceType('doc.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'))
  166. .toBe(FileAppearanceTypeEnum.word)
  167. })
  168. it('should identify word files', () => {
  169. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['ppt']))
  170. expect(getFileAppearanceType('doc.ppt', 'application/vnd.ms-powerpoint'))
  171. .toBe(FileAppearanceTypeEnum.ppt)
  172. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pptx']))
  173. expect(getFileAppearanceType('doc.pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'))
  174. .toBe(FileAppearanceTypeEnum.ppt)
  175. })
  176. it('should identify document files', () => {
  177. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['txt']))
  178. expect(getFileAppearanceType('file.txt', 'text/plain'))
  179. .toBe(FileAppearanceTypeEnum.document)
  180. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['csv']))
  181. expect(getFileAppearanceType('file.csv', 'text/csv'))
  182. .toBe(FileAppearanceTypeEnum.document)
  183. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['msg']))
  184. expect(getFileAppearanceType('file.msg', 'application/vnd.ms-outlook'))
  185. .toBe(FileAppearanceTypeEnum.document)
  186. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['eml']))
  187. expect(getFileAppearanceType('file.eml', 'message/rfc822'))
  188. .toBe(FileAppearanceTypeEnum.document)
  189. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xml']))
  190. expect(getFileAppearanceType('file.xml', 'application/rssxml'))
  191. .toBe(FileAppearanceTypeEnum.document)
  192. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['epub']))
  193. expect(getFileAppearanceType('file.epub', 'application/epubzip'))
  194. .toBe(FileAppearanceTypeEnum.document)
  195. })
  196. it('should handle null mime extension', () => {
  197. jest.mocked(mime.getAllExtensions).mockReturnValue(null)
  198. expect(getFileAppearanceType('file.txt', 'text/plain'))
  199. .toBe(FileAppearanceTypeEnum.document)
  200. })
  201. })
  202. describe('getSupportFileType', () => {
  203. it('should return custom type when isCustom is true', () => {
  204. expect(getSupportFileType('file.txt', '', true))
  205. .toBe(SupportUploadFileTypes.custom)
  206. })
  207. it('should return file type when isCustom is false', () => {
  208. expect(getSupportFileType('file.txt', 'text/plain'))
  209. .toBe(SupportUploadFileTypes.document)
  210. })
  211. })
  212. describe('getProcessedFiles', () => {
  213. it('should process files correctly', () => {
  214. const files = [{
  215. id: '123',
  216. name: 'test.txt',
  217. size: 1024,
  218. type: 'text/plain',
  219. progress: 100,
  220. supportFileType: 'document',
  221. transferMethod: TransferMethod.remote_url,
  222. url: 'http://example.com',
  223. uploadedId: '123',
  224. }]
  225. const result = getProcessedFiles(files)
  226. expect(result[0]).toEqual({
  227. type: 'document',
  228. transfer_method: TransferMethod.remote_url,
  229. url: 'http://example.com',
  230. upload_file_id: '123',
  231. })
  232. })
  233. })
  234. describe('getProcessedFilesFromResponse', () => {
  235. beforeEach(() => {
  236. jest.mocked(mime.getAllExtensions).mockImplementation((mimeType: string) => {
  237. const mimeMap: Record<string, Set<string>> = {
  238. 'image/jpeg': new Set(['jpg', 'jpeg']),
  239. 'image/png': new Set(['png']),
  240. 'image/gif': new Set(['gif']),
  241. 'video/mp4': new Set(['mp4']),
  242. 'audio/mp3': new Set(['mp3']),
  243. 'application/pdf': new Set(['pdf']),
  244. 'text/plain': new Set(['txt']),
  245. 'application/json': new Set(['json']),
  246. }
  247. return mimeMap[mimeType] || new Set()
  248. })
  249. })
  250. it('should process files correctly without type correction', () => {
  251. const files = [{
  252. related_id: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
  253. extension: '.jpeg',
  254. filename: 'test.jpeg',
  255. size: 2881761,
  256. mime_type: 'image/jpeg',
  257. transfer_method: TransferMethod.local_file,
  258. type: 'image',
  259. url: 'https://upload.dify.dev/files/xxx/file-preview',
  260. upload_file_id: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
  261. remote_url: '',
  262. }]
  263. const result = getProcessedFilesFromResponse(files)
  264. expect(result[0]).toEqual({
  265. id: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
  266. name: 'test.jpeg',
  267. size: 2881761,
  268. type: 'image/jpeg',
  269. progress: 100,
  270. transferMethod: TransferMethod.local_file,
  271. supportFileType: 'image',
  272. uploadedId: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
  273. url: 'https://upload.dify.dev/files/xxx/file-preview',
  274. })
  275. })
  276. it('should correct image file misclassified as document', () => {
  277. const files = [{
  278. related_id: '123',
  279. extension: '.jpg',
  280. filename: 'image.jpg',
  281. size: 1024,
  282. mime_type: 'image/jpeg',
  283. transfer_method: TransferMethod.local_file,
  284. type: 'document',
  285. url: 'https://example.com/image.jpg',
  286. upload_file_id: '123',
  287. remote_url: '',
  288. }]
  289. const result = getProcessedFilesFromResponse(files)
  290. expect(result[0].supportFileType).toBe('image')
  291. })
  292. it('should correct video file misclassified as document', () => {
  293. const files = [{
  294. related_id: '123',
  295. extension: '.mp4',
  296. filename: 'video.mp4',
  297. size: 1024,
  298. mime_type: 'video/mp4',
  299. transfer_method: TransferMethod.local_file,
  300. type: 'document',
  301. url: 'https://example.com/video.mp4',
  302. upload_file_id: '123',
  303. remote_url: '',
  304. }]
  305. const result = getProcessedFilesFromResponse(files)
  306. expect(result[0].supportFileType).toBe('video')
  307. })
  308. it('should correct audio file misclassified as document', () => {
  309. const files = [{
  310. related_id: '123',
  311. extension: '.mp3',
  312. filename: 'audio.mp3',
  313. size: 1024,
  314. mime_type: 'audio/mp3',
  315. transfer_method: TransferMethod.local_file,
  316. type: 'document',
  317. url: 'https://example.com/audio.mp3',
  318. upload_file_id: '123',
  319. remote_url: '',
  320. }]
  321. const result = getProcessedFilesFromResponse(files)
  322. expect(result[0].supportFileType).toBe('audio')
  323. })
  324. it('should correct document file misclassified as image', () => {
  325. const files = [{
  326. related_id: '123',
  327. extension: '.pdf',
  328. filename: 'document.pdf',
  329. size: 1024,
  330. mime_type: 'application/pdf',
  331. transfer_method: TransferMethod.local_file,
  332. type: 'image',
  333. url: 'https://example.com/document.pdf',
  334. upload_file_id: '123',
  335. remote_url: '',
  336. }]
  337. const result = getProcessedFilesFromResponse(files)
  338. expect(result[0].supportFileType).toBe('document')
  339. })
  340. it('should NOT correct when filename and MIME type conflict', () => {
  341. const files = [{
  342. related_id: '123',
  343. extension: '.pdf',
  344. filename: 'document.pdf',
  345. size: 1024,
  346. mime_type: 'image/jpeg',
  347. transfer_method: TransferMethod.local_file,
  348. type: 'document',
  349. url: 'https://example.com/document.pdf',
  350. upload_file_id: '123',
  351. remote_url: '',
  352. }]
  353. const result = getProcessedFilesFromResponse(files)
  354. expect(result[0].supportFileType).toBe('document')
  355. })
  356. it('should NOT correct when filename and MIME type both point to wrong type', () => {
  357. const files = [{
  358. related_id: '123',
  359. extension: '.jpg',
  360. filename: 'image.jpg',
  361. size: 1024,
  362. mime_type: 'image/jpeg',
  363. transfer_method: TransferMethod.local_file,
  364. type: 'image',
  365. url: 'https://example.com/image.jpg',
  366. upload_file_id: '123',
  367. remote_url: '',
  368. }]
  369. const result = getProcessedFilesFromResponse(files)
  370. expect(result[0].supportFileType).toBe('image')
  371. })
  372. it('should handle files with missing filename', () => {
  373. const files = [{
  374. related_id: '123',
  375. extension: '',
  376. filename: '',
  377. size: 1024,
  378. mime_type: 'image/jpeg',
  379. transfer_method: TransferMethod.local_file,
  380. type: 'document',
  381. url: 'https://example.com/file',
  382. upload_file_id: '123',
  383. remote_url: '',
  384. }]
  385. const result = getProcessedFilesFromResponse(files)
  386. expect(result[0].supportFileType).toBe('document')
  387. })
  388. it('should handle files with missing MIME type', () => {
  389. const files = [{
  390. related_id: '123',
  391. extension: '.jpg',
  392. filename: 'image.jpg',
  393. size: 1024,
  394. mime_type: '',
  395. transfer_method: TransferMethod.local_file,
  396. type: 'document',
  397. url: 'https://example.com/image.jpg',
  398. upload_file_id: '123',
  399. remote_url: '',
  400. }]
  401. const result = getProcessedFilesFromResponse(files)
  402. expect(result[0].supportFileType).toBe('document')
  403. })
  404. it('should handle files with unknown extensions', () => {
  405. const files = [{
  406. related_id: '123',
  407. extension: '.unknown',
  408. filename: 'file.unknown',
  409. size: 1024,
  410. mime_type: 'application/unknown',
  411. transfer_method: TransferMethod.local_file,
  412. type: 'document',
  413. url: 'https://example.com/file.unknown',
  414. upload_file_id: '123',
  415. remote_url: '',
  416. }]
  417. const result = getProcessedFilesFromResponse(files)
  418. expect(result[0].supportFileType).toBe('document')
  419. })
  420. it('should handle multiple different file types correctly', () => {
  421. const files = [
  422. {
  423. related_id: '1',
  424. extension: '.jpg',
  425. filename: 'correct-image.jpg',
  426. mime_type: 'image/jpeg',
  427. type: 'image',
  428. size: 1024,
  429. transfer_method: TransferMethod.local_file,
  430. url: 'https://example.com/correct-image.jpg',
  431. upload_file_id: '1',
  432. remote_url: '',
  433. },
  434. {
  435. related_id: '2',
  436. extension: '.png',
  437. filename: 'misclassified-image.png',
  438. mime_type: 'image/png',
  439. type: 'document',
  440. size: 2048,
  441. transfer_method: TransferMethod.local_file,
  442. url: 'https://example.com/misclassified-image.png',
  443. upload_file_id: '2',
  444. remote_url: '',
  445. },
  446. {
  447. related_id: '3',
  448. extension: '.pdf',
  449. filename: 'conflicted.pdf',
  450. mime_type: 'image/jpeg',
  451. type: 'document',
  452. size: 3072,
  453. transfer_method: TransferMethod.local_file,
  454. url: 'https://example.com/conflicted.pdf',
  455. upload_file_id: '3',
  456. remote_url: '',
  457. },
  458. ]
  459. const result = getProcessedFilesFromResponse(files)
  460. expect(result[0].supportFileType).toBe('image') // correct, no change
  461. expect(result[1].supportFileType).toBe('image') // corrected from document to image
  462. expect(result[2].supportFileType).toBe('document') // conflict, no change
  463. })
  464. })
  465. describe('getFileNameFromUrl', () => {
  466. it('should extract filename from URL', () => {
  467. expect(getFileNameFromUrl('http://example.com/path/file.txt'))
  468. .toBe('file.txt')
  469. })
  470. })
  471. describe('getSupportFileExtensionList', () => {
  472. it('should handle custom file types', () => {
  473. const result = getSupportFileExtensionList(
  474. [SupportUploadFileTypes.custom],
  475. ['.pdf', '.txt', '.doc'],
  476. )
  477. expect(result).toEqual(['PDF', 'TXT', 'DOC'])
  478. })
  479. it('should handle standard file types', () => {
  480. const mockFileExts = {
  481. image: ['JPG', 'PNG'],
  482. document: ['PDF', 'TXT'],
  483. video: ['MP4', 'MOV'],
  484. }
  485. // Temporarily mock FILE_EXTS
  486. const originalFileExts = { ...FILE_EXTS }
  487. Object.assign(FILE_EXTS, mockFileExts)
  488. const result = getSupportFileExtensionList(
  489. ['image', 'document'],
  490. [],
  491. )
  492. expect(result).toEqual(['JPG', 'PNG', 'PDF', 'TXT'])
  493. // Restore original FILE_EXTS
  494. Object.assign(FILE_EXTS, originalFileExts)
  495. })
  496. it('should return empty array for empty inputs', () => {
  497. const result = getSupportFileExtensionList([], [])
  498. expect(result).toEqual([])
  499. })
  500. it('should prioritize custom types over standard types', () => {
  501. const mockFileExts = {
  502. image: ['JPG', 'PNG'],
  503. }
  504. // Temporarily mock FILE_EXTS
  505. const originalFileExts = { ...FILE_EXTS }
  506. Object.assign(FILE_EXTS, mockFileExts)
  507. const result = getSupportFileExtensionList(
  508. [SupportUploadFileTypes.custom, 'image'],
  509. ['.csv', '.xml'],
  510. )
  511. expect(result).toEqual(['CSV', 'XML'])
  512. // Restore original FILE_EXTS
  513. Object.assign(FILE_EXTS, originalFileExts)
  514. })
  515. })
  516. describe('isAllowedFileExtension', () => {
  517. it('should validate allowed file extensions', () => {
  518. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
  519. expect(isAllowedFileExtension(
  520. 'test.pdf',
  521. 'application/pdf',
  522. ['document'],
  523. ['.pdf'],
  524. )).toBe(true)
  525. })
  526. })
  527. describe('getFilesInLogs', () => {
  528. const mockFileData = {
  529. dify_model_identity: '__dify__file__',
  530. related_id: '123',
  531. filename: 'test.pdf',
  532. size: 1024,
  533. mime_type: 'application/pdf',
  534. transfer_method: 'local_file',
  535. type: 'document',
  536. url: 'http://example.com/test.pdf',
  537. }
  538. it('should handle empty or null input', () => {
  539. expect(getFilesInLogs(null)).toEqual([])
  540. expect(getFilesInLogs({})).toEqual([])
  541. expect(getFilesInLogs(undefined)).toEqual([])
  542. })
  543. it('should process single file object', () => {
  544. const input = {
  545. file1: mockFileData,
  546. }
  547. const expected = [{
  548. varName: 'file1',
  549. list: [{
  550. id: '123',
  551. name: 'test.pdf',
  552. size: 1024,
  553. type: 'application/pdf',
  554. progress: 100,
  555. transferMethod: 'local_file',
  556. supportFileType: 'document',
  557. uploadedId: '123',
  558. url: 'http://example.com/test.pdf',
  559. }],
  560. }]
  561. expect(getFilesInLogs(input)).toEqual(expected)
  562. })
  563. it('should process array of files', () => {
  564. const input = {
  565. files: [mockFileData, mockFileData],
  566. }
  567. const expected = [{
  568. varName: 'files',
  569. list: [
  570. {
  571. id: '123',
  572. name: 'test.pdf',
  573. size: 1024,
  574. type: 'application/pdf',
  575. progress: 100,
  576. transferMethod: 'local_file',
  577. supportFileType: 'document',
  578. uploadedId: '123',
  579. url: 'http://example.com/test.pdf',
  580. },
  581. {
  582. id: '123',
  583. name: 'test.pdf',
  584. size: 1024,
  585. type: 'application/pdf',
  586. progress: 100,
  587. transferMethod: 'local_file',
  588. supportFileType: 'document',
  589. uploadedId: '123',
  590. url: 'http://example.com/test.pdf',
  591. },
  592. ],
  593. }]
  594. expect(getFilesInLogs(input)).toEqual(expected)
  595. })
  596. it('should ignore non-file objects and arrays', () => {
  597. const input = {
  598. regularString: 'not a file',
  599. regularNumber: 123,
  600. regularArray: [1, 2, 3],
  601. regularObject: { key: 'value' },
  602. file: mockFileData,
  603. }
  604. const expected = [{
  605. varName: 'file',
  606. list: [{
  607. id: '123',
  608. name: 'test.pdf',
  609. size: 1024,
  610. type: 'application/pdf',
  611. progress: 100,
  612. transferMethod: 'local_file',
  613. supportFileType: 'document',
  614. uploadedId: '123',
  615. url: 'http://example.com/test.pdf',
  616. }],
  617. }]
  618. expect(getFilesInLogs(input)).toEqual(expected)
  619. })
  620. it('should handle mixed file types in array', () => {
  621. const input = {
  622. mixedFiles: [
  623. mockFileData,
  624. { notAFile: true },
  625. mockFileData,
  626. ],
  627. }
  628. const expected = [{
  629. varName: 'mixedFiles',
  630. list: [
  631. {
  632. id: '123',
  633. name: 'test.pdf',
  634. size: 1024,
  635. type: 'application/pdf',
  636. progress: 100,
  637. transferMethod: 'local_file',
  638. supportFileType: 'document',
  639. uploadedId: '123',
  640. url: 'http://example.com/test.pdf',
  641. },
  642. {
  643. id: undefined,
  644. name: undefined,
  645. progress: 100,
  646. size: 0,
  647. supportFileType: undefined,
  648. transferMethod: undefined,
  649. type: undefined,
  650. uploadedId: undefined,
  651. url: undefined,
  652. },
  653. {
  654. id: '123',
  655. name: 'test.pdf',
  656. size: 1024,
  657. type: 'application/pdf',
  658. progress: 100,
  659. transferMethod: 'local_file',
  660. supportFileType: 'document',
  661. uploadedId: '123',
  662. url: 'http://example.com/test.pdf',
  663. },
  664. ],
  665. }]
  666. expect(getFilesInLogs(input)).toEqual(expected)
  667. })
  668. })
  669. describe('fileIsUploaded', () => {
  670. it('should identify uploaded files', () => {
  671. expect(fileIsUploaded({
  672. uploadedId: '123',
  673. progress: 100,
  674. } as any)).toBe(true)
  675. })
  676. it('should identify remote files as uploaded', () => {
  677. expect(fileIsUploaded({
  678. transferMethod: TransferMethod.remote_url,
  679. progress: 100,
  680. } as any)).toBe(true)
  681. })
  682. })
  683. describe('downloadFile', () => {
  684. let mockAnchor: HTMLAnchorElement
  685. let createElementMock: jest.SpyInstance
  686. let appendChildMock: jest.SpyInstance
  687. let removeChildMock: jest.SpyInstance
  688. beforeEach(() => {
  689. // Mock createElement and appendChild
  690. mockAnchor = {
  691. href: '',
  692. download: '',
  693. style: { display: '' },
  694. target: '',
  695. title: '',
  696. click: jest.fn(),
  697. } as unknown as HTMLAnchorElement
  698. createElementMock = jest.spyOn(document, 'createElement').mockReturnValue(mockAnchor as any)
  699. appendChildMock = jest.spyOn(document.body, 'appendChild').mockImplementation((node: Node) => {
  700. return node
  701. })
  702. removeChildMock = jest.spyOn(document.body, 'removeChild').mockImplementation((node: Node) => {
  703. return node
  704. })
  705. })
  706. afterEach(() => {
  707. jest.resetAllMocks()
  708. })
  709. it('should create and trigger download with correct attributes', () => {
  710. const url = 'https://example.com/test.pdf'
  711. const filename = 'test.pdf'
  712. downloadFile(url, filename)
  713. // Verify anchor element was created with correct properties
  714. expect(createElementMock).toHaveBeenCalledWith('a')
  715. expect(mockAnchor.href).toBe(url)
  716. expect(mockAnchor.download).toBe(filename)
  717. expect(mockAnchor.style.display).toBe('none')
  718. expect(mockAnchor.target).toBe('_blank')
  719. expect(mockAnchor.title).toBe(filename)
  720. // Verify DOM operations
  721. expect(appendChildMock).toHaveBeenCalledWith(mockAnchor)
  722. expect(mockAnchor.click).toHaveBeenCalled()
  723. expect(removeChildMock).toHaveBeenCalledWith(mockAnchor)
  724. })
  725. it('should handle empty filename', () => {
  726. const url = 'https://example.com/test.pdf'
  727. const filename = ''
  728. downloadFile(url, filename)
  729. expect(mockAnchor.download).toBe('')
  730. expect(mockAnchor.title).toBe('')
  731. })
  732. it('should handle empty url', () => {
  733. const url = ''
  734. const filename = 'test.pdf'
  735. downloadFile(url, filename)
  736. expect(mockAnchor.href).toBe('')
  737. })
  738. })
  739. })