Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce tests & GitHub Actions #18

Merged
merged 10 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CD

on:
push:
branches:
- main

jobs:
docker:
name: Build and Push Docker Images
runs-on: ubuntu-latest
needs: [ci] # Docker image should be built after CI jobs are successful.

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

- name: Build and push Docker images using Docker Compose
run: |
docker-compose build
docker-compose push
88 changes: 88 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: CI

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
backend:
name: Test Backend
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 18

- name: Install dependencies
run: npm install
working-directory: backend

- name: Lint code
run: yarn lint
working-directory: backend

- name: Run backend tests
run: yarn test
working-directory: backend

- name: Upload Backend Test Results
if: always()
uses: actions/upload-artifact@v3
with:
name: backend-jest-results
path: backend/jest-results.xml

- name: Upload Backend Coverage Report
if: success()
uses: actions/upload-artifact@v3
with:
name: backend-coverage-report
path: backend/coverage

frontend:
name: Test Frontend
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 18

- name: Install dependencies
run: npm install
working-directory: frontend

- name: Lint code
run: yarn lint
working-directory: frontend

- name: Run frontend tests
run: yarn test
working-directory: frontend

- name: Upload Frontend Test Results
if: always()
uses: actions/upload-artifact@v3
with:
name: frontend-jest-results
path: frontend/jest-results.xml

- name: Upload Frontend Coverage Report
if: success()
uses: actions/upload-artifact@v3
with:
name: frontend-coverage-report
path: frontend/coverage
124 changes: 124 additions & 0 deletions backend/__tests__/parametersController.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
const request = require('supertest');
const express = require('express');
const path = require('path');
const fs = require('fs').promises;
const jest = require('jest');

const { updateParameters, getParameters } = require('../controllers/parametersController');
const {describe, it, expect, beforeEach} = jest;

// Mock the fs and path modules
jest.mock('fs').promises;
jest.mock('path');

const app = express();
app.use(express.json());

// Set up routes for testing
app.post('/api/parameters/update', updateParameters);
app.get('/api/parameters', getParameters);

// Mock data
const mockParameters = {
parameters: {
"preset1": { threshold1: 0.5, threshold2: 0.8 },
},
};

describe('Parameters Controller', () => {

beforeEach(() => {
// Mock the path to parameters.json
path.join.mockReturnValue('/fake/path/parameters.json');
});

describe('updateParameters', () => {
it('should update parameters successfully when valid data is provided', async () => {
const newParameters = { threshold1: 0.6, threshold2: 0.9 };

fs.readFile.mockResolvedValue(JSON.stringify(mockParameters));
fs.writeFile.mockResolvedValue(undefined); // No return value expected from writeFile

const res = await request(app)
.post('/api/parameters/update')
.send({
preset_name: 'preset1',
defekt_proportion_thresholds: newParameters,
});

expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ message: 'Parameters updated successfully' });
expect(fs.writeFile).toHaveBeenCalledWith(
'/fake/path/parameters.json',
JSON.stringify({ parameters: { preset1: newParameters } }, null, 4),
'utf8'
);
});

it('should return 400 if the preset is not found', async () => {
const newParameters = { threshold1: 0.6, threshold2: 0.9 };

fs.readFile.mockResolvedValue(JSON.stringify(mockParameters));

const res = await request(app)
.post('/api/parameters/update')
.send({
preset_name: 'nonexistent_preset',
defekt_proportion_thresholds: newParameters,
});

expect(res.statusCode).toBe(400);
expect(res.body).toEqual({ message: 'Preset is not found' });
});

it('should return 400 if the parameters are invalid', async () => {
const invalidParameters = { threshold1: 1.5, threshold2: 0.9 };

fs.readFile.mockResolvedValue(JSON.stringify(mockParameters));

const res = await request(app)
.post('/api/parameters/update')
.send({
preset_name: 'preset1',
defekt_proportion_thresholds: invalidParameters,
});

expect(res.statusCode).toBe(400);
expect(res.body).toEqual({ message: 'Invalid parameters format or values out of range' });
});

it('should return 500 if there is an error reading or writing the file', async () => {
fs.readFile.mockRejectedValue(new Error('File read error'));

const res = await request(app)
.post('/api/parameters/update')
.send({
preset_name: 'preset1',
defekt_proportion_thresholds: { threshold1: 0.6, threshold2: 0.9 },
});

expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ message: 'Error reading or writing file', error: {} });
});
});

describe('getParameters', () => {
it('should return the parameters successfully', async () => {
fs.readFile.mockResolvedValue(JSON.stringify(mockParameters));

const res = await request(app).get('/api/parameters');

expect(res.statusCode).toBe(200);
expect(res.body).toEqual(mockParameters.parameters);
});

it('should return 500 if there is an error reading the file', async () => {
fs.readFile.mockRejectedValue(new Error('File read error'));

const res = await request(app).get('/api/parameters');

expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ message: 'Error reading parameters file', error: {} });
});
});
});
106 changes: 106 additions & 0 deletions backend/__tests__/userController.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
const request = require('supertest');
const express = require('express');
const { getAllUsers, loginUser } = require('../controllers/userController');
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const jest = require('jest');

