Skip to content

Commit

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

Feature/456 Add Financial Report API with Controller, Model, Route, and Test Suite
  • Loading branch information
urbantech authored Nov 6, 2024
2 parents caf9507 + 5a330c5 commit 4f02316
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 87 deletions.
17 changes: 10 additions & 7 deletions __tests__/CompanyRoutes.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const request = require('supertest');
const { app, connectDB } = require('../app'); // Your Express app
const mongoose = require('mongoose');
const app = require('../app');
const { connectDB, disconnectDB } = require('../db');
const Company = require('../models/Company');

beforeAll(async () => {
Expand All @@ -9,11 +10,12 @@ beforeAll(async () => {
});

beforeEach(async () => {
await Company.deleteMany({}); // Clear companies collection before each test
// Clear the companies collection before each test and set up test data
await Company.deleteMany({});
const company = new Company({
companyId: 'test-company-id',
CompanyName: 'Test Company',
CompanyType: 'startup', // Assuming 'startup' is one of the valid enum values
CompanyType: 'startup',
RegisteredAddress: '123 Test Street, Test City, TC',
TaxID: '123-45-6789',
corporationDate: new Date(),
Expand All @@ -22,22 +24,23 @@ beforeEach(async () => {
});

afterAll(async () => {
await mongoose.connection.close(); // Close the database connection after all tests
// Disconnect from the database after all tests are done
await disconnectDB();
});

describe('Company API Test', () => {
describe('Company API Tests', () => {
it('should get all companies', async () => {
const res = await request(app).get('/api/companies');
expect(res.statusCode).toEqual(200);
expect(res.body).toBeInstanceOf(Array);
expect(res.body.length).toBeGreaterThan(0); // Ensure that the array is not empty
expect(res.body.length).toBeGreaterThan(0);
});

it('should create a new company', async () => {
const newCompany = {
companyId: 'new-company-id',
CompanyName: 'New Test Company',
CompanyType: 'corporation', // Assuming 'corporation' is one of the valid enum values
CompanyType: 'corporation',
RegisteredAddress: '456 New Avenue, New City, NC',
TaxID: '987-65-4321',
corporationDate: new Date(),
Expand Down
67 changes: 48 additions & 19 deletions __tests__/financialReportingController.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
const { createFinancialReport } = require('../controllers/financialReportingController');
const FinancialReport = require('../models/financialReport');

const httpMocks = require('node-mocks-http');
const mongoose = require('mongoose');

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

describe('createFinancialReport Controller', () => {
Expand All @@ -14,38 +12,69 @@ describe('createFinancialReport Controller', () => {
req = httpMocks.createRequest();
res = httpMocks.createResponse();
next = jest.fn();
jest.clearAllMocks();
});

it('should create a new financial report and return 201 status', async () => {
// Mock request body
req.body = {
// Setup request body with fixed timestamp
const testData = {
ReportID: 'test-id',
Type: 'test-type',
Data: 'test-data',
Timestamp: new Date(),
Type: 'Annual',
Data: { key: 'test-data' },
TotalRevenue: '1000.00',
TotalExpenses: '500.00',
NetIncome: '500.00',
EquitySummary: ['uuid1', 'uuid2'],
Timestamp: new Date().toISOString() // Convert to ISO string format
};

// Mock the save function
FinancialReport.prototype.save = jest.fn().mockResolvedValue(req.body);
req.body = testData;

// Mock the constructor and save method
const mockSave = jest.fn().mockResolvedValue(testData);
const mockInstance = { save: mockSave };
FinancialReport.mockImplementation(() => mockInstance);

// Execute controller
await createFinancialReport(req, res, next);

// Get response data and parse it
const responseData = JSON.parse(res._getData());

// Assertions
expect(FinancialReport).toHaveBeenCalledWith(req.body);
expect(FinancialReport.prototype.save).toHaveBeenCalled();
expect(FinancialReport).toHaveBeenCalledWith(testData);
expect(mockSave).toHaveBeenCalled();
expect(res.statusCode).toBe(201);
expect(res._getJSONData()).toEqual(req.body);
expect(responseData).toEqual(testData); // Compare with the original test data
expect(next).not.toHaveBeenCalled();
});

it('should handle errors and pass them to the error handling middleware', async () => {
const errorMessage = 'Failed to create financial report';
const rejectedPromise = Promise.reject(new Error(errorMessage));
FinancialReport.prototype.save = jest.fn().mockReturnValue(rejectedPromise);
// Setup request body with fixed timestamp
const testData = {
ReportID: 'test-id',
Type: 'Annual',
Data: { key: 'test-data' },
TotalRevenue: '1000.00',
TotalExpenses: '500.00',
NetIncome: '500.00',
EquitySummary: ['uuid1', 'uuid2'],
Timestamp: new Date().toISOString() // Convert to ISO string format
};

req.body = testData;

// Mock constructor and save method to throw error
const error = new Error('Failed to create financial report');
const mockSave = jest.fn().mockRejectedValue(error);
FinancialReport.mockImplementation(() => ({ save: mockSave }));

// Execute controller
await createFinancialReport(req, res, next);

expect(FinancialReport.prototype.save).toHaveBeenCalled();
expect(next).toHaveBeenCalledWith(expect.objectContaining({ message: errorMessage }));
// Assertions
expect(FinancialReport).toHaveBeenCalledWith(testData);
expect(mockSave).toHaveBeenCalled();
expect(next).toHaveBeenCalledWith(error);
});
});
});
81 changes: 30 additions & 51 deletions __tests__/globalSetup.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,38 @@
require('dotenv').config(); // Load the .env file
const mongoose = require("mongoose");
const { Client } = require('pg');
require('dotenv').config(); // Ensure dotenv loads
const mongoose = require('mongoose');

module.exports = async () => {
// Set up MongoDB
const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/opencap_test'; // Fallback to hardcoded URI
async function connectToMongoDB() {
const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/opencap_test';
if (!mongoUri) {
throw new Error('MongoDB URI is not defined.');
}

await mongoose.connect(mongoUri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});

// Drop the MongoDB database to clean up before tests
await mongoose.connection.dropDatabase();

// Set up PostgreSQL for metadata management
const pgClient = new Client({
user: process.env.PG_USER || 'lakehouse_user',
host: process.env.PG_HOST || 'localhost',
database: process.env.PG_DATABASE || 'lakehouse_metadata',
password: process.env.PG_PASSWORD || 'password',
port: process.env.PG_PORT || 5432,
});

try {
await pgClient.connect();
console.log('Connected to PostgreSQL database.');

// Clean up PostgreSQL data before tests (if necessary)
await pgClient.query('TRUNCATE TABLE datasets, dataset_schema, ingestion_logs RESTART IDENTITY CASCADE;');
console.log('PostgreSQL tables truncated.');

} catch (error) {
console.error('Error connecting to PostgreSQL:', error);
throw new Error('PostgreSQL connection failed.');
} finally {
await pgClient.end();
await mongoose.connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true });
console.log('Connected to MongoDB for setup.');
}

async function dropDatabaseWithRetry() {
let attempts = 3;
while (attempts > 0) {
try {
await mongoose.connection.dropDatabase();
console.log('MongoDB test database dropped successfully.');
return;
} catch (error) {
if (error.codeName === 'DatabaseDropPending') {
console.log('Database drop pending, retrying...');
attempts -= 1;
await new Promise((resolve) => setTimeout(resolve, 1000));
} else {
throw error;
}
}
}
throw new Error('Failed to drop MongoDB test database.');
}

// Set up MinIO client (mock or real)
process.env.MINIO_ENDPOINT = process.env.MINIO_ENDPOINT || 'localhost';
process.env.MINIO_PORT = process.env.MINIO_PORT || '9000';
process.env.MINIO_ACCESS_KEY = process.env.MINIO_ACCESS_KEY || 'your-access-key';
process.env.MINIO_SECRET_KEY = process.env.MINIO_SECRET_KEY || 'your-secret-key';
console.log('MinIO environment variables set.');

// Set up Airflow (mock or real)
process.env.AIRFLOW_BASE_URL = process.env.AIRFLOW_BASE_URL || 'http://localhost:8080';
process.env.AIRFLOW_USERNAME = process.env.AIRFLOW_USERNAME || 'admin';
process.env.AIRFLOW_PASSWORD = process.env.AIRFLOW_PASSWORD || 'admin_password';
console.log('Airflow environment variables set.');

// Close the MongoDB connection
module.exports = async () => {
await connectToMongoDB();
await dropDatabaseWithRetry();
await mongoose.connection.close();
console.log('MongoDB connection closed after setup.');
};
30 changes: 20 additions & 10 deletions controllers/financialReportingController.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
// Import necessary modules and models
// controllers/financialReportingController.js
const FinancialReport = require("../models/financialReport");

// Controller function to create a new financial report
async function createFinancialReport(req, res, next) {
const createFinancialReport = async (req, res, next) => {
try {
// Extract financial report data from the request body
const { ReportID, Type, Data, Timestamp } = req.body;
const {
ReportID,
Type,
Data,
TotalRevenue,
TotalExpenses,
NetIncome,
EquitySummary,
Timestamp
} = req.body;

// Create a new financial report document
const financialReport = new FinancialReport({
ReportID,
Type,
Data,
Timestamp,
TotalRevenue,
TotalExpenses,
NetIncome,
EquitySummary,
Timestamp
});

// Save the financial report document to the database
Expand All @@ -24,11 +37,8 @@ async function createFinancialReport(req, res, next) {
// Handle errors and pass them to the error handling middleware
next(error);
}
}

// Add more controller functions for other routes related to Financial Reporting Tool here
};

module.exports = {
createFinancialReport,
// Add more controller functions here as needed
};
createFinancialReport
};
46 changes: 46 additions & 0 deletions models/financialReport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// models/financialReport.js

const mongoose = require('mongoose');
const { Schema } = mongoose;
const { v4: uuidv4 } = require('uuid');

const FinancialReportSchema = new Schema({
ReportID: {
type: String,
default: uuidv4, // Generate a UUID by default
unique: true,
required: true,
},
Type: {
type: String,
enum: ['Annual', 'Quarterly'], // Enum values
required: true,
},
Data: {
type: Schema.Types.Mixed, // Stores JSON data
required: true,
},
TotalRevenue: {
type: Schema.Types.Decimal128, // Decimal for monetary values
required: true,
},
TotalExpenses: {
type: Schema.Types.Decimal128,
required: true,
},
NetIncome: {
type: Schema.Types.Decimal128,
required: true,
},
EquitySummary: {
type: [String], // Array of UUIDs (shareClassId)
default: [], // Optional field
},
Timestamp: {
type: Date,
default: Date.now,
required: true,
},
});

module.exports = mongoose.model('FinancialReport', FinancialReportSchema);

0 comments on commit 4f02316

Please sign in to comment.