@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
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import test from "ava";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import FormData from "form-data";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { v4 as uuidv4 } from "uuid";
|
|
8
|
+
|
|
9
|
+
import { port } from "../src/start.js";
|
|
10
|
+
import {
|
|
11
|
+
cleanupHashAndFile,
|
|
12
|
+
createTestMediaFile,
|
|
13
|
+
startTestServer,
|
|
14
|
+
stopTestServer,
|
|
15
|
+
setupTestDirectory
|
|
16
|
+
} from "./testUtils.helper.js";
|
|
17
|
+
import {
|
|
18
|
+
setFileStoreMap,
|
|
19
|
+
getFileStoreMap,
|
|
20
|
+
removeFromFileStoreMap
|
|
21
|
+
} from "../src/redis.js";
|
|
22
|
+
|
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname = path.dirname(__filename);
|
|
25
|
+
const baseUrl = `http://localhost:${port}/api/CortexFileHandler`;
|
|
26
|
+
|
|
27
|
+
// Helper function to create test files
|
|
28
|
+
async function createTestFile(content, extension) {
|
|
29
|
+
const testDir = path.join(__dirname, "test-files");
|
|
30
|
+
if (!fs.existsSync(testDir)) {
|
|
31
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
const filename = path.join(
|
|
34
|
+
testDir,
|
|
35
|
+
`test-delete-${uuidv4().slice(0, 8)}.${extension}`,
|
|
36
|
+
);
|
|
37
|
+
fs.writeFileSync(filename, content);
|
|
38
|
+
return filename;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Helper function to upload file
|
|
42
|
+
async function uploadFile(filePath, requestId = null, hash = null) {
|
|
43
|
+
const form = new FormData();
|
|
44
|
+
form.append("file", fs.createReadStream(filePath));
|
|
45
|
+
if (requestId) form.append("requestId", requestId);
|
|
46
|
+
if (hash) form.append("hash", hash);
|
|
47
|
+
|
|
48
|
+
return await axios.post(baseUrl, form, {
|
|
49
|
+
headers: form.getHeaders(),
|
|
50
|
+
validateStatus: (status) => true,
|
|
51
|
+
timeout: 15000,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Helper function to delete file by hash
|
|
56
|
+
async function deleteFileByHash(hash) {
|
|
57
|
+
return await axios.delete(`${baseUrl}?hash=${hash}`, {
|
|
58
|
+
validateStatus: (status) => true,
|
|
59
|
+
timeout: 10000,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Helper function to check if hash exists
|
|
64
|
+
async function checkHashExists(hash) {
|
|
65
|
+
return await axios.get(`${baseUrl}?hash=${hash}&checkHash=true`, {
|
|
66
|
+
validateStatus: (status) => true,
|
|
67
|
+
timeout: 10000,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Test setup
|
|
72
|
+
test.before(async (t) => {
|
|
73
|
+
await setupTestDirectory(__dirname);
|
|
74
|
+
await startTestServer();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test.after(async (t) => {
|
|
78
|
+
await stopTestServer();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Basic delete by hash tests
|
|
82
|
+
test.serial("should delete file by hash successfully", async (t) => {
|
|
83
|
+
const testContent = "test content for hash deletion";
|
|
84
|
+
const testHash = `test-hash-${uuidv4()}`;
|
|
85
|
+
const filePath = await createTestFile(testContent, "txt");
|
|
86
|
+
let uploadResponse;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Upload file with hash
|
|
90
|
+
uploadResponse = await uploadFile(filePath, null, testHash);
|
|
91
|
+
t.is(uploadResponse.status, 200, "Upload should succeed");
|
|
92
|
+
t.truthy(uploadResponse.data.url, "Should have file URL");
|
|
93
|
+
|
|
94
|
+
// Verify file exists via hash
|
|
95
|
+
const hashCheckBefore = await checkHashExists(testHash);
|
|
96
|
+
t.is(hashCheckBefore.status, 200, "Hash should exist before deletion");
|
|
97
|
+
|
|
98
|
+
// Delete file by hash
|
|
99
|
+
const deleteResponse = await deleteFileByHash(testHash);
|
|
100
|
+
t.is(deleteResponse.status, 200, "Delete should succeed");
|
|
101
|
+
t.truthy(deleteResponse.data.message, "Should have success message");
|
|
102
|
+
t.true(deleteResponse.data.message.includes(testHash), "Message should include hash");
|
|
103
|
+
t.truthy(deleteResponse.data.deleted, "Should have deletion details");
|
|
104
|
+
|
|
105
|
+
// Verify file is gone via hash
|
|
106
|
+
const hashCheckAfter = await checkHashExists(testHash);
|
|
107
|
+
t.is(hashCheckAfter.status, 404, "Hash should not exist after deletion");
|
|
108
|
+
|
|
109
|
+
// Wait a moment for deletion to propagate
|
|
110
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
111
|
+
|
|
112
|
+
// Verify file URL is no longer accessible
|
|
113
|
+
const fileResponse = await axios.get(uploadResponse.data.url, {
|
|
114
|
+
validateStatus: (status) => true,
|
|
115
|
+
});
|
|
116
|
+
t.is(fileResponse.status, 404, "File URL should return 404 after deletion");
|
|
117
|
+
|
|
118
|
+
} finally {
|
|
119
|
+
fs.unlinkSync(filePath);
|
|
120
|
+
// Additional cleanup just in case
|
|
121
|
+
try {
|
|
122
|
+
await removeFromFileStoreMap(testHash);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
// Ignore cleanup errors
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test.serial("should return 404 when deleting non-existent hash", async (t) => {
|
|
130
|
+
const nonExistentHash = `non-existent-${uuidv4()}`;
|
|
131
|
+
|
|
132
|
+
const deleteResponse = await deleteFileByHash(nonExistentHash);
|
|
133
|
+
t.is(deleteResponse.status, 404, "Should return 404 for non-existent hash");
|
|
134
|
+
t.truthy(deleteResponse.data, "Should have error message");
|
|
135
|
+
t.true(deleteResponse.data.includes("not found"), "Error should indicate file not found");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test.serial("should return 400 when hash parameter is missing", async (t) => {
|
|
139
|
+
const deleteResponse = await axios.delete(baseUrl, {
|
|
140
|
+
validateStatus: (status) => true,
|
|
141
|
+
timeout: 10000,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
t.is(deleteResponse.status, 400, "Should return 400 for missing parameters");
|
|
145
|
+
t.truthy(deleteResponse.data, "Should have error response");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test.serial("should delete file with both primary and backup storage", async (t) => {
|
|
149
|
+
const testContent = "test content for dual storage deletion";
|
|
150
|
+
const testHash = `test-dual-${uuidv4()}`;
|
|
151
|
+
const filePath = await createTestFile(testContent, "txt");
|
|
152
|
+
let uploadResponse;
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
// Upload file with hash
|
|
156
|
+
uploadResponse = await uploadFile(filePath, null, testHash);
|
|
157
|
+
t.is(uploadResponse.status, 200, "Upload should succeed");
|
|
158
|
+
|
|
159
|
+
// Check if GCS backup was created
|
|
160
|
+
const hasGcsBackup = uploadResponse.data.gcs && uploadResponse.data.gcs.startsWith("gs://");
|
|
161
|
+
|
|
162
|
+
// Delete file by hash
|
|
163
|
+
const deleteResponse = await deleteFileByHash(testHash);
|
|
164
|
+
t.is(deleteResponse.status, 200, "Delete should succeed");
|
|
165
|
+
t.truthy(deleteResponse.data.deleted, "Should have deletion details");
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
// Should have at least primary storage deletion
|
|
169
|
+
const deletionDetails = deleteResponse.data.deleted.deleted;
|
|
170
|
+
t.true(Array.isArray(deletionDetails), "Deletion details should be an array");
|
|
171
|
+
t.true(deletionDetails.length >= 1, "Should have at least primary deletion result");
|
|
172
|
+
const primaryDeletion = deletionDetails.find(d => d.provider === 'primary');
|
|
173
|
+
t.truthy(primaryDeletion, "Should have primary storage deletion");
|
|
174
|
+
|
|
175
|
+
// If GCS backup existed, should also have backup deletion
|
|
176
|
+
if (hasGcsBackup) {
|
|
177
|
+
const backupDeletion = deletionDetails.find(d => d.provider === 'backup');
|
|
178
|
+
t.truthy(backupDeletion, "Should have backup storage deletion when GCS is configured");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Verify hash is completely removed
|
|
182
|
+
const hashCheckAfter = await checkHashExists(testHash);
|
|
183
|
+
t.is(hashCheckAfter.status, 404, "Hash should not exist after deletion");
|
|
184
|
+
|
|
185
|
+
} finally {
|
|
186
|
+
fs.unlinkSync(filePath);
|
|
187
|
+
try {
|
|
188
|
+
await removeFromFileStoreMap(testHash);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
// Ignore cleanup errors
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test.serial("should handle malformed hash gracefully", async (t) => {
|
|
196
|
+
const malformedHashes = ["", " ", "null", "undefined", "{}"];
|
|
197
|
+
|
|
198
|
+
for (const badHash of malformedHashes) {
|
|
199
|
+
const deleteResponse = await deleteFileByHash(badHash);
|
|
200
|
+
t.true(
|
|
201
|
+
deleteResponse.status === 404 || deleteResponse.status === 400,
|
|
202
|
+
`Should return 404 or 400 for malformed hash: "${badHash}"`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test.serial("should prioritize requestId over hash when both provided", async (t) => {
|
|
208
|
+
const testContent = "test content for priority test";
|
|
209
|
+
const testHash = `test-priority-${uuidv4()}`;
|
|
210
|
+
const requestId = uuidv4();
|
|
211
|
+
const filePath = await createTestFile(testContent, "txt");
|
|
212
|
+
let uploadResponse;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
// Upload file with both hash and requestId
|
|
216
|
+
uploadResponse = await uploadFile(filePath, requestId, testHash);
|
|
217
|
+
t.is(uploadResponse.status, 200, "Upload should succeed");
|
|
218
|
+
|
|
219
|
+
// Delete using both hash and requestId - should delete by requestId (current behavior)
|
|
220
|
+
const deleteResponse = await axios.delete(`${baseUrl}?hash=${testHash}&requestId=${requestId}`, {
|
|
221
|
+
validateStatus: (status) => true,
|
|
222
|
+
timeout: 10000,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
t.is(deleteResponse.status, 200, "Delete should succeed");
|
|
226
|
+
t.truthy(deleteResponse.data.body, "Should have deletion body");
|
|
227
|
+
t.true(Array.isArray(deleteResponse.data.body), "Deletion body should be array of deleted files");
|
|
228
|
+
|
|
229
|
+
// Verify hash is gone (because the file was deleted via requestId)
|
|
230
|
+
const hashCheckAfter = await checkHashExists(testHash);
|
|
231
|
+
t.is(hashCheckAfter.status, 404, "Hash should not exist after deletion");
|
|
232
|
+
|
|
233
|
+
} finally {
|
|
234
|
+
fs.unlinkSync(filePath);
|
|
235
|
+
try {
|
|
236
|
+
await removeFromFileStoreMap(testHash);
|
|
237
|
+
} catch (e) {
|
|
238
|
+
// Ignore cleanup errors
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test.serial("should return proper response format for successful deletion", async (t) => {
|
|
244
|
+
const testContent = "test content for response format";
|
|
245
|
+
const testHash = `test-response-${uuidv4()}`;
|
|
246
|
+
const filePath = await createTestFile(testContent, "txt");
|
|
247
|
+
let uploadResponse;
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
// Upload file with hash
|
|
251
|
+
uploadResponse = await uploadFile(filePath, null, testHash);
|
|
252
|
+
t.is(uploadResponse.status, 200, "Upload should succeed");
|
|
253
|
+
|
|
254
|
+
// Delete file by hash
|
|
255
|
+
const deleteResponse = await deleteFileByHash(testHash);
|
|
256
|
+
t.is(deleteResponse.status, 200, "Delete should succeed");
|
|
257
|
+
|
|
258
|
+
// Verify response structure
|
|
259
|
+
t.truthy(deleteResponse.data, "Should have response data");
|
|
260
|
+
t.truthy(deleteResponse.data.message, "Should have success message");
|
|
261
|
+
t.is(deleteResponse.data.deleted.hash, testHash, "Should include deleted hash");
|
|
262
|
+
t.truthy(deleteResponse.data.deleted.filename, "Should include filename");
|
|
263
|
+
t.truthy(deleteResponse.data.deleted.deleted, "Should include deletion details");
|
|
264
|
+
t.true(Array.isArray(deleteResponse.data.deleted.deleted), "Deletion details should be array");
|
|
265
|
+
|
|
266
|
+
} finally {
|
|
267
|
+
fs.unlinkSync(filePath);
|
|
268
|
+
try {
|
|
269
|
+
await removeFromFileStoreMap(testHash);
|
|
270
|
+
} catch (e) {
|
|
271
|
+
// Ignore cleanup errors
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test.serial("should handle deletion when Redis is temporarily unavailable", async (t) => {
|
|
277
|
+
const testContent = "test content for Redis failure";
|
|
278
|
+
const testHash = `test-redis-fail-${uuidv4()}`;
|
|
279
|
+
const filePath = await createTestFile(testContent, "txt");
|
|
280
|
+
let uploadResponse;
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
// Upload file with hash
|
|
284
|
+
uploadResponse = await uploadFile(filePath, null, testHash);
|
|
285
|
+
t.is(uploadResponse.status, 200, "Upload should succeed");
|
|
286
|
+
|
|
287
|
+
// Manually corrupt the Redis entry to simulate Redis issues
|
|
288
|
+
await setFileStoreMap(testHash, { corrupted: "data" });
|
|
289
|
+
|
|
290
|
+
// Delete file by hash - should handle Redis issues gracefully
|
|
291
|
+
const deleteResponse = await deleteFileByHash(testHash);
|
|
292
|
+
|
|
293
|
+
// The behavior may vary depending on how Redis failures are handled
|
|
294
|
+
// It should either succeed with a warning or fail gracefully
|
|
295
|
+
t.true(
|
|
296
|
+
deleteResponse.status === 200 || deleteResponse.status === 404 || deleteResponse.status === 500,
|
|
297
|
+
"Should handle Redis failure gracefully"
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
} finally {
|
|
301
|
+
fs.unlinkSync(filePath);
|
|
302
|
+
try {
|
|
303
|
+
await removeFromFileStoreMap(testHash);
|
|
304
|
+
} catch (e) {
|
|
305
|
+
// Ignore cleanup errors
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test.serial("should delete file uploaded with different filename", async (t) => {
|
|
311
|
+
const testContent = "test content with special filename";
|
|
312
|
+
const testHash = `test-filename-${uuidv4()}`;
|
|
313
|
+
const specialFilename = "test file with spaces & symbols!@#.txt";
|
|
314
|
+
const filePath = await createTestFile(testContent, "txt");
|
|
315
|
+
let uploadResponse;
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
// Upload file with special filename and hash
|
|
319
|
+
const form = new FormData();
|
|
320
|
+
form.append("file", fs.createReadStream(filePath), specialFilename);
|
|
321
|
+
form.append("hash", testHash);
|
|
322
|
+
|
|
323
|
+
uploadResponse = await axios.post(baseUrl, form, {
|
|
324
|
+
headers: form.getHeaders(),
|
|
325
|
+
validateStatus: (status) => true,
|
|
326
|
+
timeout: 15000,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
t.is(uploadResponse.status, 200, "Upload should succeed");
|
|
330
|
+
|
|
331
|
+
// Delete file by hash
|
|
332
|
+
const deleteResponse = await deleteFileByHash(testHash);
|
|
333
|
+
t.is(deleteResponse.status, 200, "Delete should succeed");
|
|
334
|
+
t.truthy(deleteResponse.data.deleted.filename, "Should include original filename");
|
|
335
|
+
|
|
336
|
+
// Verify hash is gone
|
|
337
|
+
const hashCheckAfter = await checkHashExists(testHash);
|
|
338
|
+
t.is(hashCheckAfter.status, 404, "Hash should not exist after deletion");
|
|
339
|
+
|
|
340
|
+
} finally {
|
|
341
|
+
fs.unlinkSync(filePath);
|
|
342
|
+
try {
|
|
343
|
+
await removeFromFileStoreMap(testHash);
|
|
344
|
+
} catch (e) {
|
|
345
|
+
// Ignore cleanup errors
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
});
|
|
@@ -209,10 +209,31 @@ test("successfully downloads file from URL", async (t) => {
|
|
|
209
209
|
|
|
210
210
|
// Test error handling for invalid URLs in download
|
|
211
211
|
test("handles invalid URLs in download gracefully", async (t) => {
|
|
212
|
-
|
|
212
|
+
// Use a localhost URL with a port that's definitely not in use
|
|
213
|
+
const invalidUrl = "http://localhost:54321/nonexistent.mp3";
|
|
213
214
|
const outputPath = join(os.tmpdir(), "should-not-exist.mp3");
|
|
214
215
|
|
|
215
|
-
|
|
216
|
+
try {
|
|
217
|
+
await downloadFile(invalidUrl, outputPath);
|
|
218
|
+
t.fail("Expected downloadFile to throw an error for invalid URL");
|
|
219
|
+
} catch (error) {
|
|
220
|
+
t.truthy(error, "Should throw an error for invalid URL");
|
|
221
|
+
// Accept various network error types
|
|
222
|
+
const isValidError =
|
|
223
|
+
error.code === 'ENOTFOUND' ||
|
|
224
|
+
error.code === 'ECONNREFUSED' ||
|
|
225
|
+
error.code === 'ENETUNREACH' ||
|
|
226
|
+
error.code === 'ETIMEDOUT' ||
|
|
227
|
+
error.message.includes('ENOTFOUND') ||
|
|
228
|
+
error.message.includes('ECONNREFUSED') ||
|
|
229
|
+
error.message.includes('ENETUNREACH') ||
|
|
230
|
+
error.message.includes('ETIMEDOUT') ||
|
|
231
|
+
error.message.includes('getaddrinfo') ||
|
|
232
|
+
error.message.includes('Network Error') ||
|
|
233
|
+
error.message.includes('Request failed');
|
|
234
|
+
|
|
235
|
+
t.true(isValidError, `Expected network error but got: ${error.code} - ${error.message}`);
|
|
236
|
+
}
|
|
216
237
|
});
|
|
217
238
|
|
|
218
239
|
// Helper to format duration nicely
|
|
@@ -10,6 +10,9 @@ import { gcs } from "../src/blobHandler.js";
|
|
|
10
10
|
import {
|
|
11
11
|
cleanupHashAndFile,
|
|
12
12
|
getFolderNameFromUrl,
|
|
13
|
+
startTestServer,
|
|
14
|
+
stopTestServer,
|
|
15
|
+
setupTestDirectory,
|
|
13
16
|
} from "./testUtils.helper.js";
|
|
14
17
|
import XLSX from "xlsx";
|
|
15
18
|
|
|
@@ -78,17 +81,20 @@ async function fetchFileContent(url) {
|
|
|
78
81
|
return Buffer.from(response.data);
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
// Setup: Create test directory
|
|
84
|
+
// Setup: Create test directory and start server
|
|
82
85
|
test.before(async (t) => {
|
|
83
|
-
|
|
84
|
-
await
|
|
85
|
-
t.context = { testDir };
|
|
86
|
+
await startTestServer();
|
|
87
|
+
await setupTestDirectory(t);
|
|
86
88
|
});
|
|
87
89
|
|
|
88
90
|
// Cleanup
|
|
89
91
|
test.after.always(async (t) => {
|
|
92
|
+
await stopTestServer();
|
|
93
|
+
|
|
90
94
|
// Clean up test directory
|
|
91
|
-
|
|
95
|
+
if (t.context?.testDir) {
|
|
96
|
+
await fs.promises.rm(t.context.testDir, { recursive: true, force: true });
|
|
97
|
+
}
|
|
92
98
|
|
|
93
99
|
// Clean up any remaining files in the files directory
|
|
94
100
|
const filesDir = path.join(__dirname, "..", "files");
|
|
@@ -6,8 +6,9 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
6
6
|
import axios from "axios";
|
|
7
7
|
import FormData from "form-data";
|
|
8
8
|
import XLSX from "xlsx";
|
|
9
|
+
import nock from "nock";
|
|
9
10
|
import { port } from "../src/start.js";
|
|
10
|
-
import { cleanupHashAndFile, createTestMediaFile } from "./testUtils.helper.js";
|
|
11
|
+
import { cleanupHashAndFile, createTestMediaFile, startTestServer, stopTestServer, setupTestDirectory } from "./testUtils.helper.js";
|
|
11
12
|
|
|
12
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
14
|
const __dirname = path.dirname(__filename);
|
|
@@ -49,11 +50,15 @@ async function uploadFile(filePath, requestId = null, hash = null) {
|
|
|
49
50
|
return response;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
// Setup: Create test directory
|
|
53
|
+
// Setup: Create test directory and start server
|
|
53
54
|
test.before(async (t) => {
|
|
54
|
-
|
|
55
|
-
await
|
|
56
|
-
|
|
55
|
+
await startTestServer();
|
|
56
|
+
await setupTestDirectory(t);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Clean up server after tests
|
|
60
|
+
test.after.always(async () => {
|
|
61
|
+
await stopTestServer();
|
|
57
62
|
});
|
|
58
63
|
|
|
59
64
|
// Test: Document processing with save=true
|
|
@@ -247,28 +252,57 @@ test.serial("should fetch remote file", async (t) => {
|
|
|
247
252
|
test.serial("should cache remote files in Redis", async (t) => {
|
|
248
253
|
const requestId = uuidv4();
|
|
249
254
|
const hash = "test-cache-" + uuidv4();
|
|
255
|
+
const testContent = "This is test content for caching";
|
|
256
|
+
const testUrl = "https://test-server.example.com/test.txt";
|
|
257
|
+
|
|
258
|
+
// Mock the external HTTP requests (HEAD for validation and GET for fetching)
|
|
259
|
+
// We need multiple HEAD mocks because each request validates the URL
|
|
260
|
+
const scope = nock("https://test-server.example.com")
|
|
261
|
+
.persist() // Allow the mocks to be reused
|
|
262
|
+
.head("/test.txt")
|
|
263
|
+
.reply(200, "", {
|
|
264
|
+
"Content-Type": "text/plain",
|
|
265
|
+
"Content-Length": testContent.length.toString(),
|
|
266
|
+
})
|
|
267
|
+
.get("/test.txt")
|
|
268
|
+
.reply(200, testContent, {
|
|
269
|
+
"Content-Type": "text/plain",
|
|
270
|
+
"Content-Length": testContent.length.toString(),
|
|
271
|
+
});
|
|
250
272
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
273
|
+
try {
|
|
274
|
+
// First request should fetch and cache the file with hash
|
|
275
|
+
const firstResponse = await axios.get(baseUrl, {
|
|
276
|
+
params: {
|
|
277
|
+
fetch: testUrl,
|
|
278
|
+
requestId,
|
|
279
|
+
hash,
|
|
280
|
+
timeout: 10000,
|
|
281
|
+
},
|
|
282
|
+
validateStatus: (status) => true,
|
|
283
|
+
});
|
|
261
284
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
params: {
|
|
265
|
-
hash,
|
|
266
|
-
checkHash: true,
|
|
267
|
-
},
|
|
268
|
-
validateStatus: (status) => true,
|
|
269
|
-
});
|
|
285
|
+
t.is(firstResponse.status, 200, "Should successfully fetch and cache remote file");
|
|
286
|
+
t.truthy(firstResponse.data.url, "Should return file URL");
|
|
270
287
|
|
|
271
|
-
|
|
288
|
+
// Second request should return cached result using hash
|
|
289
|
+
const secondResponse = await axios.get(baseUrl, {
|
|
290
|
+
params: {
|
|
291
|
+
hash,
|
|
292
|
+
checkHash: true,
|
|
293
|
+
},
|
|
294
|
+
validateStatus: (status) => true,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
t.is(secondResponse.status, 200, "Should return cached file from Redis");
|
|
298
|
+
t.truthy(secondResponse.data.url, "Should return cached file URL");
|
|
299
|
+
|
|
300
|
+
// Cleanup
|
|
301
|
+
await cleanupHashAndFile(hash, secondResponse.data.url, baseUrl);
|
|
302
|
+
} finally {
|
|
303
|
+
// Clean up nock
|
|
304
|
+
nock.cleanAll();
|
|
305
|
+
}
|
|
272
306
|
});
|
|
273
307
|
|
|
274
308
|
// Test: Error cases for invalid URLs
|
|
@@ -9,6 +9,9 @@ import { port } from "../src/start.js";
|
|
|
9
9
|
import {
|
|
10
10
|
cleanupHashAndFile,
|
|
11
11
|
getFolderNameFromUrl,
|
|
12
|
+
startTestServer,
|
|
13
|
+
stopTestServer,
|
|
14
|
+
setupTestDirectory,
|
|
12
15
|
} from "./testUtils.helper.js";
|
|
13
16
|
|
|
14
17
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -55,11 +58,15 @@ async function uploadFile(filePath, requestId = null, hash = null) {
|
|
|
55
58
|
return response;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
// Setup: Create test directory
|
|
61
|
+
// Setup: Create test directory and start server
|
|
59
62
|
test.before(async (t) => {
|
|
60
|
-
|
|
61
|
-
await
|
|
62
|
-
|
|
63
|
+
await startTestServer();
|
|
64
|
+
await setupTestDirectory(t);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Clean up server after tests
|
|
68
|
+
test.after.always(async () => {
|
|
69
|
+
await stopTestServer();
|
|
63
70
|
});
|
|
64
71
|
|
|
65
72
|
// Test: Upload with hash and verify Redis storage
|