@aj-archipelago/cortex 1.3.62 → 1.3.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.github/workflows/cortex-file-handler-test.yml +61 -0
  2. package/README.md +31 -7
  3. package/config/default.example.json +15 -0
  4. package/config.js +133 -12
  5. package/helper-apps/cortex-autogen2/DigiCertGlobalRootCA.crt.pem +22 -0
  6. package/helper-apps/cortex-autogen2/Dockerfile +31 -0
  7. package/helper-apps/cortex-autogen2/Dockerfile.worker +41 -0
  8. package/helper-apps/cortex-autogen2/README.md +183 -0
  9. package/helper-apps/cortex-autogen2/__init__.py +1 -0
  10. package/helper-apps/cortex-autogen2/agents.py +131 -0
  11. package/helper-apps/cortex-autogen2/docker-compose.yml +20 -0
  12. package/helper-apps/cortex-autogen2/function_app.py +55 -0
  13. package/helper-apps/cortex-autogen2/host.json +15 -0
  14. package/helper-apps/cortex-autogen2/main.py +126 -0
  15. package/helper-apps/cortex-autogen2/poetry.lock +3652 -0
  16. package/helper-apps/cortex-autogen2/pyproject.toml +36 -0
  17. package/helper-apps/cortex-autogen2/requirements.txt +20 -0
  18. package/helper-apps/cortex-autogen2/send_task.py +105 -0
  19. package/helper-apps/cortex-autogen2/services/__init__.py +1 -0
  20. package/helper-apps/cortex-autogen2/services/azure_queue.py +85 -0
  21. package/helper-apps/cortex-autogen2/services/redis_publisher.py +153 -0
  22. package/helper-apps/cortex-autogen2/task_processor.py +488 -0
  23. package/helper-apps/cortex-autogen2/tools/__init__.py +24 -0
  24. package/helper-apps/cortex-autogen2/tools/azure_blob_tools.py +175 -0
  25. package/helper-apps/cortex-autogen2/tools/azure_foundry_agents.py +601 -0
  26. package/helper-apps/cortex-autogen2/tools/coding_tools.py +72 -0
  27. package/helper-apps/cortex-autogen2/tools/download_tools.py +48 -0
  28. package/helper-apps/cortex-autogen2/tools/file_tools.py +545 -0
  29. package/helper-apps/cortex-autogen2/tools/search_tools.py +646 -0
  30. package/helper-apps/cortex-azure-cleaner/README.md +36 -0
  31. package/helper-apps/cortex-file-converter/README.md +93 -0
  32. package/helper-apps/cortex-file-converter/key_to_pdf.py +104 -0
  33. package/helper-apps/cortex-file-converter/list_blob_extensions.py +89 -0
  34. package/helper-apps/cortex-file-converter/process_azure_keynotes.py +181 -0
  35. package/helper-apps/cortex-file-converter/requirements.txt +1 -0
  36. package/helper-apps/cortex-file-handler/.env.test.azure.ci +7 -0
  37. package/helper-apps/cortex-file-handler/.env.test.azure.sample +1 -1
  38. package/helper-apps/cortex-file-handler/.env.test.gcs.ci +10 -0
  39. package/helper-apps/cortex-file-handler/.env.test.gcs.sample +2 -2
  40. package/helper-apps/cortex-file-handler/INTERFACE.md +41 -0
  41. package/helper-apps/cortex-file-handler/package.json +1 -1
  42. package/helper-apps/cortex-file-handler/scripts/setup-azure-container.js +41 -17
  43. package/helper-apps/cortex-file-handler/scripts/setup-test-containers.js +30 -15
  44. package/helper-apps/cortex-file-handler/scripts/test-azure.sh +32 -6
  45. package/helper-apps/cortex-file-handler/scripts/test-gcs.sh +24 -2
  46. package/helper-apps/cortex-file-handler/scripts/validate-env.js +128 -0
  47. package/helper-apps/cortex-file-handler/src/blobHandler.js +161 -51
  48. package/helper-apps/cortex-file-handler/src/constants.js +3 -0
  49. package/helper-apps/cortex-file-handler/src/fileChunker.js +10 -8
  50. package/helper-apps/cortex-file-handler/src/index.js +116 -9
  51. package/helper-apps/cortex-file-handler/src/redis.js +61 -1
  52. package/helper-apps/cortex-file-handler/src/services/ConversionService.js +11 -8
  53. package/helper-apps/cortex-file-handler/src/services/FileConversionService.js +2 -2
  54. package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +88 -6
  55. package/helper-apps/cortex-file-handler/src/services/storage/GCSStorageProvider.js +58 -0
  56. package/helper-apps/cortex-file-handler/src/services/storage/StorageFactory.js +25 -5
  57. package/helper-apps/cortex-file-handler/src/services/storage/StorageProvider.js +9 -0
  58. package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +120 -16
  59. package/helper-apps/cortex-file-handler/src/start.js +27 -17
  60. package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +52 -1
  61. package/helper-apps/cortex-file-handler/tests/blobHandler.test.js +40 -0
  62. package/helper-apps/cortex-file-handler/tests/checkHashShortLived.test.js +553 -0
  63. package/helper-apps/cortex-file-handler/tests/cleanup.test.js +46 -52
  64. package/helper-apps/cortex-file-handler/tests/containerConversionFlow.test.js +451 -0
  65. package/helper-apps/cortex-file-handler/tests/containerNameParsing.test.js +229 -0
  66. package/helper-apps/cortex-file-handler/tests/containerParameterFlow.test.js +392 -0
  67. package/helper-apps/cortex-file-handler/tests/conversionResilience.test.js +7 -2
  68. package/helper-apps/cortex-file-handler/tests/deleteOperations.test.js +348 -0
  69. package/helper-apps/cortex-file-handler/tests/fileChunker.test.js +23 -2
  70. package/helper-apps/cortex-file-handler/tests/fileUpload.test.js +11 -5
  71. package/helper-apps/cortex-file-handler/tests/getOperations.test.js +58 -24
  72. package/helper-apps/cortex-file-handler/tests/postOperations.test.js +11 -4
  73. package/helper-apps/cortex-file-handler/tests/shortLivedUrlConversion.test.js +225 -0
  74. package/helper-apps/cortex-file-handler/tests/start.test.js +8 -12
  75. package/helper-apps/cortex-file-handler/tests/storage/StorageFactory.test.js +80 -0
  76. package/helper-apps/cortex-file-handler/tests/storage/StorageService.test.js +388 -22
  77. package/helper-apps/cortex-file-handler/tests/testUtils.helper.js +74 -0
  78. package/lib/cortexResponse.js +153 -0
  79. package/lib/entityConstants.js +21 -3
  80. package/lib/logger.js +21 -4
  81. package/lib/pathwayTools.js +28 -9
  82. package/lib/util.js +49 -0
  83. package/package.json +1 -1
  84. package/pathways/basePathway.js +1 -0
  85. package/pathways/bing_afagent.js +54 -1
  86. package/pathways/call_tools.js +2 -3
  87. package/pathways/chat_jarvis.js +1 -1
  88. package/pathways/google_cse.js +27 -0
  89. package/pathways/grok_live_search.js +18 -0
  90. package/pathways/system/entity/memory/sys_memory_lookup_required.js +1 -0
  91. package/pathways/system/entity/memory/sys_memory_required.js +1 -0
  92. package/pathways/system/entity/memory/sys_search_memory.js +1 -0
  93. package/pathways/system/entity/sys_entity_agent.js +56 -4
  94. package/pathways/system/entity/sys_generator_quick.js +1 -0
  95. package/pathways/system/entity/tools/sys_tool_bing_search_afagent.js +26 -0
  96. package/pathways/system/entity/tools/sys_tool_google_search.js +141 -0
  97. package/pathways/system/entity/tools/sys_tool_grok_x_search.js +237 -0
  98. package/pathways/system/entity/tools/sys_tool_image.js +1 -1
  99. package/pathways/system/rest_streaming/sys_claude_37_sonnet.js +21 -0
  100. package/pathways/system/rest_streaming/sys_claude_41_opus.js +21 -0
  101. package/pathways/system/rest_streaming/sys_claude_4_sonnet.js +21 -0
  102. package/pathways/system/rest_streaming/sys_google_gemini_25_flash.js +25 -0
  103. package/pathways/system/rest_streaming/{sys_google_gemini_chat.js → sys_google_gemini_25_pro.js} +6 -4
  104. package/pathways/system/rest_streaming/sys_grok_4.js +23 -0
  105. package/pathways/system/rest_streaming/sys_grok_4_fast_non_reasoning.js +23 -0
  106. package/pathways/system/rest_streaming/sys_grok_4_fast_reasoning.js +23 -0
  107. package/pathways/system/rest_streaming/sys_openai_chat.js +3 -0
  108. package/pathways/system/rest_streaming/sys_openai_chat_gpt41.js +22 -0
  109. package/pathways/system/rest_streaming/sys_openai_chat_gpt41_mini.js +21 -0
  110. package/pathways/system/rest_streaming/sys_openai_chat_gpt41_nano.js +21 -0
  111. package/pathways/system/rest_streaming/{sys_claude_35_sonnet.js → sys_openai_chat_gpt4_omni.js} +6 -4
  112. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_omni_mini.js +21 -0
  113. package/pathways/system/rest_streaming/{sys_claude_3_haiku.js → sys_openai_chat_gpt5.js} +7 -5
  114. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_chat.js +21 -0
  115. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_mini.js +21 -0
  116. package/pathways/system/rest_streaming/sys_openai_chat_gpt5_nano.js +21 -0
  117. package/pathways/system/rest_streaming/{sys_openai_chat_o1.js → sys_openai_chat_o3.js} +6 -3
  118. package/pathways/system/rest_streaming/sys_openai_chat_o3_mini.js +3 -0
  119. package/pathways/system/workspaces/run_workspace_prompt.js +99 -0
  120. package/pathways/vision.js +1 -1
  121. package/server/graphql.js +1 -1
  122. package/server/modelExecutor.js +8 -0
  123. package/server/pathwayResolver.js +166 -16
  124. package/server/pathwayResponseParser.js +16 -8
  125. package/server/plugins/azureFoundryAgentsPlugin.js +1 -1
  126. package/server/plugins/claude3VertexPlugin.js +193 -45
  127. package/server/plugins/gemini15ChatPlugin.js +21 -0
  128. package/server/plugins/gemini15VisionPlugin.js +360 -0
  129. package/server/plugins/googleCsePlugin.js +94 -0
  130. package/server/plugins/grokVisionPlugin.js +365 -0
  131. package/server/plugins/modelPlugin.js +3 -1
  132. package/server/plugins/openAiChatPlugin.js +106 -13
  133. package/server/plugins/openAiVisionPlugin.js +42 -30
  134. package/server/resolver.js +28 -4
  135. package/server/rest.js +270 -53
  136. package/server/typeDef.js +1 -0
  137. package/tests/{mocks.js → helpers/mocks.js} +5 -2
  138. package/tests/{server.js → helpers/server.js} +2 -2
  139. package/tests/helpers/sseAssert.js +23 -0
  140. package/tests/helpers/sseClient.js +73 -0
  141. package/tests/helpers/subscriptionAssert.js +11 -0
  142. package/tests/helpers/subscriptions.js +113 -0
  143. package/tests/{sublong.srt → integration/features/translate/sublong.srt} +4543 -4543
  144. package/tests/integration/features/translate/translate_chunking_stream.test.js +100 -0
  145. package/tests/{translate_srt.test.js → integration/features/translate/translate_srt.test.js} +2 -2
  146. package/tests/integration/graphql/async/stream/agentic.test.js +477 -0
  147. package/tests/integration/graphql/async/stream/subscription_streaming.test.js +62 -0
  148. package/tests/integration/graphql/async/stream/sys_entity_start_streaming.test.js +71 -0
  149. package/tests/integration/graphql/async/stream/vendors/claude_streaming.test.js +56 -0
  150. package/tests/integration/graphql/async/stream/vendors/gemini_streaming.test.js +66 -0
  151. package/tests/integration/graphql/async/stream/vendors/grok_streaming.test.js +56 -0
  152. package/tests/integration/graphql/async/stream/vendors/openai_streaming.test.js +72 -0
  153. package/tests/integration/graphql/features/google/sysToolGoogleSearch.test.js +96 -0
  154. package/tests/integration/graphql/features/grok/grok.test.js +688 -0
  155. package/tests/integration/graphql/features/grok/grok_x_search_tool.test.js +354 -0
  156. package/tests/{main.test.js → integration/graphql/features/main.test.js} +1 -1
  157. package/tests/{call_tools.test.js → integration/graphql/features/tools/call_tools.test.js} +2 -2
  158. package/tests/{vision.test.js → integration/graphql/features/vision/vision.test.js} +1 -1
  159. package/tests/integration/graphql/subscriptions/connection.test.js +26 -0
  160. package/tests/{openai_api.test.js → integration/rest/oai/openai_api.test.js} +63 -238
  161. package/tests/integration/rest/oai/tool_calling_api.test.js +343 -0
  162. package/tests/integration/rest/oai/tool_calling_streaming.test.js +85 -0
  163. package/tests/integration/rest/vendors/claude_streaming.test.js +47 -0
  164. package/tests/integration/rest/vendors/claude_tool_calling_streaming.test.js +75 -0
  165. package/tests/integration/rest/vendors/gemini_streaming.test.js +47 -0
  166. package/tests/integration/rest/vendors/gemini_tool_calling_streaming.test.js +75 -0
  167. package/tests/integration/rest/vendors/grok_streaming.test.js +55 -0
  168. package/tests/integration/rest/vendors/grok_tool_calling_streaming.test.js +75 -0
  169. package/tests/{azureAuthTokenHelper.test.js → unit/core/azureAuthTokenHelper.test.js} +1 -1
  170. package/tests/{chunkfunction.test.js → unit/core/chunkfunction.test.js} +2 -2
  171. package/tests/{config.test.js → unit/core/config.test.js} +3 -3
  172. package/tests/{encodeCache.test.js → unit/core/encodeCache.test.js} +1 -1
  173. package/tests/{fastLruCache.test.js → unit/core/fastLruCache.test.js} +1 -1
  174. package/tests/{handleBars.test.js → unit/core/handleBars.test.js} +1 -1
  175. package/tests/{memoryfunction.test.js → unit/core/memoryfunction.test.js} +2 -2
  176. package/tests/unit/core/mergeResolver.test.js +952 -0
  177. package/tests/{parser.test.js → unit/core/parser.test.js} +3 -3
  178. package/tests/unit/core/pathwayResolver.test.js +187 -0
  179. package/tests/{requestMonitor.test.js → unit/core/requestMonitor.test.js} +1 -1
  180. package/tests/{requestMonitorDurationEstimator.test.js → unit/core/requestMonitorDurationEstimator.test.js} +1 -1
  181. package/tests/{truncateMessages.test.js → unit/core/truncateMessages.test.js} +3 -3
  182. package/tests/{util.test.js → unit/core/util.test.js} +1 -1
  183. package/tests/{apptekTranslatePlugin.test.js → unit/plugins/apptekTranslatePlugin.test.js} +3 -3
  184. package/tests/{azureFoundryAgents.test.js → unit/plugins/azureFoundryAgents.test.js} +136 -1
  185. package/tests/{claude3VertexPlugin.test.js → unit/plugins/claude3VertexPlugin.test.js} +32 -10
  186. package/tests/{claude3VertexToolConversion.test.js → unit/plugins/claude3VertexToolConversion.test.js} +3 -3
  187. package/tests/unit/plugins/googleCsePlugin.test.js +111 -0
  188. package/tests/unit/plugins/grokVisionPlugin.test.js +1392 -0
  189. package/tests/{modelPlugin.test.js → unit/plugins/modelPlugin.test.js} +3 -3
  190. package/tests/{multimodal_conversion.test.js → unit/plugins/multimodal_conversion.test.js} +4 -4
  191. package/tests/{openAiChatPlugin.test.js → unit/plugins/openAiChatPlugin.test.js} +13 -4
  192. package/tests/{openAiToolPlugin.test.js → unit/plugins/openAiToolPlugin.test.js} +35 -27
  193. package/tests/{tokenHandlingTests.test.js → unit/plugins/tokenHandlingTests.test.js} +5 -5
  194. package/tests/{translate_apptek.test.js → unit/plugins/translate_apptek.test.js} +3 -3
  195. package/tests/{streaming.test.js → unit/plugins.streaming/plugin_stream_events.test.js} +19 -58
  196. package/helper-apps/mogrt-handler/tests/test-files/test.gif +0 -1
  197. package/helper-apps/mogrt-handler/tests/test-files/test.mogrt +0 -1
  198. package/helper-apps/mogrt-handler/tests/test-files/test.mp4 +0 -1
  199. package/pathways/system/rest_streaming/sys_openai_chat_gpt4.js +0 -19
  200. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_32.js +0 -19
  201. package/pathways/system/rest_streaming/sys_openai_chat_gpt4_turbo.js +0 -19
  202. package/pathways/system/workspaces/run_claude35_sonnet.js +0 -21
  203. package/pathways/system/workspaces/run_claude3_haiku.js +0 -20
  204. package/pathways/system/workspaces/run_gpt35turbo.js +0 -20
  205. package/pathways/system/workspaces/run_gpt4.js +0 -20
  206. package/pathways/system/workspaces/run_gpt4_32.js +0 -20
  207. package/tests/agentic.test.js +0 -256
  208. package/tests/pathwayResolver.test.js +0 -78
  209. package/tests/subscription.test.js +0 -387
  210. /package/tests/{subchunk.srt → integration/features/translate/subchunk.srt} +0 -0
  211. /package/tests/{subhorizontal.srt → integration/features/translate/subhorizontal.srt} +0 -0
