@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,229 @@
1
+ import test from "ava";
2
+ import {
3
+ AZURE_STORAGE_CONTAINER_NAMES,
4
+ DEFAULT_AZURE_STORAGE_CONTAINER_NAME,
5
+ isValidContainerName
6
+ } from "../src/blobHandler.js";
7
+
8
+ // Mock environment variables for testing
9
+ const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
10
+
11
+ test.beforeEach(() => {
12
+ // Reset to original environment
13
+ process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
14
+ });
15
+
16
+ test.afterEach(() => {
17
+ // Restore original environment
18
+ process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
19
+ });
20
+
21
+ test("parseContainerNames should handle single container name", (t) => {
22
+ // Set environment variable for single container
23
+ process.env.AZURE_STORAGE_CONTAINER_NAME = "single-container";
24
+
25
+ // We need to reload the module to pick up the new environment variable
26
+ // Since we can't easily reload ES modules, we'll test the logic directly
27
+ const parseContainerNames = () => {
28
+ const containerStr = process.env.AZURE_STORAGE_CONTAINER_NAME || "whispertempfiles";
29
+ return containerStr.split(',').map(name => name.trim());
30
+ };
31
+
32
+ const result = parseContainerNames();
33
+
34
+ t.is(result.length, 1);
35
+ t.is(result[0], "single-container");
36
+ });
37
+
38
+ test("parseContainerNames should handle comma-separated container names", (t) => {
39
+ // Set environment variable for multiple containers
40
+ process.env.AZURE_STORAGE_CONTAINER_NAME = "container1,container2,container3";
41
+
42
+ const parseContainerNames = () => {
43
+ const containerStr = process.env.AZURE_STORAGE_CONTAINER_NAME || "whispertempfiles";
44
+ return containerStr.split(',').map(name => name.trim());
45
+ };
46
+
47
+ const result = parseContainerNames();
48
+
49
+ t.is(result.length, 3);
50
+ t.is(result[0], "container1");
51
+ t.is(result[1], "container2");
52
+ t.is(result[2], "container3");
53
+ });
54
+
55
+ test("parseContainerNames should handle comma-separated names with whitespace", (t) => {
56
+ // Set environment variable with spaces around commas
57
+ process.env.AZURE_STORAGE_CONTAINER_NAME = " container1 , container2 , container3 ";
58
+
59
+ const parseContainerNames = () => {
60
+ const containerStr = process.env.AZURE_STORAGE_CONTAINER_NAME || "whispertempfiles";
61
+ return containerStr.split(',').map(name => name.trim());
62
+ };
63
+
64
+ const result = parseContainerNames();
65
+
66
+ t.is(result.length, 3);
67
+ t.is(result[0], "container1");
68
+ t.is(result[1], "container2");
69
+ t.is(result[2], "container3");
70
+ });
71
+
72
+ test("parseContainerNames should default to whispertempfiles when env var is not set", (t) => {
73
+ // Unset environment variable
74
+ delete process.env.AZURE_STORAGE_CONTAINER_NAME;
75
+
76
+ const parseContainerNames = () => {
77
+ const containerStr = process.env.AZURE_STORAGE_CONTAINER_NAME || "whispertempfiles";
78
+ return containerStr.split(',').map(name => name.trim());
79
+ };
80
+
81
+ const result = parseContainerNames();
82
+
83
+ t.is(result.length, 1);
84
+ t.is(result[0], "whispertempfiles");
85
+ });
86
+
87
+ test("parseContainerNames should handle empty string", (t) => {
88
+ // Set environment variable to empty string
89
+ process.env.AZURE_STORAGE_CONTAINER_NAME = "";
90
+
91
+ const parseContainerNames = () => {
92
+ const containerStr = process.env.AZURE_STORAGE_CONTAINER_NAME || "whispertempfiles";
93
+ return containerStr.split(',').map(name => name.trim());
94
+ };
95
+
96
+ const result = parseContainerNames();
97
+
98
+ t.is(result.length, 1);
99
+ t.is(result[0], "whispertempfiles");
100
+ });
101
+
102
+ test("parseContainerNames should handle only commas", (t) => {
103
+ // Set environment variable to only commas
104
+ process.env.AZURE_STORAGE_CONTAINER_NAME = ",,,";
105
+
106
+ const parseContainerNames = () => {
107
+ const containerStr = process.env.AZURE_STORAGE_CONTAINER_NAME || "whispertempfiles";
108
+ return containerStr.split(',').map(name => name.trim());
109
+ };
110
+
111
+ const result = parseContainerNames();
112
+
113
+ // Should result in 4 empty strings after trimming
114
+ t.is(result.length, 4);
115
+ t.is(result[0], "");
116
+ t.is(result[1], "");
117
+ t.is(result[2], "");
118
+ t.is(result[3], "");
119
+ });
120
+
121
+ test("DEFAULT_AZURE_STORAGE_CONTAINER_NAME should be the first container in the list", (t) => {
122
+ // Test with current module exports (these are loaded at import time)
123
+ // The default should be the first item in the array
124
+ t.is(DEFAULT_AZURE_STORAGE_CONTAINER_NAME, AZURE_STORAGE_CONTAINER_NAMES[0]);
125
+
126
+ // Additional validation that it's a non-empty string
127
+ t.truthy(DEFAULT_AZURE_STORAGE_CONTAINER_NAME);
128
+ t.is(typeof DEFAULT_AZURE_STORAGE_CONTAINER_NAME, 'string');
129
+ t.true(DEFAULT_AZURE_STORAGE_CONTAINER_NAME.length > 0);
130
+ });
131
+
132
+ test("isValidContainerName should return true for valid container names", (t) => {
133
+ // Test with each container name from the current configuration
134
+ AZURE_STORAGE_CONTAINER_NAMES.forEach(containerName => {
135
+ t.true(isValidContainerName(containerName), `Container name '${containerName}' should be valid`);
136
+ });
137
+ });
138
+
139
+ test("isValidContainerName should return false for invalid container names", (t) => {
140
+ const invalidNames = [
141
+ "invalid-container",
142
+ "not-in-list",
143
+ "",
144
+ null,
145
+ undefined,
146
+ "container-that-does-not-exist"
147
+ ];
148
+
149
+ invalidNames.forEach(containerName => {
150
+ t.false(isValidContainerName(containerName), `Container name '${containerName}' should be invalid`);
151
+ });
152
+ });
153
+
154
+ test("isValidContainerName should handle edge cases", (t) => {
155
+ // Test with various edge cases
156
+ t.false(isValidContainerName(null));
157
+ t.false(isValidContainerName(undefined));
158
+ t.false(isValidContainerName(""));
159
+ t.false(isValidContainerName(" ")); // whitespace only
160
+ t.false(isValidContainerName(123)); // number
161
+ t.false(isValidContainerName({})); // object
162
+ t.false(isValidContainerName([])); // array
163
+ });
164
+
165
+ test("container configuration should have at least one container", (t) => {
166
+ t.true(AZURE_STORAGE_CONTAINER_NAMES.length > 0, "Should have at least one container configured");
167
+ t.truthy(AZURE_STORAGE_CONTAINER_NAMES[0], "First container should not be empty");
168
+ });
169
+
170
+ test("all configured container names should be non-empty strings", (t) => {
171
+ AZURE_STORAGE_CONTAINER_NAMES.forEach((containerName, index) => {
172
+ t.is(typeof containerName, 'string', `Container at index ${index} should be a string`);
173
+ t.true(containerName.length > 0, `Container at index ${index} should not be empty`);
174
+ t.true(containerName.trim().length > 0, `Container at index ${index} should not be only whitespace`);
175
+ });
176
+ });
177
+
178
+ test("container names should not contain duplicates", (t) => {
179
+ const uniqueNames = new Set(AZURE_STORAGE_CONTAINER_NAMES);
180
+ t.is(uniqueNames.size, AZURE_STORAGE_CONTAINER_NAMES.length, "Container names should be unique");
181
+ });
182
+
183
+ // Integration test with actual environment simulation
184
+ test("complete container parsing workflow", (t) => {
185
+ const testCases = [
186
+ {
187
+ env: "single",
188
+ expected: ["single"],
189
+ description: "single container name"
190
+ },
191
+ {
192
+ env: "first,second",
193
+ expected: ["first", "second"],
194
+ description: "two container names"
195
+ },
196
+ {
197
+ env: "one, two, three",
198
+ expected: ["one", "two", "three"],
199
+ description: "three container names with spaces"
200
+ },
201
+ {
202
+ env: " leading , trailing ",
203
+ expected: ["leading", "trailing"],
204
+ description: "names with leading/trailing spaces"
205
+ }
206
+ ];
207
+
208
+ testCases.forEach(({ env, expected, description }) => {
209
+ const parseContainerNames = () => {
210
+ return env.split(',').map(name => name.trim());
211
+ };
212
+
213
+ const containerNames = parseContainerNames();
214
+ const defaultContainer = containerNames[0];
215
+
216
+ // Test parsing
217
+ t.deepEqual(containerNames, expected, `Parsing should work for ${description}`);
218
+
219
+ // Test default is first
220
+ t.is(defaultContainer, expected[0], `Default should be first container for ${description}`);
221
+
222
+ // Test validation
223
+ expected.forEach(name => {
224
+ t.true(containerNames.includes(name), `${name} should be valid for ${description}`);
225
+ });
226
+
227
+ t.false(containerNames.includes("invalid"), `Invalid name should not be valid for ${description}`);
228
+ });
229
+ });
@@ -0,0 +1,392 @@
1
+ import test from "ava";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import os from "os";
5
+ import { fileURLToPath } from "url";
6
+ import { v4 as uuidv4 } from "uuid";
7
+ import axios from "axios";
8
+ import FormData from "form-data";
9
+ import { port } from "../src/start.js";
10
+ import {
11
+ uploadBlob,
12
+ isValidContainerName,
13
+ AZURE_STORAGE_CONTAINER_NAMES,
14
+ DEFAULT_AZURE_STORAGE_CONTAINER_NAME,
15
+ } from "../src/blobHandler.js";
16
+ import CortexFileHandler from "../src/index.js";
17
+ import {
18
+ startTestServer,
19
+ stopTestServer,
20
+ setupTestDirectory,
21
+ cleanupHashAndFile,
22
+ getFolderNameFromUrl,
23
+ } from "./testUtils.helper.js";
24
+
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = path.dirname(__filename);
27
+ const baseUrl = `http://localhost:${port}/api/CortexFileHandler`;
28
+
29
+ // Helper function to create test files
30
+ async function createTestFile(content, extension) {
31
+ const testDir = path.join(__dirname, "test-files");
32
+ if (!fs.existsSync(testDir)) {
33
+ fs.mkdirSync(testDir, { recursive: true });
34
+ }
35
+ const filename = path.join(testDir, `${uuidv4()}.${extension}`);
36
+ fs.writeFileSync(filename, content);
37
+ return filename;
38
+ }
39
+
40
+ // Mock context for testing
41
+ const mockContext = {
42
+ log: (message) => console.log(`[TEST] ${message}`),
43
+ res: null,
44
+ };
45
+
46
+ // Setup: Create test directory and start server
47
+ test.before(async (t) => {
48
+ await startTestServer();
49
+ await setupTestDirectory(t);
50
+ });
51
+
52
+ // Cleanup
53
+ test.after.always(async (t) => {
54
+ await stopTestServer();
55
+
56
+ // Clean up test directory
57
+ if (t.context?.testDir) {
58
+ await fs.promises.rm(t.context.testDir, { recursive: true, force: true });
59
+ }
60
+
61
+ // Clean up any remaining files in the test-files directory
62
+ const testFilesDir = path.join(__dirname, "test-files");
63
+ if (fs.existsSync(testFilesDir)) {
64
+ try {
65
+ await fs.promises.rm(testFilesDir, { recursive: true, force: true });
66
+ } catch (error) {
67
+ console.log("Error cleaning test files:", error);
68
+ }
69
+ }
70
+ });
71
+
72
+ // Test container parameter validation
73
+ test("should validate container names correctly", (t) => {
74
+ // Test with valid container names from configuration
75
+ AZURE_STORAGE_CONTAINER_NAMES.forEach(containerName => {
76
+ t.true(isValidContainerName(containerName), `${containerName} should be valid`);
77
+ });
78
+
79
+ // Test with invalid container names
80
+ const invalidNames = ["invalid-container", "", null, undefined, "nonexistent"];
81
+ invalidNames.forEach(name => {
82
+ t.false(isValidContainerName(name), `${name} should be invalid`);
83
+ });
84
+ });
85
+
86
+ // Test uploadBlob function with container parameter
87
+ test("uploadBlob should accept and use container parameter from function parameter", async (t) => {
88
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
89
+ t.pass("Skipping test - Azure not configured");
90
+ return;
91
+ }
92
+
93
+ // Create a test file
94
+ const testFile = await createTestFile("test content", "txt");
95
+ const testStream = fs.createReadStream(testFile);
96
+
97
+ // Mock request with container parameter in function call
98
+ const mockReq = {
99
+ headers: { "content-type": "application/octet-stream" },
100
+ };
101
+
102
+ const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
103
+ process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
104
+
105
+ try {
106
+ // Call uploadBlob with container parameter
107
+ const result = await uploadBlob(
108
+ mockContext,
109
+ mockReq,
110
+ false, // saveToLocal
111
+ testFile, // filePath
112
+ null, // hash
113
+ "test2" // container parameter
114
+ );
115
+
116
+ t.truthy(result);
117
+ t.truthy(result.url || mockContext.res?.body?.url);
118
+
119
+ // Cleanup
120
+ const uploadedUrl = result.url || mockContext.res?.body?.url;
121
+ if (uploadedUrl) {
122
+ const folderName = getFolderNameFromUrl(uploadedUrl);
123
+ await cleanupHashAndFile(null, uploadedUrl, baseUrl);
124
+ }
125
+ } finally {
126
+ // Restore environment
127
+ if (originalEnv) {
128
+ process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
129
+ } else {
130
+ delete process.env.AZURE_STORAGE_CONTAINER_NAME;
131
+ }
132
+
133
+ // Cleanup test file
134
+ if (fs.existsSync(testFile)) {
135
+ fs.unlinkSync(testFile);
136
+ }
137
+ }
138
+ });
139
+
140
+ // Test uploadBlob function with container parameter in form data
141
+ test("uploadBlob should accept container parameter from form data", async (t) => {
142
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
143
+ t.pass("Skipping test - Azure not configured");
144
+ return;
145
+ }
146
+
147
+ const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
148
+ process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
149
+
150
+ try {
151
+ // Create a test file
152
+ const testContent = "test content for form data";
153
+ const testFile = await createTestFile(testContent, "txt");
154
+
155
+ // Create form data with container parameter
156
+ const form = new FormData();
157
+ form.append("file", fs.createReadStream(testFile), "test.txt");
158
+ form.append("container", "test3");
159
+
160
+ const response = await axios.post(baseUrl, form, {
161
+ headers: {
162
+ ...form.getHeaders(),
163
+ "Content-Type": "multipart/form-data",
164
+ },
165
+ validateStatus: (status) => true,
166
+ timeout: 30000,
167
+ });
168
+
169
+ t.is(response.status, 200);
170
+ t.truthy(response.data.url);
171
+
172
+ // Cleanup
173
+ await cleanupHashAndFile(null, response.data.url, baseUrl);
174
+ } finally {
175
+ // Restore environment
176
+ if (originalEnv) {
177
+ process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
178
+ } else {
179
+ delete process.env.AZURE_STORAGE_CONTAINER_NAME;
180
+ }
181
+ }
182
+ });
183
+
184
+ // Test invalid container name in form data
185
+ test("uploadBlob should reject invalid container names in form data", async (t) => {
186
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
187
+ t.pass("Skipping test - Azure not configured");
188
+ return;
189
+ }
190
+
191
+ // Create a test file
192
+ const testContent = "test content";
193
+ const testFile = await createTestFile(testContent, "txt");
194
+
195
+ try {
196
+ // Create form data with invalid container parameter
197
+ const form = new FormData();
198
+ form.append("file", fs.createReadStream(testFile), "test.txt");
199
+ form.append("container", "invalid-container-name");
200
+
201
+ const response = await axios.post(baseUrl, form, {
202
+ headers: {
203
+ ...form.getHeaders(),
204
+ "Content-Type": "multipart/form-data",
205
+ },
206
+ validateStatus: (status) => true,
207
+ timeout: 30000,
208
+ });
209
+
210
+ t.is(response.status, 400);
211
+ t.truthy(response.data);
212
+ t.true(
213
+ response.data.includes("Invalid container name")
214
+ );
215
+ } finally {
216
+ // Cleanup test file
217
+ if (fs.existsSync(testFile)) {
218
+ fs.unlinkSync(testFile);
219
+ }
220
+ }
221
+ });
222
+
223
+ // Test container parameter flow through index.js
224
+ test("CortexFileHandler should pass container parameter for remote file downloads", async (t) => {
225
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
226
+ t.pass("Skipping test - Azure not configured");
227
+ return;
228
+ }
229
+
230
+ const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
231
+ process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
232
+
233
+ try {
234
+ // Create a test file URL (this would typically be a remote URL)
235
+ const testUrl = "https://example.com/test.txt";
236
+
237
+ // Mock request for remote file with container parameter
238
+ const mockReq = {
239
+ query: {
240
+ fetch: testUrl,
241
+ container: "test2"
242
+ },
243
+ method: "GET"
244
+ };
245
+
246
+ // Since we can't easily test real remote downloads in unit tests,
247
+ // we'll test the parameter extraction and validation
248
+ const { container } = mockReq.query;
249
+
250
+ t.is(container, "test2");
251
+ t.true(isValidContainerName(container));
252
+ } finally {
253
+ // Restore environment
254
+ if (originalEnv) {
255
+ process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
256
+ } else {
257
+ delete process.env.AZURE_STORAGE_CONTAINER_NAME;
258
+ }
259
+ }
260
+ });
261
+
262
+ // Test container parameter flow for document processing
263
+ test("CortexFileHandler should pass container parameter for document processing", async (t) => {
264
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
265
+ t.pass("Skipping test - Azure not configured");
266
+ return;
267
+ }
268
+
269
+ const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
270
+ process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
271
+
272
+ try {
273
+ // Mock request for document processing with container parameter
274
+ const mockReq = {
275
+ body: {
276
+ params: {
277
+ uri: "https://example.com/test.pdf",
278
+ requestId: "test-request",
279
+ container: "test3"
280
+ }
281
+ },
282
+ query: {},
283
+ method: "GET"
284
+ };
285
+
286
+ // Extract parameters like index.js does
287
+ const {
288
+ uri,
289
+ requestId,
290
+ container,
291
+ } = mockReq.body?.params || mockReq.query;
292
+
293
+ t.is(uri, "https://example.com/test.pdf");
294
+ t.is(requestId, "test-request");
295
+ t.is(container, "test3");
296
+ t.true(isValidContainerName(container));
297
+ } finally {
298
+ // Restore environment
299
+ if (originalEnv) {
300
+ process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
301
+ } else {
302
+ delete process.env.AZURE_STORAGE_CONTAINER_NAME;
303
+ }
304
+ }
305
+ });
306
+
307
+ // Test default container behavior
308
+ test("should use default container when no container specified", async (t) => {
309
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
310
+ t.pass("Skipping test - Azure not configured");
311
+ return;
312
+ }
313
+
314
+ // Test that default container is first in the list
315
+ t.is(DEFAULT_AZURE_STORAGE_CONTAINER_NAME, AZURE_STORAGE_CONTAINER_NAMES[0]);
316
+ t.true(isValidContainerName(DEFAULT_AZURE_STORAGE_CONTAINER_NAME));
317
+
318
+ // Create a test file
319
+ const testContent = "test content for default container";
320
+ const testFile = await createTestFile(testContent, "txt");
321
+
322
+ try {
323
+ // Create form data without container parameter (should use default)
324
+ const form = new FormData();
325
+ form.append("file", fs.createReadStream(testFile), "test.txt");
326
+
327
+ const response = await axios.post(baseUrl, form, {
328
+ headers: {
329
+ ...form.getHeaders(),
330
+ "Content-Type": "multipart/form-data",
331
+ },
332
+ validateStatus: (status) => true,
333
+ timeout: 30000,
334
+ });
335
+
336
+ t.is(response.status, 200);
337
+ t.truthy(response.data.url);
338
+
339
+ // Cleanup
340
+ await cleanupHashAndFile(null, response.data.url, baseUrl);
341
+ } finally {
342
+ // Cleanup test file
343
+ if (fs.existsSync(testFile)) {
344
+ fs.unlinkSync(testFile);
345
+ }
346
+ }
347
+ });
348
+
349
+ // Test container parameter with media chunking
350
+ test("should pass container parameter for media file chunking", async (t) => {
351
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
352
+ t.pass("Skipping test - Azure not configured");
353
+ return;
354
+ }
355
+
356
+ const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
357
+ process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
358
+
359
+ try {
360
+ // Mock request for media file processing with container parameter
361
+ const mockReq = {
362
+ body: {
363
+ params: {
364
+ uri: "https://example.com/test.mp3",
365
+ requestId: "test-media-request",
366
+ container: "test1"
367
+ }
368
+ },
369
+ query: {},
370
+ method: "GET"
371
+ };
372
+
373
+ // Extract parameters like index.js does
374
+ const {
375
+ uri,
376
+ requestId,
377
+ container,
378
+ } = mockReq.body?.params || mockReq.query;
379
+
380
+ t.is(uri, "https://example.com/test.mp3");
381
+ t.is(requestId, "test-media-request");
382
+ t.is(container, "test1");
383
+ t.true(isValidContainerName(container));
384
+ } finally {
385
+ // Restore environment
386
+ if (originalEnv) {
387
+ process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
388
+ } else {
389
+ delete process.env.AZURE_STORAGE_CONTAINER_NAME;
390
+ }
391
+ }
392
+ });
@@ -8,7 +8,7 @@ import XLSX from "xlsx";
8
8
  import { port } from "../src/start.js";
