feat: add CI/CD pipeline with OIDC, ECR, Docker Hub, lint and tests

This commit is contained in:
ikechukwu Elizabeth
2026-04-19 18:45:47 +01:00
parent 0a92e6838d
commit ee42969cca
11 changed files with 6073 additions and 35 deletions
+36
View File
@@ -0,0 +1,36 @@
const js = require("@eslint/js");
module.exports = [
js.configs.recommended,
{
languageOptions: {
ecmaVersion: 2021,
globals: {
require: "readonly",
module: "readonly",
exports: "readonly",
process: "readonly",
__dirname: "readonly",
console: "readonly",
},
},
rules: {
"no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"no-console": "off",
},
},
{
files: ["**/*.test.js"],
languageOptions: {
globals: {
describe: "readonly",
it: "readonly",
expect: "readonly",
beforeEach: "readonly",
afterEach: "readonly",
beforeAll: "readonly",
afterAll: "readonly",
},
},
},
];
+5465
View File
File diff suppressed because it is too large Load Diff
+10 -2
View File
@@ -5,10 +5,18 @@
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js"
"dev": "node --watch server.js",
"test": "jest --forceExit",
"lint": "eslint . --ext .js --max-warnings=0"
},
"dependencies": {
"dotenv": "^17.4.0",
"express": "^4.21.2"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"eslint": "^10.2.1",
"jest": "^30.3.0",
"supertest": "^7.2.2"
}
}
}
+19 -11
View File
@@ -11,19 +11,23 @@ let tasks = [];
let nextId = 1;
// Routes
app.get("/", (req, res) => {
app.get("/api", (req, res) => {
res.json({ message: "Task API is running", version: "1.0.0" });
});
app.get("/health", (req, res) => {
res.status(200).json({ status: "healthy", uptime: process.uptime() });
app.get("/api/health", (req, res) => {
res.status(200).json({
status: "ok",
uptime: process.uptime(),
timestamp: new Date().toISOString(),
});
});
app.get("/tasks", (req, res) => {
app.get("/api/tasks", (req, res) => {
res.json(tasks);
});
app.post("/tasks", (req, res) => {
app.post("/api/tasks", (req, res) => {
const { title } = req.body;
if (!title) {
return res.status(400).json({ error: "Title is required" });
@@ -33,14 +37,14 @@ app.post("/tasks", (req, res) => {
res.status(201).json(task);
});
app.patch("/tasks/:id", (req, res) => {
app.patch("/api/tasks/:id", (req, res) => {
const task = tasks.find((t) => t.id === parseInt(req.params.id));
if (!task) return res.status(404).json({ error: "Task not found" });
task.completed = !task.completed;
res.json(task);
});
app.delete("/tasks/:id", (req, res) => {
app.delete("/api/tasks/:id", (req, res) => {
const index = tasks.findIndex((t) => t.id === parseInt(req.params.id));
if (index === -1) return res.status(404).json({ error: "Task not found" });
tasks.splice(index, 1);
@@ -53,11 +57,15 @@ app.use((req, res) => {
});
// Global error handler
app.use((err, req, res, next) => {
app.use((err, req, res, _next) => {
console.error(err.stack);
res.status(500).json({ error: "Internal server error" });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
if (require.main === module) {
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
}
module.exports = app;
+34
View File
@@ -0,0 +1,34 @@
const request = require('supertest');
const app = require('./server');
describe('Health Check', () => {
it('GET /api/health returns 200 with status ok', async () => {
const res = await request(app).get('/api/health');
expect(res.statusCode).toBe(200);
expect(res.body.status).toBe('ok');
});
});
describe('Tasks API', () => {
it('GET /api/tasks returns empty array initially', async () => {
const res = await request(app).get('/api/tasks');
expect(res.statusCode).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
});
it('POST /api/tasks creates a new task', async () => {
const res = await request(app)
.post('/api/tasks')
.send({ title: 'Test task' });
expect(res.statusCode).toBe(201);
expect(res.body.title).toBe('Test task');
expect(res.body.completed).toBe(false);
});
it('POST /api/tasks returns 400 when title is missing', async () => {
const res = await request(app)
.post('/api/tasks')
.send({});
expect(res.statusCode).toBe(400);
});
});