const {describe, it, expect} = jest;

// Mock the User model and jwt
jest.mock('../models/User');
jest.mock('jsonwebtoken');

const app = express();
app.use(express.json());

// Set up routes for testing
app.get('/api/users', getAllUsers);
app.post('/api/login', loginUser);

describe('User Controller', () => {
describe('getAllUsers', () => {
it('should return a list of users', async () => {
const mockUsers = [
{ _id: '1', email: '[email protected]', password: 'password1' },
{ _id: '2', email: '[email protected]', password: 'password2' },
];
User.find.mockResolvedValue(mockUsers);

const res = await request(app).get('/api/users');

expect(res.statusCode).toBe(200);
expect(res.body).toEqual(mockUsers);
});

it('should return a 500 error if something goes wrong', async () => {
User.find.mockRejectedValue(new Error('Database error'));

const res = await request(app).get('/api/users');

expect(res.statusCode).toBe(500);
expect(res.body).toEqual({});
});
});

describe('loginUser', () => {
it('should return a token if the login is successful', async () => {
const mockUser = {
_id: '1',
email: '[email protected]',
password: 'password1',
isAdmin: false,
};

User.findOne.mockResolvedValue(mockUser);
jwt.sign.mockReturnValue('fake-jwt-token');

const res = await request(app)
.post('/api/login')
.send({ email: '[email protected]', password: 'password1' });

expect(res.statusCode).toBe(200);
expect(res.body).toHaveProperty('token', 'fake-jwt-token');
});

it('should return a 404 error if the user is not found', async () => {
User.findOne.mockResolvedValue(null);

const res = await request(app)
.post('/api/login')
.send({ email: '[email protected]', password: 'password1' });

expect(res.statusCode).toBe(404);
expect(res.body).toEqual({ message: 'User not found' });
});

it('should return a 401 error if the password is incorrect', async () => {
const mockUser = {
_id: '1',
email: '[email protected]',
password: 'password1',
isAdmin: false,
};

User.findOne.mockResolvedValue(mockUser);

const res = await request(app)
.post('/api/login')
.send({ email: '[email protected]', password: 'wrong-password' });

expect(res.statusCode).toBe(401);
expect(res.body).toEqual({ message: 'Invalid password' });
});

it('should return a 500 error if there is an error during login', async () => {
User.findOne.mockRejectedValue(new Error('Database error'));

const res = await request(app)
.post('/api/login')
.send({ email: '[email protected]', password: 'password1' });

expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ message: 'Error logging in', error: {} });
});
});
});
1 change: 0 additions & 1 deletion backend/controllers/modelController.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const DetectionModel = require("../models/DetectionModel");
const jwt = require("jsonwebtoken");

const getAllModels = async (req, res) => {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
const fs = require("fs").promises;
const path = require("path");
const _ = require("lodash");

const ParametersSchema = require("../models/parameters");

// Define the path to your parameters.json file
const filePath = path.join(__dirname, "../models/parameters.json");

Check failure on line 7 in backend/controllers/parametersController.js

View workflow job for this annotation

GitHub Actions / Test Backend

'__dirname' is not defined

const updateParameters = async (req, res) => {
const { preset_name, defekt_proportion_thresholds } = req.body;
Expand All @@ -21,7 +20,7 @@
}

// Check if preset is existing
if (!parameters.parameters.hasOwnProperty(preset_name)) {

Check failure on line 23 in backend/controllers/parametersController.js

View workflow job for this annotation

GitHub Actions / Test Backend

Do not access Object.prototype method 'hasOwnProperty' from target object
return res.status(400).json({ message: "Preset is not found" });
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const User = require("../models/User");
const jwt = require('jsonwebtoken');
const process = require('process')

const getAllUsers = async (req, res) => {
try {
Expand All @@ -12,7 +13,6 @@ const getAllUsers = async (req, res) => {

const loginUser = async (req, res) => {
const { email, password } = req.body;
console.log(req.body)

try {
const user = await User.findOne({ email });
Expand Down
1 change: 1 addition & 0 deletions backend/database/db.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const mongoose = require("mongoose");
require("dotenv").config();
const process = require("process");

const mongoURI = process.env.MONGO_URI;

Expand Down
9 changes: 9 additions & 0 deletions backend/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import globals from "globals";
import pluginJs from "@eslint/js";


export default [
{files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}},
{languageOptions: { globals: globals.browser }},
pluginJs.configs.recommended,
];
Loading
Loading