diff --git a/backend/app.js b/backend/app.js index 5657f4f..464cb80 100644 --- a/backend/app.js +++ b/backend/app.js @@ -9,7 +9,7 @@ const session = require('express-session'); const SequelizeStore = require('connect-session-sequelize')(session.Store); const { sequelize } = require('./models'); const { initializeTelegramPolling } = require('./services/telegramInitializer'); -const TaskScheduler = require('./services/taskScheduler'); +const taskScheduler = require('./services/taskScheduler'); const app = express(); @@ -143,8 +143,7 @@ async function startServer() { await initializeTelegramPolling(); // Initialize task scheduler - const scheduler = TaskScheduler.getInstance(); - await scheduler.initialize(); + await taskScheduler.initialize(); const server = app.listen(PORT, '0.0.0.0', () => { console.log(`Server running on port ${PORT}`); diff --git a/backend/routes/auth.js b/backend/routes/auth.js index e6ea09e..3af9917 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -41,7 +41,7 @@ router.post('/login', async (req, res) => { return res.status(401).json({ errors: ['Invalid credentials'] }); } - const isValidPassword = await user.checkPassword(password); + const isValidPassword = await User.checkPassword(password, user.password_digest); if (!isValidPassword) { return res.status(401).json({ errors: ['Invalid credentials'] }); } diff --git a/backend/routes/telegram.js b/backend/routes/telegram.js index 2a5cce3..4273cfa 100644 --- a/backend/routes/telegram.js +++ b/backend/routes/telegram.js @@ -1,6 +1,6 @@ const express = require('express'); const { User } = require('../models'); -const TelegramPoller = require('../services/telegramPoller'); +const telegramPoller = require('../services/telegramPoller'); const router = express.Router(); // POST /api/telegram/start-polling @@ -15,14 +15,13 @@ router.post('/telegram/start-polling', async (req, res) => { return res.status(400).json({ error: 'Telegram bot token not set.' }); } - const poller = TelegramPoller.getInstance(); - const success = await poller.addUser(user); + const success = await telegramPoller.addUser(user); if (success) { res.json({ success: true, message: 'Telegram polling started', - status: poller.getStatus() + status: telegramPoller.getStatus() }); } else { res.status(500).json({ error: 'Failed to start Telegram polling.' }); @@ -40,13 +39,12 @@ router.post('/telegram/stop-polling', async (req, res) => { return res.status(401).json({ error: 'Authentication required' }); } - const poller = TelegramPoller.getInstance(); - const success = poller.removeUser(req.session.userId); + const success = telegramPoller.removeUser(req.session.userId); res.json({ success: true, message: 'Telegram polling stopped', - status: poller.getStatus() + status: telegramPoller.getStatus() }); } catch (error) { console.error('Error stopping Telegram polling:', error); @@ -61,11 +59,9 @@ router.get('/telegram/polling-status', async (req, res) => { return res.status(401).json({ error: 'Authentication required' }); } - const poller = TelegramPoller.getInstance(); - res.json({ success: true, - status: poller.getStatus() + status: telegramPoller.getStatus() }); } catch (error) { console.error('Error getting Telegram polling status:', error); diff --git a/backend/routes/users.js b/backend/routes/users.js index 24abfff..6616df2 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -1,6 +1,6 @@ const express = require('express'); const { User } = require('../models'); -const TaskSummaryService = require('../services/taskSummaryService'); +const taskSummaryService = require('../services/taskSummaryService'); const router = express.Router(); const VALID_FREQUENCIES = ['daily', 'weekdays', 'weekly', '1h', '2h', '4h', '8h', '12h']; @@ -159,7 +159,7 @@ router.post('/profile/task-summary/send-now', async (req, res) => { } // Send the task summary - const success = await TaskSummaryService.sendSummaryToUser(user.id); + const success = await taskSummaryService.sendSummaryToUser(user.id); if (success) { res.json({ diff --git a/backend/services/quotesService.js b/backend/services/quotesService.js index 2a54e2e..3bd26d5 100644 --- a/backend/services/quotesService.js +++ b/backend/services/quotesService.js @@ -34,7 +34,7 @@ const parseYamlContent = (content) => { // validate quotes data structure const validateQuotesData = (data) => - data && data.quotes && Array.isArray(data.quotes); + !!(data && data.quotes && Array.isArray(data.quotes)); // extract quotes from data const extractQuotes = (data) => { diff --git a/backend/services/telegramInitializer.js b/backend/services/telegramInitializer.js index 07b9ed0..a5fcc0d 100644 --- a/backend/services/telegramInitializer.js +++ b/backend/services/telegramInitializer.js @@ -1,4 +1,4 @@ -const TelegramPoller = require('./telegramPoller'); +const telegramPoller = require('./telegramPoller'); const { User } = require('../models'); async function initializeTelegramPolling() { @@ -9,8 +9,7 @@ async function initializeTelegramPolling() { console.log('Initializing Telegram polling for configured users...'); try { - // Get singleton instance of the poller - const poller = TelegramPoller.getInstance(); + // Use functional telegram poller // Find users with configured Telegram tokens const usersWithTelegram = await User.findAll({ @@ -27,7 +26,7 @@ async function initializeTelegramPolling() { // Add each user to the polling list for (const user of usersWithTelegram) { console.log(`Starting Telegram polling for user ${user.id}`); - await poller.addUser(user); + await telegramPoller.addUser(user); } console.log('Telegram polling initialized successfully'); diff --git a/backend/tests/unit/models/task.test.js b/backend/tests/unit/models/task.test.js index d8a9ffb..53b34cc 100644 --- a/backend/tests/unit/models/task.test.js +++ b/backend/tests/unit/models/task.test.js @@ -95,30 +95,30 @@ describe('Task Model', () => { it('should return correct priority name', async () => { task.priority = Task.PRIORITY.LOW; - expect(task.getPriorityName()).toBe('low'); + expect(Task.getPriorityName(task.priority)).toBe('low'); task.priority = Task.PRIORITY.MEDIUM; - expect(task.getPriorityName()).toBe('medium'); + expect(Task.getPriorityName(task.priority)).toBe('medium'); task.priority = Task.PRIORITY.HIGH; - expect(task.getPriorityName()).toBe('high'); + expect(Task.getPriorityName(task.priority)).toBe('high'); }); it('should return correct status name', async () => { task.status = Task.STATUS.NOT_STARTED; - expect(task.getStatusName()).toBe('not_started'); + expect(Task.getStatusName(task.status)).toBe('not_started'); task.status = Task.STATUS.IN_PROGRESS; - expect(task.getStatusName()).toBe('in_progress'); + expect(Task.getStatusName(task.status)).toBe('in_progress'); task.status = Task.STATUS.DONE; - expect(task.getStatusName()).toBe('done'); + expect(Task.getStatusName(task.status)).toBe('done'); task.status = Task.STATUS.ARCHIVED; - expect(task.getStatusName()).toBe('archived'); + expect(Task.getStatusName(task.status)).toBe('archived'); task.status = Task.STATUS.WAITING; - expect(task.getStatusName()).toBe('waiting'); + expect(Task.getStatusName(task.status)).toBe('waiting'); }); }); diff --git a/backend/tests/unit/models/user.test.js b/backend/tests/unit/models/user.test.js index 0ea4520..eb682f6 100644 --- a/backend/tests/unit/models/user.test.js +++ b/backend/tests/unit/models/user.test.js @@ -85,23 +85,25 @@ describe('User Model', () => { }); it('should check password correctly', async () => { - const isValid = await user.checkPassword('password123'); + const isValid = await User.checkPassword('password123', user.password_digest); expect(isValid).toBe(true); - const isInvalid = await user.checkPassword('wrongpassword'); + const isInvalid = await User.checkPassword('wrongpassword', user.password_digest); expect(isInvalid).toBe(false); }); it('should set new password using setPassword method', async () => { const oldPasswordDigest = user.password_digest; - await user.setPassword('newpassword'); + const newPasswordDigest = await User.hashPassword('newpassword'); + user.password_digest = newPasswordDigest; + await user.save(); expect(user.password_digest).not.toBe(oldPasswordDigest); - const isValidNew = await user.checkPassword('newpassword'); + const isValidNew = await User.checkPassword('newpassword', user.password_digest); expect(isValidNew).toBe(true); - const isValidOld = await user.checkPassword('password123'); + const isValidOld = await User.checkPassword('password123', user.password_digest); expect(isValidOld).toBe(false); }); @@ -112,7 +114,7 @@ describe('User Model', () => { expect(user.password_digest).not.toBe(oldPasswordDigest); - const isValidNew = await user.checkPassword('newpassword'); + const isValidNew = await User.checkPassword('newpassword', user.password_digest); expect(isValidNew).toBe(true); }); }); diff --git a/backend/tests/unit/services/functional-services.test.js b/backend/tests/unit/services/functional-services.test.js new file mode 100644 index 0000000..407ca41 --- /dev/null +++ b/backend/tests/unit/services/functional-services.test.js @@ -0,0 +1,113 @@ +const taskScheduler = require('../../../services/taskScheduler'); +const telegramPoller = require('../../../services/telegramPoller'); +const quotesService = require('../../../services/quotesService'); +const taskSummaryService = require('../../../services/taskSummaryService'); + +describe('Functional Services', () => { + describe('TaskScheduler', () => { + it('should export functional interface', () => { + expect(typeof taskScheduler.initialize).toBe('function'); + expect(typeof taskScheduler.stop).toBe('function'); + expect(typeof taskScheduler.restart).toBe('function'); + expect(typeof taskScheduler.getStatus).toBe('function'); + }); + + it('should have pure helper functions for testing', () => { + expect(typeof taskScheduler._createSchedulerState).toBe('function'); + expect(typeof taskScheduler._shouldDisableScheduler).toBe('function'); + expect(typeof taskScheduler._getCronExpression).toBe('function'); + }); + + it('should return proper cron expressions', () => { + expect(taskScheduler._getCronExpression('daily')).toBe('0 7 * * *'); + expect(taskScheduler._getCronExpression('weekly')).toBe('0 7 * * 1'); + expect(taskScheduler._getCronExpression('1h')).toBe('0 * * * *'); + }); + }); + + describe('TelegramPoller', () => { + it('should export functional interface', () => { + expect(typeof telegramPoller.addUser).toBe('function'); + expect(typeof telegramPoller.removeUser).toBe('function'); + expect(typeof telegramPoller.getStatus).toBe('function'); + expect(typeof telegramPoller.sendTelegramMessage).toBe('function'); + }); + + it('should have pure helper functions for testing', () => { + expect(typeof telegramPoller._userExistsInList).toBe('function'); + expect(typeof telegramPoller._addUserToList).toBe('function'); + expect(typeof telegramPoller._removeUserFromList).toBe('function'); + expect(typeof telegramPoller._createMessageParams).toBe('function'); + }); + + it('should handle user list operations functionally', () => { + const users = [{ id: 1 }, { id: 2 }]; + const newUser = { id: 3 }; + + expect(telegramPoller._userExistsInList(users, 1)).toBe(true); + expect(telegramPoller._userExistsInList(users, 3)).toBe(false); + + const updatedUsers = telegramPoller._addUserToList(users, newUser); + expect(updatedUsers).toHaveLength(3); + expect(users).toHaveLength(2); // Original array unchanged + + const filteredUsers = telegramPoller._removeUserFromList(updatedUsers, 2); + expect(filteredUsers).toHaveLength(2); + expect(filteredUsers.find(u => u.id === 2)).toBeUndefined(); + }); + }); + + describe('QuotesService', () => { + it('should export functional interface', () => { + expect(typeof quotesService.getRandomQuote).toBe('function'); + expect(typeof quotesService.getAllQuotes).toBe('function'); + expect(typeof quotesService.getQuotesCount).toBe('function'); + expect(typeof quotesService.reloadQuotes).toBe('function'); + }); + + it('should have pure helper functions for testing', () => { + expect(typeof quotesService._createDefaultQuotes).toBe('function'); + expect(typeof quotesService._getRandomIndex).toBe('function'); + expect(typeof quotesService._validateQuotesData).toBe('function'); + }); + + it('should validate quotes data structure correctly', () => { + const validData = { quotes: ['quote1', 'quote2'] }; + const invalidData1 = { quotes: 'not-array' }; + const invalidData2 = { notQuotes: ['quote1'] }; + const invalidData3 = null; + + expect(quotesService._validateQuotesData(validData)).toBe(true); + expect(quotesService._validateQuotesData(invalidData1)).toBe(false); + expect(quotesService._validateQuotesData(invalidData2)).toBe(false); + expect(quotesService._validateQuotesData(invalidData3)).toBe(false); + }); + }); + + describe('TaskSummaryService', () => { + it('should export functional interface', () => { + expect(typeof taskSummaryService.generateSummaryForUser).toBe('function'); + expect(typeof taskSummaryService.sendSummaryToUser).toBe('function'); + expect(typeof taskSummaryService.calculateNextRunTime).toBe('function'); + }); + + it('should have pure helper functions for testing', () => { + expect(typeof taskSummaryService._escapeMarkdown).toBe('function'); + expect(typeof taskSummaryService._getPriorityEmoji).toBe('function'); + expect(typeof taskSummaryService._buildTaskSection).toBe('function'); + }); + + it('should escape markdown correctly', () => { + const text = 'Task with *bold* and _italic_ text'; + const escaped = taskSummaryService._escapeMarkdown(text); + expect(escaped).toBe('Task with \\*bold\\* and \\_italic\\_ text'); + }); + + it('should return correct priority emojis', () => { + expect(taskSummaryService._getPriorityEmoji(0)).toBe('🟢'); // low + expect(taskSummaryService._getPriorityEmoji(1)).toBe('🟠'); // medium + expect(taskSummaryService._getPriorityEmoji(2)).toBe('🔴'); // high + expect(taskSummaryService._getPriorityEmoji(99)).toBe('⚪'); // unknown + }); + }); +}); \ No newline at end of file