| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814 |
- 'use strict'
- const { expect } = require('chai')
- const fs = require('node:fs')
- const { Readable } = require('node:stream')
- const HTTPStatus = require('http-status')
- const fetch = require('node-fetch')
- const sinon = require('sinon')
- const cleanup = require('../storage/support/cleanup')
- const fixtures = require('../storage/support/fixtures')
- const testFiles = require('../storage/support/test_files')
- const {
- zipStore,
- BlobStore,
- persistChanges,
- redisBuffer,
- blobHash,
- } = require('../../../../storage')
- const { expectHttpError } = require('./support/expect_response')
- const testServer = require('./support/test_server')
- const { createEmptyProject } = require('./support/test_projects')
- const {
- File,
- Snapshot,
- Change,
- AddFileOperation,
- EditFileOperation,
- TextOperation,
- Operation,
- } = require('overleaf-editor-core')
- const testProjects = require('./support/test_projects')
- const { ObjectId } = require('mongodb')
- describe('project controller', function () {
- beforeEach(cleanup.everything)
- beforeEach(fixtures.create)
- describe('initializeProject', function () {
- it('can initialize a new project', async function () {
- const projectId = await testProjects.createEmptyProject()
- expect(projectId).to.be.a('string')
- })
- })
- describe('createZip', function () {
- let importSnapshot
- let createZip
- before(function () {
- importSnapshot =
- testServer.basicAuthClient.apis.ProjectImport.importSnapshot1
- createZip = testServer.basicAuthClient.apis.Project.createZip
- })
- beforeEach(function () {
- // Don't start the work in the background in this test --- it is flaky.
- sinon.stub(zipStore, 'storeZip').resolves()
- })
- afterEach(function () {
- zipStore.storeZip.restore()
- })
- it('creates a URL to a zip file', async function () {
- // Create a test blob.
- const testProjectId = fixtures.docs.uninitializedProject.id
- const response = await fetch(
- testServer.url(
- `/api/projects/${testProjectId}/blobs/${testFiles.HELLO_TXT_HASH}`
- ),
- {
- method: 'PUT',
- body: fs.createReadStream(testFiles.path('hello.txt')),
- headers: {
- Authorization: testServer.basicAuthHeader,
- },
- }
- )
- expect(response.ok).to.be.true
- // Import a project with the test blob.
- const testFilePathname = 'hello.txt'
- const testSnapshot = new Snapshot()
- testSnapshot.addFile(
- testFilePathname,
- File.fromHash(testFiles.HELLO_TXT_HASH)
- )
- const importResponse = await importSnapshot({
- project_id: testProjectId,
- snapshot: testSnapshot.toRaw(),
- })
- expect(importResponse.obj.projectId).to.equal(testProjectId)
- const createZipResponse = await createZip({
- project_id: testProjectId,
- version: 0,
- })
- expect(createZipResponse.status).to.equal(HTTPStatus.OK)
- const zipInfo = createZipResponse.obj
- expect(zipInfo.zipUrl).to.match(
- /^http:\/\/gcs:9090\/download\/storage\/v1\/b\/overleaf-test-zips/
- )
- expect(zipStore.storeZip.calledOnce).to.be.true
- })
- })
- describe('getLatestZip', function () {
- it('returns a zip of the latest snapshot', async function () {
- const projectId = fixtures.docs.uninitializedProject.id
- const uploadResponse = await fetch(
- testServer.url(
- `/api/projects/${projectId}/blobs/${testFiles.HELLO_TXT_HASH}`
- ),
- {
- method: 'PUT',
- body: fs.createReadStream(testFiles.path('hello.txt')),
- headers: { Authorization: testServer.basicAuthHeader },
- }
- )
- expect(uploadResponse.ok).to.be.true
- const snapshot = new Snapshot()
- snapshot.addFile('hello.txt', File.fromHash(testFiles.HELLO_TXT_HASH))
- const importResponse =
- await testServer.basicAuthClient.apis.ProjectImport.importSnapshot1({
- project_id: projectId,
- snapshot: snapshot.toRaw(),
- })
- expect(importResponse.obj.projectId).to.equal(projectId)
- const downloadClient =
- await testServer.createClientForDownloadZip(projectId)
- const zipResponse = await downloadClient.apis.Project.getLatestZip({
- project_id: projectId,
- })
- expect(zipResponse.status).to.equal(HTTPStatus.OK)
- expect(zipResponse.headers['x-history-version']).to.equal('0')
- expect(zipResponse.headers['content-type']).to.equal(
- 'application/octet-stream'
- )
- expect(zipResponse.headers['content-disposition']).to.equal(
- 'attachment; filename=project.zip'
- )
- const testFile = File.fromHash(testFiles.HELLO_TXT_HASH)
- const testChange = new Change(
- [Operation.addFile('main.tex', testFile)],
- new Date()
- )
- const importchangesResponse =
- await testServer.basicAuthClient.apis.ProjectImport.importChanges1({
- project_id: projectId,
- end_version: 0,
- changes: [testChange.toRaw()],
- })
- expect(importchangesResponse.status).to.equal(HTTPStatus.CREATED)
- expect(importchangesResponse.obj).to.deep.equal({ resyncNeeded: false })
- const zipResponse2 = await downloadClient.apis.Project.getLatestZip({
- project_id: projectId,
- })
- expect(zipResponse2.status).to.equal(HTTPStatus.OK)
- expect(zipResponse2.headers['x-history-version']).to.equal('1')
- })
- it('returns 404 for an unknown project', async function () {
- const unknownProjectId = new ObjectId().toString()
- const downloadClient =
- await testServer.createClientForDownloadZip(unknownProjectId)
- await expectHttpError(
- downloadClient.apis.Project.getLatestZip({
- project_id: unknownProjectId,
- }),
- HTTPStatus.NOT_FOUND
- )
- })
- })
- describe('blob stats', function () {
- let populatedPostgresProjectId,
- populatedMongoProjectId,
- emptyPostgresProjectId,
- emptyMongoProjectId
- async function populateProject(projectId) {
- const files = {
- [testFiles.GRAPH_PNG_HASH]: testFiles.path('graph.png'),
- [testFiles.HELLO_TXT_HASH]: testFiles.path('hello.txt'),
- }
- for (const [hash, path] of Object.entries(files)) {
- const response = await fetch(
- testServer.url(`/api/projects/${projectId}/blobs/${hash}`),
- {
- method: 'PUT',
- body: fs.createReadStream(path),
- headers: {
- Authorization: testServer.basicAuthHeader,
- },
- }
- )
- expect(response.status).to.equal(201)
- }
- }
- beforeEach(async function () {
- emptyPostgresProjectId = await testProjects.createEmptyProject()
- emptyMongoProjectId = await testProjects.createEmptyProject(
- new ObjectId().toString()
- )
- populatedPostgresProjectId = await testProjects.createEmptyProject()
- await populateProject(populatedPostgresProjectId)
- populatedMongoProjectId = await testProjects.createEmptyProject(
- new ObjectId().toString()
- )
- await populateProject(populatedMongoProjectId)
- })
- describe('getProjectBlobsStats', function () {
- it('handles empty postgres project', async function () {
- const { body } =
- await testServer.basicAuthClient.apis.Project.getProjectBlobsStats({
- body: { projectIds: [emptyPostgresProjectId] },
- })
- expect(body).to.deep.equal([
- {
- projectId: emptyPostgresProjectId,
- textBlobBytes: 0,
- binaryBlobBytes: 0,
- totalBytes: 0,
- nTextBlobs: 0,
- nBinaryBlobs: 0,
- },
- ])
- })
- it('handles populated postgres project', async function () {
- const { body } =
- await testServer.basicAuthClient.apis.Project.getProjectBlobsStats({
- body: { projectIds: [populatedPostgresProjectId] },
- })
- expect(body).to.deep.equal([
- {
- projectId: populatedPostgresProjectId,
- textBlobBytes: testFiles.HELLO_TXT_BYTE_LENGTH,
- binaryBlobBytes: testFiles.GRAPH_PNG_BYTE_LENGTH,
- totalBytes:
- testFiles.HELLO_TXT_BYTE_LENGTH + testFiles.GRAPH_PNG_BYTE_LENGTH,
- nTextBlobs: 1,
- nBinaryBlobs: 1,
- },
- ])
- })
- it('handles empty mongo project', async function () {
- const { body } =
- await testServer.basicAuthClient.apis.Project.getProjectBlobsStats({
- body: { projectIds: [emptyMongoProjectId] },
- })
- expect(body).to.deep.equal([
- {
- projectId: emptyMongoProjectId,
- textBlobBytes: 0,
- binaryBlobBytes: 0,
- totalBytes: 0,
- nTextBlobs: 0,
- nBinaryBlobs: 0,
- },
- ])
- })
- it('handles populated mongo project', async function () {
- const { body } =
- await testServer.basicAuthClient.apis.Project.getProjectBlobsStats({
- body: { projectIds: [populatedMongoProjectId] },
- })
- expect(body).to.deep.equal([
- {
- projectId: populatedMongoProjectId,
- textBlobBytes: testFiles.HELLO_TXT_BYTE_LENGTH,
- binaryBlobBytes: testFiles.GRAPH_PNG_BYTE_LENGTH,
- totalBytes:
- testFiles.HELLO_TXT_BYTE_LENGTH + testFiles.GRAPH_PNG_BYTE_LENGTH,
- nTextBlobs: 1,
- nBinaryBlobs: 1,
- },
- ])
- })
- it('handles batch of projects', async function () {
- const { body } =
- await testServer.basicAuthClient.apis.Project.getProjectBlobsStats({
- body: {
- projectIds: [
- populatedPostgresProjectId,
- populatedMongoProjectId,
- emptyPostgresProjectId,
- emptyMongoProjectId,
- ],
- },
- })
- expect(body).to.deep.equal([
- {
- projectId: populatedPostgresProjectId,
- textBlobBytes: testFiles.HELLO_TXT_BYTE_LENGTH,
- binaryBlobBytes: testFiles.GRAPH_PNG_BYTE_LENGTH,
- totalBytes:
- testFiles.HELLO_TXT_BYTE_LENGTH + testFiles.GRAPH_PNG_BYTE_LENGTH,
- nTextBlobs: 1,
- nBinaryBlobs: 1,
- },
- {
- projectId: populatedMongoProjectId,
- textBlobBytes: testFiles.HELLO_TXT_BYTE_LENGTH,
- binaryBlobBytes: testFiles.GRAPH_PNG_BYTE_LENGTH,
- totalBytes:
- testFiles.HELLO_TXT_BYTE_LENGTH + testFiles.GRAPH_PNG_BYTE_LENGTH,
- nTextBlobs: 1,
- nBinaryBlobs: 1,
- },
- {
- projectId: emptyPostgresProjectId,
- textBlobBytes: 0,
- binaryBlobBytes: 0,
- totalBytes: 0,
- nTextBlobs: 0,
- nBinaryBlobs: 0,
- },
- {
- projectId: emptyMongoProjectId,
- textBlobBytes: 0,
- binaryBlobBytes: 0,
- totalBytes: 0,
- nTextBlobs: 0,
- nBinaryBlobs: 0,
- },
- ])
- })
- })
- describe('getBlobStats', function () {
- it('handles empty list of hashes', async function () {
- const { body } =
- await testServer.basicAuthClient.apis.Project.getBlobStats({
- project_id: populatedPostgresProjectId,
- body: { blobHashes: [] },
- })
- expect(body).to.deep.equal({
- projectId: populatedPostgresProjectId,
- textBlobBytes: 0,
- binaryBlobBytes: 0,
- totalBytes: 0,
- nTextBlobs: 0,
- nBinaryBlobs: 0,
- })
- })
- it('handles a mix of text and binary blobs', async function () {
- const { body } =
- await testServer.basicAuthClient.apis.Project.getBlobStats({
- project_id: populatedPostgresProjectId,
- body: {
- blobHashes: [testFiles.HELLO_TXT_HASH, testFiles.GRAPH_PNG_HASH],
- },
- })
- expect(body).to.deep.equal({
- projectId: populatedPostgresProjectId,
- textBlobBytes: testFiles.HELLO_TXT_BYTE_LENGTH,
- binaryBlobBytes: testFiles.GRAPH_PNG_BYTE_LENGTH,
- totalBytes:
- testFiles.HELLO_TXT_BYTE_LENGTH + testFiles.GRAPH_PNG_BYTE_LENGTH,
- nTextBlobs: 1,
- nBinaryBlobs: 1,
- })
- })
- it('handles only text blobs', async function () {
- const { body } =
- await testServer.basicAuthClient.apis.Project.getBlobStats({
- project_id: populatedPostgresProjectId,
- body: {
- blobHashes: [testFiles.HELLO_TXT_HASH],
- },
- })
- expect(body).to.deep.equal({
- projectId: populatedPostgresProjectId,
- textBlobBytes: testFiles.HELLO_TXT_BYTE_LENGTH,
- binaryBlobBytes: 0,
- totalBytes: testFiles.HELLO_TXT_BYTE_LENGTH,
- nTextBlobs: 1,
- nBinaryBlobs: 0,
- })
- })
- it('handles only binary blobs', async function () {
- const { body } =
- await testServer.basicAuthClient.apis.Project.getBlobStats({
- project_id: populatedPostgresProjectId,
- body: {
- blobHashes: [testFiles.GRAPH_PNG_HASH],
- },
- })
- expect(body).to.deep.equal({
- projectId: populatedPostgresProjectId,
- textBlobBytes: 0,
- binaryBlobBytes: testFiles.GRAPH_PNG_BYTE_LENGTH,
- totalBytes: testFiles.GRAPH_PNG_BYTE_LENGTH,
- nTextBlobs: 0,
- nBinaryBlobs: 1,
- })
- })
- it('handles non-existent blobs', async function () {
- const { body } =
- await testServer.basicAuthClient.apis.Project.getBlobStats({
- project_id: populatedPostgresProjectId,
- body: {
- blobHashes: [testFiles.STRING_AB_HASH],
- },
- })
- expect(body).to.deep.equal({
- projectId: populatedPostgresProjectId,
- textBlobBytes: 0,
- binaryBlobBytes: 0,
- totalBytes: 0,
- nTextBlobs: 0,
- nBinaryBlobs: 0,
- })
- })
- it('throws an error for bad hashes', async function () {
- await expectHttpError(
- testServer.basicAuthClient.apis.Project.getBlobStats({
- project_id: populatedPostgresProjectId,
- body: {
- blobHashes: ['non-existent-hash'],
- },
- }),
- HTTPStatus.INTERNAL_SERVER_ERROR
- )
- })
- it('handles a request with a large number of blobs', async function () {
- const projectId = await testProjects.createEmptyProject()
- const blobHashes = []
- let expectedTextBytes = 0
- let expectedBinaryBytes = 0
- const nTextBlobs = 10
- const nBinaryBlobs = 10
- for (let i = 0; i < nTextBlobs; i++) {
- const content = `text blob ${i}`
- const hash = blobHash.fromString(content)
- blobHashes.push(hash)
- expectedTextBytes += content.length
- const res = await fetch(
- testServer.url(`/api/projects/${projectId}/blobs/${hash}`),
- {
- method: 'PUT',
- body: content,
- headers: { Authorization: testServer.basicAuthHeader },
- }
- )
- expect(res.status).to.equal(HTTPStatus.CREATED)
- }
- for (let i = 0; i < nBinaryBlobs; i++) {
- const content = Buffer.from([0, i, i + 1, i + 2])
- const hash = await blobHash.fromStream(
- content.length,
- Readable.from(content)
- )
- blobHashes.push(hash)
- expectedBinaryBytes += content.length
- const res = await fetch(
- testServer.url(`/api/projects/${projectId}/blobs/${hash}`),
- {
- method: 'PUT',
- body: content,
- headers: {
- Authorization: testServer.basicAuthHeader,
- 'Content-Type': 'application/octet-stream',
- },
- }
- )
- expect(res.status).to.equal(HTTPStatus.CREATED)
- }
- const { body } =
- await testServer.basicAuthClient.apis.Project.getBlobStats({
- project_id: projectId,
- body: { blobHashes },
- })
- expect(body).to.deep.equal({
- projectId,
- textBlobBytes: expectedTextBytes,
- binaryBlobBytes: expectedBinaryBytes,
- totalBytes: expectedTextBytes + expectedBinaryBytes,
- nTextBlobs,
- nBinaryBlobs,
- })
- })
- })
- })
- // eslint-disable-next-line mocha/no-pending-tests
- describe.skip('getLatestContent', function () {
- // TODO: remove this endpoint entirely, see
- // https://github.com/overleaf/write_latex/pull/5120#discussion_r244291862
- })
- describe('project with changes', function () {
- let projectId
- beforeEach(async function () {
- // used to provide a limit which forces us to persist all of the changes.
- const farFuture = new Date()
- farFuture.setTime(farFuture.getTime() + 7 * 24 * 3600 * 1000)
- const limits = {
- minChangeTimestamp: farFuture,
- maxChangeTimestamp: farFuture,
- }
- const changes = [
- new Change(
- [new AddFileOperation('test.tex', File.fromString('ab'))],
- new Date(),
- []
- ),
- new Change(
- [new AddFileOperation('other.tex', File.fromString('hello'))],
- new Date(),
- []
- ),
- ]
- projectId = await createEmptyProject()
- await persistChanges(projectId, changes, limits, 0)
- })
- describe('getLatestHashedContent', function () {
- it('returns a snapshot', async function () {
- const response =
- await testServer.basicAuthClient.apis.Project.getLatestHashedContent({
- project_id: projectId,
- })
- expect(response.status).to.equal(HTTPStatus.OK)
- const snapshot = Snapshot.fromRaw(response.obj)
- expect(snapshot.countFiles()).to.equal(2)
- expect(snapshot.getFile('test.tex').getHash()).to.equal(
- testFiles.STRING_AB_HASH
- )
- })
- })
- describe('getChanges', function () {
- it('returns all changes when not given a limit', async function () {
- const response =
- await testServer.basicAuthClient.apis.Project.getChanges({
- project_id: projectId,
- })
- expect(response.status).to.equal(HTTPStatus.OK)
- const { changes, hasMore } = response.obj
- expect(changes.length).to.equal(2)
- const filenames = changes
- .flatMap(change => change.operations)
- .map(operation => operation.pathname)
- expect(filenames).to.deep.equal(['test.tex', 'other.tex'])
- expect(hasMore).to.be.false
- })
- it('returns only requested changes', async function () {
- const response =
- await testServer.basicAuthClient.apis.Project.getChanges({
- project_id: projectId,
- since: 1,
- })
- expect(response.status).to.equal(HTTPStatus.OK)
- const { changes, hasMore } = response.obj
- expect(changes.length).to.equal(1)
- const filenames = changes
- .flatMap(change => change.operations)
- .map(operation => operation.pathname)
- expect(filenames).to.deep.equal(['other.tex'])
- expect(hasMore).to.be.false
- })
- it('rejects negative versions', async function () {
- await expect(
- testServer.basicAuthClient.apis.Project.getChanges({
- project_id: projectId,
- since: -1,
- })
- ).to.be.rejectedWith('request failed with status 400')
- })
- it('rejects out of bounds versions', async function () {
- await expect(
- testServer.basicAuthClient.apis.Project.getChanges({
- project_id: projectId,
- since: 20,
- })
- ).to.be.rejectedWith('request failed with status 400')
- })
- })
- describe('project with many chunks', function () {
- let projectId, changes
- beforeEach(async function () {
- // used to provide a limit which forces us to persist all of the changes.
- const farFuture = new Date()
- farFuture.setTime(farFuture.getTime() + 7 * 24 * 3600 * 1000)
- const limits = {
- minChangeTimestamp: farFuture,
- maxChangeTimestamp: farFuture,
- maxChunkChanges: 5,
- }
- projectId = await createEmptyProject()
- const blobStore = new BlobStore(projectId)
- const blob = await blobStore.putString('')
- changes = [
- new Change(
- [new AddFileOperation('test.tex', File.createLazyFromBlobs(blob))],
- new Date(),
- []
- ),
- ]
- for (let i = 0; i < 20; i++) {
- const textOperation = new TextOperation()
- textOperation.retain(i)
- textOperation.insert('x')
- changes.push(
- new Change(
- [new EditFileOperation('test.tex', textOperation)],
- new Date(),
- []
- )
- )
- }
- await persistChanges(projectId, changes, limits, 0)
- })
- it('returns the first chunk when not given a limit', async function () {
- const response =
- await testServer.basicAuthClient.apis.Project.getChanges({
- project_id: projectId,
- })
- expect(response.status).to.equal(HTTPStatus.OK)
- expect(response.obj).to.deep.equal({
- changes: changes.slice(0, 5).map(c => c.toRaw()),
- hasMore: true,
- })
- })
- it('returns only requested changes', async function () {
- const response =
- await testServer.basicAuthClient.apis.Project.getChanges({
- project_id: projectId,
- since: 12,
- })
- expect(response.status).to.equal(HTTPStatus.OK)
- expect(response.obj).to.deep.equal({
- changes: changes.slice(12, 15).map(c => c.toRaw()),
- hasMore: true,
- })
- })
- it('returns changes in the latest chunk', async function () {
- const response =
- await testServer.basicAuthClient.apis.Project.getChanges({
- project_id: projectId,
- since: 20,
- })
- expect(response.status).to.equal(HTTPStatus.OK)
- expect(response.obj).to.deep.equal({
- changes: changes.slice(20).map(c => c.toRaw()),
- hasMore: false,
- })
- })
- })
- })
- describe('getLatestHistoryRaw', function () {
- it('should handles read', async function () {
- const projectId = fixtures.docs.initializedProject.id
- const response =
- await testServer.pseudoJwtBasicAuthClient.apis.Project.getLatestHistoryRaw(
- {
- project_id: projectId,
- readOnly: 'true',
- }
- )
- expect(response.body).to.deep.equal({
- startVersion: 0,
- endVersion: 1,
- endTimestamp: '2032-01-01T00:00:00.000Z',
- })
- })
- })
- describe('deleteProject', function () {
- it('deletes the project chunks', async function () {
- const projectId = fixtures.docs.initializedProject.id
- const historyResponse =
- await testServer.pseudoJwtBasicAuthClient.apis.Project.getLatestHistory(
- {
- project_id: projectId,
- }
- )
- expect(historyResponse.status).to.equal(HTTPStatus.OK)
- expect(historyResponse.body).to.have.property('chunk')
- const deleteResponse =
- await testServer.basicAuthClient.apis.Project.deleteProject({
- project_id: projectId,
- })
- expect(deleteResponse.status).to.equal(HTTPStatus.NO_CONTENT)
- await expectHttpError(
- testServer.pseudoJwtBasicAuthClient.apis.Project.getLatestHistory({
- project_id: projectId,
- }),
- HTTPStatus.NOT_FOUND
- )
- })
- it('deletes the project blobs', async function () {
- const projectId = fixtures.docs.initializedProject.id
- const token = testServer.createTokenForProject(projectId)
- const authHeaders = { Authorization: `Bearer ${token}` }
- const hash = testFiles.HELLO_TXT_HASH
- const fileContents = await fs.promises.readFile(
- testFiles.path('hello.txt')
- )
- const blobUrl = testServer.url(`/api/projects/${projectId}/blobs/${hash}`)
- const response1 = await fetch(blobUrl, {
- method: 'PUT',
- headers: authHeaders,
- body: fileContents,
- })
- expect(response1.ok).to.be.true
- const response2 = await fetch(blobUrl, { headers: authHeaders })
- const payload = await response2.text()
- expect(payload).to.equal(fileContents.toString())
- const deleteResponse =
- await testServer.basicAuthClient.apis.Project.deleteProject({
- project_id: projectId,
- })
- expect(deleteResponse.status).to.equal(HTTPStatus.NO_CONTENT)
- const response3 = await fetch(blobUrl, { headers: authHeaders })
- expect(response3.status).to.equal(HTTPStatus.NOT_FOUND)
- })
- it('deletes the project from the redis buffer', async function () {
- const projectId = await createEmptyProject()
- const blobStore = new BlobStore(projectId)
- const blob = await blobStore.putString('this is a test')
- const snapshot = new Snapshot()
- const change = new Change(
- [new AddFileOperation('test.tex', File.createLazyFromBlobs(blob))],
- new Date(),
- []
- )
- await redisBuffer.queueChanges(projectId, snapshot, 0, [change])
- const changesBefore = await redisBuffer.getNonPersistedChanges(
- projectId,
- 0
- )
- expect(changesBefore.length).to.equal(1)
- const deleteResponse =
- await testServer.basicAuthClient.apis.Project.deleteProject({
- project_id: projectId,
- })
- expect(deleteResponse.status).to.equal(HTTPStatus.NO_CONTENT)
- const changesAfter = await redisBuffer.getNonPersistedChanges(
- projectId,
- 0
- )
- expect(changesAfter.length).to.equal(0)
- const finalState = await redisBuffer.getState(projectId)
- expect(finalState).to.deep.equal({
- changes: [],
- expireTime: null,
- headSnapshot: null,
- headVersion: null,
- persistTime: null,
- persistedVersion: null,
- })
- })
- it('deletes an empty project from the redis buffer', async function () {
- const projectId = await createEmptyProject()
- const deleteResponse =
- await testServer.basicAuthClient.apis.Project.deleteProject({
- project_id: projectId,
- })
- expect(deleteResponse.status).to.equal(HTTPStatus.NO_CONTENT)
- const changesAfter = await redisBuffer.getNonPersistedChanges(
- projectId,
- 0
- )
- expect(changesAfter.length).to.equal(0)
- })
- })
- })
|