diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..99991fd --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Server Configuration +PORT=5000 +NODE_ENV=development + +# Database Configuration +DB_PATH=./data/music.db + +# CORS Configuration +CORS_ORIGIN=http://localhost:5000 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..c5ed25e --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,24 @@ +module.exports = { + env: { + node: true, + es2021: true, + jest: true + }, + extends: [ + 'eslint:recommended' + ], + parserOptions: { + ecmaVersion: 12, + sourceType: 'module' + }, + rules: { + 'indent': ['error', 2], + 'linebreak-style': ['error', 'unix'], + 'quotes': ['error', 'single'], + 'semi': ['error', 'always'], + 'no-unused-vars': ['warn'], + 'no-console': ['warn'], + 'prefer-const': ['error'], + 'no-var': ['error'] + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..75c0e50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Environment variables +.env +.env.local +.env.production + +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Database +*.db +data/ + +# Logs +logs/ +*.log + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +.nyc_output/ + +# IDE and Editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Temporary files +tmp/ +temp/ diff --git a/SQlitess.png b/SQlitess.png deleted file mode 100644 index 54c297c..0000000 Binary files a/SQlitess.png and /dev/null differ diff --git a/_tests_/Unit/songController.test.js b/_tests_/Unit/songController.test.js deleted file mode 100644 index 85b0db8..0000000 --- a/_tests_/Unit/songController.test.js +++ /dev/null @@ -1,137 +0,0 @@ -const songController = require('../../controllers/songController'); -const db = require('../../db'); -jest.mock('../../db'); -afterEach(() => jest.clearAllMocks()); -describe('addSong', () => { - test('❌ should return 400 if fields are missing', () => { - const req = { body: { title: '', artist: '', genre: '' } }; - const res = { - status: jest.fn(() => res), - json: jest.fn() - }; - - songController.addSong(req, res); - - expect(res.status).toHaveBeenCalledWith(400); - expect(res.json).toHaveBeenCalledWith({ error: 'Missing song fields' }); - }); - - test('✅ should insert song and return 201 if data is valid', () => { - const req = { body: { title: 'Believer', artist: 'Imagine Dragons', genre: 'Rock' } }; - const res = { - status: jest.fn(() => res), - json: jest.fn() - }; - - db.run.mockImplementation((sql, params, cb) => { - cb.call({ lastID: 1 }, null); - }); - - songController.addSong(req, res); - - expect(db.run).toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(201); - expect(res.json).toHaveBeenCalledWith({ - id: 1, - title: 'Believer', - artist: 'Imagine Dragons', - genre: 'Rock' - }); - }); -}); - -describe('getSongs', () => { - test('✅ should return list of songs', () => { - const mockSongs = [{ id: 1, title: 'Test Song', artist: 'Test Artist', genre: 'Pop' }]; - db.all.mockImplementation((sql, cb) => cb(null, mockSongs)); - - const req = {}; - const res = { - json: jest.fn(), - status: jest.fn(() => res) - }; - - songController.getSongs(req, res); - - expect(res.json).toHaveBeenCalledWith(mockSongs); - }); - - test('❌ should return 500 if DB fails', () => { - db.all.mockImplementation((sql, cb) => cb(new Error('DB error'), null)); - - const req = {}; - const res = { - json: jest.fn(), - status: jest.fn(() => res) - }; - - songController.getSongs(req, res); - - expect(res.status).toHaveBeenCalledWith(500); - expect(res.json).toHaveBeenCalledWith({ error: 'DB error' }); - }); -}); - -describe('getPlaylists', () => { - test('✅ should return playlists', () => { - const mockPlaylists = [{ id: 1, name: 'Workout' }]; - db.all.mockImplementation((sql, cb) => cb(null, mockPlaylists)); - - const req = {}; - const res = { - json: jest.fn(), - status: jest.fn(() => res) - }; - - songController.getPlaylists(req, res); - - expect(res.json).toHaveBeenCalledWith(mockPlaylists); - }); - - test('❌ should return 500 if DB error', () => { - db.all.mockImplementation((sql, cb) => cb(new Error('DB error'), null)); - - const req = {}; - const res = { - json: jest.fn(), - status: jest.fn(() => res) - }; - - songController.getPlaylists(req, res); - - expect(res.status).toHaveBeenCalledWith(500); - expect(res.json).toHaveBeenCalledWith({ error: 'DB error' }); - }); -}); - -describe('createPlaylist', () => { - test('❌ should return 400 if name is missing', () => { - const req = { body: {} }; - const res = { - json: jest.fn(), - status: jest.fn(() => res) - }; - - songController.createPlaylist(req, res); - - expect(res.status).toHaveBeenCalledWith(400); - expect(res.json).toHaveBeenCalledWith({ error: 'Playlist name is required' }); - }); - - test('✅ should create playlist and return 201', () => { - const req = { body: { name: 'Chill Vibes' } }; - const res = { - json: jest.fn(), - status: jest.fn(() => res) - }; - - db.run.mockImplementation((sql, params, cb) => { - cb.call({ lastID: 2 }, null); - }); - - songController.createPlaylist(req, res); - - expect(res.status).toHaveBeenCalledWith(201); - expect(res.json).toHaveBeenCalledWith({ id: 2, name: 'Chill Vibes' }); - }); -}); diff --git a/_tests_/api/apiEndpoints.test.js b/_tests_/api/apiEndpoints.test.js deleted file mode 100644 index 89ce8de..0000000 --- a/_tests_/api/apiEndpoints.test.js +++ /dev/null @@ -1,64 +0,0 @@ -const request = require('supertest'); -const express = require('express'); -const songRoutes = require('../../routes/songRoutes'); -const db = require('../../db'); -const app = express(); -app.use(express.json()); -app.use('/api/songs', songRoutes); -app.use('/api/playlists', songRoutes); - -beforeAll(done => { - db.run('DELETE FROM songs'); - db.run('DELETE FROM playlists', done); -}); - -describe('🎯 API Endpoint Tests', () => { - let songId; - let playlistId; - - it('POST /api/songs/songs → adds a new song', async () => { - const res = await request(app).post('/api/songs/songs').send({ - title: 'Fix You', - artist: 'Coldplay', - genre: 'Rock' - }); - - expect(res.statusCode).toBe(201); - expect(res.body).toHaveProperty('id'); - songId = res.body.id; - }); - - it('GET /api/songs/songs → returns all songs', async () => { - const res = await request(app).get('/api/songs/songs'); - expect(res.statusCode).toBe(200); - expect(Array.isArray(res.body)).toBe(true); - }); - - it('POST /api/playlists/playlists → creates playlist', async () => { - const res = await request(app).post('/api/playlists/playlists').send({ - name: 'Evening Chill' - }); - - expect(res.statusCode).toBe(201); - expect(res.body).toHaveProperty('id'); - playlistId = res.body.id; - }); - - it('GET /api/playlists/playlists → gets playlists', async () => { - const res = await request(app).get('/api/playlists/playlists'); - expect(res.statusCode).toBe(200); - expect(Array.isArray(res.body)).toBe(true); - }); - - it('POST /api/songs/songs → fails with missing fields', async () => { - const res = await request(app).post('/api/songs/songs').send({}); - expect(res.statusCode).toBe(400); - expect(res.body).toHaveProperty('error'); - }); - - it('POST /api/playlists/playlists → fails with no name', async () => { - const res = await request(app).post('/api/playlists/playlists').send({}); - expect(res.statusCode).toBe(400); - expect(res.body).toHaveProperty('error'); - }); -}); diff --git a/_tests_/integration/api.test.js b/_tests_/integration/api.test.js deleted file mode 100644 index 84c2e9c..0000000 --- a/_tests_/integration/api.test.js +++ /dev/null @@ -1,52 +0,0 @@ -const request = require('supertest'); -const express = require('express'); -const db = require('../../db'); -const songRoutes = require('../../routes/songRoutes'); - -const app = express(); -app.use(express.json()); -app.use('/api/songs', songRoutes); -app.use('/api/playlists', songRoutes); - -beforeAll(done => { - db.run('DELETE FROM songs'); - db.run('DELETE FROM playlists', done); -}); -describe('🎵 API Integration Tests', () => { - let songId; - let playlistId; - - test('POST /api/songs/songs → should add a song', async () => { - const res = await request(app).post('/api/songs/songs').send({ - title: 'Test Song', - artist: 'Test Artist', - genre: 'Pop' - }); - - expect(res.statusCode).toBe(201); - expect(res.body.title).toBe('Test Song'); - songId = res.body.id; - }); - - test('GET /api/songs/songs → should return all songs', async () => { - const res = await request(app).get('/api/songs/songs'); - expect(res.statusCode).toBe(200); - expect(res.body.length).toBeGreaterThan(0); - }); - - test('POST /api/playlists/playlists → should create a playlist', async () => { - const res = await request(app).post('/api/playlists/playlists').send({ - name: 'My Playlist' - }); - - expect(res.statusCode).toBe(201); - expect(res.body.name).toBe('My Playlist'); - playlistId = res.body.id; - }); - - test('GET /api/playlists/playlists → should return playlists', async () => { - const res = await request(app).get('/api/playlists/playlists'); - expect(res.statusCode).toBe(200); - expect(res.body.length).toBeGreaterThan(0); - }); -}); diff --git a/appss.png b/appss.png deleted file mode 100644 index d873aae..0000000 Binary files a/appss.png and /dev/null differ diff --git a/appss2.png b/appss2.png deleted file mode 100644 index 81048fe..0000000 Binary files a/appss2.png and /dev/null differ diff --git a/controllers/music.db b/controllers/music.db deleted file mode 100644 index 2992b01..0000000 Binary files a/controllers/music.db and /dev/null differ diff --git a/controllers/songController.js b/controllers/songController.js deleted file mode 100644 index fcde7e8..0000000 --- a/controllers/songController.js +++ /dev/null @@ -1,35 +0,0 @@ -const db = require('../db'); -exports.getSongs = (req, res) => { - db.all('SELECT * FROM songs', (err, rows) => { - if (err) return res.status(500).json({ error: err.message }); - res.json(rows); - }); -}; - -exports.addSong = (req, res) => { - const { title, artist, genre } = req.body; - if (!title || !artist || !genre) { - return res.status(400).json({ error: 'Missing song fields' }); - } - - db.run('INSERT INTO songs (title, artist, genre) VALUES (?, ?, ?)', [title, artist, genre], function (err) { - if (err) return res.status(500).json({ error: err.message }); - res.status(201).json({ id: this.lastID, title, artist, genre }); - }); -}; -exports.getPlaylists = (req, res) => { - db.all('SELECT * FROM playlists', (err, rows) => { - if (err) return res.status(500).json({ error: err.message }); - res.json(rows); - }); -}; - -exports.createPlaylist = (req, res) => { - const { name } = req.body; - if (!name) return res.status(400).json({ error: 'Playlist name is required' }); - - db.run('INSERT INTO playlists (name) VALUES (?)', [name], function (err) { - if (err) return res.status(500).json({ error: err.message }); - res.status(201).json({ id: this.lastID, name }); - }); -}; diff --git a/coverage/lcov-report/index.html b/coverage/lcov-report/index.html index fc5d790..9181c8e 100644 --- a/coverage/lcov-report/index.html +++ b/coverage/lcov-report/index.html @@ -23,30 +23,30 @@
| Music Tracker API | +src | +
+
+ |
+ 70.96% | +22/31 | +50% | +1/2 | +55.55% | +5/9 | +70.96% | +22/31 | +|||||||
| src/config |
|
100% | -7/7 | +2/2 | +50% | +7/14 | 100% | 0/0 | 100% | -1/1 | -100% | -7/7 | +2/2 | +||||
| src/controllers | +
+
+ |
+ 25.37% | +17/67 | +0% | +0/14 | +33.33% | +4/12 | +25.37% | +17/67 | +||||||||
| src/middleware | +
+
+ |
+ 53.33% | +16/30 | +15% | +3/20 | +60% | +3/5 | +53.33% | +16/30 | ||||||||
| Music Tracker API/controllers | -
-
+ | src/routes | +
+ |
- 92.59% | -25/27 | -86.66% | -13/15 | 100% | -8/8 | +31/31 | +100% | +0/0 | +100% | +1/1 | 100% | -22/22 | +31/31 | +
| src/services | +
+
+ |
+ 32.55% | +28/86 | +16.66% | +7/42 | +40.54% | +15/37 | +32.55% | +28/86 | ||||||||
| Music Tracker API/routes | +src/utils |
|
100% | -8/8 | +5/5 | 100% | 0/0 | 100% | 0/0 | 100% | -8/8 | +5/5 |