Skip to content
Draft
6,132 changes: 5,699 additions & 433 deletions node_modules/.package-lock.json

Large diffs are not rendered by default.

7,033 changes: 5,195 additions & 1,838 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
},
"devDependencies": {
"@types/express": "^4.17.17",
"jest": "^30.0.3",
"typescript": "^4.9.5",
"vitest": "^0.34.6"
}
}
}
62 changes: 62 additions & 0 deletions src/fileErrorHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const fs = require('fs/promises');

/**
* Custom error class for file access and permission errors
*/
class FileAccessError extends Error {
constructor(message) {
super(message);
this.name = 'FileAccessError';
}
}

/**
* Middleware to handle file access and permission errors
* @param {Error} err - Error object
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @param {Function} next - Express next function
*/
const fileErrorHandler = (err, req, res, next) => {
// Log the error for internal tracking
console.error(`File Access Error: ${err.message}`);

// Handle specific file-related errors
if (err instanceof FileAccessError) {
return res.status(403).json({
error: 'File Access Denied',
message: 'Unable to access the requested file.'
});
}

// Fallback to generic server error
res.status(500).json({
error: 'Internal Server Error',
message: 'An unexpected error occurred while processing the file.'
});
};

/**
* Safely check file access and permissions
* @param {string} filePath - Path to the file to check
* @throws {FileAccessError} If file cannot be accessed
*/
const checkFileAccess = async (filePath) => {
try {
// Attempt to access file metadata
await fs.access(filePath);
await fs.stat(filePath);
} catch (error) {
// Translate system errors to custom FileAccessError
if (error instanceof Error) {
throw new FileAccessError(`Cannot access file: ${error.message}`);
}
throw new FileAccessError('Unknown file access error');
}
};

module.exports = {
FileAccessError,
fileErrorHandler,
checkFileAccess
};
6 changes: 3 additions & 3 deletions src/utils/error-response.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const ERROR_TYPES = {
* @param {Object} [additionalDetails] - Optional additional error details
* @returns {Object} HTTP response with error details
*/
export function createErrorResponse(res, type, customMessage, additionalDetails = {}) {
function createErrorResponse(res, type, customMessage, additionalDetails = {}) {
// Validate input
if (!ERROR_TYPES[type]) {
type = 'INTERNAL_SERVER_ERROR';
Expand All @@ -61,7 +61,7 @@ export function createErrorResponse(res, type, customMessage, additionalDetails
* @param {Object} [context] - Additional context for logging
* @returns {Object} Structured error object
*/
export function createErrorLog(type, message, context = {}) {
function createErrorLog(type, message, context = {}) {
return {
timestamp: new Date().toISOString(),
type,
Expand All @@ -70,7 +70,7 @@ export function createErrorLog(type, message, context = {}) {
};
}

export default {
module.exports = {
createErrorResponse,
createErrorLog,
ERROR_TYPES
Expand Down
7 changes: 3 additions & 4 deletions test/error-response.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { describe, it, expect, vi } from 'vitest';
import { createErrorResponse, createErrorLog } from '../src/utils/error-response.js';
const { createErrorResponse, createErrorLog } = require('../src/utils/error-response.js');

describe('Error Response Utility', () => {
// Mock Express response object
const createMockResponse = () => ({
status: vi.fn().mockReturnThis(),
json: vi.fn().mockReturnThis()
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis()
});

describe('createErrorResponse', () => {
Expand Down
30 changes: 30 additions & 0 deletions test/fileErrorHandler.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const fs = require('fs/promises');
const { checkFileAccess, FileAccessError } = require('../src/fileErrorHandler');

describe('File Error Handling', () => {
beforeEach(() => {
jest.restoreAllMocks();
});

it('should throw FileAccessError when file cannot be accessed', async () => {
// Mock fs.access to simulate permission denied
jest.spyOn(fs, 'access').mockRejectedValue(new Error('Permission denied'));

await expect(checkFileAccess('/path/to/nonexistent/file')).rejects.toThrow(FileAccessError);
});

it('should successfully check file access when file exists', async () => {
// Create mock for successful file access
jest.spyOn(fs, 'access').mockResolvedValue();
jest.spyOn(fs, 'stat').mockResolvedValue({});

await expect(checkFileAccess('/path/to/valid/file')).resolves.not.toThrow();
});

it('should handle unknown errors during file access', async () => {
// Simulate an unknown error
jest.spyOn(fs, 'access').mockRejectedValue(new Error());

await expect(checkFileAccess('/path/to/file')).rejects.toThrow(FileAccessError);
});
});
27 changes: 0 additions & 27 deletions tests/fileErrorHandler.test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +0,0 @@
import { describe, it, expect, vi } from 'vitest';
import fs from 'fs/promises';
import { checkFileAccess, FileAccessError } from '../src/fileErrorHandler';

describe('File Error Handling', () => {
it('should throw FileAccessError when file cannot be accessed', async () => {
// Mock fs.access to simulate permission denied
vi.spyOn(fs, 'access').mockRejectedValue(new Error('Permission denied'));

await expect(checkFileAccess('/path/to/nonexistent/file')).rejects.toThrow(FileAccessError);
});

it('should successfully check file access when file exists', async () => {
// Create mock for successful file access
vi.spyOn(fs, 'access').mockResolvedValue(undefined);
vi.spyOn(fs, 'stat').mockResolvedValue({} as any);

await expect(checkFileAccess('/path/to/valid/file')).resolves.not.toThrow();
});

it('should handle unknown errors during file access', async () => {
// Simulate an unknown error
vi.spyOn(fs, 'access').mockRejectedValue(new Error());

await expect(checkFileAccess('/path/to/file')).rejects.toThrow(FileAccessError);
});
});