diff --git a/server/__tests__/folders.test.js b/server/__tests__/folders.test.js new file mode 100644 index 0000000..056ce28 --- /dev/null +++ b/server/__tests__/folders.test.js @@ -0,0 +1,230 @@ +const Folders = require('../models/folders'); +const ObjectId = require('mongodb').ObjectId; +const Quiz = require('../models/quiz'); + +describe('Folders', () => { + let folders; + let db; + let collection; + + beforeEach(() => { + jest.clearAllMocks(); // Clear any previous mock calls + + // Mock the collection object + collection = { + findOne: jest.fn(), + insertOne: jest.fn(), + find: jest.fn().mockReturnValue({ toArray: jest.fn() }), // Mock the find method + }; + + // Mock the database connection + db = { + connect: jest.fn(), + getConnection: jest.fn().mockReturnThis(), // Add getConnection method + collection: jest.fn().mockReturnValue(collection), + }; + + // Initialize the Folders model with the mocked db + folders = new Folders(db); + }); + + describe('folderExists', () => { + it('should return true if folder exists', async () => { + const title = 'Test Folder'; + const userId = '12345'; + + // Mock the database response + collection.findOne.mockResolvedValue({ title, userId }); + + // Spy on console.log + const consoleSpy = jest.spyOn(console, 'log'); + + const result = await folders.folderExists(title, userId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ title, userId }); + expect(result).toBe(true); + }); + + it('should return false if folder does not exist', async () => { + const title = 'Nonexistent Folder'; + const userId = '12345'; + + // Mock the database response + collection.findOne.mockResolvedValue(null); + + const result = await folders.folderExists(title, userId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ title, userId }); + expect(result).toBe(false); + }); + }); + + describe('copy', () => { + it('should copy a folder and return the new folder ID', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const userId = '12345'; + const newFolderId = new ObjectId(); + // Mock some quizzes that are in folder.content + const sourceFolder = { + title: 'Test Folder', + content: [ + { title: 'Quiz 1', content: [] }, + { title: 'Quiz 2', content: [] }, + ], + }; + + // Mock the response from getFolderWithContent + jest.spyOn(folders, 'getFolderWithContent').mockResolvedValue(sourceFolder); + jest.spyOn(folders, 'create').mockResolvedValue(newFolderId); + // Mock the response from Quiz.createQuiz + jest.spyOn(Quiz, 'create').mockImplementation(() => {}); + + const result = await folders.copy(folderId, userId); + + // expect(db.connect).toHaveBeenCalled(); + // expect(db.collection).toHaveBeenCalledWith('folders'); + // expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + // expect(collection.insertOne).toHaveBeenCalledWith(expect.objectContaining({ userId })); + expect(result).toBe(newFolderId); + }); + + it('should throw an error if the folder does not exist', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const userId = '12345'; + + // Mock the response from getFolderWithContent + jest.spyOn(folders, 'getFolderWithContent').mockImplementation(() => { + throw new Error(`Folder ${folderId} not found`); + }); + + await expect(folders.copy(folderId, userId)).rejects.toThrow(`Folder ${folderId} not found`); + + // expect(db.connect).toHaveBeenCalled(); + // expect(db.collection).toHaveBeenCalledWith('folders'); + // expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + }); + }); + + // write a test for getFolderWithContent + describe('getFolderWithContent', () => { + it('should return a folder with content', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const folder = { + _id: new ObjectId(folderId), + title: 'Test Folder', + }; + const content = { + content : [ + { title: 'Quiz 1', content: [] }, + { title: 'Quiz 2', content: [] }, + ]}; + + // Mock the response from getFolderById + jest.spyOn(folders, 'getFolderById').mockResolvedValue(folder); + + // Mock the response from getContent + jest.spyOn(folders, 'getContent').mockResolvedValue(content); + + const result = await folders.getFolderWithContent(folderId); + + // expect(db.connect).toHaveBeenCalled(); + // expect(db.collection).toHaveBeenCalledWith('folders'); + // expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + expect(result).toEqual({ + ...folder, + content: content + }); + }); + + it('should throw an error if the folder does not exist', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // // Mock the database response + // collection.findOne.mockResolvedValue(null); + + // Mock getFolderById to throw an error + jest.spyOn(folders, 'getFolderById').mockImplementation(() => { + throw new Error(`Folder ${folderId} not found`); + }); + + await expect(folders.getFolderWithContent(folderId)).rejects.toThrow(`Folder ${folderId} not found`); + + // expect(db.connect).toHaveBeenCalled(); + // expect(db.collection).toHaveBeenCalledWith('folders'); + // expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + }); + }); + + // write a test for getContent + describe('getContent', () => { + it('should return the content of a folder', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const content = [ + { title: 'Quiz 1', content: [] }, + { title: 'Quiz 2', content: [] }, + ]; + + // Mock the database response + collection.find().toArray.mockResolvedValue(content); + + const result = await folders.getContent(folderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('files'); + expect(collection.find).toHaveBeenCalledWith({ folderId }); + expect(result).toEqual(content); + }); + + it('should return an empty array if the folder has no content', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // Mock the database response + collection.find().toArray.mockResolvedValue([]); + + const result = await folders.getContent(folderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('files'); + expect(collection.find).toHaveBeenCalledWith({ folderId }); + expect(result).toEqual([]); + }); + }); + + // write a test for getFolderById + describe('getFolderById', () => { + it('should return a folder by ID', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + const folder = { + _id: new ObjectId(folderId), + title: 'Test Folder', + }; + + // Mock the database response + collection.findOne.mockResolvedValue(folder); + + const result = await folders.getFolderById(folderId); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + expect(result).toEqual(folder); + }); + + it('should throw an error if the folder does not exist', async () => { + const folderId = '60c72b2f9b1d8b3a4c8e4d3b'; + + // Mock the database response + collection.findOne.mockResolvedValue(null); + + await expect(folders.getFolderById(folderId)).resolves.toThrow(`Folder ${folderId} not found`); + + expect(db.connect).toHaveBeenCalled(); + expect(db.collection).toHaveBeenCalledWith('folders'); + expect(collection.findOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }); + }); + }); +}); diff --git a/server/controllers/folders.js b/server/controllers/folders.js index 17a5039..9cd81bb 100644 --- a/server/controllers/folders.js +++ b/server/controllers/folders.js @@ -1,174 +1,176 @@ //controller const model = require('../models/folders.js'); +const db = require('../config/db.js'); const AppError = require('../middleware/AppError.js'); const { MISSING_REQUIRED_PARAMETER, NOT_IMPLEMENTED, FOLDER_NOT_FOUND, FOLDER_ALREADY_EXISTS, GETTING_FOLDER_ERROR, DELETE_FOLDER_ERROR, UPDATE_FOLDER_ERROR, MOVING_FOLDER_ERROR, DUPLICATE_FOLDER_ERROR, COPY_FOLDER_ERROR } = require('../constants/errorCodes'); +// controllers must use arrow functions to bind 'this' to the class instance in order to access class properties as callbacks in Express class FoldersController { + constructor() { + console.log("FoldersController constructor: db", db) + this.db = db; + this.folders = new model(this.db); + console.log("FoldersController constructor: folders", this.folders); + } + /*** * Basic queries */ - async create(req, res, next) { + create = async (req, res, next) => { try { const { title } = req.body; - + if (!title) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - - const result = await model.create(title, req.user.userId); - + + const result = await this.folders.create(title, req.user.userId); + if (!result) { throw new AppError(FOLDER_ALREADY_EXISTS); } - + return res.status(200).json({ message: 'Dossier créé avec succès.' }); - - } - catch (error) { + + } catch (error) { return next(error); } } - - async getUserFolders(req, res, next) { - + + getUserFolders = async (req, res, next) => { try { - const folders = await model.getUserFolders(req.user.userId); - + const folders = await this.folders.getUserFolders(req.user.userId); + if (!folders) { throw new AppError(FOLDER_NOT_FOUND); } - + return res.status(200).json({ data: folders }); - - } - catch (error) { + + } catch (error) { return next(error); } } - - async getFolderContent(req, res, next) { + + getFolderContent = async (req, res, next) => { try { const { folderId } = req.params; - + if (!folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const content = await model.getContent(folderId); - + + const content = await this.folders.getContent(folderId); + if (!content) { throw new AppError(GETTING_FOLDER_ERROR); } - + return res.status(200).json({ data: content }); - - } - catch (error) { + + } catch (error) { return next(error); } } - - async delete(req, res, next) { + + delete = async (req, res, next) => { try { const { folderId } = req.params; - + if (!folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const result = await model.delete(folderId); - + + const result = await this.folders.delete(folderId); + if (!result) { throw new AppError(DELETE_FOLDER_ERROR); } - + return res.status(200).json({ message: 'Dossier supprimé avec succès.' }); - - } - catch (error) { + + } catch (error) { return next(error); } } - - async rename(req, res, next) { + + rename = async (req, res, next) => { try { const { folderId, newTitle } = req.body; - + if (!folderId || !newTitle) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const result = await model.rename(folderId, newTitle); - + + const result = await this.folders.rename(folderId, newTitle); + if (!result) { throw new AppError(UPDATE_FOLDER_ERROR); } - + return res.status(200).json({ message: 'Dossier mis à jours avec succès.' }); - - } - catch (error) { + + } catch (error) { return next(error); } } - - - async duplicate(req, res, next) { + + duplicate = async (req, res, next) => { try { - const { folderId, } = req.body; - - if (!folderId ) { + const { folderId } = req.body; + + if (!folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const userId = req.user.userId; - - const newFolderId = await model.duplicate(folderId, userId); - + + const userId = req.user.userId; + + const newFolderId = await this.folders.duplicate(folderId, userId); + if (!newFolderId) { throw new AppError(DUPLICATE_FOLDER_ERROR); } - + return res.status(200).json({ message: 'Dossier dupliqué avec succès.', newFolderId: newFolderId @@ -177,30 +179,30 @@ class FoldersController { return next(error); } } - - async copy(req, res, next) { + + copy = async (req, res, next) => { try { const { folderId, newTitle } = req.body; - + if (!folderId || !newTitle) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - + const userId = req.user.userId; // Assuming userId is obtained from authentication - - const newFolderId = await model.copy(folderId, userId); - + + const newFolderId = await this.folders.copy(folderId, userId); + if (!newFolderId) { throw new AppError(COPY_FOLDER_ERROR); } - + return res.status(200).json({ message: 'Dossier copié avec succès.', newFolderId: newFolderId @@ -210,27 +212,27 @@ class FoldersController { } } - async getFolderById(req, res, next) { + getFolderById = async (req, res, next) => { try { const { folderId } = req.params; - + if (!folderId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - + // Is this folder mine - const owner = await model.getOwner(folderId); - + const owner = await this.folders.getOwner(folderId); + if (owner != req.user.userId) { throw new AppError(FOLDER_NOT_FOUND); } - - const folder = await model.getFolderById(folderId); - + + const folder = await this.folders.getFolderById(folderId); + if (!folder) { throw new AppError(FOLDER_NOT_FOUND); } - + return res.status(200).json({ data: folder }); @@ -238,8 +240,8 @@ class FoldersController { return next(error); } } - - async folderExists(req, res, next) { + + folderExists = async (req, res, next) => { try { const { title } = req.body; @@ -247,10 +249,10 @@ class FoldersController { throw new AppError(MISSING_REQUIRED_PARAMETER); } - const userId = req.user.userId; + const userId = req.user.userId; // Vérifie si le dossier existe pour l'utilisateur donné - const exists = await model.folderExists(title, userId); + const exists = await this.folders.folderExists(title, userId); return res.status(200).json({ exists: exists @@ -260,9 +262,8 @@ class FoldersController { } } - } -module.exports = new FoldersController; \ No newline at end of file +module.exports = new FoldersController; diff --git a/server/models/folders.js b/server/models/folders.js index 5ecfca5..f831d50 100644 --- a/server/models/folders.js +++ b/server/models/folders.js @@ -1,13 +1,16 @@ //model -const db = require('../config/db.js') -const { ObjectId } = require('mongodb'); +// const db = require('../config/db.js') +const ObjectId = require('mongodb').ObjectId; const Quiz = require('./quiz.js'); class Folders { + constructor(db) { + this.db = db; + } async create(title, userId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); @@ -27,8 +30,8 @@ class Folders { } async getUserFolders(userId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); @@ -38,8 +41,8 @@ class Folders { } async getOwner(folderId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); @@ -49,8 +52,8 @@ class Folders { } async getContent(folderId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const filesCollection = conn.collection('files'); @@ -60,8 +63,8 @@ class Folders { } async delete(folderId) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); @@ -74,8 +77,8 @@ class Folders { } async rename(folderId, newTitle) { - await db.connect() - const conn = db.getConnection(); + await this.db.connect() + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); @@ -118,39 +121,41 @@ class Folders { } async folderExists(title, userId) { - await db.connect(); - const conn = db.getConnection(); + console.log("LOG: folderExists", title, userId); + await this.db.connect(); + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); const existingFolder = await foldersCollection.findOne({ title: title, userId: userId }); - return existingFolder !== null; + return !!existingFolder; } async copy(folderId, userId) { - const sourceFolder = await this.getFolderWithContent(folderId); const newFolderId = await this.create(sourceFolder.title, userId); if (!newFolderId) { throw new Error('Failed to create a new folder.'); } for (const quiz of sourceFolder.content) { - await this.createQuiz(quiz.title, quiz.content, newFolderId, userId); + await Quiz.create(quiz.title, quiz.content, newFolderId, userId); } return newFolderId; - } + async getFolderById(folderId) { - await db.connect(); - const conn = db.getConnection(); + await this.db.connect(); + const conn = this.db.getConnection(); const foldersCollection = conn.collection('folders'); const folder = await foldersCollection.findOne({ _id: new ObjectId(folderId) }); + if (!folder) return new Error(`Folder ${folderId} not found`); + return folder; } @@ -171,4 +176,4 @@ class Folders { } -module.exports = new Folders; +module.exports = Folders;