Skip to content

Commit 23c878c

Browse files
umutozdemirUmut Özdemir
and
Umut Özdemir
authored
Introduce tests & GitHub Actions (#18)
* introduce tests * add github actions * refactor github actions * introduce linting check * typo * introduce continous deployment * fix ci.yml * fix linting issues * fix linting errors * typo --------- Co-authored-by: Umut Özdemir <[email protected]>
1 parent 8430f9c commit 23c878c

23 files changed

+5265
-1176
lines changed

.github/workflows/cd.yml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: CD
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
docker:
10+
name: Build and Push Docker Images
11+
runs-on: ubuntu-latest
12+
needs: [ci] # Docker image should be built after CI jobs are successful.
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v3
17+
18+
- name: Set up Docker Buildx
19+
uses: docker/setup-buildx-action@v2
20+
21+
- name: Log in to Docker Hub
22+
uses: docker/login-action@v2
23+
with:
24+
username: ${{ secrets.DOCKER_HUB_USERNAME }}
25+
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
26+
27+
- name: Build and push Docker images using Docker Compose
28+
run: |
29+
docker-compose build
30+
docker-compose push

.github/workflows/ci.yml

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
backend:
13+
name: Test Backend
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v3
19+
20+
- name: Set up Node.js
21+
uses: actions/setup-node@v3
22+
with:
23+
node-version: 18
24+
25+
- name: Install dependencies
26+
run: npm install
27+
working-directory: backend
28+
29+
- name: Lint code
30+
run: yarn lint
31+
working-directory: backend
32+
33+
- name: Run backend tests
34+
run: yarn test
35+
working-directory: backend
36+
37+
- name: Upload Backend Test Results
38+
if: always()
39+
uses: actions/upload-artifact@v3
40+
with:
41+
name: backend-jest-results
42+
path: backend/jest-results.xml
43+
44+
- name: Upload Backend Coverage Report
45+
if: success()
46+
uses: actions/upload-artifact@v3
47+
with:
48+
name: backend-coverage-report
49+
path: backend/coverage
50+
51+
frontend:
52+
name: Test Frontend
53+
runs-on: ubuntu-latest
54+
55+
steps:
56+
- name: Checkout code
57+
uses: actions/checkout@v3
58+
59+
- name: Set up Node.js
60+
uses: actions/setup-node@v3
61+
with:
62+
node-version: 18
63+
64+
- name: Install dependencies
65+
run: npm install
66+
working-directory: frontend
67+
68+
- name: Lint code
69+
run: yarn lint
70+
working-directory: frontend
71+
72+
- name: Run frontend tests
73+
run: yarn test
74+
working-directory: frontend
75+
76+
- name: Upload Frontend Test Results
77+
if: always()
78+
uses: actions/upload-artifact@v3
79+
with:
80+
name: frontend-jest-results
81+
path: frontend/jest-results.xml
82+
83+
- name: Upload Frontend Coverage Report
84+
if: success()
85+
uses: actions/upload-artifact@v3
86+
with:
87+
name: frontend-coverage-report
88+
path: frontend/coverage
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
const request = require('supertest');
2+
const express = require('express');
3+
const path = require('path');
4+
const fs = require('fs').promises;
5+
const jest = require('jest');
6+
7+
const { updateParameters, getParameters } = require('../controllers/parametersController');
8+
const {describe, it, expect, beforeEach} = jest;
9+
10+
// Mock the fs and path modules
11+
jest.mock('fs').promises;
12+
jest.mock('path');
13+
14+
const app = express();
15+
app.use(express.json());
16+
17+
// Set up routes for testing
18+
app.post('/api/parameters/update', updateParameters);
19+
app.get('/api/parameters', getParameters);
20+
21+
// Mock data
22+
const mockParameters = {
23+
parameters: {
24+
"preset1": { threshold1: 0.5, threshold2: 0.8 },
25+
},
26+
};
27+
28+
describe('Parameters Controller', () => {
29+
30+
beforeEach(() => {
31+
// Mock the path to parameters.json
32+
path.join.mockReturnValue('/fake/path/parameters.json');
33+
});
34+
35+
describe('updateParameters', () => {
36+
it('should update parameters successfully when valid data is provided', async () => {
37+
const newParameters = { threshold1: 0.6, threshold2: 0.9 };
38+
39+
fs.readFile.mockResolvedValue(JSON.stringify(mockParameters));
40+
fs.writeFile.mockResolvedValue(undefined); // No return value expected from writeFile
41+
42+
const res = await request(app)
43+
.post('/api/parameters/update')
44+
.send({
45+
preset_name: 'preset1',
46+
defekt_proportion_thresholds: newParameters,
47+
});
48+
49+
expect(res.statusCode).toBe(200);
50+
expect(res.body).toEqual({ message: 'Parameters updated successfully' });
51+
expect(fs.writeFile).toHaveBeenCalledWith(
52+
'/fake/path/parameters.json',
53+
JSON.stringify({ parameters: { preset1: newParameters } }, null, 4),
54+
'utf8'
55+
);
56+
});
57+
58+
it('should return 400 if the preset is not found', async () => {
59+
const newParameters = { threshold1: 0.6, threshold2: 0.9 };
60+
61+
fs.readFile.mockResolvedValue(JSON.stringify(mockParameters));
62+
63+
const res = await request(app)
64+
.post('/api/parameters/update')
65+
.send({
66+
preset_name: 'nonexistent_preset',
67+
defekt_proportion_thresholds: newParameters,
68+
});
69+
70+
expect(res.statusCode).toBe(400);
71+
expect(res.body).toEqual({ message: 'Preset is not found' });
72+
});
73+
74+
it('should return 400 if the parameters are invalid', async () => {
75+
const invalidParameters = { threshold1: 1.5, threshold2: 0.9 };
76+
77+
fs.readFile.mockResolvedValue(JSON.stringify(mockParameters));
78+
79+
const res = await request(app)
80+
.post('/api/parameters/update')
81+
.send({
82+
preset_name: 'preset1',
83+
defekt_proportion_thresholds: invalidParameters,
84+
});
85+
86+
expect(res.statusCode).toBe(400);
87+
expect(res.body).toEqual({ message: 'Invalid parameters format or values out of range' });
88+
});
89+
90+
it('should return 500 if there is an error reading or writing the file', async () => {
91+
fs.readFile.mockRejectedValue(new Error('File read error'));
92+
93+
const res = await request(app)
94+
.post('/api/parameters/update')
95+
.send({
96+
preset_name: 'preset1',
97+
defekt_proportion_thresholds: { threshold1: 0.6, threshold2: 0.9 },
98+
});
99+
100+
expect(res.statusCode).toBe(500);
101+
expect(res.body).toEqual({ message: 'Error reading or writing file', error: {} });
102+
});
103+
});
104+
105+
describe('getParameters', () => {
106+
it('should return the parameters successfully', async () => {
107+
fs.readFile.mockResolvedValue(JSON.stringify(mockParameters));
108+
109+
const res = await request(app).get('/api/parameters');
110+
111+
expect(res.statusCode).toBe(200);
112+
expect(res.body).toEqual(mockParameters.parameters);
113+
});
114+
115+
it('should return 500 if there is an error reading the file', async () => {
116+
fs.readFile.mockRejectedValue(new Error('File read error'));
117+
118+
const res = await request(app).get('/api/parameters');
119+
120+
expect(res.statusCode).toBe(500);
121+
expect(res.body).toEqual({ message: 'Error reading parameters file', error: {} });
122+
});
123+
});
124+
});
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
const request = require('supertest');
2+
const express = require('express');
3+
const { getAllUsers, loginUser } = require('../controllers/userController');
4+
const User = require('../models/User');
5+
const jwt = require('jsonwebtoken');
6+
const jest = require('jest');
7+
8+
const {describe, it, expect} = jest;
9+
10+
// Mock the User model and jwt
11+
jest.mock('../models/User');
12+
jest.mock('jsonwebtoken');
13+
14+
const app = express();
15+
app.use(express.json());
16+
17+
// Set up routes for testing
18+
app.get('/api/users', getAllUsers);
19+
app.post('/api/login', loginUser);
20+
21+
describe('User Controller', () => {
22+
describe('getAllUsers', () => {
23+
it('should return a list of users', async () => {
24+
const mockUsers = [
25+
{ _id: '1', email: '[email protected]', password: 'password1' },
26+
{ _id: '2', email: '[email protected]', password: 'password2' },
27+
];
28+
User.find.mockResolvedValue(mockUsers);
29+
30+
const res = await request(app).get('/api/users');
31+
32+
expect(res.statusCode).toBe(200);
33+
expect(res.body).toEqual(mockUsers);
34+
});
35+
36+
it('should return a 500 error if something goes wrong', async () => {
37+
User.find.mockRejectedValue(new Error('Database error'));
38+
39+
const res = await request(app).get('/api/users');
40+
41+
expect(res.statusCode).toBe(500);
42+
expect(res.body).toEqual({});
43+
});
44+
});
45+
46+
describe('loginUser', () => {
47+
it('should return a token if the login is successful', async () => {
48+
const mockUser = {
49+
_id: '1',
50+
51+
password: 'password1',
52+
isAdmin: false,
53+
};
54+
55+
User.findOne.mockResolvedValue(mockUser);
56+
jwt.sign.mockReturnValue('fake-jwt-token');
57+
58+
const res = await request(app)
59+
.post('/api/login')
60+
.send({ email: '[email protected]', password: 'password1' });
61+
62+
expect(res.statusCode).toBe(200);
63+
expect(res.body).toHaveProperty('token', 'fake-jwt-token');
64+
});
65+
66+
it('should return a 404 error if the user is not found', async () => {
67+
User.findOne.mockResolvedValue(null);
68+
69+
const res = await request(app)
70+
.post('/api/login')
71+
.send({ email: '[email protected]', password: 'password1' });
72+
73+
expect(res.statusCode).toBe(404);
74+
expect(res.body).toEqual({ message: 'User not found' });
75+
});
76+
77+
it('should return a 401 error if the password is incorrect', async () => {
78+
const mockUser = {
79+
_id: '1',
80+
81+
password: 'password1',
82+
isAdmin: false,
83+
};
84+
85+
User.findOne.mockResolvedValue(mockUser);
86+
87+
const res = await request(app)
88+
.post('/api/login')
89+
.send({ email: '[email protected]', password: 'wrong-password' });
90+
91+
expect(res.statusCode).toBe(401);
92+
expect(res.body).toEqual({ message: 'Invalid password' });
93+
});
94+
95+
it('should return a 500 error if there is an error during login', async () => {
96+
User.findOne.mockRejectedValue(new Error('Database error'));
97+
98+
const res = await request(app)
99+
.post('/api/login')
100+
.send({ email: '[email protected]', password: 'password1' });
101+
102+
expect(res.statusCode).toBe(500);
103+
expect(res.body).toEqual({ message: 'Error logging in', error: {} });
104+
});
105+
});
106+
});

backend/controllers/modelController.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const DetectionModel = require("../models/DetectionModel");
2-
const jwt = require("jsonwebtoken");
32

43
const getAllModels = async (req, res) => {
54
try {

backend/controllers/parameterControllers.js backend/controllers/parametersController.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const fs = require("fs").promises;
22
const path = require("path");
3-
const _ = require("lodash");
43

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

backend/controllers/userControllers.js backend/controllers/userController.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const User = require("../models/User");
22
const jwt = require('jsonwebtoken');
3+
const process = require('process')
34

45
const getAllUsers = async (req, res) => {
56
try {
@@ -12,7 +13,6 @@ const getAllUsers = async (req, res) => {
1213

1314
const loginUser = async (req, res) => {
1415
const { email, password } = req.body;
15-
console.log(req.body)
1616

1717
try {
1818
const user = await User.findOne({ email });

backend/database/db.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const mongoose = require("mongoose");
22
require("dotenv").config();
3+
const process = require("process");
34

45
const mongoURI = process.env.MONGO_URI;
56

backend/eslint.config.mjs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import globals from "globals";
2+
import pluginJs from "@eslint/js";
3+
4+
5+
export default [
6+
{files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}},
7+
{languageOptions: { globals: globals.browser }},
8+
pluginJs.configs.recommended,
9+
];

0 commit comments

Comments
 (0)