mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 21:23:45 +01:00
[web] platform: adopt strict zod schema and extend test coverage GitOrigin-RevId: e3a0b7b4c52ac116f28be0d284284e3bd3994cda
143 lines
5.8 KiB
JavaScript
143 lines
5.8 KiB
JavaScript
import { expect } from 'chai'
|
|
import UserHelper from './helpers/User.mjs'
|
|
import { expectValidationErrorRaw } from '@overleaf/validation-tools/testUtils.js'
|
|
import MockClsiCacheClass from './mocks/MockClsiCache.mjs'
|
|
|
|
const User = UserHelper.promises
|
|
|
|
let MockClsiCache
|
|
|
|
before(function () {
|
|
MockClsiCache = MockClsiCacheClass.instance()
|
|
})
|
|
|
|
describe('ClsiCacheController', function () {
|
|
let owner, projectId
|
|
|
|
beforeEach(async function () {
|
|
owner = new User()
|
|
await owner.login()
|
|
projectId = await owner.createProject('clsi-cache-controller-test', {
|
|
template: 'blank',
|
|
})
|
|
})
|
|
|
|
describe('GET /project/:Project_id/output/cached/output.overleaf.json', function () {
|
|
it('should reject a malformed project id with 404', async function () {
|
|
const { response, body } = await owner.doRequest('get', {
|
|
url: '/project/not-an-object-id/output/cached/output.overleaf.json',
|
|
})
|
|
expectValidationErrorRaw(
|
|
{ statusCode: response.statusCode, body },
|
|
404,
|
|
'Project_id'
|
|
)
|
|
})
|
|
|
|
it('should return 404 when nothing is cached', async function () {
|
|
// getLatestBuildFromCache uses res.sendStatus(), unlike
|
|
// downloadFromCache's res.status().end() -- sendStatus fills the
|
|
// body with the status text, so this is genuinely different from
|
|
// (and not to be confused with) an empty-body response.
|
|
const { response, body } = await owner.doRequest('get', {
|
|
url: `/project/${projectId}/output/cached/output.overleaf.json`,
|
|
})
|
|
expect(response.statusCode).to.equal(404)
|
|
expect(body).to.equal('Not Found')
|
|
})
|
|
|
|
it('should return 410 when the cached build is stale', async function () {
|
|
// isUpToDate compares this X-Last-Modified against the project's own
|
|
// lastUpdated timestamp (always "now", since the project was just
|
|
// created) -- an ancient date always loses that comparison.
|
|
MockClsiCache.addEntry(
|
|
`/project/${projectId}/user/${owner.id}/latest/output/output.overleaf.json`,
|
|
{
|
|
content: '{}',
|
|
contentType: 'application/json',
|
|
lastModified: new Date(0),
|
|
}
|
|
)
|
|
const { response, body } = await owner.doRequest('get', {
|
|
url: `/project/${projectId}/output/cached/output.overleaf.json`,
|
|
})
|
|
expect(response.statusCode).to.equal(410)
|
|
expect(body).to.equal('Gone')
|
|
})
|
|
})
|
|
|
|
describe('GET /download/project/:Project_id/build/:editorBuildId/output/cached/:filename', function () {
|
|
it('should reject a malformed project id with 404', async function () {
|
|
const { response, body } = await owner.doRequest('get', {
|
|
url: '/download/project/not-an-object-id/build/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa-1111111-2222222/output/cached/output.pdf',
|
|
})
|
|
expectValidationErrorRaw(
|
|
{ statusCode: response.statusCode, body },
|
|
404,
|
|
'Project_id'
|
|
)
|
|
})
|
|
|
|
it('should reject a malformed editorBuildId with 404', async function () {
|
|
const { response, body } = await owner.doRequest('get', {
|
|
url: `/download/project/${projectId}/build/not-a-valid-build-id/output/cached/output.pdf`,
|
|
})
|
|
expectValidationErrorRaw(
|
|
{ statusCode: response.statusCode, body },
|
|
404,
|
|
'editorBuildId'
|
|
)
|
|
})
|
|
|
|
it('should reject a path traversal filename with 404', async function () {
|
|
const { response, body } = await owner.doRequest('get', {
|
|
url: `/download/project/${projectId}/build/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa-1111111-2222222/output/cached/..%2F..%2F..%2F..%2Fetc%2Fpasswd`,
|
|
})
|
|
expectValidationErrorRaw(
|
|
{ statusCode: response.statusCode, body },
|
|
404,
|
|
'filename'
|
|
)
|
|
})
|
|
|
|
it('should return 404 when nothing is cached for this build', async function () {
|
|
// Asserting the empty body (rather than just the status code) is what
|
|
// distinguishes "reached the handler and the real cache genuinely has
|
|
// nothing" from "rejected by the schema", which also 404s but with a
|
|
// JSON `{ error, statusCode }` body.
|
|
const { response, body } = await owner.doRequest('get', {
|
|
url: `/download/project/${projectId}/build/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa-1111111-2222222/output/cached/output.pdf`,
|
|
})
|
|
expect(response.statusCode).to.equal(404)
|
|
expect(body).to.equal('')
|
|
})
|
|
|
|
it('should return the cached file when the build is genuinely cached', async function () {
|
|
MockClsiCache.addEntry(
|
|
`/project/${projectId}/user/${owner.id}/build/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa-1111111-2222222/search/output/output.pdf`,
|
|
{ content: 'mock-pdf-bytes', contentType: 'application/pdf' }
|
|
)
|
|
const { response, body } = await owner.doRequest('get', {
|
|
url: `/download/project/${projectId}/build/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa-1111111-2222222/output/cached/output.pdf`,
|
|
})
|
|
expect(response.statusCode).to.equal(200)
|
|
expect(body).to.equal('mock-pdf-bytes')
|
|
})
|
|
|
|
it('should return a nested filename when the build is genuinely cached', async function () {
|
|
// some.blg is allowed by isAllowedFilename's `.blg` suffix branch
|
|
// rather than its list of literal names -- proves that branch reaches
|
|
// the cache lookup and back.
|
|
MockClsiCache.addEntry(
|
|
`/project/${projectId}/user/${owner.id}/build/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa-1111111-2222222/search/output/some.blg`,
|
|
{ content: 'mock-blg-bytes', contentType: 'text/plain' }
|
|
)
|
|
const { response, body } = await owner.doRequest('get', {
|
|
url: `/download/project/${projectId}/build/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa-1111111-2222222/output/cached/some.blg`,
|
|
})
|
|
expect(response.statusCode).to.equal(200)
|
|
expect(body).to.equal('mock-blg-bytes')
|
|
})
|
|
})
|
|
})
|