Skip to content

Commit

Permalink
Merge pull request Open-Cap-Stack#101 from Open-Cap-Stack/feature/456…
Browse files Browse the repository at this point in the history
…-financial-report-api

Feature/456 Add Financial Report Controllers and Refactor Tests for Comprehensive Coverage
  • Loading branch information
urbantech authored Nov 6, 2024
2 parents 4f02316 + 49c2965 commit c386c37
Show file tree
Hide file tree
Showing 18 changed files with 1,722 additions and 66 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');
});
});
});
119 changes: 119 additions & 0 deletions __tests__/financialReportAuth.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// __tests__/financialReportAuth.test.js
const {
checkUserPermissions,
validateApiKey,
authorizeReportAccess
} = require('../controllers/financialReportAuthController');
const FinancialReport = require('../models/financialReport');
const httpMocks = require('node-mocks-http');

jest.mock('../models/financialReport');

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
})
);
});
});

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

await validateApiKey(req, res, next);
expect(next).toHaveBeenCalled();
expect(next).not.toHaveBeenCalledWith(expect.any(Error));
expect(req.apiPermissions).toContain('read:reports');
});

it('should reject missing API key', async () => {
req.headers = {};

await validateApiKey(req, res, next);
expect(next).toHaveBeenCalledWith(
expect.objectContaining({
message: 'API key is required',
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: reportId,
userId: 'user-123'
};

FinancialReport.findOne = jest.fn().mockResolvedValue(mockReport);

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

it('should deny access to other users reports', async () => {
const reportId = 'test-report-123';
req.params = { id: reportId };
req.user = {
id: 'different-user',
role: 'user'
};

const mockReport = {
ReportID: reportId,
userId: 'user-123'
};

FinancialReport.findOne = jest.fn().mockResolvedValue(mockReport);

await authorizeReportAccess(req, res, next);
expect(next).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Unauthorized access to report',
statusCode: 403
})
);
});
});
});
Loading

0 comments on commit c386c37

Please sign in to comment.