@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.
- package/.github/workflows/cortex-file-handler-test.yml +61 -0
- package/README.md +31 -7
- package/config/default.example.json +15 -0
- package/config.js +133 -12
- package/helper-apps/cortex-autogen2/DigiCertGlobalRootCA.crt.pem +22 -0
- package/helper-apps/cortex-autogen2/Dockerfile +31 -0
- package/helper-apps/cortex-autogen2/Dockerfile.worker +41 -0
- package/helper-apps/cortex-autogen2/README.md +183 -0
- package/helper-apps/cortex-autogen2/__init__.py +1 -0
- package/helper-apps/cortex-autogen2/agents.py +131 -0
- package/helper-apps/cortex-autogen2/docker-compose.yml +20 -0
- package/helper-apps/cortex-autogen2/function_app.py +55 -0
- package/helper-apps/cortex-autogen2/host.json +15 -0
- package/helper-apps/cortex-autogen2/main.py +126 -0
- package/helper-apps/cortex-autogen2/poetry.lock +3652 -0
- package/helper-apps/cortex-autogen2/pyproject.toml +36 -0
- package/helper-apps/cortex-autogen2/requirements.txt +20 -0
- package/helper-apps/cortex-autogen2/send_task.py +105 -0
- package/helper-apps/cortex-autogen2/services/__init__.py +1 -0
- package/helper-apps/cortex-autogen2/services/azure_queue.py +85 -0
- package/helper-apps/cortex-autogen2/services/redis_publisher.py +153 -0
- package/helper-apps/cortex-autogen2/task_processor.py +488 -0
- package/helper-apps/cortex-autogen2/tools/__init__.py +24 -0
- package/helper-apps/cortex-autogen2/tools/azure_blob_tools.py +175 -0
- package/helper-apps/cortex-autogen2/tools/azure_foundry_agents.py +601 -0
- package/helper-apps/cortex-autogen2/tools/coding_tools.py +72 -0
- package/helper-apps/cortex-autogen2/tools/download_tools.py +48 -0
- package/helper-apps/cortex-autogen2/tools/file_tools.py +545 -0
- package/helper-apps/cortex-autogen2/tools/search_tools.py +646 -0
- package/helper-apps/cortex-azure-cleaner/README.md +36 -0
- package/helper-apps/cortex-file-converter/README.md +93 -0
- package/helper-apps/cortex-file-converter/key_to_pdf.py +104 -0
- package/helper-apps/cortex-file-converter/list_blob_extensions.py +89 -0
- package/helper-apps/cortex-file-converter/process_azure_keynotes.py +181 -0
- package/helper-apps/cortex-file-converter/requirements.txt +1 -0
- package/helper-apps/cortex-file-handler/.env.test.azure.ci +7 -0
- package/helper-apps/cortex-file-handler/.env.test.azure.sample +1 -1
- package/helper-apps/cortex-file-handler/.env.test.gcs.ci +10 -0
- package/helper-apps/cortex-file-handler/.env.test.gcs.sample +2 -2
- package/helper-apps/cortex-file-handler/INTERFACE.md +41 -0
- package/helper-apps/cortex-file-handler/package.json +1 -1
- package/helper-apps/cortex-file-handler/scripts/setup-azure-container.js +41 -17
- package/helper-apps/cortex-file-handler/scripts/setup-test-containers.js +30 -15
- package/helper-apps/cortex-file-handler/scripts/test-azure.sh +32 -6
- package/helper-apps/cortex-file-handler/scripts/test-gcs.sh +24 -2
- package/helper-apps/cortex-file-handler/scripts/validate-env.js +128 -0
- package/helper-apps/cortex-file-handler/src/blobHandler.js +161 -51
- package/helper-apps/cortex-file-handler/src/constants.js +3 -0
- package/helper-apps/cortex-file-handler/src/fileChunker.js +10 -8
- package/helper-apps/cortex-file-handler/src/index.js +116 -9
- package/helper-apps/cortex-file-handler/src/redis.js +61 -1
- package/helper-apps/cortex-file-handler/src/services/ConversionService.js +11 -8
- package/helper-apps/cortex-file-handler/src/services/FileConversionService.js +2 -2
- package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +88 -6
- package/helper-apps/cortex-file-handler/src/services/storage/GCSStorageProvider.js +58 -0
- package/helper-apps/cortex-file-handler/src/services/storage/StorageFactory.js +25 -5
- package/helper-apps/cortex-file-handler/src/services/storage/StorageProvider.js +9 -0
- package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +120 -16
- package/helper-apps/cortex-file-handler/src/start.js +27 -17
- package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +52 -1
- package/helper-apps/cortex-file-handler/tests/blobHandler.test.js +40 -0
- package/helper-apps/cortex-file-handler/tests/checkHashShortLived.test.js +553 -0
- package/helper-apps/cortex-file-handler/tests/cleanup.test.js +46 -52
- package/helper-apps/cortex-file-handler/tests/containerConversionFlow.test.js +451 -0
- package/helper-apps/cortex-file-handler/tests/containerNameParsing.test.js +229 -0
- package/helper-apps/cortex-file-handler/tests/containerParameterFlow.test.js +392 -0
- package/helper-apps/cortex-file-handler/tests/conversionResilience.test.js +7 -2
- package/helper-apps/cortex-file-handler/tests/deleteOperations.test.js +348 -0
- package/helper-apps/cortex-file-handler/tests/fileChunker.test.js +23 -2
- package/helper-apps/cortex-file-handler/tests/fileUpload.test.js +11 -5
- package/helper-apps/cortex-file-handler/tests/getOperations.test.js +58 -24
- package/helper-apps/cortex-file-handler/tests/postOperations.test.js +11 -4
- package/helper-apps/cortex-file-handler/tests/shortLivedUrlConversion.test.js +225 -0
- package/helper-apps/cortex-file-handler/tests/start.test.js +8 -12
- package/helper-apps/cortex-file-handler/tests/storage/StorageFactory.test.js +80 -0
- package/helper-apps/cortex-file-handler/tests/storage/StorageService.test.js +388 -22
- package/helper-apps/cortex-file-handler/tests/testUtils.helper.js +74 -0
- package/lib/cortexResponse.js +153 -0
- package/lib/entityConstants.js +21 -3
- package/lib/logger.js +21 -4
- package/lib/pathwayTools.js +28 -9
- package/lib/util.js +49 -0
- package/package.json +1 -1
- package/pathways/basePathway.js +1 -0
- package/pathways/bing_afagent.js +54 -1
- package/pathways/call_tools.js +2 -3
- package/pathways/chat_jarvis.js +1 -1
- package/pathways/google_cse.js +27 -0
- package/pathways/grok_live_search.js +18 -0
- package/pathways/system/entity/memory/sys_memory_lookup_required.js +1 -0
- package/pathways/system/entity/memory/sys_memory_required.js +1 -0
- package/pathways/system/entity/memory/sys_search_memory.js +1 -0
- package/pathways/system/entity/sys_entity_agent.js +56 -4
- package/pathways/system/entity/sys_generator_quick.js +1 -0
- package/pathways/system/entity/tools/sys_tool_bing_search_afagent.js +26 -0
- package/pathways/system/entity/tools/sys_tool_google_search.js +141 -0
- package/pathways/system/entity/tools/sys_tool_grok_x_search.js +237 -0
- package/pathways/system/entity/tools/sys_tool_image.js +1 -1
- package/pathways/system/rest_streaming/sys_claude_37_sonnet.js +21 -0
- package/pathways/system/rest_streaming/sys_claude_41_opus.js +21 -0
- package/pathways/system/rest_streaming/sys_claude_4_sonnet.js +21 -0
- package/pathways/system/rest_streaming/sys_google_gemini_25_flash.js +25 -0
- package/pathways/system/rest_streaming/{sys_google_gemini_chat.js → sys_google_gemini_25_pro.js} +6 -4
- package/pathways/system/rest_streaming/sys_grok_4.js +23 -0
- package/pathways/system/rest_streaming/sys_grok_4_fast_non_reasoning.js +23 -0
- package/pathways/system/rest_streaming/sys_grok_4_fast_reasoning.js +23 -0
- package/pathways/system/rest_streaming/sys_openai_chat.js +3 -0
- package/pathways/system/rest_streaming/sys_openai_chat_gpt41.js +22 -0
- package/pathways/system/rest_streaming/sys_openai_chat_gpt41_mini.js +21 -0
- package/pathways/system/rest_streaming/sys_openai_chat_gpt41_nano.js +21 -0
- package/pathways/system/rest_streaming/{sys_claude_35_sonnet.js → sys_openai_chat_gpt4_omni.js} +6 -4
- package/pathways/system/rest_streaming/sys_openai_chat_gpt4_omni_mini.js +21 -0
- package/pathways/system/rest_streaming/{sys_claude_3_haiku.js → sys_openai_chat_gpt5.js} +7 -5
- package/pathways/system/rest_streaming/sys_openai_chat_gpt5_chat.js +21 -0
- package/pathways/system/rest_streaming/sys_openai_chat_gpt5_mini.js +21 -0
- package/pathways/system/rest_streaming/sys_openai_chat_gpt5_nano.js +21 -0
- package/pathways/system/rest_streaming/{sys_openai_chat_o1.js → sys_openai_chat_o3.js} +6 -3
- package/pathways/system/rest_streaming/sys_openai_chat_o3_mini.js +3 -0
- package/pathways/system/workspaces/run_workspace_prompt.js +99 -0
- package/pathways/vision.js +1 -1
- package/server/graphql.js +1 -1
- package/server/modelExecutor.js +8 -0
- package/server/pathwayResolver.js +166 -16
- package/server/pathwayResponseParser.js +16 -8
- package/server/plugins/azureFoundryAgentsPlugin.js +1 -1
- package/server/plugins/claude3VertexPlugin.js +193 -45
- package/server/plugins/gemini15ChatPlugin.js +21 -0
- package/server/plugins/gemini15VisionPlugin.js +360 -0
- package/server/plugins/googleCsePlugin.js +94 -0
- package/server/plugins/grokVisionPlugin.js +365 -0
- package/server/plugins/modelPlugin.js +3 -1
- package/server/plugins/openAiChatPlugin.js +106 -13
- package/server/plugins/openAiVisionPlugin.js +42 -30
- package/server/resolver.js +28 -4
- package/server/rest.js +270 -53
- package/server/typeDef.js +1 -0
- package/tests/{mocks.js → helpers/mocks.js} +5 -2
- package/tests/{server.js → helpers/server.js} +2 -2
- package/tests/helpers/sseAssert.js +23 -0
- package/tests/helpers/sseClient.js +73 -0
- package/tests/helpers/subscriptionAssert.js +11 -0
- package/tests/helpers/subscriptions.js +113 -0
- package/tests/{sublong.srt → integration/features/translate/sublong.srt} +4543 -4543
- package/tests/integration/features/translate/translate_chunking_stream.test.js +100 -0
- package/tests/{translate_srt.test.js → integration/features/translate/translate_srt.test.js} +2 -2
- package/tests/integration/graphql/async/stream/agentic.test.js +477 -0
- package/tests/integration/graphql/async/stream/subscription_streaming.test.js +62 -0
- package/tests/integration/graphql/async/stream/sys_entity_start_streaming.test.js +71 -0
- package/tests/integration/graphql/async/stream/vendors/claude_streaming.test.js +56 -0
- package/tests/integration/graphql/async/stream/vendors/gemini_streaming.test.js +66 -0
- package/tests/integration/graphql/async/stream/vendors/grok_streaming.test.js +56 -0
- package/tests/integration/graphql/async/stream/vendors/openai_streaming.test.js +72 -0
- package/tests/integration/graphql/features/google/sysToolGoogleSearch.test.js +96 -0
- package/tests/integration/graphql/features/grok/grok.test.js +688 -0
- package/tests/integration/graphql/features/grok/grok_x_search_tool.test.js +354 -0
- package/tests/{main.test.js → integration/graphql/features/main.test.js} +1 -1
- package/tests/{call_tools.test.js → integration/graphql/features/tools/call_tools.test.js} +2 -2
- package/tests/{vision.test.js → integration/graphql/features/vision/vision.test.js} +1 -1
- package/tests/integration/graphql/subscriptions/connection.test.js +26 -0
- package/tests/{openai_api.test.js → integration/rest/oai/openai_api.test.js} +63 -238
- package/tests/integration/rest/oai/tool_calling_api.test.js +343 -0
- package/tests/integration/rest/oai/tool_calling_streaming.test.js +85 -0
- package/tests/integration/rest/vendors/claude_streaming.test.js +47 -0
- package/tests/integration/rest/vendors/claude_tool_calling_streaming.test.js +75 -0
- package/tests/integration/rest/vendors/gemini_streaming.test.js +47 -0
- package/tests/integration/rest/vendors/gemini_tool_calling_streaming.test.js +75 -0
- package/tests/integration/rest/vendors/grok_streaming.test.js +55 -0
- package/tests/integration/rest/vendors/grok_tool_calling_streaming.test.js +75 -0
- package/tests/{azureAuthTokenHelper.test.js → unit/core/azureAuthTokenHelper.test.js} +1 -1
- package/tests/{chunkfunction.test.js → unit/core/chunkfunction.test.js} +2 -2
- package/tests/{config.test.js → unit/core/config.test.js} +3 -3
- package/tests/{encodeCache.test.js → unit/core/encodeCache.test.js} +1 -1
- package/tests/{fastLruCache.test.js → unit/core/fastLruCache.test.js} +1 -1
- package/tests/{handleBars.test.js → unit/core/handleBars.test.js} +1 -1
- package/tests/{memoryfunction.test.js → unit/core/memoryfunction.test.js} +2 -2
- package/tests/unit/core/mergeResolver.test.js +952 -0
- package/tests/{parser.test.js → unit/core/parser.test.js} +3 -3
- package/tests/unit/core/pathwayResolver.test.js +187 -0
- package/tests/{requestMonitor.test.js → unit/core/requestMonitor.test.js} +1 -1
- package/tests/{requestMonitorDurationEstimator.test.js → unit/core/requestMonitorDurationEstimator.test.js} +1 -1
- package/tests/{truncateMessages.test.js → unit/core/truncateMessages.test.js} +3 -3
- package/tests/{util.test.js → unit/core/util.test.js} +1 -1
- package/tests/{apptekTranslatePlugin.test.js → unit/plugins/apptekTranslatePlugin.test.js} +3 -3
- package/tests/{azureFoundryAgents.test.js → unit/plugins/azureFoundryAgents.test.js} +136 -1
- package/tests/{claude3VertexPlugin.test.js → unit/plugins/claude3VertexPlugin.test.js} +32 -10
- package/tests/{claude3VertexToolConversion.test.js → unit/plugins/claude3VertexToolConversion.test.js} +3 -3
- package/tests/unit/plugins/googleCsePlugin.test.js +111 -0
- package/tests/unit/plugins/grokVisionPlugin.test.js +1392 -0
- package/tests/{modelPlugin.test.js → unit/plugins/modelPlugin.test.js} +3 -3
- package/tests/{multimodal_conversion.test.js → unit/plugins/multimodal_conversion.test.js} +4 -4
- package/tests/{openAiChatPlugin.test.js → unit/plugins/openAiChatPlugin.test.js} +13 -4
- package/tests/{openAiToolPlugin.test.js → unit/plugins/openAiToolPlugin.test.js} +35 -27
- package/tests/{tokenHandlingTests.test.js → unit/plugins/tokenHandlingTests.test.js} +5 -5
- package/tests/{translate_apptek.test.js → unit/plugins/translate_apptek.test.js} +3 -3
- package/tests/{streaming.test.js → unit/plugins.streaming/plugin_stream_events.test.js} +19 -58
- package/helper-apps/mogrt-handler/tests/test-files/test.gif +0 -1
- package/helper-apps/mogrt-handler/tests/test-files/test.mogrt +0 -1
- package/helper-apps/mogrt-handler/tests/test-files/test.mp4 +0 -1
- package/pathways/system/rest_streaming/sys_openai_chat_gpt4.js +0 -19
- package/pathways/system/rest_streaming/sys_openai_chat_gpt4_32.js +0 -19
- package/pathways/system/rest_streaming/sys_openai_chat_gpt4_turbo.js +0 -19
- package/pathways/system/workspaces/run_claude35_sonnet.js +0 -21
- package/pathways/system/workspaces/run_claude3_haiku.js +0 -20
- package/pathways/system/workspaces/run_gpt35turbo.js +0 -20
- package/pathways/system/workspaces/run_gpt4.js +0 -20
- package/pathways/system/workspaces/run_gpt4_32.js +0 -20
- package/tests/agentic.test.js +0 -256
- package/tests/pathwayResolver.test.js +0 -78
- package/tests/subscription.test.js +0 -387
- /package/tests/{subchunk.srt → integration/features/translate/subchunk.srt} +0 -0
- /package/tests/{subhorizontal.srt → integration/features/translate/subhorizontal.srt} +0 -0
|
@@ -7,6 +7,7 @@ import axios from "axios";
|
|
|
7
7
|
|
|
8
8
|
import { uploadBlob } from "../src/blobHandler.js";
|
|
9
9
|
import { urlExists } from "../src/helper.js";
|
|
10
|
+
import { port } from "../src/start.js";
|
|
10
11
|
import {
|
|
11
12
|
setFileStoreMap,
|
|
12
13
|
getFileStoreMap,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
cleanupRedisFileStoreMapAge,
|
|
15
16
|
} from "../src/redis.js";
|
|
16
17
|
import { StorageService } from "../src/services/storage/StorageService.js";
|
|
18
|
+
import { startTestServer, stopTestServer } from "./testUtils.helper.js";
|
|
17
19
|
|
|
18
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
21
|
|
|
@@ -29,7 +31,7 @@ function shouldUseLocalStorage() {
|
|
|
29
31
|
}
|
|
30
32
|
const __dirname = path.dirname(__filename);
|
|
31
33
|
|
|
32
|
-
const baseUrl =
|
|
34
|
+
const baseUrl = `http://localhost:${port}/api/CortexFileHandler`;
|
|
33
35
|
|
|
34
36
|
// Helper function to create a test file
|
|
35
37
|
async function createTestFile(content, extension = "txt") {
|
|
@@ -75,18 +77,26 @@ function getRequestIdFromUploadResult(uploadResult) {
|
|
|
75
77
|
return uploadResult.hash || "test-request-id";
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
// Ensure server is ready before tests
|
|
78
81
|
test.before(async () => {
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
// Start the server with Redis connection setup
|
|
83
|
+
await startTestServer({
|
|
84
|
+
beforeReady: async () => {
|
|
85
|
+
// Ensure Redis is connected
|
|
86
|
+
const { connectClient } = await import("../src/redis.js");
|
|
87
|
+
await connectClient();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
82
90
|
});
|
|
83
91
|
|
|
84
92
|
test.after(async () => {
|
|
85
|
-
// Clean up
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
// Clean up server with cleanup logic
|
|
94
|
+
await stopTestServer(async () => {
|
|
95
|
+
// Clean up any remaining test entries
|
|
96
|
+
const testKeys = [
|
|
97
|
+
"test-lazy-cleanup",
|
|
98
|
+
"test-age-cleanup",
|
|
99
|
+
"test-old-entry",
|
|
90
100
|
"test-missing-file",
|
|
91
101
|
"test-gcs-backup",
|
|
92
102
|
"test-recent-entry",
|
|
@@ -125,6 +135,7 @@ test.after(async () => {
|
|
|
125
135
|
} catch (error) {
|
|
126
136
|
console.error("Error cleaning up test files:", error);
|
|
127
137
|
}
|
|
138
|
+
});
|
|
128
139
|
});
|
|
129
140
|
|
|
130
141
|
test("lazy cleanup should remove cache entry when file is missing", async (t) => {
|
|
@@ -510,22 +521,13 @@ test("cleanup should handle entries without timestamps gracefully", async (t) =>
|
|
|
510
521
|
console.log(`Storing entry without timestamp:`, entryWithoutTimestamp);
|
|
511
522
|
|
|
512
523
|
// Store directly in Redis to avoid timestamp being added
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
}
|
|
521
|
-
await client.hset(
|
|
522
|
-
"FileStoreMap",
|
|
523
|
-
hash,
|
|
524
|
-
JSON.stringify(entryWithoutTimestamp),
|
|
525
|
-
);
|
|
526
|
-
} finally {
|
|
527
|
-
await client.disconnect();
|
|
528
|
-
}
|
|
524
|
+
const { client } = await import("../src/redis.js");
|
|
525
|
+
|
|
526
|
+
await client.hset(
|
|
527
|
+
"FileStoreMap",
|
|
528
|
+
hash,
|
|
529
|
+
JSON.stringify(entryWithoutTimestamp),
|
|
530
|
+
);
|
|
529
531
|
|
|
530
532
|
// Verify it exists initially
|
|
531
533
|
const initialResult = await getFileStoreMap(hash, true);
|
|
@@ -566,37 +568,29 @@ test("cleanup should handle malformed entries gracefully", async (t) => {
|
|
|
566
568
|
|
|
567
569
|
// Store the hash in Redis with malformed data
|
|
568
570
|
const malformedKey = "test-malformed";
|
|
569
|
-
const
|
|
570
|
-
const connectionString = process.env["REDIS_CONNECTION_STRING"];
|
|
571
|
-
const client = redis.default.createClient(connectionString);
|
|
571
|
+
const { client } = await import("../src/redis.js");
|
|
572
572
|
|
|
573
|
-
|
|
574
|
-
// Don't try to connect if already connected
|
|
575
|
-
if (client.status !== "ready" && client.status !== "connecting") {
|
|
576
|
-
await client.connect();
|
|
577
|
-
}
|
|
578
|
-
await client.hset("FileStoreMap", malformedKey, "this is not json");
|
|
573
|
+
await client.hset("FileStoreMap", malformedKey, "this is not json");
|
|
579
574
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
575
|
+
// Verify malformed entry exists
|
|
576
|
+
const initialResult = await getFileStoreMap(malformedKey, true);
|
|
577
|
+
t.truthy(initialResult, "Malformed entry should exist initially");
|
|
583
578
|
|
|
584
|
-
|
|
585
|
-
|
|
579
|
+
// Run age-based cleanup - should not crash
|
|
580
|
+
const cleaned = await cleanupRedisFileStoreMapAge(7, 10);
|
|
586
581
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
582
|
+
// Malformed entry should not be cleaned up (no timestamp)
|
|
583
|
+
const cleanedHash = cleaned.find(
|
|
584
|
+
(entry) => entry.hash === "test-malformed",
|
|
585
|
+
);
|
|
586
|
+
t.falsy(cleanedHash, "Malformed entry should not be cleaned up");
|
|
587
|
+
|
|
588
|
+
// Verify the entry still exists
|
|
589
|
+
const resultAfterCleanup = await getFileStoreMap(malformedKey, true);
|
|
590
|
+
t.truthy(resultAfterCleanup, "Malformed entry should still exist");
|
|
591
|
+
|
|
592
|
+
// Cleanup
|
|
593
|
+
await removeFromFileStoreMap(malformedKey);
|
|
600
594
|
} finally {
|
|
601
595
|
cleanupTestFile(testFile);
|
|
602
596
|
}
|
|
@@ -0,0 +1,451 @@
|
|
|
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 XLSX from "xlsx";
|
|
10
|
+
import { port } from "../src/start.js";
|
|
11
|
+
import {
|
|
12
|
+
uploadBlob,
|
|
13
|
+
isValidContainerName,
|
|
14
|
+
AZURE_STORAGE_CONTAINER_NAMES,
|
|
15
|
+
saveFileToBlob,
|
|
16
|
+
} from "../src/blobHandler.js";
|
|
17
|
+
import { FileConversionService } from "../src/services/FileConversionService.js";
|
|
18
|
+
import CortexFileHandler from "../src/index.js";
|
|
19
|
+
import {
|
|
20
|
+
startTestServer,
|
|
21
|
+
stopTestServer,
|
|
22
|
+
setupTestDirectory,
|
|
23
|
+
cleanupHashAndFile,
|
|
24
|
+
getFolderNameFromUrl,
|
|
25
|
+
} from "./testUtils.helper.js";
|
|
26
|
+
|
|
27
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
28
|
+
const __dirname = path.dirname(__filename);
|
|
29
|
+
const baseUrl = `http://localhost:${port}/api/CortexFileHandler`;
|
|
30
|
+
|
|
31
|
+
// Mock context for testing
|
|
32
|
+
const mockContext = {
|
|
33
|
+
log: (message) => console.log(`[CONTAINER_CONVERSION_TEST] ${message}`),
|
|
34
|
+
res: null,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Helper function to create test files
|
|
38
|
+
async function createTestFile(content, extension, filename = null) {
|
|
39
|
+
const testDir = path.join(__dirname, "test-conversion-files");
|
|
40
|
+
if (!fs.existsSync(testDir)) {
|
|
41
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
const testFilename = filename || `${uuidv4()}.${extension}`;
|
|
44
|
+
const filePath = path.join(testDir, testFilename);
|
|
45
|
+
|
|
46
|
+
if (extension === 'xlsx') {
|
|
47
|
+
// Create Excel file
|
|
48
|
+
const workbook = XLSX.utils.book_new();
|
|
49
|
+
const ws1 = XLSX.utils.aoa_to_sheet(content);
|
|
50
|
+
XLSX.utils.book_append_sheet(workbook, ws1, "Sheet1");
|
|
51
|
+
XLSX.writeFile(workbook, filePath);
|
|
52
|
+
} else {
|
|
53
|
+
fs.writeFileSync(filePath, content);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return filePath;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Helper function to check if URL belongs to specific container
|
|
60
|
+
function getContainerFromUrl(url) {
|
|
61
|
+
try {
|
|
62
|
+
const urlObj = new URL(url);
|
|
63
|
+
const pathSegments = urlObj.pathname.split('/').filter(Boolean);
|
|
64
|
+
|
|
65
|
+
// For Azure URLs, container is typically the first segment after the account
|
|
66
|
+
// Format: https://account.blob.core.windows.net/container/blob...
|
|
67
|
+
// For Azurite (local emulator), format is: http://127.0.0.1:10000/devstoreaccount1/container/blob...
|
|
68
|
+
if (pathSegments.length > 0) {
|
|
69
|
+
// Check if this is an Azurite URL (localhost with devstoreaccount1)
|
|
70
|
+
if (urlObj.hostname === '127.0.0.1' && pathSegments[0] === 'devstoreaccount1') {
|
|
71
|
+
// For Azurite, container is the second segment
|
|
72
|
+
return pathSegments.length > 1 ? pathSegments[1] : null;
|
|
73
|
+
} else {
|
|
74
|
+
// For production Azure, container is the first segment
|
|
75
|
+
return pathSegments[0];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.log("Error parsing container from URL:", error);
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Setup: Create test directory and start server
|
|
85
|
+
test.before(async (t) => {
|
|
86
|
+
await startTestServer();
|
|
87
|
+
await setupTestDirectory(t);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Cleanup
|
|
91
|
+
test.after.always(async (t) => {
|
|
92
|
+
await stopTestServer();
|
|
93
|
+
|
|
94
|
+
// Clean up test directory
|
|
95
|
+
if (t.context?.testDir) {
|
|
96
|
+
await fs.promises.rm(t.context.testDir, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Clean up any remaining files in the test-conversion-files directory
|
|
100
|
+
const testFilesDir = path.join(__dirname, "test-conversion-files");
|
|
101
|
+
if (fs.existsSync(testFilesDir)) {
|
|
102
|
+
try {
|
|
103
|
+
await fs.promises.rm(testFilesDir, { recursive: true, force: true });
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.log("Error cleaning test files:", error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Test that FileConversionService._saveConvertedFile respects container parameter
|
|
111
|
+
test("FileConversionService._saveConvertedFile should use specified container", async (t) => {
|
|
112
|
+
if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
|
|
113
|
+
t.pass("Skipping test - Azure not configured");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
118
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const service = new FileConversionService(mockContext, true); // useAzure = true
|
|
122
|
+
|
|
123
|
+
// Create a test file to save
|
|
124
|
+
const testContent = "This is converted file content";
|
|
125
|
+
const testFile = await createTestFile(testContent, "txt", "converted-test.txt");
|
|
126
|
+
const requestId = uuidv4();
|
|
127
|
+
const targetContainer = "test2";
|
|
128
|
+
|
|
129
|
+
// Call _saveConvertedFile with container parameter
|
|
130
|
+
const result = await service._saveConvertedFile(
|
|
131
|
+
testFile,
|
|
132
|
+
requestId,
|
|
133
|
+
null, // filename
|
|
134
|
+
targetContainer
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
t.truthy(result);
|
|
138
|
+
t.truthy(result.url);
|
|
139
|
+
|
|
140
|
+
// Verify the URL indicates it was uploaded to the correct container
|
|
141
|
+
const containerFromUrl = getContainerFromUrl(result.url);
|
|
142
|
+
t.is(containerFromUrl, targetContainer,
|
|
143
|
+
`File should be uploaded to container ${targetContainer}, but was uploaded to ${containerFromUrl}`);
|
|
144
|
+
|
|
145
|
+
// Cleanup
|
|
146
|
+
await cleanupHashAndFile(null, result.url, baseUrl);
|
|
147
|
+
} finally {
|
|
148
|
+
// Restore environment
|
|
149
|
+
if (originalEnv) {
|
|
150
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
|
|
151
|
+
} else {
|
|
152
|
+
delete process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Test that file upload with conversion respects container parameter
|
|
158
|
+
test("File upload with conversion should upload both original and converted files to specified container", async (t) => {
|
|
159
|
+
if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
|
|
160
|
+
t.pass("Skipping test - Azure not configured");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
165
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
// Create an Excel file that will need conversion
|
|
169
|
+
const excelData = [
|
|
170
|
+
["Name", "Age", "City"],
|
|
171
|
+
["John", 30, "New York"],
|
|
172
|
+
["Jane", 25, "Boston"],
|
|
173
|
+
];
|
|
174
|
+
const testFile = await createTestFile(excelData, "xlsx", "test-conversion.xlsx");
|
|
175
|
+
const targetContainer = "test3";
|
|
176
|
+
|
|
177
|
+
// Create form data with container parameter
|
|
178
|
+
const form = new FormData();
|
|
179
|
+
form.append("file", fs.createReadStream(testFile), "test-conversion.xlsx");
|
|
180
|
+
form.append("container", targetContainer);
|
|
181
|
+
|
|
182
|
+
const response = await axios.post(baseUrl, form, {
|
|
183
|
+
headers: {
|
|
184
|
+
...form.getHeaders(),
|
|
185
|
+
"Content-Type": "multipart/form-data",
|
|
186
|
+
},
|
|
187
|
+
validateStatus: (status) => true,
|
|
188
|
+
timeout: 60000, // Longer timeout for conversion
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
t.is(response.status, 200);
|
|
192
|
+
t.truthy(response.data.url);
|
|
193
|
+
|
|
194
|
+
// Check that the main uploaded file is in the correct container
|
|
195
|
+
const mainContainerFromUrl = getContainerFromUrl(response.data.url);
|
|
196
|
+
t.is(mainContainerFromUrl, targetContainer,
|
|
197
|
+
`Original file should be in container ${targetContainer}, but was in ${mainContainerFromUrl}`);
|
|
198
|
+
|
|
199
|
+
// If there's a converted file mentioned in the response, check its container too
|
|
200
|
+
if (response.data.converted && response.data.converted.url) {
|
|
201
|
+
const convertedContainerFromUrl = getContainerFromUrl(response.data.converted.url);
|
|
202
|
+
t.is(convertedContainerFromUrl, targetContainer,
|
|
203
|
+
`Converted file should be in container ${targetContainer}, but was in ${convertedContainerFromUrl}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Cleanup
|
|
207
|
+
await cleanupHashAndFile(null, response.data.url, baseUrl);
|
|
208
|
+
if (response.data.converted && response.data.converted.url) {
|
|
209
|
+
await cleanupHashAndFile(null, response.data.converted.url, baseUrl);
|
|
210
|
+
}
|
|
211
|
+
} finally {
|
|
212
|
+
// Restore environment
|
|
213
|
+
if (originalEnv) {
|
|
214
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
|
|
215
|
+
} else {
|
|
216
|
+
delete process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Test document processing with save=true and container parameter
|
|
222
|
+
test("Document processing with save=true should save converted file to specified container", async (t) => {
|
|
223
|
+
if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
|
|
224
|
+
t.pass("Skipping test - Azure not configured");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
229
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
// First upload a document file to get a URI
|
|
233
|
+
const docContent = "This is a test document content for processing.";
|
|
234
|
+
const testFile = await createTestFile(docContent, "txt", "test-doc.txt");
|
|
235
|
+
const targetContainer = "test1";
|
|
236
|
+
|
|
237
|
+
// Upload the file first
|
|
238
|
+
const uploadForm = new FormData();
|
|
239
|
+
uploadForm.append("file", fs.createReadStream(testFile), "test-doc.txt");
|
|
240
|
+
uploadForm.append("container", targetContainer);
|
|
241
|
+
|
|
242
|
+
const uploadResponse = await axios.post(baseUrl, uploadForm, {
|
|
243
|
+
headers: {
|
|
244
|
+
...uploadForm.getHeaders(),
|
|
245
|
+
"Content-Type": "multipart/form-data",
|
|
246
|
+
},
|
|
247
|
+
validateStatus: (status) => true,
|
|
248
|
+
timeout: 30000,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
t.is(uploadResponse.status, 200);
|
|
252
|
+
const documentUri = uploadResponse.data.url;
|
|
253
|
+
|
|
254
|
+
// Now process the document with save=true and container parameter
|
|
255
|
+
const processResponse = await axios.get(baseUrl, {
|
|
256
|
+
params: {
|
|
257
|
+
uri: documentUri,
|
|
258
|
+
requestId: uuidv4(),
|
|
259
|
+
save: true,
|
|
260
|
+
container: targetContainer,
|
|
261
|
+
},
|
|
262
|
+
validateStatus: (status) => true,
|
|
263
|
+
timeout: 60000,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
t.is(processResponse.status, 200);
|
|
267
|
+
t.truthy(processResponse.data.url);
|
|
268
|
+
|
|
269
|
+
// Check that the saved file is in the correct container
|
|
270
|
+
const savedContainerFromUrl = getContainerFromUrl(processResponse.data.url);
|
|
271
|
+
t.is(savedContainerFromUrl, targetContainer,
|
|
272
|
+
`Saved processed file should be in container ${targetContainer}, but was in ${savedContainerFromUrl}`);
|
|
273
|
+
|
|
274
|
+
// Cleanup
|
|
275
|
+
await cleanupHashAndFile(null, documentUri, baseUrl);
|
|
276
|
+
await cleanupHashAndFile(null, processResponse.data.url, baseUrl);
|
|
277
|
+
} finally {
|
|
278
|
+
// Restore environment
|
|
279
|
+
if (originalEnv) {
|
|
280
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
|
|
281
|
+
} else {
|
|
282
|
+
delete process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Test checkHash operation preserves container for converted files
|
|
288
|
+
test("checkHash operation should respect container parameter for converted files", async (t) => {
|
|
289
|
+
if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
|
|
290
|
+
t.pass("Skipping test - Azure not configured");
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
295
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
// Create an Excel file that will need conversion
|
|
299
|
+
const excelData = [
|
|
300
|
+
["Product", "Price"],
|
|
301
|
+
["Widget", 10.99],
|
|
302
|
+
["Gadget", 15.50],
|
|
303
|
+
];
|
|
304
|
+
const testFile = await createTestFile(excelData, "xlsx", "hash-test.xlsx");
|
|
305
|
+
const targetContainer = "test2";
|
|
306
|
+
const testHash = uuidv4();
|
|
307
|
+
|
|
308
|
+
// Upload the file with a hash and container parameter
|
|
309
|
+
const form = new FormData();
|
|
310
|
+
form.append("file", fs.createReadStream(testFile), "hash-test.xlsx");
|
|
311
|
+
form.append("hash", testHash);
|
|
312
|
+
form.append("container", targetContainer);
|
|
313
|
+
|
|
314
|
+
const uploadResponse = await axios.post(baseUrl, form, {
|
|
315
|
+
headers: {
|
|
316
|
+
...form.getHeaders(),
|
|
317
|
+
"Content-Type": "multipart/form-data",
|
|
318
|
+
},
|
|
319
|
+
validateStatus: (status) => true,
|
|
320
|
+
timeout: 60000,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
t.is(uploadResponse.status, 200);
|
|
324
|
+
|
|
325
|
+
// Now check the hash with container parameter
|
|
326
|
+
const checkResponse = await axios.get(baseUrl, {
|
|
327
|
+
params: {
|
|
328
|
+
hash: testHash,
|
|
329
|
+
checkHash: true,
|
|
330
|
+
container: targetContainer,
|
|
331
|
+
},
|
|
332
|
+
validateStatus: (status) => true,
|
|
333
|
+
timeout: 60000,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
t.is(checkResponse.status, 200);
|
|
337
|
+
t.truthy(checkResponse.data.url);
|
|
338
|
+
|
|
339
|
+
// Check that the original file is in the correct container
|
|
340
|
+
const originalContainerFromUrl = getContainerFromUrl(checkResponse.data.url);
|
|
341
|
+
t.is(originalContainerFromUrl, targetContainer,
|
|
342
|
+
`Original file should be in container ${targetContainer}, but was in ${originalContainerFromUrl}`);
|
|
343
|
+
|
|
344
|
+
// If there's a converted file, check its container too
|
|
345
|
+
if (checkResponse.data.converted && checkResponse.data.converted.url) {
|
|
346
|
+
const convertedContainerFromUrl = getContainerFromUrl(checkResponse.data.converted.url);
|
|
347
|
+
t.is(convertedContainerFromUrl, targetContainer,
|
|
348
|
+
`Converted file should be in container ${targetContainer}, but was in ${convertedContainerFromUrl}`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Cleanup
|
|
352
|
+
await cleanupHashAndFile(testHash, checkResponse.data.url, baseUrl);
|
|
353
|
+
if (checkResponse.data.converted && checkResponse.data.converted.url) {
|
|
354
|
+
await cleanupHashAndFile(null, checkResponse.data.converted.url, baseUrl);
|
|
355
|
+
}
|
|
356
|
+
} finally {
|
|
357
|
+
// Restore environment
|
|
358
|
+
if (originalEnv) {
|
|
359
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
|
|
360
|
+
} else {
|
|
361
|
+
delete process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Test that default container is used when no container specified for conversions
|
|
367
|
+
test("Conversion should use default container when no container specified", async (t) => {
|
|
368
|
+
if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
|
|
369
|
+
t.pass("Skipping test - Azure not configured");
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
374
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const service = new FileConversionService(mockContext, true);
|
|
378
|
+
|
|
379
|
+
// Create a test file to save
|
|
380
|
+
const testContent = "This is converted file content for default container test";
|
|
381
|
+
const testFile = await createTestFile(testContent, "txt", "default-container-test.txt");
|
|
382
|
+
const requestId = uuidv4();
|
|
383
|
+
|
|
384
|
+
// Call _saveConvertedFile without container parameter (should use default)
|
|
385
|
+
const result = await service._saveConvertedFile(
|
|
386
|
+
testFile,
|
|
387
|
+
requestId,
|
|
388
|
+
null, // filename
|
|
389
|
+
null // container - should use default
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
t.truthy(result);
|
|
393
|
+
t.truthy(result.url);
|
|
394
|
+
|
|
395
|
+
// Verify the URL indicates it was uploaded to the default container
|
|
396
|
+
const containerFromUrl = getContainerFromUrl(result.url);
|
|
397
|
+
t.is(containerFromUrl, AZURE_STORAGE_CONTAINER_NAMES[0],
|
|
398
|
+
`File should be uploaded to default container ${AZURE_STORAGE_CONTAINER_NAMES[0]}, but was uploaded to ${containerFromUrl}`);
|
|
399
|
+
|
|
400
|
+
// Cleanup
|
|
401
|
+
await cleanupHashAndFile(null, result.url, baseUrl);
|
|
402
|
+
} finally {
|
|
403
|
+
// Restore environment
|
|
404
|
+
if (originalEnv) {
|
|
405
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
|
|
406
|
+
} else {
|
|
407
|
+
delete process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Test saveFileToBlob function directly with container parameter
|
|
413
|
+
test("saveFileToBlob should respect container parameter", async (t) => {
|
|
414
|
+
if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
|
|
415
|
+
t.pass("Skipping test - Azure not configured");
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const originalEnv = process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
420
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = "test1,test2,test3";
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
// Create a test file
|
|
424
|
+
const testContent = "This is a test for saveFileToBlob with container parameter";
|
|
425
|
+
const testFile = await createTestFile(testContent, "txt", "save-blob-test.txt");
|
|
426
|
+
const requestId = uuidv4();
|
|
427
|
+
const targetContainer = "test3";
|
|
428
|
+
|
|
429
|
+
// Call saveFileToBlob directly with container parameter
|
|
430
|
+
const result = await saveFileToBlob(testFile, requestId, null, targetContainer);
|
|
431
|
+
|
|
432
|
+
t.truthy(result);
|
|
433
|
+
t.truthy(result.url);
|
|
434
|
+
t.truthy(result.blobName);
|
|
435
|
+
|
|
436
|
+
// Verify the URL indicates it was uploaded to the correct container
|
|
437
|
+
const containerFromUrl = getContainerFromUrl(result.url);
|
|
438
|
+
t.is(containerFromUrl, targetContainer,
|
|
439
|
+
`File should be uploaded to container ${targetContainer}, but was uploaded to ${containerFromUrl}`);
|
|
440
|
+
|
|
441
|
+
// Cleanup
|
|
442
|
+
await cleanupHashAndFile(null, result.url, baseUrl);
|
|
443
|
+
} finally {
|
|
444
|
+
// Restore environment
|
|
445
|
+
if (originalEnv) {
|
|
446
|
+
process.env.AZURE_STORAGE_CONTAINER_NAME = originalEnv;
|
|
447
|
+
} else {
|
|
448
|
+
delete process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
});
|