add tests, put on ci
This commit is contained in:
parent
cd4f39fcd4
commit
df66720f33
5 changed files with 4073 additions and 65 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
name: Build and Push Docker Image
|
name: CI/CD Pipeline
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
@ -11,7 +11,26 @@ env:
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test
|
||||||
|
|
||||||
build-and-push:
|
build-and-push:
|
||||||
|
needs: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
3827
package-lock.json
generated
3827
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,6 +6,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"dev": "node server.js",
|
"dev": "node server.js",
|
||||||
|
"test": "jest",
|
||||||
"docker:build": "docker build -t ntfy-emergency-app .",
|
"docker:build": "docker build -t ntfy-emergency-app .",
|
||||||
"docker:tag": "bash scripts/docker-tag.sh",
|
"docker:tag": "bash scripts/docker-tag.sh",
|
||||||
"docker:push": "bash scripts/docker-push.sh",
|
"docker:push": "bash scripts/docker-push.sh",
|
||||||
|
|
@ -16,6 +17,10 @@
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"dotenv": "^16.3.1"
|
"dotenv": "^16.3.1"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"supertest": "^6.3.3"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
142
server.js
142
server.js
|
|
@ -3,78 +3,98 @@ const axios = require('axios');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
const app = express();
|
function createApp(axiosClient) {
|
||||||
const PORT = process.env.PORT || 3000;
|
const app = express();
|
||||||
const UI_MESSAGE = process.env.UI_MESSAGE || 'Emergency Message';
|
const UI_MESSAGE = process.env.UI_MESSAGE || 'Emergency Message';
|
||||||
|
|
||||||
// Environment variables for ntfy configuration
|
// Environment variables for ntfy configuration
|
||||||
const NTFY_URL = process.env.NTFY_URL;
|
const NTFY_URL = process.env.NTFY_URL;
|
||||||
const NTFY_USER = process.env.NTFY_USER;
|
const NTFY_USER = process.env.NTFY_USER;
|
||||||
const NTFY_PASSWORD = process.env.NTFY_PASSWORD;
|
const NTFY_PASSWORD = process.env.NTFY_PASSWORD;
|
||||||
const NTFY_TOPIC = process.env.NTFY_TOPIC;
|
const NTFY_TOPIC = process.env.NTFY_TOPIC;
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
app.use(express.static('public'));
|
app.use(express.static('public'));
|
||||||
|
|
||||||
if (!NTFY_URL || !NTFY_USER || !NTFY_PASSWORD || !NTFY_TOPIC) {
|
app.get('/', (req, res) => {
|
||||||
console.error('Missing required environment variables: NTFY_URL, NTFY_USER, NTFY_PASSWORD, NTFY_TOPIC');
|
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
|
||||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/api/config', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
uiMessage: UI_MESSAGE || 'Emergency Message'
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/send-message', async (req, res) => {
|
app.get('/api/config', (req, res) => {
|
||||||
try {
|
res.json({
|
||||||
const { name, message } = req.body;
|
uiMessage: UI_MESSAGE || 'Emergency Message'
|
||||||
|
|
||||||
if (!name || !message) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Name and message are required'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullMessage = `From [${name}], message: ${message}`;
|
|
||||||
|
|
||||||
const ntfyResponse = await axios.post(`${NTFY_URL}/${NTFY_TOPIC}`, fullMessage, {
|
|
||||||
auth: {
|
|
||||||
username: NTFY_USER,
|
|
||||||
password: NTFY_PASSWORD
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (ntfyResponse.status === 200) {
|
app.post('/send-message', async (req, res) => {
|
||||||
res.json({ success: true, message: 'Message sent successfully' });
|
try {
|
||||||
} else {
|
const { name, message } = req.body;
|
||||||
|
|
||||||
|
if (!name || !message) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Name and message are required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullMessage = `From [${name}], message: ${message}`;
|
||||||
|
|
||||||
|
const ntfyResponse = await axiosClient.post(`${NTFY_URL}/${NTFY_TOPIC}`, fullMessage, {
|
||||||
|
auth: {
|
||||||
|
username: NTFY_USER,
|
||||||
|
password: NTFY_PASSWORD
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ntfyResponse.status === 200) {
|
||||||
|
res.json({ success: true, message: 'Message sent successfully' });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Error sending message'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending message:', error.message);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Error sending message'
|
error: 'Internal server error'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error) {
|
return app;
|
||||||
console.error('Error sending message:', error.message);
|
}
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
// Export the createApp function for testing
|
||||||
error: 'Internal server error'
|
module.exports = createApp;
|
||||||
});
|
|
||||||
|
// Only start server if this file is run directly (not imported)
|
||||||
|
if (require.main === module) {
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// Environment variables for ntfy configuration
|
||||||
|
const NTFY_URL = process.env.NTFY_URL;
|
||||||
|
const NTFY_USER = process.env.NTFY_USER;
|
||||||
|
const NTFY_PASSWORD = process.env.NTFY_PASSWORD;
|
||||||
|
const NTFY_TOPIC = process.env.NTFY_TOPIC;
|
||||||
|
|
||||||
|
if (!NTFY_URL || !NTFY_USER || !NTFY_PASSWORD || !NTFY_TOPIC) {
|
||||||
|
console.error('Missing required environment variables: NTFY_URL, NTFY_USER, NTFY_PASSWORD, NTFY_TOPIC');
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
// Create app with real axios
|
||||||
console.log(`Server running on port ${PORT}`);
|
const app = createApp(axios);
|
||||||
console.log(`ntfy URL: ${NTFY_URL}`);
|
|
||||||
console.log(`ntfy Topic: ${NTFY_TOPIC}`);
|
app.listen(PORT, () => {
|
||||||
});
|
console.log(`Server running on port ${PORT}`);
|
||||||
|
console.log(`ntfy URL: ${NTFY_URL}`);
|
||||||
|
console.log(`ntfy Topic: ${NTFY_TOPIC}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
137
test.js
Normal file
137
test.js
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
const request = require('supertest');
|
||||||
|
const axios = require('axios');
|
||||||
|
const createApp = require('./server');
|
||||||
|
|
||||||
|
// Mock axios to avoid actual ntfy calls during testing
|
||||||
|
jest.mock('axios');
|
||||||
|
const mockedAxios = axios;
|
||||||
|
|
||||||
|
// Mock environment variables
|
||||||
|
process.env.NTFY_URL = 'https://ntfy.sh';
|
||||||
|
process.env.NTFY_USER = 'testuser';
|
||||||
|
process.env.NTFY_PASSWORD = 'testpass';
|
||||||
|
process.env.NTFY_TOPIC = 'test-topic';
|
||||||
|
process.env.UI_MESSAGE = 'Test Emergency Message';
|
||||||
|
|
||||||
|
// Create app with mocked axios
|
||||||
|
const app = createApp(mockedAxios);
|
||||||
|
|
||||||
|
describe('Emergency Message App', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /', () => {
|
||||||
|
it('should serve the index.html file', async () => {
|
||||||
|
const response = await request(app).get('/');
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.text).toContain('Emergency Message');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/config', () => {
|
||||||
|
it('should return the UI message configuration', async () => {
|
||||||
|
const response = await request(app).get('/api/config');
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.body).toEqual({
|
||||||
|
uiMessage: 'Test Emergency Message'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /send-message', () => {
|
||||||
|
it('should send a message successfully', async () => {
|
||||||
|
mockedAxios.post.mockResolvedValue({ status: 200 });
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/send-message')
|
||||||
|
.send({
|
||||||
|
name: 'Test User',
|
||||||
|
message: 'Test emergency message'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.body).toEqual({
|
||||||
|
success: true,
|
||||||
|
message: 'Message sent successfully'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify axios was called with correct parameters
|
||||||
|
expect(mockedAxios.post).toHaveBeenCalledWith(
|
||||||
|
'https://ntfy.sh/test-topic',
|
||||||
|
'From [Test User], message: Test emergency message',
|
||||||
|
{
|
||||||
|
auth: {
|
||||||
|
username: 'testuser',
|
||||||
|
password: 'testpass'
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 400 when name is missing', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/send-message')
|
||||||
|
.send({
|
||||||
|
message: 'Test message'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(400);
|
||||||
|
expect(response.body).toEqual({
|
||||||
|
success: false,
|
||||||
|
error: 'Name and message are required'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 400 when message is missing', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/send-message')
|
||||||
|
.send({
|
||||||
|
name: 'Test User'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(400);
|
||||||
|
expect(response.body).toEqual({
|
||||||
|
success: false,
|
||||||
|
error: 'Name and message are required'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle ntfy API errors', async () => {
|
||||||
|
mockedAxios.post.mockRejectedValue(new Error('Network error'));
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/send-message')
|
||||||
|
.send({
|
||||||
|
name: 'Test User',
|
||||||
|
message: 'Test message'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(500);
|
||||||
|
expect(response.body).toEqual({
|
||||||
|
success: false,
|
||||||
|
error: 'Internal server error'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle non-200 ntfy responses', async () => {
|
||||||
|
mockedAxios.post.mockResolvedValue({ status: 500 });
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/send-message')
|
||||||
|
.send({
|
||||||
|
name: 'Test User',
|
||||||
|
message: 'Test message'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(500);
|
||||||
|
expect(response.body).toEqual({
|
||||||
|
success: false,
|
||||||
|
error: 'Error sending message'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue