diff --git a/server/controllers/fileController.js b/server/controllers/fileController.js index 25f3f69..e42d8e6 100644 --- a/server/controllers/fileController.js +++ b/server/controllers/fileController.js @@ -27,7 +27,7 @@ export const getUserFiles = async (req, res, next) => { const userId = req.user.id; const { folderId, page = 1, limit = 50 } = req.query; // 'null' string or objectId - const query = { userId, isDeleted: false }; + const query = { userId, isDeleted: false, status: { $ne: "PENDING" } }; if (folderId !== undefined) { query.folderId = folderId === "null" || folderId === "" ? null : folderId; @@ -110,6 +110,7 @@ if (checksum) { const duplicateFile = await File.findOne({ checksum, userId, + status: { $ne: "PENDING" }, }); if (duplicateFile) { @@ -125,7 +126,54 @@ if (checksum) { } } - const existingFile = await File.findOne({ fileName, userId }); + // Try to find a pending file to complete + const pendingFile = await File.findOne({ + fileName, + userId, + status: "PENDING", + }).sort({ updatedAt: -1 }); + + if (pendingFile) { + let hashedPassword = null; + if (password) { + const salt = await bcrypt.genSalt(10); + hashedPassword = await bcrypt.hash(password, salt); + } + + // Update the pending file with final completed info + pendingFile.fileUrl = fileUrl; + pendingFile.fileType = fileType || pendingFile.fileType; + pendingFile.fileSize = fileSize || pendingFile.fileSize; + pendingFile.fileSizeBytes = fileSizeBytes || pendingFile.fileSizeBytes; + pendingFile.checksum = checksum || pendingFile.checksum; + pendingFile.status = "COMPLETED"; + pendingFile.folderId = folderId || pendingFile.folderId; + pendingFile.tags = Array.isArray(tags) ? tags : []; + pendingFile.password = hashedPassword; + + await pendingFile.save(); + + // Enqueue file for malware scanning and document indexing + try { + await enqueueScan(pendingFile._id); + await enqueueIndex(pendingFile._id); + } catch (queueErr) { + console.error("Failed to enqueue background jobs:", queueErr); + } + + const io = req.app.get("io"); + if (io) io.to(`user_${userId}`).emit("FILE_UPLOADED", pendingFile); + + await invalidateDashboardStats(userId); + + return res.status(201).json({ + success: true, + message: "File info saved successfully", + file: pendingFile, + }); + } + + const existingFile = await File.findOne({ fileName, userId, status: { $ne: "PENDING" } }); if (existingFile) { // Archive the current state into versions array @@ -583,9 +631,9 @@ export const getFileStats = async (req, res, next) => { } } - const totalFiles = await File.countDocuments({ userId }); + const totalFiles = await File.countDocuments({ userId, status: { $ne: "PENDING" } }); const aggregations = await File.aggregate([ - { $match: { userId } }, + { $match: { userId, status: { $ne: "PENDING" } } }, { $group: { _id: null, @@ -750,7 +798,7 @@ export const getFavoriteFiles = async (req, res, next) => { const { page = 1, limit = 50 } = req.query; const skip = (parseInt(page) - 1) * parseInt(limit); - const query = { userId, isFavorite: true }; + const query = { userId, isFavorite: true, status: { $ne: "PENDING" } }; const files = await File.find(query) .sort({ updatedAt: -1 }) .skip(skip) @@ -816,6 +864,7 @@ export const getFilesByTag = async (req, res, next) => { const files = await File.find({ userId, + status: { $ne: "PENDING" }, tags: { $in: [tag] }, }).sort({ createdAt: -1 }); @@ -1007,6 +1056,7 @@ export const searchFiles = async (req, res, next) => { const query = { userId, isDeleted: false, + status: { $ne: "PENDING" }, fileName: { $regex: q, $options: "i" } }; diff --git a/server/controllers/folderController.js b/server/controllers/folderController.js index e930cf5..5b24994 100644 --- a/server/controllers/folderController.js +++ b/server/controllers/folderController.js @@ -83,7 +83,7 @@ export const getFolderContents = async (req, res, next) => { const subfolders = await Folder.find({ parentId: folderId, userId }).sort({ name: 1 }); const skip = (parseInt(page) - 1) * parseInt(limit); - const query = { folderId: folderId, userId, isDeleted: false }; + const query = { folderId: folderId, userId, isDeleted: false, status: { $ne: "PENDING" } }; const files = await File.find(query) .sort({ createdAt: -1 }) diff --git a/server/controllers/uploadController.js b/server/controllers/uploadController.js index 479edcc..66caf9b 100644 --- a/server/controllers/uploadController.js +++ b/server/controllers/uploadController.js @@ -105,7 +105,7 @@ async function loadUploadSessionForChunk(req, res, next) { } } -export const initUpload = async (req, res) => { +export const initUpload = async (req, res, next) => { try { const { fileName, fileSizeBytes, mimeType, expectedChecksum, sessionId, forceUpload } = req.body; const userId = req.user.id; @@ -165,6 +165,19 @@ export const initUpload = async (req, res) => { const totalChunks = calculateTotalChunks(Number(fileSizeBytes)); const expiresAt = new Date(Date.now() + UPLOAD_SESSION_TTL_HOURS * 60 * 60 * 1000); + // Create the File record in PENDING state + const pendingFile = new File({ + fileName, + fileUrl: `${req.protocol || "http"}://${(typeof req.get === "function" ? req.get("host") : null) || "localhost"}/pending/${uploadId}`, + fileType: mimeType ? (mimeType.split("/")[0] || "application") : "application", + fileSize: "0 KB", + fileSizeBytes: Number(fileSizeBytes), + userId, + status: "PENDING", + folderId: req.body.folderId || null, + }); + await pendingFile.save(); + const session = await UploadSession.create({ userId, uploadId, @@ -177,6 +190,7 @@ export const initUpload = async (req, res) => { tempDir, expiresAt, status: "uploading", + fileId: pendingFile._id, }); res.status(201).json({ @@ -188,7 +202,7 @@ export const initUpload = async (req, res) => { } }; -export const getUploadStatus = async (req, res) => { +export const getUploadStatus = async (req, res, next) => { try { const session = await UploadSession.findOne({ _id: req.params.sessionId, @@ -216,7 +230,7 @@ export const getUploadStatus = async (req, res) => { } }; -export const getResumableUploads = async (req, res) => { +export const getResumableUploads = async (req, res, next) => { try { const sessions = await UploadSession.find({ userId: req.user.id, diff --git a/server/index.js b/server/index.js index fb20cb6..f3a4471 100644 --- a/server/index.js +++ b/server/index.js @@ -21,6 +21,7 @@ import { startWebhookWorker } from "./jobs/webhookWorker.js"; import webhookRoutes from "./routes/webhooks.js"; import { startUploadSessionCleanupJob } from "./jobs/uploadSessionCleanup.js"; import { initQuotaResetJob } from "./jobs/quotaResetJob.js"; +import { initUploadCleanupWorker } from "./jobs/uploadCleanupWorker.js"; import { connectRedis } from "./config/redis.js"; import { ensureUploadTempRoot } from "./utils/chunkStorage.js"; import { @@ -53,6 +54,7 @@ startIndexWorker(); startWebhookWorker(); startUploadSessionCleanupJob(); initQuotaResetJob(); +initUploadCleanupWorker(); ensureUploadTempRoot().catch(console.error); app.use(express.json()); diff --git a/server/jobs/indexQueue.js b/server/jobs/indexQueue.js index c0f499b..9187e7f 100644 --- a/server/jobs/indexQueue.js +++ b/server/jobs/indexQueue.js @@ -11,6 +11,10 @@ const connection = { export const indexQueue = new Queue("document-indexing", { connection }); export const enqueueIndex = async (fileId) => { + if (process.env.ENABLE_DOCUMENT_INDEXING === "false") { + console.log("Document indexing is disabled. Skipping for file:", fileId); + return; + } console.log(`[Queue] Enqueuing document indexing job for file: ${fileId}`); await indexQueue.add("index-file", { fileId }, { attempts: 3, diff --git a/server/jobs/uploadCleanupWorker.js b/server/jobs/uploadCleanupWorker.js new file mode 100644 index 0000000..7276026 --- /dev/null +++ b/server/jobs/uploadCleanupWorker.js @@ -0,0 +1,47 @@ +import cron from "node-cron"; +import File from "../models/File.js"; +import UploadSession from "../models/UploadSession.js"; +import { removeSessionTempDir } from "../utils/chunkStorage.js"; + +/** + * Initializes the background cron worker to clean up stale PENDING uploads. + * Runs daily at midnight (0 0 * * *). + */ +export const initUploadCleanupWorker = () => { + cron.schedule("0 0 * * *", async () => { + const thresholdTime = new Date(Date.now() - 24 * 60 * 60 * 1000); // 24 Hours ago + console.log(`[Cleanup Worker] Starting sweep of stale PENDING uploads older than ${thresholdTime.toISOString()}`); + + try { + // Find all incomplete files older than 24 hours + const staleUploads = await File.find({ + status: "PENDING", + updatedAt: { $lt: thresholdTime } + }); + + let clearedCount = 0; + for (const file of staleUploads) { + try { + // Find matching UploadSession to cleanup temp directory + const session = await UploadSession.findOne({ fileId: file._id }); + if (session) { + if (session.tempDir) { + await removeSessionTempDir(session.tempDir); + } + await UploadSession.findByIdAndDelete(session._id); + } + + // Delete the stale File document from MongoDB + await File.findByIdAndDelete(file._id); + clearedCount++; + } catch (fileErr) { + console.error(`[Cleanup Worker] Failed to clean up file ${file._id}:`, fileErr); + } + } + + console.log(`[Cleanup Worker] Successfully cleared ${clearedCount} stale uploads.`); + } catch (error) { + console.error("[Cleanup Worker Error]:", error); + } + }); +}; diff --git a/server/models/File.js b/server/models/File.js index c7ac6ec..11731c7 100644 --- a/server/models/File.js +++ b/server/models/File.js @@ -170,6 +170,12 @@ const fileSchema = new mongoose.Schema( enum: ['pending', 'processing', 'completed', 'failed', 'skipped'], default: 'pending', }, + status: { + type: String, + enum: ['PENDING', 'COMPLETED'], + default: 'PENDING', + index: true, + }, }, { timestamps: true, diff --git a/server/models/UploadSession.js b/server/models/UploadSession.js index e52816c..4893293 100644 --- a/server/models/UploadSession.js +++ b/server/models/UploadSession.js @@ -54,6 +54,11 @@ const uploadSessionSchema = new mongoose.Schema( type: String, required: true, }, + fileId: { + type: mongoose.Schema.Types.ObjectId, + ref: "File", + default: null, + }, fileUrl: { type: String, default: null, diff --git a/server/package-lock.json b/server/package-lock.json index f309119..13bdeaa 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -490,40 +490,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@hapi/address": { "version": "5.1.1", "resolved": "https://registry.npmmirror.com/@hapi/address/-/address-5.1.1.tgz", @@ -1219,17 +1185,6 @@ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "dev": true, @@ -1343,25 +1298,6 @@ "dev": true, "license": "ISC" }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.12.2", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.13", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", diff --git a/server/test-cleanup-worker.js b/server/test-cleanup-worker.js new file mode 100644 index 0000000..8f613aa --- /dev/null +++ b/server/test-cleanup-worker.js @@ -0,0 +1,315 @@ +process.env.ENABLE_DOCUMENT_INDEXING = "false"; +process.env.ENABLE_MALWARE_SCANNING = "false"; + +import mongoose from "mongoose"; +import File from "./models/File.js"; +import UploadSession from "./models/UploadSession.js"; +import { initUpload } from "./controllers/uploadController.js"; +import { saveFileInfo } from "./controllers/fileController.js"; +import { initUploadCleanupWorker } from "./jobs/uploadCleanupWorker.js"; + +// Mock database storage +const filesDb = new Map(); +const sessionsDb = new Map(); +let cronCallback = null; + +// Monkey patch ObjectId validation for testing +mongoose.Types.ObjectId.isValid = () => true; + +// Mock node-cron +import cron from "node-cron"; +cron.schedule = (pattern, callback) => { + cronCallback = callback; + console.log(`[Mock Cron] Scheduled pattern: ${pattern}`); + return { start: () => {} }; +}; + +const createMockQuery = (result) => { + const queryObj = { + select: function() { return queryObj; }, + populate: function() { return queryObj; }, + sort: function() { return queryObj; }, + then: function(onFulfilled, onRejected) { + return Promise.resolve(result).then(onFulfilled, onRejected); + }, + catch: function(onRejected) { + return Promise.resolve(result).catch(onRejected); + } + }; + return queryObj; +}; + +// Setup Mocking for File model +File.findOne = (query) => { + console.log("[Mock DB File.findOne] Query:", query); + for (const [id, doc] of filesDb.entries()) { + let match = true; + for (let key in query) { + if (key === "status" && typeof query[key] === "object") { + if (query[key].$ne && doc.status === query[key].$ne) match = false; + } else { + const docVal = doc[key] && typeof doc[key] === 'object' && doc[key].toString ? doc[key].toString() : doc[key]; + const queryVal = query[key] && typeof query[key] === 'object' && query[key].toString ? query[key].toString() : query[key]; + if (docVal !== queryVal) { + match = false; + } + } + } + console.log(`- Comparing with doc ${id} (name: ${doc.fileName}, status: ${doc.status}, userId: ${doc.userId}) -> match: ${match}`); + if (match) { + doc.save = async function() { + filesDb.set(this._id.toString(), this); + return this; + }; + return createMockQuery(doc); + } + } + return createMockQuery(null); +}; + +File.find = async (query) => { + const matched = []; + for (const [id, doc] of filesDb.entries()) { + let match = true; + for (let key in query) { + if (key === "updatedAt" && query[key].$lt) { + if (new Date(doc.updatedAt) >= new Date(query[key].$lt)) match = false; + } else if (key === "status" && typeof query[key] === "object") { + if (query[key].$ne && doc.status === query[key].$ne) match = false; + } else { + const docVal = doc[key] && typeof doc[key] === 'object' && doc[key].toString ? doc[key].toString() : doc[key]; + const queryVal = query[key] && typeof query[key] === 'object' && query[key].toString ? query[key].toString() : query[key]; + if (docVal !== queryVal) { + match = false; + } + } + } + if (match) matched.push(doc); + } + return matched; +}; + +File.findByIdAndDelete = async (id) => { + console.log(`[Mock DB] Deleting File ID: ${id}`); + filesDb.delete(id.toString()); + return true; +}; + +// We mock the constructor and save methods on File prototype +File.prototype.save = async function() { + if (!this._id) { + this._id = new mongoose.Types.ObjectId().toString(); + } + this.createdAt = this.createdAt || new Date(); + this.updatedAt = new Date(); + filesDb.set(this._id.toString(), this); + return this; +}; + +// Setup Mocking for UploadSession model +UploadSession.create = async (data) => { + const session = { + _id: new mongoose.Types.ObjectId().toString(), + ...data, + receivedChunks: [], + uploadedBytes: 0, + expiresAt: new Date(Date.now() + 3600000), + save: async function() { + sessionsDb.set(this._id, this); + return this; + } + }; + sessionsDb.set(session._id, session); + return session; +}; + +UploadSession.findOne = (query) => { + for (const [id, doc] of sessionsDb.entries()) { + let match = true; + for (let key in query) { + if (key === "status" && typeof query[key] === "object") { + if (query[key].$in && !query[key].$in.includes(doc.status)) match = false; + } else if (doc[key] && doc[key].toString() !== query[key].toString()) { + match = false; + } + } + if (match) return createMockQuery(doc); + } + return createMockQuery(null); +}; + +UploadSession.findByIdAndDelete = async (id) => { + console.log(`[Mock DB] Deleting UploadSession ID: ${id}`); + sessionsDb.delete(id.toString()); + return true; +}; + +// Mock Express req/res +const mockRes = () => { + const res = { statusCode: 200 }; + res.status = (code) => { + res.statusCode = code; + return res; + }; + res.json = (data) => { + res.body = data; + return res; + }; + return res; +}; + +const mockNext = (err) => { + if (err) throw err; +}; + +async function runTests() { + console.log("--- Starting Mock Tests for Stale Upload Cleanup ---"); + + try { + // Enable cleanup worker registration (registers mock cron) + initUploadCleanupWorker(); + + console.log("\n--- Test Case 1: Initialize Upload ---"); + let req1 = { + user: { id: "60c72b2f9b1d8e1f5c8b4567" }, + get: (header) => "localhost", + body: { + fileName: "large_movie.mp4", + fileSizeBytes: 104857600, // 100 MB + mimeType: "video/mp4" + } + }; + let res1 = mockRes(); + await initUpload(req1, res1, mockNext); + + console.log("initUpload Response status:", res1.statusCode); + console.log("initUpload Response session ID:", res1.body.session.sessionId); + + // Verify a PENDING File document was created + const pendingFile = Array.from(filesDb.values()).find(f => f.status === "PENDING"); + if (!pendingFile) throw new Error("Test 1 Failed: No PENDING file was created!"); + console.log(`āœ… PENDING File document created successfully. ID: ${pendingFile._id}`); + + // Verify session references the pending file + const activeSession = sessionsDb.get(res1.body.session.sessionId); + if (!activeSession || activeSession.fileId.toString() !== pendingFile._id.toString()) { + throw new Error("Test 1 Failed: UploadSession does not reference the PENDING file ID."); + } + console.log(`āœ… UploadSession successfully linked to PENDING File ID.`); + + console.log("\n--- Test Case 2: Save File Info (Transitions to COMPLETED) ---"); + let req2 = { + user: { id: "60c72b2f9b1d8e1f5c8b4567" }, + body: { + fileName: "large_movie.mp4", + fileUrl: "https://cloudinary.com/secureshare/large_movie.mp4", + fileType: "video", + fileSize: "100 MB", + fileSizeBytes: 104857600, + checksum: "sha256checksumvalue" + }, + app: { + get: () => ({ to: () => ({ emit: () => {} }) }) // mock socket.io + } + }; + let res2 = mockRes(); + await saveFileInfo(req2, res2, mockNext); + + console.log("saveFileInfo Response status:", res2.statusCode); + const completedFile = filesDb.get(pendingFile._id.toString()); + if (!completedFile || completedFile.status !== "COMPLETED") { + throw new Error("Test 2 Failed: File status did not transition to COMPLETED!"); + } + if (completedFile.fileUrl !== req2.body.fileUrl) { + throw new Error("Test 2 Failed: File URL was not updated correctly!"); + } + console.log("āœ… File status transitioned to COMPLETED and data updated successfully."); + + console.log("\n--- Test Case 3: Sweep Stale PENDING Uploads via Cron Worker ---"); + // Clear DB and create one stale pending file and one recent pending file + filesDb.clear(); + sessionsDb.clear(); + + // Stale upload (25 hours ago) + const staleFileId = new mongoose.Types.ObjectId().toString(); + const staleFile = { + _id: staleFileId, + fileName: "abandoned.txt", + fileUrl: "http://temp/abandoned", + fileType: "text", + fileSize: "5 KB", + fileSizeBytes: 5120, + userId: "60c72b2f9b1d8e1f5c8b4567", + status: "PENDING", + createdAt: new Date(Date.now() - 25 * 60 * 60 * 1000), + updatedAt: new Date(Date.now() - 25 * 60 * 60 * 1000) + }; + filesDb.set(staleFileId, staleFile); + + const staleSessionId = new mongoose.Types.ObjectId().toString(); + const staleSession = { + _id: staleSessionId, + userId: "60c72b2f9b1d8e1f5c8b4567", + fileName: "abandoned.txt", + tempDir: "/tmp/upload-stale-dir", + fileId: staleFileId, + status: "uploading" + }; + sessionsDb.set(staleSessionId, staleSession); + + // Recent upload (1 hour ago) + const recentFileId = new mongoose.Types.ObjectId().toString(); + const recentFile = { + _id: recentFileId, + fileName: "active.txt", + fileUrl: "http://temp/active", + fileType: "text", + fileSize: "2 KB", + fileSizeBytes: 2048, + userId: "60c72b2f9b1d8e1f5c8b4567", + status: "PENDING", + createdAt: new Date(Date.now() - 1 * 60 * 60 * 1000), + updatedAt: new Date(Date.now() - 1 * 60 * 60 * 1000) + }; + filesDb.set(recentFileId, recentFile); + + const recentSessionId = new mongoose.Types.ObjectId().toString(); + const recentSession = { + _id: recentSessionId, + userId: "60c72b2f9b1d8e1f5c8b4567", + fileName: "active.txt", + tempDir: "/tmp/upload-active-dir", + fileId: recentFileId, + status: "uploading" + }; + sessionsDb.set(recentSessionId, recentSession); + + console.log(`Initial DB state: ${filesDb.size} files, ${sessionsDb.size} sessions.`); + + // Trigger cron sweep callback + if (!cronCallback) throw new Error("Test 3 Failed: Cron callback not captured!"); + await cronCallback(); + + console.log(`Post-sweep DB state: ${filesDb.size} files, ${sessionsDb.size} sessions.`); + + // Check if stale upload is gone + if (filesDb.has(staleFileId) || sessionsDb.has(staleSessionId)) { + throw new Error("Test 3 Failed: Stale file or session was not deleted!"); + } + + // Check if recent upload is kept + if (!filesDb.has(recentFileId) || !sessionsDb.has(recentSessionId)) { + throw new Error("Test 3 Failed: Recent pending file or session was deleted!"); + } + + console.log("āœ… Cron sweep cleared stale uploads, keeping active ones."); + console.log("\nšŸŽ‰ ALL CLEANUP WORKER TESTS PASSED SUCCESSFULLY!"); + process.exit(0); + + } catch (error) { + console.error("\nāŒ Test Case Failed:", error.message || error); + process.exit(1); + } +} + +runTests(); diff --git a/server/test.js b/server/test.js index 26c6b09..5003bf1 100644 --- a/server/test.js +++ b/server/test.js @@ -1,3 +1,6 @@ +process.env.ENABLE_DOCUMENT_INDEXING = "false"; +process.env.ENABLE_MALWARE_SCANNING = "false"; + import { saveFileInfo, getFileVersions, restoreFileVersion, updateFilePassword, getSharedFileById, verifySharedFilePassword } from './controllers/fileController.js'; import File from './models/File.js'; import mongoose from 'mongoose';