| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625 |
- import { assert, beforeAll, beforeEach, describe, it, vi, expect } from 'vitest'
- import Errors from '../../../../app/src/Features/Errors/Errors.js'
- import tk from 'timekeeper'
- import { RequestFailedError } from '@overleaf/fetch-utils'
- const modulePath = '../../../../app/src/Features/Docstore/DocstoreManager'
- vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
- vi.importActual('../../../../app/src/Features/Errors/Errors.js')
- )
- describe('DocstoreManager', function () {
- let DocstoreManager, FetchUtils, projectId, docId, settings
- beforeEach(async function () {
- settings = {
- apis: {
- docstore: {
- url: 'http://docstore.overleaf.com',
- },
- },
- }
- vi.doMock('@overleaf/settings', () => ({
- default: settings,
- }))
- FetchUtils = {
- fetchNothing: vi.fn().mockResolvedValue(),
- fetchJson: vi.fn().mockResolvedValue({}),
- RequestFailedError,
- }
- vi.doMock('@overleaf/fetch-utils', () => FetchUtils)
- DocstoreManager = (await import(modulePath)).default
- projectId = 'project-id-123'
- docId = 'doc-id-123'
- })
- describe('deleteDoc', function () {
- describe('with a successful response code', function () {
- // for assertions on the deletedAt timestamp, we need to freeze the clock.
- beforeAll(function () {
- tk.freeze(Date.now())
- })
- afterAll(function () {
- tk.reset()
- })
- beforeEach(async function () {
- await DocstoreManager.promises.deleteDoc(
- projectId,
- docId,
- 'wombat.tex',
- new Date()
- )
- })
- it('should delete the doc in the docstore api', function () {
- const url = new URL(settings.apis.docstore.url)
- url.pathname = `/project/${projectId}/doc/${docId}`
- expect(FetchUtils.fetchNothing).toHaveBeenCalledWith(url, {
- json: { deleted: true, deletedAt: new Date(), name: 'wombat.tex' },
- signal: expect.anything(),
- method: 'PATCH',
- })
- })
- })
- describe('with a failed response code', function () {
- beforeEach(function () {
- FetchUtils.fetchNothing.mockImplementation((url, opts) => {
- throw new RequestFailedError(url, opts, { status: 500 })
- })
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.deleteDoc(
- projectId,
- docId,
- 'main.tex',
- new Date()
- )
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Error)
- expect(error).to.have.property(
- 'message',
- 'docstore api responded with non-success code'
- )
- })
- })
- describe('with a missing (404) response code', function () {
- beforeEach(function () {
- FetchUtils.fetchNothing.mockImplementation((url, opts) => {
- throw new RequestFailedError(url, opts, { status: 404 })
- })
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.deleteDoc(
- projectId,
- docId,
- 'main.tex',
- new Date()
- )
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Error)
- expect(error).to.have.property(
- 'message',
- 'tried to delete doc not in docstore'
- )
- })
- })
- })
- describe('updateDoc', function () {
- let lines, modified, ranges, rev, updateDocResponse, version
- beforeEach(function () {
- lines = ['mock', 'doc', 'lines']
- rev = 5
- version = 42
- ranges = { mock: 'ranges' }
- modified = true
- })
- describe('with a successful response code', async function () {
- beforeEach(async function () {
- FetchUtils.fetchJson.mockResolvedValue({
- modified,
- rev,
- })
- updateDocResponse = await DocstoreManager.promises.updateDoc(
- projectId,
- docId,
- lines,
- version,
- ranges
- )
- })
- it('should update the doc in the docstore api', function () {
- expect(FetchUtils.fetchJson).toHaveBeenCalledWith(
- new URL(
- `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}`
- ),
- {
- signal: expect.anything(),
- method: 'POST',
- json: {
- lines,
- version,
- ranges,
- },
- }
- )
- })
- it('should return the modified status and revision', function () {
- expect(updateDocResponse).to.haveOwnProperty('modified', modified)
- expect(updateDocResponse).to.haveOwnProperty('rev', rev)
- })
- })
- describe('with a failed response code', function () {
- beforeEach(function () {
- FetchUtils.fetchJson.mockImplementation((url, opts) => {
- throw new RequestFailedError(url, opts, { status: 500 })
- })
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.updateDoc(
- projectId,
- docId,
- lines,
- version,
- ranges
- )
- assert.fail('updateDoc should have thrown an error')
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Error)
- expect(error).to.have.property(
- 'message',
- 'docstore api responded with non-success code'
- )
- })
- })
- })
- describe('getDoc', function () {
- let doc, getDocResponse, lines, ranges, rev, version
- beforeEach(function () {
- lines = ['mock', 'doc', 'lines']
- rev = 5
- version = 42
- ranges = { mock: 'ranges' }
- doc = {
- lines,
- rev,
- version,
- ranges,
- }
- })
- describe('with a successful response code', function () {
- beforeEach(async function () {
- FetchUtils.fetchJson.mockResolvedValue(doc)
- getDocResponse = await DocstoreManager.promises.getDoc(projectId, docId)
- })
- it('should get the doc from the docstore api', function () {
- expect(FetchUtils.fetchJson).toHaveBeenCalledWith(
- new URL(
- `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}`
- ),
- {
- signal: expect.anything(),
- }
- )
- })
- it('should resolve with the lines, version and rev', function () {
- expect(getDocResponse).to.eql({
- lines,
- rev,
- version,
- ranges,
- })
- })
- })
- describe('with a failed response code', function () {
- beforeEach(function () {
- FetchUtils.fetchJson.mockImplementation((url, opts) => {
- throw new RequestFailedError(url, opts, { status: 500 })
- })
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.getDoc(projectId, docId)
- assert.fail('getDoc should have thrown an error')
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Error)
- expect(error).to.have.property(
- 'message',
- 'docstore api responded with non-success code'
- )
- })
- })
- describe('with include_deleted=true', function () {
- beforeEach(async function () {
- FetchUtils.fetchJson.mockResolvedValue(doc)
- getDocResponse = await DocstoreManager.promises.getDoc(
- projectId,
- docId,
- { include_deleted: true }
- )
- })
- it('should get the doc from the docstore api (including deleted)', function () {
- expect(FetchUtils.fetchJson).toHaveBeenCalledWith(
- new URL(
- `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}?include_deleted=true`
- ),
- {
- signal: expect.anything(),
- }
- )
- })
- it('should resolve with the lines, version and rev', function () {
- expect(getDocResponse).to.eql({
- lines,
- rev,
- version,
- ranges,
- })
- })
- })
- describe('with peek=true', function () {
- beforeEach(async function () {
- await DocstoreManager.promises.getDoc(projectId, docId, {
- peek: true,
- })
- })
- it('should call the docstore peek url', function () {
- expect(FetchUtils.fetchJson).toHaveBeenCalledWith(
- new URL(
- `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}/peek`
- ),
- {
- signal: expect.anything(),
- }
- )
- })
- })
- describe('with a missing (404) response code', function () {
- beforeEach(function () {
- FetchUtils.fetchJson.mockImplementation((url, opts) => {
- throw new RequestFailedError(url, opts, { status: 404 })
- })
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.getDoc(projectId, docId)
- assert.fail('getDoc should have thrown an error')
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Errors.NotFoundError)
- expect(error).to.have.property('message', 'doc not found in docstore')
- })
- })
- })
- describe('getAllDocs', function () {
- describe('with a successful response code', function () {
- let getAllDocsResult
- let docs
- beforeEach(async function () {
- docs = [{ _id: 'mock-doc-id' }]
- FetchUtils.fetchJson.mockResolvedValue(docs)
- getAllDocsResult = await DocstoreManager.promises.getAllDocs(projectId)
- })
- it('should get all the project docs in the docstore api', function () {
- expect(FetchUtils.fetchJson).toBeCalledWith(
- new URL(`${settings.apis.docstore.url}/project/${projectId}/doc`),
- {
- signal: expect.anything(),
- }
- )
- })
- it('should return the docs', function () {
- expect(getAllDocsResult).to.eql(docs)
- })
- })
- describe('with a failed response code', function () {
- beforeEach(function () {
- FetchUtils.fetchJson.mockImplementation((url, opts) => {
- throw new RequestFailedError(url, opts, { status: 500 })
- })
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.getAllDocs(projectId)
- assert.fail('getAllDocs should have thrown an error')
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Error)
- expect(error).to.have.property(
- 'message',
- 'docstore api responded with non-success code'
- )
- })
- })
- })
- describe('getAllDeletedDocs', function () {
- describe('with a successful response code', function () {
- let getAllDeletedDocsResponse
- let docs
- beforeEach(async function () {
- docs = [{ _id: 'mock-doc-id', name: 'foo.tex' }]
- FetchUtils.fetchJson.mockResolvedValue(docs)
- getAllDeletedDocsResponse =
- await DocstoreManager.promises.getAllDeletedDocs(projectId)
- })
- it('should get all the project docs in the docstore api', function () {
- expect(FetchUtils.fetchJson).toHaveBeenCalledWith(
- new URL(
- `${settings.apis.docstore.url}/project/${projectId}/doc-deleted`
- ),
- {
- signal: expect.anything(),
- }
- )
- })
- it('should resolve with the docs', function () {
- expect(getAllDeletedDocsResponse).to.eql(docs)
- })
- })
- describe('with an error', function () {
- beforeEach(async function () {
- FetchUtils.fetchJson.mockRejectedValue(new Error('connect failed'))
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.getAllDeletedDocs(projectId)
- assert.fail('getAllDeletedDocs should have thrown an error')
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Error)
- expect(error).to.have.property('message', 'connect failed')
- })
- })
- describe('with a failed response code', function () {
- beforeEach(function () {
- FetchUtils.fetchJson.mockImplementation((url, opts) => {
- throw new RequestFailedError(url, opts, { status: 500 })
- })
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.getAllDeletedDocs(projectId)
- assert.fail('getAllDeletedDocs should have thrown an error')
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Error)
- expect(error).to.have.property(
- 'message',
- 'docstore api responded with non-success code'
- )
- })
- })
- })
- describe('getAllRanges', function () {
- describe('with a successful response code', function () {
- let getAllRangesResult
- let docs
- beforeEach(async function () {
- docs = [{ _id: 'mock-doc-id', ranges: 'mock-ranges' }]
- FetchUtils.fetchJson.mockResolvedValue(docs)
- getAllRangesResult =
- await DocstoreManager.promises.getAllRanges(projectId)
- })
- it('should get all the project doc ranges in the docstore api', function () {
- expect(FetchUtils.fetchJson).toHaveBeenCalledWith(
- new URL(`${settings.apis.docstore.url}/project/${projectId}/ranges`),
- {
- signal: expect.anything(),
- }
- )
- })
- it('should return the docs', async function () {
- expect(getAllRangesResult).to.eql(docs)
- })
- })
- describe('with a failed response code', function () {
- beforeEach(function () {
- FetchUtils.fetchJson.mockImplementation((url, opts) => {
- throw new RequestFailedError(url, opts, { status: 500 })
- })
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.getAllRanges(projectId)
- assert.fail('getAllRanges should have thrown an error')
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Error)
- expect(error).to.have.property(
- 'message',
- 'docstore api responded with non-success code'
- )
- })
- })
- })
- describe('archiveProject', function () {
- describe('with a successful response code', function () {
- it('should resolve', async function () {
- await expect(DocstoreManager.promises.archiveProject(projectId)).to
- .eventually.be.fulfilled
- })
- })
- describe('with a failed response code', function () {
- beforeEach(function () {
- FetchUtils.fetchNothing.mockImplementation((url, opts) => {
- throw new RequestFailedError(url, opts, { status: 500 })
- })
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.archiveProject(projectId)
- assert.fail('archiveProject should have thrown an error')
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Error)
- expect(error).to.have.property(
- 'message',
- 'docstore api responded with non-success code'
- )
- })
- })
- })
- describe('unarchiveProject', function () {
- describe('with a successful response code', function () {
- it('should resolve', async function () {
- await expect(DocstoreManager.promises.unarchiveProject(projectId)).to
- .eventually.be.fulfilled
- })
- })
- describe('with a failed response code', function () {
- beforeEach(function () {
- FetchUtils.fetchNothing.mockImplementation((url, opts) => {
- throw new RequestFailedError(url, opts, { status: 500 })
- })
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.unarchiveProject(projectId)
- assert.fail('unarchiveProject should have thrown an error')
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Error)
- expect(error).to.have.property(
- 'message',
- 'docstore api responded with non-success code'
- )
- })
- })
- })
- describe('destroyProject', function () {
- describe('with a successful response code', function () {
- it('should resolve', async function () {
- await expect(DocstoreManager.promises.destroyProject(projectId)).to
- .eventually.be.fulfilled
- })
- })
- describe('with a failed response code', function () {
- beforeEach(function () {
- FetchUtils.fetchNothing.mockImplementation((url, opts) => {
- throw new RequestFailedError(url, opts, { status: 500 })
- })
- })
- it('should reject with an error', async function () {
- let error
- try {
- await DocstoreManager.promises.destroyProject(projectId)
- assert.fail('destroyProject should have thrown an error')
- } catch (err) {
- error = err
- }
- expect(error).to.be.instanceOf(Error)
- expect(error).to.have.property(
- 'message',
- 'docstore api responded with non-success code'
- )
- })
- })
- })
- })
|