@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,225 @@
1
+ import test from 'ava';
2
+ import axios from 'axios';
3
+ import FormData from 'form-data';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import path from 'path';
6
+ import fs from 'fs';
7
+ import XLSX from 'xlsx';
8
+ import os from 'os';
9
+ import { fileURLToPath } from 'url';
10
+ import {
11
+ startTestServer,
12
+ stopTestServer,
13
+ cleanupHashAndFile
14
+ } from './testUtils.helper.js';
15
+ import { port } from '../src/start.js';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+ const baseUrl = `http://localhost:${port}/api/CortexFileHandler`;
20
+
21
+ // Helper function to extract file extension from URL
22
+ function getFileExtensionFromUrl(url) {
23
+ try {
24
+ const urlObj = new URL(url);
25
+ const pathname = urlObj.pathname;
26
+ const lastDotIndex = pathname.lastIndexOf('.');
27
+ if (lastDotIndex !== -1 && lastDotIndex < pathname.length - 1) {
28
+ return pathname.substring(lastDotIndex);
29
+ }
30
+ } catch (error) {
31
+ console.log("Error parsing extension from URL:", error);
32
+ }
33
+ return null;
34
+ }
35
+
36
+ // Helper function to upload files
37
+ async function uploadFile(filePath, requestId = null, hash = null) {
38
+ const form = new FormData();
39
+ form.append('file', fs.createReadStream(filePath));
40
+
41
+ if (requestId) {
42
+ form.append('requestId', requestId);
43
+ }
44
+ if (hash) {
45
+ form.append('hash', hash);
46
+ }
47
+
48
+ const response = await axios.post(baseUrl, form, {
49
+ headers: form.getHeaders(),
50
+ validateStatus: (status) => true,
51
+ timeout: 60000,
52
+ });
53
+
54
+ return response;
55
+ }
56
+
57
+ // Test setup
58
+ test.before(async (t) => {
59
+ await startTestServer();
60
+ // Create a test directory for temporary files
61
+ t.context.testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-shortlived-conversion-'));
62
+ });
63
+
64
+ test.after(async (t) => {
65
+ // Clean up test directory
66
+ if (t.context.testDir && fs.existsSync(t.context.testDir)) {
67
+ fs.rmSync(t.context.testDir, { recursive: true, force: true });
68
+ }
69
+ await stopTestServer();
70
+ });
71
+
72
+ /**
73
+ * Test that short-lived URLs are generated from converted files, not original files
74
+ */
75
+ test.serial(
76
+ "checkHash should generate shortLivedUrl from converted file URL for Excel files",
77
+ async (t) => {
78
+ // Create a minimal XLSX workbook in-memory
79
+ const workbook = XLSX.utils.book_new();
80
+ const worksheet = XLSX.utils.aoa_to_sheet([
81
+ ["Product", "Price", "Quantity"],
82
+ ["Apple", 1.50, 100],
83
+ ["Banana", 0.75, 200],
84
+ ["Cherry", 2.00, 50],
85
+ ]);
86
+ XLSX.utils.book_append_sheet(workbook, worksheet, "Products");
87
+
88
+ // Write it to a temp file
89
+ const filePath = path.join(t.context.testDir, `${uuidv4()}.xlsx`);
90
+ XLSX.writeFile(workbook, filePath);
91
+
92
+ const hash = `test-shortlived-conversion-${uuidv4()}`;
93
+ let uploadedUrl;
94
+ let convertedUrl;
95
+
96
+ try {
97
+ // 1. Upload the XLSX file with hash (conversion should run automatically)
98
+ const uploadResponse = await uploadFile(filePath, null, hash);
99
+ t.is(uploadResponse.status, 200, "Upload should succeed");
100
+ t.truthy(uploadResponse.data.converted, "Upload response must contain converted info");
101
+ t.truthy(uploadResponse.data.converted.url, "Converted URL should be present");
102
+
103
+ uploadedUrl = uploadResponse.data.url;
104
+ convertedUrl = uploadResponse.data.converted.url;
105
+
106
+ // Verify the original file is .xlsx and converted file is .csv
107
+ t.is(getFileExtensionFromUrl(uploadedUrl), '.xlsx', "Original URL should point to .xlsx file");
108
+ t.is(getFileExtensionFromUrl(convertedUrl), '.csv', "Converted URL should point to .csv file");
109
+
110
+ // 2. Give Redis a moment to persist
111
+ await new Promise((resolve) => setTimeout(resolve, 2000));
112
+
113
+ // 3. Check hash to get short-lived URL
114
+ const checkResponse = await axios.get(baseUrl, {
115
+ params: { hash, checkHash: true },
116
+ validateStatus: (status) => true,
117
+ timeout: 30000,
118
+ });
119
+
120
+ t.is(checkResponse.status, 200, "checkHash should succeed");
121
+ t.truthy(checkResponse.data.shortLivedUrl, "Response should include shortLivedUrl");
122
+ t.truthy(checkResponse.data.converted, "Response should include converted info");
123
+
124
+ // 4. CRITICAL TEST: Verify short-lived URL is based on converted file, not original
125
+ const shortLivedUrlBase = checkResponse.data.shortLivedUrl.split('?')[0]; // Remove query params
126
+ const convertedUrlBase = checkResponse.data.converted.url.split('?')[0]; // Remove query params
127
+ const originalUrlBase = checkResponse.data.url.split('?')[0]; // Remove query params
128
+
129
+ // The short-lived URL should be based on the converted file URL
130
+ t.is(
131
+ shortLivedUrlBase,
132
+ convertedUrlBase,
133
+ "Short-lived URL should be based on converted file URL (.csv), not original file URL (.xlsx)"
134
+ );
135
+
136
+ // Double-check: short-lived URL should NOT be based on original file
137
+ t.not(
138
+ shortLivedUrlBase,
139
+ originalUrlBase,
140
+ "Short-lived URL should NOT be based on original file URL (.xlsx)"
141
+ );
142
+
143
+ // Verify the short-lived URL points to a CSV file, not Excel
144
+ t.true(
145
+ checkResponse.data.shortLivedUrl.includes('.csv'),
146
+ "Short-lived URL should point to .csv file (converted), not .xlsx file (original)"
147
+ );
148
+
149
+ t.false(
150
+ checkResponse.data.shortLivedUrl.includes('.xlsx'),
151
+ "Short-lived URL should NOT contain .xlsx extension"
152
+ );
153
+
154
+ } finally {
155
+ // Clean up
156
+ fs.unlinkSync(filePath);
157
+ await cleanupHashAndFile(hash, uploadedUrl, baseUrl);
158
+ if (convertedUrl) {
159
+ await cleanupHashAndFile(null, convertedUrl, baseUrl);
160
+ }
161
+ }
162
+ },
163
+ );
164
+
165
+ /**
166
+ * Test that short-lived URLs fallback to original files when no conversion exists
167
+ */
168
+ test.serial(
169
+ "checkHash should generate shortLivedUrl from original file URL when no conversion exists",
170
+ async (t) => {
171
+ // Create a simple text file (no conversion needed)
172
+ const testContent = "This is a simple text file that doesn't need conversion.";
173
+ const filePath = path.join(t.context.testDir, `${uuidv4()}.txt`);
174
+ fs.writeFileSync(filePath, testContent);
175
+
176
+ const hash = `test-shortlived-original-${uuidv4()}`;
177
+ let uploadedUrl;
178
+
179
+ try {
180
+ // 1. Upload the text file with hash (no conversion should occur)
181
+ const uploadResponse = await uploadFile(filePath, null, hash);
182
+ t.is(uploadResponse.status, 200, "Upload should succeed");
183
+ t.falsy(uploadResponse.data.converted, "Upload response should NOT contain converted info for .txt files");
184
+
185
+ uploadedUrl = uploadResponse.data.url;
186
+ t.is(getFileExtensionFromUrl(uploadedUrl), '.txt', "Original URL should point to .txt file");
187
+
188
+ // 2. Give Redis a moment to persist
189
+ await new Promise((resolve) => setTimeout(resolve, 1000));
190
+
191
+ // 3. Check hash to get short-lived URL
192
+ const checkResponse = await axios.get(baseUrl, {
193
+ params: { hash, checkHash: true },
194
+ validateStatus: (status) => true,
195
+ timeout: 30000,
196
+ });
197
+
198
+ t.is(checkResponse.status, 200, "checkHash should succeed");
199
+ t.truthy(checkResponse.data.shortLivedUrl, "Response should include shortLivedUrl");
200
+ t.falsy(checkResponse.data.converted, "Response should NOT include converted info for .txt files");
201
+
202
+ // 4. CRITICAL TEST: Verify short-lived URL is based on original file when no conversion exists
203
+ const shortLivedUrlBase = checkResponse.data.shortLivedUrl.split('?')[0]; // Remove query params
204
+ const originalUrlBase = checkResponse.data.url.split('?')[0]; // Remove query params
205
+
206
+ // The short-lived URL should be based on the original file URL
207
+ t.is(
208
+ shortLivedUrlBase,
209
+ originalUrlBase,
210
+ "Short-lived URL should be based on original file URL when no conversion exists"
211
+ );
212
+
213
+ // Verify the short-lived URL points to the text file
214
+ t.true(
215
+ checkResponse.data.shortLivedUrl.includes('.txt'),
216
+ "Short-lived URL should point to .txt file (original)"
217
+ );
218
+
219
+ } finally {
220
+ // Clean up
221
+ fs.unlinkSync(filePath);
222
+ await cleanupHashAndFile(hash, uploadedUrl, baseUrl);
223
+ }
224
+ },
225
+ );
@@ -15,6 +15,8 @@ import { port, publicFolder, ipAddress } from "../src/start.js";
15
15
  import {
16
16
  cleanupHashAndFile,
17
17
  getFolderNameFromUrl,
18
+ startTestServer,
19
+ stopTestServer,
18
20
  } from "./testUtils.helper.js";
