Skip to content

Commit

Permalink
Implement Financial Report CRUD API with comprehensive testing for re…
Browse files Browse the repository at this point in the history
…ad, update, and delete operations
  • Loading branch information
urbantech committed Nov 6, 2024
1 parent 5a330c5 commit 761b249
Show file tree
Hide file tree
Showing 14 changed files with 920 additions and 58 deletions.
173 changes: 173 additions & 0 deletions __tests__/ComprehensiveController.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
const { createFinancialReport } = require('../controllers/financialReportingController');
const FinancialReport = require('../models/financialReport');
const httpMocks = require('node-mocks-http');
const mongoose = require('mongoose');

// Mock the entire model
jest.mock('../models/financialReport');

describe('createFinancialReport Controller', () => {
let req, res, next;

beforeEach(() => {
req = httpMocks.createRequest();
res = httpMocks.createResponse();
next = jest.fn();
jest.clearAllMocks();
});

const validTestData = {
ReportID: 'test-id',
Type: 'Annual',
Data: {
revenue: { q1: 250000, q2: 250000, q3: 250000, q4: 250000 },
expenses: { q1: 125000, q2: 125000, q3: 125000, q4: 125000 }
},
TotalRevenue: '1000000.00',
TotalExpenses: '500000.00',
NetIncome: '500000.00',
EquitySummary: ['uuid1', 'uuid2'],
Timestamp: new Date().toISOString()
};

describe('Successful Operations', () => {
it('should create a new financial report and return 201 status', async () => {
req.body = validTestData;
const mockSave = jest.fn().mockResolvedValue(validTestData);
FinancialReport.mockImplementation(() => ({ save: mockSave }));

await createFinancialReport(req, res, next);

const responseData = JSON.parse(res._getData());
expect(FinancialReport).toHaveBeenCalledWith(validTestData);
expect(mockSave).toHaveBeenCalled();
expect(res.statusCode).toBe(201);
expect(responseData).toEqual(validTestData);
expect(next).not.toHaveBeenCalled();
});

it('should handle quarterly reports correctly', async () => {
const quarterlyData = { ...validTestData, Type: 'Quarterly' };
req.body = quarterlyData;
const mockSave = jest.fn().mockResolvedValue(quarterlyData);
FinancialReport.mockImplementation(() => ({ save: mockSave }));

await createFinancialReport(req, res, next);

expect(res.statusCode).toBe(201);
expect(JSON.parse(res._getData())).toEqual(quarterlyData);
});
});

describe('Validation Errors', () => {
it('should handle invalid report type', async () => {
const invalidData = { ...validTestData, Type: 'Monthly' };
req.body = invalidData;

const validationError = new mongoose.Error.ValidationError();
validationError.errors.Type = new mongoose.Error.ValidatorError({
message: 'Invalid report type. Must be either Annual or Quarterly',
path: 'Type'
});

const mockSave = jest.fn().mockRejectedValue(validationError);
FinancialReport.mockImplementation(() => ({ save: mockSave }));

await createFinancialReport(req, res, next);

expect(next).toHaveBeenCalledWith(expect.objectContaining({
name: 'ValidationError'
}));
});

it('should handle missing required fields', async () => {
const { TotalRevenue, ...incompleteData } = validTestData;
req.body = incompleteData;

const validationError = new mongoose.Error.ValidationError();
validationError.errors.TotalRevenue = new mongoose.Error.ValidatorError({
message: 'TotalRevenue is required',
path: 'TotalRevenue'
});

const mockSave = jest.fn().mockRejectedValue(validationError);
FinancialReport.mockImplementation(() => ({ save: mockSave }));

await createFinancialReport(req, res, next);

expect(next).toHaveBeenCalledWith(expect.objectContaining({
name: 'ValidationError'
}));
});
});

describe('Data Integrity', () => {
it('should handle duplicate ReportID', async () => {
req.body = validTestData;

// Create a duplicate key error that matches Mongoose's error structure
const duplicateError = new Error('E11000 duplicate key error');
duplicateError.code = 11000;
duplicateError.index = 0;
duplicateError.keyPattern = { ReportID: 1 };
duplicateError.keyValue = { ReportID: validTestData.ReportID };

const mockSave = jest.fn().mockRejectedValue(duplicateError);
FinancialReport.mockImplementation(() => ({ save: mockSave }));

await createFinancialReport(req, res, next);

expect(next).toHaveBeenCalledWith(expect.objectContaining({
code: 11000,
keyPattern: { ReportID: 1 },
keyValue: { ReportID: validTestData.ReportID }
}));
});

it('should verify financial calculations', async () => {
const invalidCalculations = {
...validTestData,
TotalRevenue: '1000000.00',
TotalExpenses: '500000.00',
NetIncome: '400000.00' // Incorrect net income
};
req.body = invalidCalculations;

const validationError = new Error('Net income does not match revenue minus expenses');
const mockSave = jest.fn().mockRejectedValue(validationError);
FinancialReport.mockImplementation(() => ({ save: mockSave }));

await createFinancialReport(req, res, next);

expect(next).toHaveBeenCalledWith(expect.objectContaining({
message: 'Net income does not match revenue minus expenses'
}));
});
});

describe('Error Handling', () => {
it('should handle database connection errors', async () => {
req.body = validTestData;

const dbError = new Error('Database connection failed');
const mockSave = jest.fn().mockRejectedValue(dbError);
FinancialReport.mockImplementation(() => ({ save: mockSave }));

await createFinancialReport(req, res, next);

expect(next).toHaveBeenCalledWith(dbError);
});

it('should handle unexpected errors', async () => {
req.body = validTestData;

const unexpectedError = new Error('Unexpected server error');
const mockSave = jest.fn().mockRejectedValue(unexpectedError);
FinancialReport.mockImplementation(() => ({ save: mockSave }));

await createFinancialReport(req, res, next);

expect(next).toHaveBeenCalledWith(unexpectedError);
});
});
});
9 changes: 6 additions & 3 deletions __tests__/authController.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const mongoose = require('mongoose');
const authController = require('../controllers/authController');
const User = require('../models/userModel');
const jwt = require('jsonwebtoken');
const { connectDB, disconnectDB } = require('../db');
const { connectDB } = require('../db');