9
9
  import { gcs, GCS_BUCKETNAME } from "../src/blobHandler.js";
10
10
  import { getFileStoreMap, setFileStoreMap } from "../src/redis.js";
11
- import { cleanupHashAndFile } from "./testUtils.helper.js";
11
+ import { cleanupHashAndFile, startTestServer, stopTestServer } from "./testUtils.helper.js";
12
12
  import { gcsUrlExists } from "../src/blobHandler.js";
13
13
 
14
14
  const baseUrl = `http://localhost:${port}/api/CortexFileHandler`;
@@ -47,13 +47,18 @@ function isGcsConfigured() {
47
47
  return !!gcs;
48
48
  }
49
49
 
50
+ // Ensure server is ready before tests
50
51
  test.before(async (t) => {
52
+ await startTestServer();
51
53
  const dir = path.join(fs.mkdtempSync(path.join(process.cwd(), "conv-test-")));
52
54
  t.context.tmpDir = dir;
53
55
  });
54
56
 
55
57
  test.after.always(async (t) => {
56
- fs.rmSync(t.context.tmpDir, { recursive: true, force: true });
58
+ await stopTestServer();
59
+ if (t.context?.tmpDir) {
60
+ fs.rmSync(t.context.tmpDir, { recursive: true, force: true });
61
+ }
57
62
  });
58
63
 
59
64
  // 1. Remote-URL upload path should still return converted info