diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index 27008d41ac..a6fdde5bdc 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -11,6 +11,7 @@ const Settings = require('@overleaf/settings') const Errors = require('../Errors/Errors') const SessionManager = require('../Authentication/SessionManager') const { RateLimiter } = require('../../infrastructure/RateLimiter') +const { z, zz, validateReq } = require('../../infrastructure/Validation') const ClsiCookieManager = require('./ClsiCookieManager')( Settings.apis.clsi?.backendGroupName ) @@ -138,6 +139,25 @@ async function _syncTeX(req, res, direction, validatedOptions) { } } +const deleteAuxFilesSchema = z.object({ + params: z.object({ + Project_id: zz.objectId(), + }), + query: z.object({ + clsiserverid: z.string().optional(), + }), +}) + +const wordCountSchema = z.object({ + params: z.object({ + Project_id: zz.objectId(), + }), + query: z.object({ + clsiserverid: z.string().optional(), + file: z.string().optional(), + }), +}) + const _CompileController = { async compile(req, res) { res.setTimeout(COMPILE_TIMEOUT_MS) @@ -384,8 +404,9 @@ const _CompileController = { }, async deleteAuxFiles(req, res) { - const projectId = req.params.Project_id - const { clsiserverid } = req.query + const { params, query } = validateReq(req, deleteAuxFilesSchema) + const projectId = params.Project_id + const { clsiserverid } = query const userId = await CompileController._getUserIdForCompile(req) await CompileManager.promises.deleteAuxFiles( projectId, @@ -650,9 +671,10 @@ const _CompileController = { }, async wordCount(req, res) { - const projectId = req.params.Project_id - const file = req.query.file || false - const { clsiserverid } = req.query + const { params, query } = validateReq(req, wordCountSchema) + const projectId = params.Project_id + const file = query.file || false + const { clsiserverid } = query const userId = CompileController._getUserIdForCompile(req) const body = await CompileManager.promises.wordCount( diff --git a/services/web/app/src/Features/Project/ProjectController.mjs b/services/web/app/src/Features/Project/ProjectController.mjs index 7405b3a2e7..681af35e5c 100644 --- a/services/web/app/src/Features/Project/ProjectController.mjs +++ b/services/web/app/src/Features/Project/ProjectController.mjs @@ -45,6 +45,7 @@ import TagsHandler from '../Tags/TagsHandler.js' import TutorialHandler from '../Tutorial/TutorialHandler.js' import UserUpdater from '../User/UserUpdater.js' import Modules from '../../infrastructure/Modules.js' +import { z, zz, validateReq } from '../../infrastructure/Validation.js' import UserGetter from '../User/UserGetter.js' import { isStandaloneAiAddOnPlanCode } from '../Subscription/AiHelper.js' import SubscriptionController from '../Subscription/SubscriptionController.js' @@ -55,6 +56,34 @@ const { ObjectId } = mongodb * @import { GetProjectsRequest, GetProjectsResponse, Project } from "./types" */ +const updateProjectAdminSettingsSchema = z.object({ + params: z.object({ + Project_id: zz.coercedObjectId(ObjectId), + }), + body: z.object({ + publicAccessLevel: z + .enum( + [PublicAccessLevels.PRIVATE, PublicAccessLevels.TOKEN_BASED], + 'unexpected access level' + ) + .optional(), + }), +}) + +const updateProjectSettingsSchema = z.object({ + params: z.object({ + Project_id: zz.coercedObjectId(), + }), + body: z.object({ + compiler: z.string().optional(), + imageName: z.string().optional(), + mainBibliographyDocId: zz.objectId().optional(), + name: z.string().optional(), + rootDocId: zz.objectId().optional(), + spellCheckLanguage: z.string().optional(), + }), +}) + const _ProjectController = { _isInPercentageRollout(rolloutName, objectId, percentage) { if (Settings.bypassPercentageRollouts === true) { @@ -67,38 +96,36 @@ const _ProjectController = { }, async updateProjectSettings(req, res) { - const projectId = req.params.Project_id + const { params, body } = validateReq(req, updateProjectSettingsSchema) + const projectId = params.Project_id - if (req.body.compiler != null) { - await EditorController.promises.setCompiler(projectId, req.body.compiler) + if (body.compiler != null) { + await EditorController.promises.setCompiler(projectId, body.compiler) } - if (req.body.imageName != null) { - await EditorController.promises.setImageName( - projectId, - req.body.imageName - ) + if (body.imageName != null) { + await EditorController.promises.setImageName(projectId, body.imageName) } - if (req.body.name != null) { - await EditorController.promises.renameProject(projectId, req.body.name) + if (body.name != null) { + await EditorController.promises.renameProject(projectId, body.name) } - if (req.body.spellCheckLanguage != null) { + if (body.spellCheckLanguage != null) { await EditorController.promises.setSpellCheckLanguage( projectId, - req.body.spellCheckLanguage + body.spellCheckLanguage ) } - if (req.body.rootDocId != null) { - await EditorController.promises.setRootDoc(projectId, req.body.rootDocId) + if (body.rootDocId != null) { + await EditorController.promises.setRootDoc(projectId, body.rootDocId) } - if (req.body.mainBibliographyDocId != null) { + if (body.mainBibliographyDocId != null) { await EditorController.promises.setMainBibliographyDoc( projectId, - req.body.mainBibliographyDocId + body.mainBibliographyDocId ) } @@ -106,26 +133,17 @@ const _ProjectController = { }, async updateProjectAdminSettings(req, res) { - const projectId = req.params.Project_id + const { params, body } = validateReq(req, updateProjectAdminSettingsSchema) + const projectId = params.Project_id const user = SessionManager.getSessionUser(req.session) if (!Features.hasFeature('link-sharing')) { return res.sendStatus(403) // return Forbidden if link sharing is not enabled } - const publicAccessLevel = req.body.publicAccessLevel - const publicAccessLevels = [ - PublicAccessLevels.READ_ONLY, - PublicAccessLevels.READ_AND_WRITE, - PublicAccessLevels.PRIVATE, - PublicAccessLevels.TOKEN_BASED, - ] - if ( - req.body.publicAccessLevel != null && - publicAccessLevels.includes(publicAccessLevel) - ) { + if (body.publicAccessLevel != null) { await EditorController.promises.setPublicAccessLevel( projectId, - req.body.publicAccessLevel + body.publicAccessLevel ) await ProjectAuditLogHandler.promises.addEntry( @@ -133,7 +151,7 @@ const _ProjectController = { 'toggle-access-level', user._id, req.ip, - { publicAccessLevel: req.body.publicAccessLevel, status: 'OK' } + { publicAccessLevel: body.publicAccessLevel, status: 'OK' } ) res.sendStatus(204) } else { diff --git a/services/web/app/src/Features/Spelling/SpellingController.mjs b/services/web/app/src/Features/Spelling/SpellingController.mjs index 7790193579..5750ee5ce3 100644 --- a/services/web/app/src/Features/Spelling/SpellingController.mjs +++ b/services/web/app/src/Features/Spelling/SpellingController.mjs @@ -1,9 +1,25 @@ +// @ts-check + import SessionManager from '../Authentication/SessionManager.js' import LearnedWordsManager from './LearnedWordsManager.js' +import { z, validateReq } from '../../infrastructure/Validation.js' + +const learnSchema = z.object({ + body: z.object({ + word: z.string().min(1), + }), +}) + +const unlearnSchema = z.object({ + body: z.object({ + word: z.string().min(1), + }), +}) export default { learn(req, res, next) { - const { word } = req.body + const { body } = validateReq(req, learnSchema) + const { word } = body const userId = SessionManager.getLoggedInUserId(req.session) LearnedWordsManager.learnWord(userId, word, err => { if (err) return next(err) @@ -12,7 +28,8 @@ export default { }, unlearn(req, res, next) { - const { word } = req.body + const { body } = validateReq(req, unlearnSchema) + const { word } = body const userId = SessionManager.getLoggedInUserId(req.session) LearnedWordsManager.unlearnWord(userId, word, err => { if (err) return next(err) diff --git a/services/web/app/src/Features/Tags/TagsController.mjs b/services/web/app/src/Features/Tags/TagsController.mjs index 807e48a9ab..a4918cd2cc 100644 --- a/services/web/app/src/Features/Tags/TagsController.mjs +++ b/services/web/app/src/Features/Tags/TagsController.mjs @@ -1,6 +1,7 @@ import TagsHandler from './TagsHandler.js' import SessionManager from '../Authentication/SessionManager.js' import Errors from '../Errors/Errors.js' +import { z, validateReq } from '../../infrastructure/Validation.js' import { expressify } from '@overleaf/promise-utils' async function _getTags(userId, _req, res) { @@ -21,9 +22,17 @@ async function getAllTags(req, res) { await _getTags(userId, req, res) } +const createTagSchema = z.object({ + body: z.object({ + name: z.string(), + color: z.string().optional(), + }), +}) + async function createTag(req, res) { + const { body } = validateReq(req, createTagSchema) + const { name, color } = body const userId = SessionManager.getLoggedInUserId(req.session) - const { name, color } = req.body const tag = await TagsHandler.promises.createTag(userId, name, color) res.json(tag) } @@ -35,10 +44,20 @@ async function addProjectToTag(req, res) { res.status(204).end() } +const addProjectsToTagSchema = z.object({ + params: z.object({ + tagId: z.string(), + }), + body: z.object({ + projectIds: z.string().array(), + }), +}) + async function addProjectsToTag(req, res) { + const { params, body } = validateReq(req, addProjectsToTagSchema) + const { tagId } = params + const { projectIds } = body const userId = SessionManager.getLoggedInUserId(req.session) - const { tagId } = req.params - const { projectIds } = req.body await TagsHandler.promises.addProjectsToTag(userId, tagId, projectIds) res.status(204).end() } @@ -50,10 +69,20 @@ async function removeProjectFromTag(req, res, next) { res.status(204).end() } +const removeProjectsFromTagSchema = z.object({ + params: z.object({ + tagId: z.string(), + }), + body: z.object({ + projectIds: z.string().array(), + }), +}) + async function removeProjectsFromTag(req, res, next) { + const { params, body } = validateReq(req, removeProjectsFromTagSchema) + const { tagId } = params + const { projectIds } = body const userId = SessionManager.getLoggedInUserId(req.session) - const { tagId } = req.params - const { projectIds } = req.body await TagsHandler.promises.removeProjectsFromTag(userId, tagId, projectIds) res.status(204).end() } @@ -65,10 +94,20 @@ async function deleteTag(req, res) { res.status(204).end() } +const renameTagSchema = z.object({ + params: z.object({ + tagId: z.string(), + }), + body: z.object({ + name: z.string(), + }), +}) + async function renameTag(req, res) { + const { params, body } = validateReq(req, renameTagSchema) const userId = SessionManager.getLoggedInUserId(req.session) - const { tagId } = req.params - const name = req.body?.name + const { tagId } = params + const name = body.name if (!name) { return res.status(400).end() } @@ -76,11 +115,22 @@ async function renameTag(req, res) { res.status(204).end() } +const editTagSchema = z.object({ + params: z.object({ + tagId: z.string(), + }), + body: z.object({ + name: z.string(), + color: z.string().optional(), + }), +}) + async function editTag(req, res) { + const { params, body } = validateReq(req, editTagSchema) + const { tagId } = params + const name = body.name + const color = body.color const userId = SessionManager.getLoggedInUserId(req.session) - const { tagId } = req.params - const name = req.body?.name - const color = req.body?.color if (!name) { return res.status(400).end() } diff --git a/services/web/app/src/Features/User/UserController.mjs b/services/web/app/src/Features/User/UserController.mjs index 257735cad5..2939b34992 100644 --- a/services/web/app/src/Features/User/UserController.mjs +++ b/services/web/app/src/Features/User/UserController.mjs @@ -8,6 +8,7 @@ import metrics from '@overleaf/metrics' import AuthenticationManager from '../Authentication/AuthenticationManager.js' import SessionManager from '../Authentication/SessionManager.js' import Features from '../../infrastructure/Features.js' +import { z, validateReq } from '../../infrastructure/Validation.js' import UserAuditLogHandler from './UserAuditLogHandler.js' import UserSessionsManager from './UserSessionsManager.js' import UserUpdater from './UserUpdater.js' @@ -328,7 +329,18 @@ async function unsubscribe(req, res, next) { }) } +const updateUserSettingsSchema = z.object({ + body: z + .object({ + first_name: z.string().max(255).nullish(), + last_name: z.string().max(255).nullish(), + }) + .passthrough(), + // TODO: complete the schema and remove the passthrough +}) + async function updateUserSettings(req, res, next) { + const { body } = validateReq(req, updateUserSettingsSchema) const userId = SessionManager.getLoggedInUserId(req.session) req.logger.addFields({ userId }) @@ -337,68 +349,67 @@ async function updateUserSettings(req, res, next) { throw new OError('problem updating user settings', { userId }) } - if (req.body.first_name != null) { - user.first_name = req.body.first_name.trim() + if (body.first_name != null) { + user.first_name = body.first_name.trim() } - if (req.body.last_name != null) { - user.last_name = req.body.last_name.trim() + if (body.last_name != null) { + user.last_name = body.last_name.trim() } - if (req.body.role != null) { - user.role = req.body.role.trim() + if (body.role != null) { + user.role = body.role.trim() } - if (req.body.institution != null) { - user.institution = req.body.institution.trim() + if (body.institution != null) { + user.institution = body.institution.trim() } - if (req.body.mode != null) { - user.ace.mode = req.body.mode + if (body.mode != null) { + user.ace.mode = body.mode } - if (req.body.editorTheme != null) { - user.ace.theme = req.body.editorTheme + if (body.editorTheme != null) { + user.ace.theme = body.editorTheme } - if (req.body.overallTheme != null) { - user.ace.overallTheme = req.body.overallTheme + if (body.overallTheme != null) { + user.ace.overallTheme = body.overallTheme } - if (req.body.fontSize != null) { - user.ace.fontSize = req.body.fontSize + if (body.fontSize != null) { + user.ace.fontSize = body.fontSize } - if (req.body.autoComplete != null) { - user.ace.autoComplete = req.body.autoComplete + if (body.autoComplete != null) { + user.ace.autoComplete = body.autoComplete } - if (req.body.autoPairDelimiters != null) { - user.ace.autoPairDelimiters = req.body.autoPairDelimiters + if (body.autoPairDelimiters != null) { + user.ace.autoPairDelimiters = body.autoPairDelimiters } - if (req.body.spellCheckLanguage != null) { - user.ace.spellCheckLanguage = req.body.spellCheckLanguage + if (body.spellCheckLanguage != null) { + user.ace.spellCheckLanguage = body.spellCheckLanguage } - if (req.body.pdfViewer != null) { - user.ace.pdfViewer = req.body.pdfViewer + if (body.pdfViewer != null) { + user.ace.pdfViewer = body.pdfViewer } - if (req.body.syntaxValidation != null) { - user.ace.syntaxValidation = req.body.syntaxValidation + if (body.syntaxValidation != null) { + user.ace.syntaxValidation = body.syntaxValidation } - if (req.body.fontFamily != null) { - user.ace.fontFamily = req.body.fontFamily + if (body.fontFamily != null) { + user.ace.fontFamily = body.fontFamily } - if (req.body.lineHeight != null) { - user.ace.lineHeight = req.body.lineHeight + if (body.lineHeight != null) { + user.ace.lineHeight = body.lineHeight } - if (req.body.mathPreview != null) { - user.ace.mathPreview = req.body.mathPreview + if (body.mathPreview != null) { + user.ace.mathPreview = body.mathPreview } - if (req.body.breadcrumbs != null) { - user.ace.breadcrumbs = Boolean(req.body.breadcrumbs) + if (body.breadcrumbs != null) { + user.ace.breadcrumbs = Boolean(body.breadcrumbs) } - if (req.body.referencesSearchMode != null) { - const mode = - req.body.referencesSearchMode === 'simple' ? 'simple' : 'advanced' + if (body.referencesSearchMode != null) { + const mode = body.referencesSearchMode === 'simple' ? 'simple' : 'advanced' user.ace.referencesSearchMode = mode } - if (req.body.enableNewEditor != null) { - user.ace.enableNewEditor = Boolean(req.body.enableNewEditor) + if (body.enableNewEditor != null) { + user.ace.enableNewEditor = Boolean(body.enableNewEditor) } await user.save() - const newEmail = req.body.email?.trim().toLowerCase() + const newEmail = body.email?.trim().toLowerCase() if ( newEmail == null || newEmail === user.email || diff --git a/services/web/app/src/router.mjs b/services/web/app/src/router.mjs index ddfabaea22..69740e61b6 100644 --- a/services/web/app/src/router.mjs +++ b/services/web/app/src/router.mjs @@ -58,12 +58,10 @@ import SystemMessageController from './Features/SystemMessages/SystemMessageCont import AnalyticsRegistrationSourceMiddleware from './Features/Analytics/AnalyticsRegistrationSourceMiddleware.js' import AnalyticsUTMTrackingMiddleware from './Features/Analytics/AnalyticsUTMTrackingMiddleware.mjs' import CaptchaMiddleware from './Features/Captcha/CaptchaMiddleware.mjs' -import { Joi, validate } from './infrastructure/Validation.js' import UnsupportedBrowserMiddleware from './infrastructure/UnsupportedBrowserMiddleware.js' import logger from '@overleaf/logger' import _ from 'lodash' import { plainTextResponse } from './infrastructure/Response.js' -import PublicAccessLevels from './Features/Authorization/PublicAccessLevels.js' import SocketDiagnostics from './Features/SocketDiagnostics/SocketDiagnostics.mjs' import ClsiCacheController from './Features/Compile/ClsiCacheController.mjs' import AsyncLocalStorage from './infrastructure/AsyncLocalStorage.js' @@ -305,12 +303,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { webRouter.post( '/user/settings', AuthenticationController.requireLogin(), - validate({ - body: Joi.object({ - first_name: Joi.string().allow(null, '').max(255), - last_name: Joi.string().allow(null, '').max(255), - }).unknown(), - }), UserController.updateUserSettings ) webRouter.post( @@ -565,13 +557,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { ) webRouter.post( '/project/:Project_id/settings', - validate({ - body: Joi.object({ - publicAccessLevel: Joi.string() - .valid(PublicAccessLevels.PRIVATE, PublicAccessLevels.TOKEN_BASED) - .optional(), - }), - }), AuthorizationMiddleware.ensureUserCanWriteProjectSettings, ProjectController.updateProjectSettings ) @@ -659,7 +644,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { webRouter.delete( '/project/:Project_id/output', - validate({ query: { clsiserverid: Joi.string() } }), AuthorizationMiddleware.ensureUserCanReadProject, CompileController.deleteAuxFiles ) @@ -675,7 +659,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { ) webRouter.get( '/project/:Project_id/wordcount', - validate({ query: { clsiserverid: Joi.string() } }), AuthorizationMiddleware.ensureUserCanReadProject, CompileController.wordCount ) @@ -817,35 +800,18 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { '/tag', AuthenticationController.requireLogin(), RateLimiterMiddleware.rateLimit(rateLimiters.createTag), - validate({ - body: Joi.object({ - name: Joi.string().required(), - color: Joi.string(), - }), - }), TagsController.createTag ) webRouter.post( '/tag/:tagId/rename', AuthenticationController.requireLogin(), RateLimiterMiddleware.rateLimit(rateLimiters.renameTag), - validate({ - body: Joi.object({ - name: Joi.string().required(), - }), - }), TagsController.renameTag ) webRouter.post( '/tag/:tagId/edit', AuthenticationController.requireLogin(), RateLimiterMiddleware.rateLimit(rateLimiters.renameTag), - validate({ - body: Joi.object({ - name: Joi.string().required(), - color: Joi.string(), - }), - }), TagsController.editTag ) webRouter.delete( @@ -864,11 +830,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { '/tag/:tagId/projects', AuthenticationController.requireLogin(), RateLimiterMiddleware.rateLimit(rateLimiters.addProjectsToTag), - validate({ - body: Joi.object({ - projectIds: Joi.array().items(Joi.string()).required(), - }), - }), TagsController.addProjectsToTag ) webRouter.delete( @@ -881,11 +842,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { '/tag/:tagId/projects/remove', AuthenticationController.requireLogin(), RateLimiterMiddleware.rateLimit(rateLimiters.removeProjectsFromTag), - validate({ - body: Joi.object({ - projectIds: Joi.array().items(Joi.string()).required(), - }), - }), TagsController.removeProjectsFromTag ) @@ -996,22 +952,12 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { webRouter.post( '/spelling/learn', - validate({ - body: Joi.object({ - word: Joi.string().required(), - }), - }), AuthenticationController.requireLogin(), SpellingController.learn ) webRouter.post( '/spelling/unlearn', - validate({ - body: Joi.object({ - word: Joi.string().required(), - }), - }), AuthenticationController.requireLogin(), SpellingController.unlearn ) diff --git a/services/web/test/acceptance/src/ProjectCRUDTests.mjs b/services/web/test/acceptance/src/ProjectCRUDTests.mjs index 082499ba91..a443f8852f 100644 --- a/services/web/test/acceptance/src/ProjectCRUDTests.mjs +++ b/services/web/test/acceptance/src/ProjectCRUDTests.mjs @@ -165,6 +165,53 @@ describe('Project CRUD', function () { expectObjectIdArrayEqual(trashedProject.trashed, []) }) }) + + describe('ProjectAdminSettings', async function () { + it('publicAccessLevel can be set to private', async function () { + const { response } = await this.user.doRequest('POST', { + url: `/project/${this.projectId}/settings/admin`, + json: { + publicAccessLevel: 'private', + }, + }) + expect(response.statusCode).to.equal(204) + const project = await Project.findById(this.projectId).exec() + expect(project.publicAccesLevel).to.equal('private') + }) + it('publicAccessLevel can be set to tokenBased', async function () { + await this.user.makePrivate(this.projectId) + const { response } = await this.user.doRequest('POST', { + url: `/project/${this.projectId}/settings/admin`, + json: { + publicAccessLevel: 'tokenBased', + }, + }) + expect(response.statusCode).to.equal(204) + const project = await Project.findById(this.projectId).exec() + expect(project.publicAccesLevel).to.equal('tokenBased') + }) + it('returns a 400 when publicAccessLevel is set an unsupported access level', async function () { + await this.user.makePrivate(this.projectId) + const { response, body } = await this.user.doRequest('POST', { + url: `/project/${this.projectId}/settings/admin`, + json: { + publicAccessLevel: 'readOnly', + }, + }) + expect(response.statusCode).to.equal(400) + expect(body).to.include('Unexpected access level') + const project = await Project.findById(this.projectId).exec() + expect(project.publicAccesLevel).to.equal('private') + }) + it('returns a 500 when no publicAccessLevel is provided', async function () { + const { response, body } = await this.user.doRequest('POST', { + url: `/project/${this.projectId}/settings/admin`, + json: {}, + }) + expect(response.statusCode).to.equal(500) + expect(body).to.equal('Internal Server Error') + }) + }) }) function expectObjectIdArrayEqual(objectIdArray, stringArray) { diff --git a/services/web/test/unit/src/Compile/CompileControllerTests.js b/services/web/test/unit/src/Compile/CompileControllerTests.js index 64b04f7299..25efe0d3d9 100644 --- a/services/web/test/unit/src/Compile/CompileControllerTests.js +++ b/services/web/test/unit/src/Compile/CompileControllerTests.js @@ -97,6 +97,15 @@ describe('CompileController', function () { done() {} }, }), + // TODO: remove this once we remove Joi/Celebrate + celebrate: (this.celebrate = { + celebrate: sinon.stub(), + errors: sinon.stub(), + Joi: { + any: sinon.stub(), + extend: sinon.stub(), + }, + }), './CompileManager': this.CompileManager, '../User/UserGetter': this.UserGetter, './ClsiManager': this.ClsiManager, @@ -118,7 +127,7 @@ describe('CompileController', function () { }, }, }) - this.projectId = 'project-id' + this.projectId = 'abc123def456abc123def456' this.build_id = '18fbe9e7564-30dcb2f71250c690' this.next = sinon.stub() this.req = new MockRequest() diff --git a/services/web/test/unit/src/Project/ProjectController.test.mjs b/services/web/test/unit/src/Project/ProjectController.test.mjs index e0c9a7b4aa..4bd364fd69 100644 --- a/services/web/test/unit/src/Project/ProjectController.test.mjs +++ b/services/web/test/unit/src/Project/ProjectController.test.mjs @@ -505,6 +505,10 @@ describe('ProjectController', function () { }) describe('updateProjectSettings', function () { + beforeEach(function (ctx) { + ctx.req.params.Project_id = ctx.project_id.toString() + }) + it('should update the name', async function (ctx) { await new Promise(resolve => { ctx.EditorController.promises.renameProject = sinon.stub().resolves() @@ -570,7 +574,9 @@ describe('ProjectController', function () { it('should update the root doc', async function (ctx) { await new Promise(resolve => { ctx.EditorController.promises.setRootDoc = sinon.stub().resolves() - ctx.req.body = { rootDocId: (ctx.rootDocId = 'root-doc-id') } + ctx.req.body = { + rootDocId: (ctx.rootDocId = 'abc123def456abc123def456'), + } ctx.res.sendStatus = code => { ctx.EditorController.promises.setRootDoc .calledWith(ctx.project_id, ctx.rootDocId) @@ -590,13 +596,14 @@ describe('ProjectController', function () { ctx.EditorController.promises.setPublicAccessLevel = sinon .stub() .resolves() + ctx.req.params.Project_id = ctx.project_id.toString() ctx.req.body = { - publicAccessLevel: 'readOnly', + publicAccessLevel: 'tokenBased', } await new Promise(resolve => { ctx.res.sendStatus = code => { ctx.EditorController.promises.setPublicAccessLevel - .calledWith(ctx.project_id, 'readOnly') + .calledWith(ctx.project_id, 'tokenBased') .should.equal(true) code.should.equal(204) resolve() @@ -611,8 +618,9 @@ describe('ProjectController', function () { .stub() .resolves() ctx.req.body = { - publicAccessLevel: 'readOnly', + publicAccessLevel: 'tokenBased', } + ctx.req.params.Project_id = ctx.project_id.toString() await new Promise(resolve => { ctx.res.sendStatus = code => { ctx.ProjectAuditLogHandler.promises.addEntry @@ -622,7 +630,7 @@ describe('ProjectController', function () { ctx.user._id, ctx.req.ip, { - publicAccessLevel: 'readOnly', + publicAccessLevel: 'tokenBased', status: 'OK', } ) @@ -638,8 +646,9 @@ describe('ProjectController', function () { ctx.EditorController.promises.setPublicAccessLevel = sinon .stub() .resolves() + ctx.req.params.Project_id = ctx.project_id.toString() ctx.req.body = { - publicAccessLevel: 'readOnly', + publicAccessLevel: 'tokenBased', } await new Promise(resolve => { ctx.res.sendStatus = code => { @@ -740,6 +749,7 @@ describe('ProjectController', function () { beforeEach(function (ctx) { ctx.newProjectName = 'my supper great new project' ctx.req.body.newProjectName = ctx.newProjectName + ctx.req.params.Project_id = ctx.project_id.toString() }) it('should call the editor controller', async function (ctx) { @@ -747,9 +757,16 @@ describe('ProjectController', function () { ctx.EditorController.promises.renameProject.resolves() ctx.res.sendStatus = code => { code.should.equal(200) - ctx.EditorController.promises.renameProject - .calledWith(ctx.project_id, ctx.newProjectName) - .should.equal(true) + + expect(ctx.EditorController.promises.renameProject).to.have.been + .called + expect( + ctx.EditorController.promises.renameProject.args[0][0].toString() + ).to.equal(ctx.project_id.toString()) + expect( + ctx.EditorController.promises.renameProject.args[0][1] + ).to.equal(ctx.newProjectName) + resolve() } ctx.ProjectController.renameProject(ctx.req, ctx.res) diff --git a/services/web/test/unit/src/Tags/TagsController.test.mjs b/services/web/test/unit/src/Tags/TagsController.test.mjs index 178217a834..9a8c249f9f 100644 --- a/services/web/test/unit/src/Tags/TagsController.test.mjs +++ b/services/web/test/unit/src/Tags/TagsController.test.mjs @@ -1,5 +1,6 @@ -import { assert, vi } from 'vitest' +import { assert, beforeEach, describe, it, vi } from 'vitest' import sinon from 'sinon' +import { ZodError } from 'zod' const modulePath = '../../../../app/src/Features/Tags/TagsController.mjs' @@ -198,20 +199,11 @@ describe('TagsController', function () { }) }) - it('without a name', async function (ctx) { - await new Promise(resolve => { - ctx.req.body = { name: undefined } - ctx.TagsController.renameTag(ctx.req, { - status: code => { - assert.equal(code, 400) - sinon.assert.notCalled(ctx.TagsHandler.promises.renameTag) - resolve() - return { - end: () => {}, - } - }, - }) - }) + it('without a name', function (ctx) { + ctx.req.body = { name: undefined } + ctx.TagsController.renameTag(ctx.req, ctx.res).should.be.rejectedWith( + ZodError + ) }) })