19
21
 
20
22
  // Add these helper functions at the top after imports
@@ -103,18 +105,12 @@ async function uploadFile(file, requestId, hash = null) {
103
105
 
104
106
  // Ensure server is ready before tests
105
107
  test.before(async (t) => {
106
- // Wait for server to be ready
107
- await new Promise((resolve) => setTimeout(resolve, 1000));
108
+ await startTestServer();
109
+ });
108
110
 
109
- // Verify server is responding
110
- try {
111
- await axios.get(`http://localhost:${port}/files`);
112
- } catch (error) {
113
- // 404 is fine, it means server is running but directory is empty
114
- if (error.response?.status !== 404) {
115
- throw new Error("Server not ready");
116
- }
117
- }
111
+ // Clean up server after tests
112
+ test.after.always(async () => {
113
+ await stopTestServer();
118
114
  });
119
115
 
120
116
  // Configuration Tests
@@ -365,7 +361,7 @@ test.serial("should validate requestId for delete operation", async (t) => {
365
361
  t.is(response.status, 400, "Should return 400 for missing requestId");
366
362
  t.is(
367
363
  response.data,
368
- "Please pass a requestId on the query string",
364
+ "Please pass either a requestId or hash on the query string",
369
365
  "Should return proper error message",
370
366
  );
371
367
  });
@@ -103,3 +103,83 @@ test("should return null for invalid gcs credentials", (t) => {
103
103
  // Cleanup
104
104
  delete process.env.GCP_SERVICE_ACCOUNT_KEY;
105
105
  });
106
+
107
+ // Container-specific tests
108
+ test("should get azure provider with default container when no container specified", async (t) => {
109
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
110
+ t.pass("Skipping test - Azure not configured");
111
+ return;
112
+ }
113
+
114
+ const factory = new StorageFactory();
115
+ const provider = await factory.getAzureProvider();
116
+ t.truthy(provider);
117
+ t.truthy(provider.containerName);
118
+ });
119
+
120
+ test("should get azure provider with specific container name", async (t) => {
121
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
122
+ t.pass("Skipping test - Azure not configured");
123
+ return;
124
+ }
125
+
126
+ const factory = new StorageFactory();
127
+
128
+ // Mock the blobHandler constants for this test
129
+ const mockConstants = {
130
+ AZURE_STORAGE_CONTAINER_NAMES: ["container1", "container2", "container3"],
131
+ DEFAULT_AZURE_STORAGE_CONTAINER_NAME: "container1",
132
+ isValidContainerName: (name) => ["container1", "container2", "container3"].includes(name)
133
+ };
134
+
135
+ // Test with valid container name
136
+ const provider = await factory.getAzureProvider("container2");
137
+ t.truthy(provider);
138
+ t.is(provider.containerName, "container2");
139
+ });
140
+
141
+ test("should throw error for invalid container name", async (t) => {
142
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
143
+ t.pass("Skipping test - Azure not configured");
144
+ return;
145
+ }
146
+
147
+ const factory = new StorageFactory();
148
+
149
+ // Test with invalid container name
150
+ await t.throwsAsync(
151
+ () => factory.getAzureProvider("invalid-container"),
152
+ { message: /Invalid container name/ }
153
+ );
154
+ });
155
+
156
+ test("should cache providers by container name", async (t) => {
157
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
158
+ t.pass("Skipping test - Azure not configured");
159
+ return;
160
+ }
161
+
162
+ const factory = new StorageFactory();
163
+
164
+ // Mock valid container names for testing
165
+ const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
166
+ process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
167
+
168
+ try {
169
+ const provider1 = await factory.getAzureProvider("test1");
170
+ const provider2 = await factory.getAzureProvider("test1");
171
+ const provider3 = await factory.getAzureProvider("test2");
172
+
173
+ // Same container should return same instance
174
+ t.is(provider1, provider2);
175
+ // Different container should return different instance
176
+ t.not(provider1, provider3);
177
+ } finally {
178
+ // Restore original env
179
+ if (originalEnv) {
180
+ process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
181
+ } else {
182
+ delete process.env.AZURE_STORAGE_CONTAINER_NAME;
183
+ }
184
+ }
185
+ });