feat: Add upload notifications to Telegram Bot (Phase 3)
- Integrate TelegramNotificationService into batchUpload route - Send notification on successful upload with group details - Add metadata parsing for year/title/name from form fields - Create integration tests for upload notifications - Fix getAdminUrl() to use INTERNAL_HOST with dev port - Update jest.config.js to transform uuid ESM module - Non-blocking async notification (won't fail upload on error) Phase 3 complete: Upload notifications working in Docker dev environment Tested successfully with real Telegram bot in test group
This commit is contained in:
parent
025578fa3d
commit
62be18ecaa
|
|
@ -240,14 +240,16 @@ scripts/package-lock.json
|
|||
|
||||
## Testing Checklist (Phase 1)
|
||||
|
||||
- [ ] Node.js Version >= 18.x
|
||||
- [ ] Telegram App installiert (Windows 11)
|
||||
- [ ] Bot via BotFather erstellt
|
||||
- [ ] Bot-Token gespeichert in `.env.telegram`
|
||||
- [ ] Test-Gruppe erstellt
|
||||
- [ ] Bot zur Gruppe hinzugefügt
|
||||
- [ ] Chat-ID ermittelt
|
||||
- [ ] Chat-ID gespeichert in `.env.telegram`
|
||||
- [x] Node.js Version >= 18.x
|
||||
- [x] Telegram App installiert (Windows 11)
|
||||
- [x] Bot via BotFather erstellt
|
||||
- [x] Bot-Token gespeichert in `.env.telegram`
|
||||
- [x] Test-Gruppe erstellt
|
||||
- [x] Bot zur Gruppe hinzugefügt
|
||||
- [x] Chat-ID ermittelt
|
||||
- [x] Chat-ID gespeichert in `.env.telegram`
|
||||
- [x] Privacy Mode deaktiviert
|
||||
- [x] Test-Nachricht erfolgreich gesendet
|
||||
- [ ] `npm install` erfolgreich
|
||||
- [ ] `node telegram-test.js` läuft ohne Fehler
|
||||
- [ ] Test-Nachricht in Telegram-Gruppe empfangen
|
||||
|
|
|
|||
|
|
@ -385,6 +385,15 @@
|
|||
},
|
||||
"description": {
|
||||
"example": "any"
|
||||
},
|
||||
"year": {
|
||||
"example": "any"
|
||||
},
|
||||
"title": {
|
||||
"example": "any"
|
||||
},
|
||||
"name": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,5 +26,9 @@ module.exports = {
|
|||
// Run tests serially to avoid DB conflicts
|
||||
maxWorkers: 1,
|
||||
// Force exit after tests complete
|
||||
forceExit: true
|
||||
forceExit: true,
|
||||
// Transform ESM modules in node_modules
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(uuid)/)'
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ const UploadGroup = require('../models/uploadGroup');
|
|||
const groupRepository = require('../repositories/GroupRepository');
|
||||
const dbManager = require('../database/DatabaseManager');
|
||||
const ImagePreviewService = require('../services/ImagePreviewService');
|
||||
const TelegramNotificationService = require('../services/TelegramNotificationService');
|
||||
|
||||
// Singleton-Instanz des Telegram Service
|
||||
const telegramService = new TelegramNotificationService();
|
||||
|
||||
const router = Router();
|
||||
|
||||
|
|
@ -117,6 +121,12 @@ router.post('/upload/batch', async (req, res) => {
|
|||
consents = {};
|
||||
}
|
||||
|
||||
// Merge separate form fields into metadata (backwards compatibility)
|
||||
if (req.body.year) metadata.year = parseInt(req.body.year);
|
||||
if (req.body.title) metadata.title = req.body.title;
|
||||
if (req.body.name) metadata.name = req.body.name;
|
||||
if (req.body.description) metadata.description = req.body.description;
|
||||
|
||||
// Validiere Workshop Consent (Pflichtfeld)
|
||||
if (!consents.workshopConsent) {
|
||||
return res.status(400).json({
|
||||
|
|
@ -229,6 +239,22 @@ router.post('/upload/batch', async (req, res) => {
|
|||
|
||||
console.log(`Successfully saved group ${group.groupId} with ${files.length} images to database`);
|
||||
|
||||
// Sende Telegram-Benachrichtigung (async, non-blocking)
|
||||
if (telegramService.isAvailable()) {
|
||||
telegramService.sendUploadNotification({
|
||||
name: group.name,
|
||||
year: group.year,
|
||||
title: group.title,
|
||||
imageCount: files.length,
|
||||
workshopConsent: consents.workshopConsent,
|
||||
socialMediaConsents: consents.socialMediaConsents || [],
|
||||
token: createResult.managementToken
|
||||
}).catch(err => {
|
||||
// Fehler loggen, aber Upload nicht fehlschlagen lassen
|
||||
console.error('[Telegram] Upload notification failed:', err.message);
|
||||
});
|
||||
}
|
||||
|
||||
// Erfolgreiche Antwort mit Management-Token
|
||||
res.json({
|
||||
groupId: group.groupId,
|
||||
|
|
|
|||
|
|
@ -307,8 +307,11 @@ ${groupsText}
|
|||
* @returns {string} Admin-Panel URL
|
||||
*/
|
||||
getAdminUrl() {
|
||||
const baseUrl = process.env.PUBLIC_URL || 'https://internal.hobbyhimmel.de';
|
||||
return `${baseUrl}/moderation`;
|
||||
const host = process.env.INTERNAL_HOST || 'internal.hobbyhimmel.de';
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const protocol = isProduction ? 'https' : 'http';
|
||||
const port = isProduction ? '' : ':3000';
|
||||
return `${protocol}://${host}${port}/moderation`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
183
backend/tests/api/telegram-upload.test.js
Normal file
183
backend/tests/api/telegram-upload.test.js
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/**
|
||||
* Integration Tests für Telegram Upload-Benachrichtigungen
|
||||
*
|
||||
* Phase 3: Upload-Benachrichtigungen
|
||||
*
|
||||
* Diese Tests prüfen die Integration zwischen Upload-Route und Telegram-Service
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { getRequest } = require('../testServer');
|
||||
|
||||
describe('Telegram Upload Notifications (Integration)', () => {
|
||||
let TelegramNotificationService;
|
||||
let sendUploadNotificationSpy;
|
||||
|
||||
beforeAll(() => {
|
||||
// Spy auf TelegramNotificationService
|
||||
TelegramNotificationService = require('../../src/services/TelegramNotificationService');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Spy auf sendUploadNotification erstellen
|
||||
sendUploadNotificationSpy = jest.spyOn(TelegramNotificationService.prototype, 'sendUploadNotification')
|
||||
.mockResolvedValue({ message_id: 42 });
|
||||
|
||||
// isAvailable() immer true zurückgeben für Tests
|
||||
jest.spyOn(TelegramNotificationService.prototype, 'isAvailable')
|
||||
.mockReturnValue(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore alle Spys
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('POST /api/upload/batch', () => {
|
||||
const testImagePath = path.join(__dirname, '../utils/test-image.jpg');
|
||||
|
||||
// Erstelle Test-Bild falls nicht vorhanden
|
||||
beforeAll(() => {
|
||||
if (!fs.existsSync(testImagePath)) {
|
||||
// Erstelle 1x1 px JPEG
|
||||
const buffer = Buffer.from([
|
||||
0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46,
|
||||
0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
|
||||
0x00, 0x01, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43,
|
||||
0x00, 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08,
|
||||
0x07, 0x07, 0x07, 0x09, 0x09, 0x08, 0x0A, 0x0C,
|
||||
0x14, 0x0D, 0x0C, 0x0B, 0x0B, 0x0C, 0x19, 0x12,
|
||||
0x13, 0x0F, 0x14, 0x1D, 0x1A, 0x1F, 0x1E, 0x1D,
|
||||
0x1A, 0x1C, 0x1C, 0x20, 0x24, 0x2E, 0x27, 0x20,
|
||||
0x22, 0x2C, 0x23, 0x1C, 0x1C, 0x28, 0x37, 0x29,
|
||||
0x2C, 0x30, 0x31, 0x34, 0x34, 0x34, 0x1F, 0x27,
|
||||
0x39, 0x3D, 0x38, 0x32, 0x3C, 0x2E, 0x33, 0x34,
|
||||
0x32, 0xFF, 0xC0, 0x00, 0x0B, 0x08, 0x00, 0x01,
|
||||
0x00, 0x01, 0x01, 0x01, 0x11, 0x00, 0xFF, 0xC4,
|
||||
0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0xFF, 0xC4, 0x00, 0x14,
|
||||
0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFF, 0xDA, 0x00, 0x08, 0x01, 0x01,
|
||||
0x00, 0x00, 0x3F, 0x00, 0x37, 0xFF, 0xD9
|
||||
]);
|
||||
fs.writeFileSync(testImagePath, buffer);
|
||||
}
|
||||
});
|
||||
|
||||
it('sollte Telegram-Benachrichtigung bei erfolgreichem Upload senden', async () => {
|
||||
const response = await getRequest()
|
||||
.post('/api/upload/batch')
|
||||
.field('year', '2024')
|
||||
.field('title', 'Test Upload')
|
||||
.field('name', 'Test User')
|
||||
.field('consents', JSON.stringify({
|
||||
workshopConsent: true,
|
||||
socialMediaConsents: ['instagram', 'tiktok']
|
||||
}))
|
||||
.attach('images', testImagePath);
|
||||
|
||||
// Upload sollte erfolgreich sein
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.message).toBe('Batch upload successful');
|
||||
|
||||
// Warte kurz auf async Telegram-Call
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
|
||||
// Telegram-Service sollte aufgerufen worden sein
|
||||
expect(sendUploadNotificationSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'Test User',
|
||||
year: 2024,
|
||||
title: 'Test Upload',
|
||||
imageCount: 1,
|
||||
workshopConsent: true,
|
||||
socialMediaConsents: ['instagram', 'tiktok']
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('sollte Upload nicht fehlschlagen wenn Telegram-Service nicht verfügbar', async () => {
|
||||
// Restore mock und setze isAvailable auf false
|
||||
jest.restoreAllMocks();
|
||||
jest.spyOn(TelegramNotificationService.prototype, 'isAvailable')
|
||||
.mockReturnValue(false);
|
||||
sendUploadNotificationSpy = jest.spyOn(TelegramNotificationService.prototype, 'sendUploadNotification');
|
||||
|
||||
const response = await getRequest()
|
||||
.post('/api/upload/batch')
|
||||
.field('year', '2024')
|
||||
.field('title', 'Test Upload')
|
||||
.field('name', 'Test User')
|
||||
.field('consents', JSON.stringify({
|
||||
workshopConsent: false,
|
||||
socialMediaConsents: []
|
||||
}))
|
||||
.attach('images', testImagePath);
|
||||
|
||||
// Upload sollte trotzdem erfolgreich sein
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.message).toBe('Batch upload successful');
|
||||
|
||||
// Telegram sollte nicht aufgerufen worden sein
|
||||
expect(sendUploadNotificationSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sollte Upload nicht fehlschlagen wenn Telegram-Benachrichtigung fehlschlägt', async () => {
|
||||
sendUploadNotificationSpy.mockRejectedValueOnce(
|
||||
new Error('Telegram API Error')
|
||||
);
|
||||
|
||||
const response = await getRequest()
|
||||
.post('/api/upload/batch')
|
||||
.field('year', '2024')
|
||||
.field('title', 'Test Upload')
|
||||
.field('name', 'Test User')
|
||||
.field('consents', JSON.stringify({
|
||||
workshopConsent: true,
|
||||
socialMediaConsents: []
|
||||
}))
|
||||
.attach('images', testImagePath);
|
||||
|
||||
// Upload sollte trotzdem erfolgreich sein
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.message).toBe('Batch upload successful');
|
||||
|
||||
// Warte auf async error handling
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
|
||||
// Telegram wurde versucht aufzurufen
|
||||
expect(sendUploadNotificationSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sollte korrekte Daten an Telegram-Service übergeben', async () => {
|
||||
const response = await getRequest()
|
||||
.post('/api/upload/batch')
|
||||
.field('year', '2025')
|
||||
.field('title', 'Schweißkurs November')
|
||||
.field('name', 'Max Mustermann')
|
||||
.field('consents', JSON.stringify({
|
||||
workshopConsent: true,
|
||||
socialMediaConsents: ['facebook', 'instagram']
|
||||
}))
|
||||
.attach('images', testImagePath)
|
||||
.attach('images', testImagePath);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
|
||||
expect(sendUploadNotificationSpy).toHaveBeenCalledWith({
|
||||
name: 'Max Mustermann',
|
||||
year: 2025,
|
||||
title: 'Schweißkurs November',
|
||||
imageCount: 2,
|
||||
workshopConsent: true,
|
||||
socialMediaConsents: ['facebook', 'instagram'],
|
||||
token: expect.any(String)
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
BIN
backend/tests/utils/test-image.jpg
Normal file
BIN
backend/tests/utils/test-image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 B |
Loading…
Reference in New Issue
Block a user