Merge pull request #28367 from overleaf/ar-migrate-routermjs-to-zod

[web] migrate router.mjs to zod

GitOrigin-RevId: d3fc21a11351f3e2deb5011cd1beeb86286a300b
This commit is contained in:
Andrew Rumble
2025-09-15 15:53:29 +01:00
committed by Copybot
parent e6e636d45c
commit 819b2a3a46
10 changed files with 295 additions and 166 deletions
@@ -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(
@@ -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 {
@@ -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)
@@ -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()
}
@@ -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 ||
-54
View File
@@ -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
)
@@ -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) {
@@ -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()
@@ -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)
@@ -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
)
})
})