// Set up the Express app
const app = express();
Expand All @@ -16,6 +16,9 @@ app.post('/auth/register', authController.registerUser);
app.post('/auth/login', authController.loginUser);
app.post('/auth/oauth-login', authController.oauthLogin);

// Set up environment variable for JWT_SECRET in tests
process.env.JWT_SECRET = process.env.JWT_SECRET || 'test_jwt_secret';

beforeAll(async () => {
await connectDB();
});
Expand Down Expand Up @@ -70,7 +73,7 @@ describe('Authentication API', () => {
const response = await request(app).post('/auth/login').send({
username: 'testuser',
password: 'TestPassword123',
});
});

expect(response.statusCode).toBe(200);
expect(response.body.token).toBeTruthy();
Expand All @@ -96,4 +99,4 @@ describe('Authentication API', () => {
expect(user).toBeTruthy();
expect(user.username).toBe('OAuth User');
});
});
});
109 changes: 109 additions & 0 deletions __tests__/financialReportAuth.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// __tests__/FinancialReportAuth.test.js
const {
checkUserPermissions,
validateApiKey,
authorizeReportAccess
} = require('../controllers/financialReportingController');
const httpMocks = require('node-mocks-http');
const jwt = require('jsonwebtoken');

jest.mock('jsonwebtoken');

describe('Financial Report Authorization', () => {
let req, res, next;

beforeEach(() => {
req = httpMocks.createRequest();
res = httpMocks.createResponse();
next = jest.fn();
jest.clearAllMocks();
});

describe('User Permissions', () => {
it('should authorize users with admin role', async () => {
req.user = {
role: 'admin',
permissions: ['create:reports', 'read:reports', 'update:reports', 'delete:reports']
};

await checkUserPermissions(req, res, next);
expect(next).toHaveBeenCalled();
expect(next).not.toHaveBeenCalledWith(expect.any(Error));
});

it('should restrict access for users without proper permissions', async () => {
req.user = {
role: 'user',
permissions: ['read:reports']
};
req.method = 'POST';

await checkUserPermissions(req, res, next);
expect(next).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Insufficient permissions',
statusCode: 403
})
);
});

it('should handle role-based access control', async () => {
req.user = {
role: 'financial_analyst',
permissions: ['create:reports', 'read:reports', 'update:reports']
};
req.method = 'DELETE';

await checkUserPermissions(req, res, next);
expect(next).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Insufficient permissions',
statusCode: 403
})
);
});
});

describe('API Key Validation', () => {
it('should validate valid API key', async () => {
const apiKey = 'valid-api-key-123';
req.headers = { 'x-api-key': apiKey };

jwt.verify.mockImplementation(() => ({
permissions: ['create:reports', 'read:reports']
}));

await validateApiKey(req, res, next);
expect(next).toHaveBeenCalled();
expect(next).not.toHaveBeenCalledWith(expect.any(Error));
});

it('should reject invalid API key', async () => {
const apiKey = 'invalid-api-key';
req.headers = { 'x-api-key': apiKey };

jwt.verify.mockImplementation(() => {
throw new Error('Invalid token');
});

await validateApiKey(req, res, next);
expect(next).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Invalid API key',
statusCode: 401
})
);
});
});

describe('Report Access Authorization', () => {
it('should authorize access to own reports', async () => {
const reportId = 'test-report-123';
req.params = { id: reportId };
req.user = {
id: 'user-123',
role: 'user'
};

const mockReport = {
ReportID: reportI
Loading

0 comments on commit 761b249

Please sign in to comment.