@@ -0,0 +1,348 @@
1
+ import test from "ava";
2
+ import axios from "axios";
3
+ import FormData from "form-data";
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import { fileURLToPath } from "url";
7
+ import { v4 as uuidv4 } from "uuid";
8
+
9
+ import { port } from "../src/start.js";
10
+ import {
11
+ cleanupHashAndFile,
12
+ createTestMediaFile,
13
+ startTestServer,
14
+ stopTestServer,
15
+ setupTestDirectory
16
+ } from "./testUtils.helper.js";
17
+ import {
18
+ setFileStoreMap,
19
+ getFileStoreMap,
20
+ removeFromFileStoreMap
21
+ } from "../src/redis.js";
22
+
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const __dirname = path.dirname(__filename);
25
+ const baseUrl = `http://localhost:${port}/api/CortexFileHandler`;
26
+
27
+ // Helper function to create test files
28
+ async function createTestFile(content, extension) {
29
+ const testDir = path.join(__dirname, "test-files");
30
+ if (!fs.existsSync(testDir)) {
31
+ fs.mkdirSync(testDir, { recursive: true });
32
+ }
33
+ const filename = path.join(
34
+ testDir,
35
+ `test-delete-${uuidv4().slice(0, 8)}.${extension}`,
36
+ );
37
+ fs.writeFileSync(filename, content);
38
+ return filename;
39
+ }
40
+
41
+ // Helper function to upload file
42
+ async function uploadFile(filePath, requestId = null, hash = null) {
43
+ const form = new FormData();
44
+ form.append("file", fs.createReadStream(filePath));
45
+ if (requestId) form.append("requestId", requestId);
46
+ if (hash) form.append("hash", hash);
47
+
48
+ return await axios.post(baseUrl, form, {
49
+ headers: form.getHeaders(),
50
+ validateStatus: (status) => true,
51
+ timeout: 15000,
52
+ });
53
+ }
54
+
55
+ // Helper function to delete file by hash
56
+ async function deleteFileByHash(hash) {
57
+ return await axios.delete(`${baseUrl}?hash=${hash}`, {
58
+ validateStatus: (status) => true,
59
+ timeout: 10000,
60
+ });
61
+ }
62
+
63
+ // Helper function to check if hash exists
64
+ async function checkHashExists(hash) {
65
+ return await axios.get(`${baseUrl}?hash=${hash}&checkHash=true`, {
66
+ validateStatus: (status) => true,
67
+ timeout: 10000,
68
+ });
69
+ }
70
+
71
+ // Test setup
72
+ test.before(async (t) => {
73
+ await setupTestDirectory(__dirname);
74
+ await startTestServer();
75
+ });
76
+
77
+ test.after(async (t) => {
78
+ await stopTestServer();
79
+ });
80
+
81
+ // Basic delete by hash tests
82
+ test.serial("should delete file by hash successfully", async (t) => {
83
+ const testContent = "test content for hash deletion";
84
+ const testHash = `test-hash-${uuidv4()}`;
85
+ const filePath = await createTestFile(testContent, "txt");
86
+ let uploadResponse;
87
+
88
+ try {
89
+ // Upload file with hash
90
+ uploadResponse = await uploadFile(filePath, null, testHash);
91
+ t.is(uploadResponse.status, 200, "Upload should succeed");
92
+ t.truthy(uploadResponse.data.url, "Should have file URL");
93
+
94
+ // Verify file exists via hash
95
+ const hashCheckBefore = await checkHashExists(testHash);
96
+ t.is(hashCheckBefore.status, 200, "Hash should exist before deletion");
97
+
98
+ // Delete file by hash
99
+ const deleteResponse = await deleteFileByHash(testHash);
100
+ t.is(deleteResponse.status, 200, "Delete should succeed");
101
+ t.truthy(deleteResponse.data.message, "Should have success message");
102
+ t.true(deleteResponse.data.message.includes(testHash), "Message should include hash");
103
+ t.truthy(deleteResponse.data.deleted, "Should have deletion details");
104
+
105
+ // Verify file is gone via hash
106
+ const hashCheckAfter = await checkHashExists(testHash);
107
+ t.is(hashCheckAfter.status, 404, "Hash should not exist after deletion");
108
+
109
+ // Wait a moment for deletion to propagate
110
+ await new Promise(resolve => setTimeout(resolve, 1000));
111
+
112
+ // Verify file URL is no longer accessible
113
+ const fileResponse = await axios.get(uploadResponse.data.url, {
114
+ validateStatus: (status) => true,
115
+ });
116
+ t.is(fileResponse.status, 404, "File URL should return 404 after deletion");
117
+
118
+ } finally {
119
+ fs.unlinkSync(filePath);
120
+ // Additional cleanup just in case
121
+ try {
122
+ await removeFromFileStoreMap(testHash);
123
+ } catch (e) {
124
+ // Ignore cleanup errors
125
+ }
126
+ }
127
+ });
128
+
129
+ test.serial("should return 404 when deleting non-existent hash", async (t) => {
130
+ const nonExistentHash = `non-existent-${uuidv4()}`;
131
+
132
+ const deleteResponse = await deleteFileByHash(nonExistentHash);
133
+ t.is(deleteResponse.status, 404, "Should return 404 for non-existent hash");
134
+ t.truthy(deleteResponse.data, "Should have error message");
135
+ t.true(deleteResponse.data.includes("not found"), "Error should indicate file not found");
136
+ });
137
+
138
+ test.serial("should return 400 when hash parameter is missing", async (t) => {
139
+ const deleteResponse = await axios.delete(baseUrl, {
140
+ validateStatus: (status) => true,
141
+ timeout: 10000,
142
+ });
143
+
144
+ t.is(deleteResponse.status, 400, "Should return 400 for missing parameters");
145
+ t.truthy(deleteResponse.data, "Should have error response");
146
+ });
147
+
148
+ test.serial("should delete file with both primary and backup storage", async (t) => {
149
+ const testContent = "test content for dual storage deletion";
150
+ const testHash = `test-dual-${uuidv4()}`;
151
+ const filePath = await createTestFile(testContent, "txt");
152
+ let uploadResponse;
153
+
154
+ try {
155
+ // Upload file with hash
156
+ uploadResponse = await uploadFile(filePath, null, testHash);
157
+ t.is(uploadResponse.status, 200, "Upload should succeed");
158
+
159
+ // Check if GCS backup was created
160
+ const hasGcsBackup = uploadResponse.data.gcs && uploadResponse.data.gcs.startsWith("gs://");
161
+
162
+ // Delete file by hash
163
+ const deleteResponse = await deleteFileByHash(testHash);
164
+ t.is(deleteResponse.status, 200, "Delete should succeed");
165
+ t.truthy(deleteResponse.data.deleted, "Should have deletion details");
166
+
167
+
168
+ // Should have at least primary storage deletion
169
+ const deletionDetails = deleteResponse.data.deleted.deleted;
170
+ t.true(Array.isArray(deletionDetails), "Deletion details should be an array");
171
+ t.true(deletionDetails.length >= 1, "Should have at least primary deletion result");
172
+ const primaryDeletion = deletionDetails.find(d => d.provider === 'primary');
173
+ t.truthy(primaryDeletion, "Should have primary storage deletion");
174
+
175
+ // If GCS backup existed, should also have backup deletion
176
+ if (hasGcsBackup) {
177
+ const backupDeletion = deletionDetails.find(d => d.provider === 'backup');
178
+ t.truthy(backupDeletion, "Should have backup storage deletion when GCS is configured");
179
+ }
180
+
181
+ // Verify hash is completely removed
182
+ const hashCheckAfter = await checkHashExists(testHash);
183
+ t.is(hashCheckAfter.status, 404, "Hash should not exist after deletion");
184
+
185
+ } finally {
186
+ fs.unlinkSync(filePath);
187
+ try {
188
+ await removeFromFileStoreMap(testHash);
189
+ } catch (e) {
190
+ // Ignore cleanup errors
191
+ }
192
+ }
193
+ });
194
+
195
+ test.serial("should handle malformed hash gracefully", async (t) => {
196
+ const malformedHashes = ["", " ", "null", "undefined", "{}"];
197
+
198
+ for (const badHash of malformedHashes) {
199
+ const deleteResponse = await deleteFileByHash(badHash);
200
+ t.true(
201
+ deleteResponse.status === 404 || deleteResponse.status === 400,
202
+ `Should return 404 or 400 for malformed hash: "${badHash}"`
203
+ );
204
+ }
205
+ });
206
+
207
+ test.serial("should prioritize requestId over hash when both provided", async (t) => {
208
+ const testContent = "test content for priority test";
209
+ const testHash = `test-priority-${uuidv4()}`;
210
+ const requestId = uuidv4();
211
+ const filePath = await createTestFile(testContent, "txt");
212
+ let uploadResponse;
213
+
214
+ try {
215
+ // Upload file with both hash and requestId
216
+ uploadResponse = await uploadFile(filePath, requestId, testHash);
217
+ t.is(uploadResponse.status, 200, "Upload should succeed");
218
+
219
+ // Delete using both hash and requestId - should delete by requestId (current behavior)
220
+ const deleteResponse = await axios.delete(`${baseUrl}?hash=${testHash}&requestId=${requestId}`, {
221
+ validateStatus: (status) => true,
222
+ timeout: 10000,
223
+ });
224
+
225
+ t.is(deleteResponse.status, 200, "Delete should succeed");
226
+ t.truthy(deleteResponse.data.body, "Should have deletion body");
227
+ t.true(Array.isArray(deleteResponse.data.body), "Deletion body should be array of deleted files");
228
+
229
+ // Verify hash is gone (because the file was deleted via requestId)
230
+ const hashCheckAfter = await checkHashExists(testHash);
231
+ t.is(hashCheckAfter.status, 404, "Hash should not exist after deletion");
232
+
233
+ } finally {
234
+ fs.unlinkSync(filePath);
235
+ try {
236
+ await removeFromFileStoreMap(testHash);
237
+ } catch (e) {
238
+ // Ignore cleanup errors
239
+ }
240
+ }
241
+ });
242
+
243
+ test.serial("should return proper response format for successful deletion", async (t) => {
244
+ const testContent = "test content for response format";
245
+ const testHash = `test-response-${uuidv4()}`;
246
+ const filePath = await createTestFile(testContent, "txt");
247
+ let uploadResponse;
248
+
249
+ try {
250
+ // Upload file with hash
251
+ uploadResponse = await uploadFile(filePath, null, testHash);
252
+ t.is(uploadResponse.status, 200, "Upload should succeed");
253
+
254
+ // Delete file by hash
255
+ const deleteResponse = await deleteFileByHash(testHash);
256
+ t.is(deleteResponse.status, 200, "Delete should succeed");
257
+
258
+ // Verify response structure
259
+ t.truthy(deleteResponse.data, "Should have response data");
260
+ t.truthy(deleteResponse.data.message, "Should have success message");
261
+ t.is(deleteResponse.data.deleted.hash, testHash, "Should include deleted hash");
262
+ t.truthy(deleteResponse.data.deleted.filename, "Should include filename");
263
+ t.truthy(deleteResponse.data.deleted.deleted, "Should include deletion details");
264
+ t.true(Array.isArray(deleteResponse.data.deleted.deleted), "Deletion details should be array");
265
+
266
+ } finally {
267
+ fs.unlinkSync(filePath);
268
+ try {
269
+ await removeFromFileStoreMap(testHash);
270
+ } catch (e) {
271
+ // Ignore cleanup errors
272
+ }
273
+ }
274
+ });
275
+
276
+ test.serial("should handle deletion when Redis is temporarily unavailable", async (t) => {
277
+ const testContent = "test content for Redis failure";
278
+ const testHash = `test-redis-fail-${uuidv4()}`;
279
+ const filePath = await createTestFile(testContent, "txt");
280
+ let uploadResponse;
281
+
282
+ try {
283
+ // Upload file with hash
284
+ uploadResponse = await uploadFile(filePath, null, testHash);
285
+ t.is(uploadResponse.status, 200, "Upload should succeed");
286
+
287
+ // Manually corrupt the Redis entry to simulate Redis issues
288
+ await setFileStoreMap(testHash, { corrupted: "data" });
289
+
290
+ // Delete file by hash - should handle Redis issues gracefully
291
+ const deleteResponse = await deleteFileByHash(testHash);
292
+
293
+ // The behavior may vary depending on how Redis failures are handled
294
+ // It should either succeed with a warning or fail gracefully
295
+ t.true(
296
+ deleteResponse.status === 200 || deleteResponse.status === 404 || deleteResponse.status === 500,
297
+ "Should handle Redis failure gracefully"
298
+ );
299
+
300
+ } finally {
301
+ fs.unlinkSync(filePath);
302
+ try {
303
+ await removeFromFileStoreMap(testHash);
304
+ } catch (e) {
305
+ // Ignore cleanup errors
306
+ }
307
+ }
308
+ });
309
+
310
+ test.serial("should delete file uploaded with different filename", async (t) => {
311
+ const testContent = "test content with special filename";
312
+ const testHash = `test-filename-${uuidv4()}`;
313
+ const specialFilename = "test file with spaces & symbols!@#.txt";
314
+ const filePath = await createTestFile(testContent, "txt");
315
+ let uploadResponse;
316
+
317
+ try {
318
+ // Upload file with special filename and hash
319
+ const form = new FormData();
320
+ form.append("file", fs.createReadStream(filePath), specialFilename);
321
+ form.append("hash", testHash);
322
+
323
+ uploadResponse = await axios.post(baseUrl, form, {
324
+ headers: form.getHeaders(),
325
+ validateStatus: (status) => true,
326
+ timeout: 15000,
327
+ });
328
+
329
+ t.is(uploadResponse.status, 200, "Upload should succeed");
330
+
331
+ // Delete file by hash
332
+ const deleteResponse = await deleteFileByHash(testHash);
333
+ t.is(deleteResponse.status, 200, "Delete should succeed");
334
+ t.truthy(deleteResponse.data.deleted.filename, "Should include original filename");
335
+
336
+ // Verify hash is gone
337
+ const hashCheckAfter = await checkHashExists(testHash);
338
+ t.is(hashCheckAfter.status, 404, "Hash should not exist after deletion");
339
+
340
+ } finally {
341
+ fs.unlinkSync(filePath);
342
+ try {
343
+ await removeFromFileStoreMap(testHash);
344
+ } catch (e) {
345
+ // Ignore cleanup errors
346
+ }
347
+ }
348
+ });
@@ -209,10 +209,31 @@ test("successfully downloads file from URL", async (t) => {
209
209
 
210
210
  // Test error handling for invalid URLs in download
211
211
  test("handles invalid URLs in download gracefully", async (t) => {
212
- const invalidUrl = "https://invalid-url-that-does-not-exist.com/test.mp3";
212
+ // Use a localhost URL with a port that's definitely not in use
213
+ const invalidUrl = "http://localhost:54321/nonexistent.mp3";
213
214
  const outputPath = join(os.tmpdir(), "should-not-exist.mp3");
214
215
 
215
- await t.throwsAsync(async () => downloadFile(invalidUrl, outputPath));
216
+ try {
217
+ await downloadFile(invalidUrl, outputPath);
218
+ t.fail("Expected downloadFile to throw an error for invalid URL");
219
+ } catch (error) {
220
+ t.truthy(error, "Should throw an error for invalid URL");
221
+ // Accept various network error types
222
+ const isValidError =
223
+ error.code === 'ENOTFOUND' ||
224
+ error.code === 'ECONNREFUSED' ||
225
+ error.code === 'ENETUNREACH' ||
226
+ error.code === 'ETIMEDOUT' ||
227
+ error.message.includes('ENOTFOUND') ||
228
+ error.message.includes('ECONNREFUSED') ||
229
+ error.message.includes('ENETUNREACH') ||
230
+ error.message.includes('ETIMEDOUT') ||
231
+ error.message.includes('getaddrinfo') ||
232
+ error.message.includes('Network Error') ||
233
+ error.message.includes('Request failed');
234
+
235
+ t.true(isValidError, `Expected network error but got: ${error.code} - ${error.message}`);
236
+ }
216
237
  });
217
238
 
218
239
  // Helper to format duration nicely
@@ -10,6 +10,9 @@ import { gcs } from "../src/blobHandler.js";
10
10
  import {
11
11
  cleanupHashAndFile,
12
12
  getFolderNameFromUrl,
13
+ startTestServer,
14
+ stopTestServer,
15
+ setupTestDirectory,
13
16
  } from "./testUtils.helper.js";
14
17
  import XLSX from "xlsx";
15
18
 
@@ -78,17 +81,20 @@ async function fetchFileContent(url) {
78
81
  return Buffer.from(response.data);
79
82
  }
80
83
 
81
- // Setup: Create test directory
84
+ // Setup: Create test directory and start server
82
85
  test.before(async (t) => {
83
- const testDir = path.join(__dirname, "test-files");
84
- await fs.promises.mkdir(testDir, { recursive: true });
85
- t.context = { testDir };
86
+ await startTestServer();
87
+ await setupTestDirectory(t);
86
88
  });
87
89
 
88
90
  // Cleanup
89
91
  test.after.always(async (t) => {
92
+ await stopTestServer();
93
+
90
94
  // Clean up test directory
91
- await fs.promises.rm(t.context.testDir, { recursive: true, force: true });
95
+ if (t.context?.testDir) {
96
+ await fs.promises.rm(t.context.testDir, { recursive: true, force: true });
97
+ }
92
98
 
93
99
  // Clean up any remaining files in the files directory
94
100
  const filesDir = path.join(__dirname, "..", "files");
@@ -6,8 +6,9 @@ import { v4 as uuidv4 } from "uuid";
6
6
  import axios from "axios";
7
7
  import FormData from "form-data";
8
8
  import XLSX from "xlsx";
9
+ import nock from "nock";
9
10
  import { port } from "../src/start.js";
10
- import { cleanupHashAndFile, createTestMediaFile } from "./testUtils.helper.js";
11
+ import { cleanupHashAndFile, createTestMediaFile, startTestServer, stopTestServer, setupTestDirectory } from "./testUtils.helper.js";
11
12
 
12
13
  const __filename = fileURLToPath(import.meta.url);
13
14
  const __dirname = path.dirname(__filename);
@@ -49,11 +50,15 @@ async function uploadFile(filePath, requestId = null, hash = null) {
49
50
  return response;
50
51
  }
51
52
 
52
- // Setup: Create test directory
53
+ // Setup: Create test directory and start server
53
54
  test.before(async (t) => {
54
- const testDir = path.join(__dirname, "test-files");
55
- await fs.promises.mkdir(testDir, { recursive: true });
56
- t.context = { testDir };
55
+ await startTestServer();
56
+ await setupTestDirectory(t);
57
+ });
58
+
59
+ // Clean up server after tests
60
+ test.after.always(async () => {
61
+ await stopTestServer();
57
62
  });
58
63
 
59
64
  // Test: Document processing with save=true
@@ -247,28 +252,57 @@ test.serial("should fetch remote file", async (t) => {
247
252
  test.serial("should cache remote files in Redis", async (t) => {
248
253
  const requestId = uuidv4();
249
254
  const hash = "test-cache-" + uuidv4();
255
+ const testContent = "This is test content for caching";
256
+ const testUrl = "https://test-server.example.com/test.txt";
257
+
258
+ // Mock the external HTTP requests (HEAD for validation and GET for fetching)
259
+ // We need multiple HEAD mocks because each request validates the URL
260
+ const scope = nock("https://test-server.example.com")
261
+ .persist() // Allow the mocks to be reused
262
+ .head("/test.txt")
263
+ .reply(200, "", {
264
+ "Content-Type": "text/plain",
265
+ "Content-Length": testContent.length.toString(),
266
+ })
267
+ .get("/test.txt")
268
+ .reply(200, testContent, {
269
+ "Content-Type": "text/plain",
270
+ "Content-Length": testContent.length.toString(),
271
+ });
250
272
 
251
- // First request should cache the file
252
- const firstResponse = await axios.get(baseUrl, {
253
- params: {
254
- fetch: "https://example.com/test.txt",
255
- requestId,
256
- hash,
257
- timeout: 10000,
258
- },
259
- validateStatus: (status) => true,
260
- });
273
+ try {
274
+ // First request should fetch and cache the file with hash
275
+ const firstResponse = await axios.get(baseUrl, {
276
+ params: {
277
+ fetch: testUrl,
278
+ requestId,
279
+ hash,
280
+ timeout: 10000,
281
+ },
282
+ validateStatus: (status) => true,
283
+ });
261
284
 
262
- // Second request should return cached result
263
- const secondResponse = await axios.get(baseUrl, {
264
- params: {
265
- hash,
266
- checkHash: true,
267
- },
268
- validateStatus: (status) => true,
269
- });
285
+ t.is(firstResponse.status, 200, "Should successfully fetch and cache remote file");
286
+ t.truthy(firstResponse.data.url, "Should return file URL");
270
287
 
271
- t.is(secondResponse.status, 404, "Should return 404 for invalid URL");
288
+ // Second request should return cached result using hash
289
+ const secondResponse = await axios.get(baseUrl, {
290
+ params: {
291
+ hash,
292
+ checkHash: true,
293
+ },
294
+ validateStatus: (status) => true,
295
+ });
296
+
297
+ t.is(secondResponse.status, 200, "Should return cached file from Redis");
298
+ t.truthy(secondResponse.data.url, "Should return cached file URL");
299
+
300
+ // Cleanup
301
+ await cleanupHashAndFile(hash, secondResponse.data.url, baseUrl);
302
+ } finally {
303
+ // Clean up nock
304
+ nock.cleanAll();
305
+ }
272
306
  });
273
307
 
274
308
  // Test: Error cases for invalid URLs
@@ -9,6 +9,9 @@ import { port } from "../src/start.js";
9
9
  import {
10
10
  cleanupHashAndFile,
11
11
  getFolderNameFromUrl,
12
+ startTestServer,
13
+ stopTestServer,
14
+ setupTestDirectory,
12
15
  } from "./testUtils.helper.js";
13
16
 
14
17
  const __filename = fileURLToPath(import.meta.url);
@@ -55,11 +58,15 @@ async function uploadFile(filePath, requestId = null, hash = null) {
55
58
  return response;
56
59
  }
57
60
 
58
- // Setup: Create test directory
61
+ // Setup: Create test directory and start server
59
62
  test.before(async (t) => {
60
- const testDir = path.join(__dirname, "test-files");
61
- await fs.promises.mkdir(testDir, { recursive: true });
62
- t.context = { testDir };
63
+ await startTestServer();
64
+ await setupTestDirectory(t);
65
+ });
66
+
67
+ // Clean up server after tests
68
+ test.after.always(async () => {
69
+ await stopTestServer();
63
70
  });
64
71
 
65
72
  // Test: Upload with hash and verify Redis storage