diff --git a/services/docstore/.gitignore b/services/docstore/.gitignore index fadf405279..99832d28c8 100644 --- a/services/docstore/.gitignore +++ b/services/docstore/.gitignore @@ -1,5 +1,6 @@ node_modules app/js/ test/unit/js +test/acceptance/js app.js forever diff --git a/services/docstore/app.coffee b/services/docstore/app.coffee index b61287fec6..e3acb4529f 100644 --- a/services/docstore/app.coffee +++ b/services/docstore/app.coffee @@ -1,14 +1,16 @@ -Settings = require('settings-sharelatex') -logger = require('logger-sharelatex') -logger.initialize("docstore") - -express = require('express') +Settings = require "settings-sharelatex" +logger = require "logger-sharelatex" +express = require "express" +bodyParser = require "body-parser" +Errors = require "./app/js/Errors" HttpController = require "./app/js/HttpController" -Errors = require "./app/js/Errors" + +logger.initialize("docstore") app = express() -app.get '/project/:project_id/doc/:doc_id', HttpController.getDoc +app.get '/project/:project_id/doc/:doc_id', HttpController.getDoc +app.post '/project/:project_id/doc/:doc_id', bodyParser.json(), HttpController.updateDoc app.get '/status', (req, res)-> res.send('docstore is alive') diff --git a/services/docstore/app/coffee/DocManager.coffee b/services/docstore/app/coffee/DocManager.coffee index 047d32f552..db60024a99 100644 --- a/services/docstore/app/coffee/DocManager.coffee +++ b/services/docstore/app/coffee/DocManager.coffee @@ -13,7 +13,7 @@ module.exports = DocManager = return callback new Errors.NotFoundError("No such doc: #{project_id}") if !doc? return callback null, doc, mongoPath - updateDoc: (project_id, doc_id, lines, callback = (error) ->) -> + updateDoc: (project_id, doc_id, lines, callback = (error, modified) ->) -> DocManager.getDoc project_id, doc_id, (error, doc, mongoPath) -> return callback(error) if error? return callback new Errors.NotFoundError("No such project/doc: #{project_id}/#{doc_id}") if !doc? @@ -22,14 +22,14 @@ module.exports = DocManager = logger.log { project_id: project_id, doc_id: doc_id, rev: doc.rev }, "doc lines have not changed" - return callback() + return callback null, false else logger.log { project_id: project_id, doc_id: doc_id, oldDocLines: doc.lines, newDocLines: lines, rev: doc.rev }, "updating doc lines" MongoManager.updateDoc project_id, mongoPath, lines, (error) -> return callback(error) if error? - callback() + callback null, true findDocInProject: (project, doc_id, callback = (error, doc, mongoPath) ->) -> result = @_findDocInFolder project.rootFolder[0], doc_id, "rootFolder.0" diff --git a/services/docstore/app/coffee/Errors.coffee b/services/docstore/app/coffee/Errors.coffee new file mode 100644 index 0000000000..4a29822efc --- /dev/null +++ b/services/docstore/app/coffee/Errors.coffee @@ -0,0 +1,10 @@ +NotFoundError = (message) -> + error = new Error(message) + error.name = "NotFoundError" + error.__proto__ = NotFoundError.prototype + return error +NotFoundError.prototype.__proto__ = Error.prototype + +module.exports = Errors = + NotFoundError: NotFoundError + diff --git a/services/docstore/app/coffee/HttpController.coffee b/services/docstore/app/coffee/HttpController.coffee index d3eef4fa7d..fc30f1adb9 100644 --- a/services/docstore/app/coffee/HttpController.coffee +++ b/services/docstore/app/coffee/HttpController.coffee @@ -4,11 +4,30 @@ logger = require "logger-sharelatex" module.exports = HttpController = getDoc: (req, res, next = (error) ->) -> project_id = req.params.project_id - doc_id = req.params.doc_id + doc_id = req.params.doc_id logger.log project_id: project_id, doc_id: doc_id, "getting doc" DocManager.getDoc project_id, doc_id, (error, doc) -> return next(error) if error? if !doc? res.send 404 else - res.send JSON.stringify({ lines: doc.lines }) \ No newline at end of file + res.json { + lines: doc.lines + } + + updateDoc: (req, res, next = (error) ->) -> + project_id = req.params.project_id + doc_id = req.params.doc_id + lines = req.body?.lines + + if !lines? or lines not instanceof Array + logger.error project_id: project_id, doc_id: doc_id, "no doc lines provided" + res.send 400 # Bad Request + return + + logger.log project_id: project_id, doc_id: doc_id, "updating doc" + DocManager.updateDoc project_id, doc_id, lines, (error, modified) -> + return next(error) if error? + res.json { + modified: modified + } \ No newline at end of file diff --git a/services/docstore/package.json b/services/docstore/package.json index 94335c97f4..d3d13b2278 100644 --- a/services/docstore/package.json +++ b/services/docstore/package.json @@ -8,7 +8,8 @@ "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master", "mongojs": "0.9.11", "express": "~4.1.1", - "underscore": "~1.6.0" + "underscore": "~1.6.0", + "body-parser": "~1.0.2" }, "devDependencies": { "grunt-execute": "~0.2.1", @@ -22,6 +23,7 @@ "sinon": "~1.5.2", "sandboxed-module": "~0.3.0", "chai": "~1.9.1", - "grunt-forever": "~0.4.4" + "grunt-forever": "~0.4.4", + "request": "~2.34.0" } } diff --git a/services/docstore/test/acceptance/coffee/GettingDocsTests.coffee b/services/docstore/test/acceptance/coffee/GettingDocsTests.coffee new file mode 100644 index 0000000000..398f8b3690 --- /dev/null +++ b/services/docstore/test/acceptance/coffee/GettingDocsTests.coffee @@ -0,0 +1,30 @@ +sinon = require "sinon" +chai = require("chai") +chai.should() +{ObjectId} = require "mongojs" + +DocstoreClient = require "./helpers/DocstoreClient" + +describe "Applying updates to a doc", -> + beforeEach (done) -> + @project_id = ObjectId() + @lines = ["original", "lines"] + DocstoreClient.createDoc @project_id, @lines, (error, @doc_id) => + done() + + afterEach (done) -> + DocstoreClient.deleteProject @project_id, done + + describe "when the doc exists", -> + it "should get the doc lines", (done) -> + DocstoreClient.getDoc @project_id, @doc_id, (error, res, doc) => + doc.lines.should.deep.equal @lines + done() + + describe "when the doc does not exist", -> + it "should return a 404", (done) -> + missing_doc_id = ObjectId() + DocstoreClient.getDoc @project_id, missing_doc_id, (error, res, doc) -> + res.statusCode.should.equal 404 + done() + diff --git a/services/docstore/test/acceptance/coffee/UpdatingDocsTests.coffee b/services/docstore/test/acceptance/coffee/UpdatingDocsTests.coffee new file mode 100644 index 0000000000..107967ebd3 --- /dev/null +++ b/services/docstore/test/acceptance/coffee/UpdatingDocsTests.coffee @@ -0,0 +1,89 @@ +sinon = require "sinon" +chai = require("chai") +chai.should() +{ObjectId} = require "mongojs" + +DocstoreClient = require "./helpers/DocstoreClient" + +describe "Applying updates to a doc", -> + beforeEach (done) -> + @project_id = ObjectId() + @originalLines = ["original", "lines"] + @newLines = ["new", "lines"] + DocstoreClient.createDoc @project_id, @originalLines, (error, @doc_id) => + done() + + afterEach (done) -> + DocstoreClient.deleteProject @project_id, done + + describe "when the content has changed", -> + beforeEach (done) -> + DocstoreClient.updateDoc @project_id, @doc_id, @newLines, (error, res, @body) => + done() + + it "should return modified = true", -> + @body.modified.should.equal true + + it "should update the doc in the API", (done) -> + DocstoreClient.getDoc @project_id, @doc_id, (error, res, doc) => + doc.lines.should.deep.equal @newLines + done() + + describe "when the content has not been updated", -> + beforeEach (done) -> + DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, (error, res, @body) => + done() + + it "should return modified = false", -> + @body.modified.should.equal false + + it "should not update the doc in the API", (done) -> + DocstoreClient.getDoc @project_id, @doc_id, (error, res, doc) => + doc.lines.should.deep.equal @originalLines + done() + + describe "when the doc does not exist", -> + beforeEach (done) -> + missing_doc_id = ObjectId() + DocstoreClient.updateDoc @project_id, missing_doc_id, @originalLines, (error, @res, @body) => + done() + + it "should return a 404", -> + @res.statusCode.should.equal 404 + + describe "when the project does not exist", -> + beforeEach (done) -> + missing_project_id = ObjectId() + DocstoreClient.updateDoc missing_project_id, @doc_id, @originalLines, (error, @res, @body) => + done() + + it "should return a 404", -> + @res.statusCode.should.equal 404 + + describe "when malformed doc lines are provided", -> + describe "when the lines are not an array", -> + beforeEach (done) -> + DocstoreClient.updateDoc @project_id, @doc_id, { foo: "bar" }, (error, @res, @body) => + done() + + it "should return 400", -> + @res.statusCode.should.equal 400 + + it "should not update the doc in the API", (done) -> + DocstoreClient.getDoc @project_id, @doc_id, (error, res, doc) => + doc.lines.should.deep.equal @originalLines + done() + + describe "when the lines are not present", -> + beforeEach (done) -> + DocstoreClient.updateDoc @project_id, @doc_id, null, (error, @res, @body) => + done() + + it "should return 400", -> + @res.statusCode.should.equal 400 + + it "should not update the doc in the API", (done) -> + DocstoreClient.getDoc @project_id, @doc_id, (error, res, doc) => + doc.lines.should.deep.equal @originalLines + done() + diff --git a/services/docstore/test/acceptance/coffee/helpers/DocstoreClient.coffee b/services/docstore/test/acceptance/coffee/helpers/DocstoreClient.coffee new file mode 100644 index 0000000000..f80afcd1af --- /dev/null +++ b/services/docstore/test/acceptance/coffee/helpers/DocstoreClient.coffee @@ -0,0 +1,37 @@ +request = require("request").defaults(jar: false) +{db, ObjectId} = require("../../../../app/js/mongojs") + +module.exports = DocstoreClient = + createDoc: (project_id, lines, callback = (error, doc_id) ->) -> + doc_id = ObjectId() + db.projects.insert { + _id: project_id + rootFolder: [{ + docs: [{ + _id: doc_id, + lines: lines + }] + }] + }, (error) -> + return callback(error) if error? + callback null, doc_id + + deleteProject: (project_id, callback = (error, res, body) ->) -> + db.projects.remove _id: project_id, callback + + getDoc: (project_id, doc_id, callback = (error, doc) ->) -> + request.get { + url: "http://localhost:3016/project/#{project_id}/doc/#{doc_id}" + json: true + }, callback + + updateDoc: (project_id, doc_id, lines, callback = (error, res, body) ->) -> + request.post { + url: "http://localhost:3016/project/#{project_id}/doc/#{doc_id}" + json: + lines: lines + }, callback + + + + diff --git a/services/docstore/test/unit/coffee/DocManagerTests.coffee b/services/docstore/test/unit/coffee/DocManagerTests.coffee index 525bf7cb7e..e884be28af 100644 --- a/services/docstore/test/unit/coffee/DocManagerTests.coffee +++ b/services/docstore/test/unit/coffee/DocManagerTests.coffee @@ -107,7 +107,7 @@ describe "DocManager", -> .should.equal true it "should return the callback", -> - @callback.called.should.equal true + @callback.calledWith(null, true).should.equal true describe "when the doc lines have not changed", -> beforeEach -> @@ -118,7 +118,7 @@ describe "DocManager", -> @MongoManager.updateDoc.called.should.equal false it "should return the callback", -> - @callback.called.should.equal true + @callback.calledWith(null, false).should.equal true describe "when the doc does not exist", -> beforeEach -> diff --git a/services/docstore/test/unit/coffee/HttpControllerTests.coffee b/services/docstore/test/unit/coffee/HttpControllerTests.coffee index 5dfaf4de7c..f942b4dbf0 100644 --- a/services/docstore/test/unit/coffee/HttpControllerTests.coffee +++ b/services/docstore/test/unit/coffee/HttpControllerTests.coffee @@ -9,8 +9,8 @@ describe "HttpController", -> beforeEach -> @HttpController = SandboxedModule.require modulePath, requires: "./DocManager": @DocManager = {} - "logger-sharelatex": @logger = { log: sinon.stub() } - @res = { send: sinon.stub() } + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + @res = { send: sinon.stub(), json: sinon.stub() } @req = {} @next = sinon.stub() @project_id = "mock-project-id" @@ -21,33 +21,68 @@ describe "HttpController", -> } describe "getDoc", -> - describe "when the doc exists", -> - beforeEach -> - @req.params = - project_id: @project_id - doc_id: @doc_id - @DocManager.getDoc = sinon.stub().callsArgWith(2, null, @doc) - @HttpController.getDoc @req, @res, @next + beforeEach -> + @req.params = + project_id: @project_id + doc_id: @doc_id + @DocManager.getDoc = sinon.stub().callsArgWith(2, null, @doc) + @HttpController.getDoc @req, @res, @next - it "should get the document", -> - @DocManager.getDoc - .calledWith(@project_id, @doc_id) + it "should get the document", -> + @DocManager.getDoc + .calledWith(@project_id, @doc_id) + .should.equal true + + it "should return the doc as JSON", -> + @res.json + .calledWith(lines: @doc.lines) + .should.equal true + + describe "updateDoc", -> + beforeEach -> + @req.params = + project_id: @project_id + doc_id: @doc_id + + describe "when the doc lines exist and were updated", -> + beforeEach -> + @req.body = + lines: @lines = ["hello", "world"] + @DocManager.updateDoc = sinon.stub().callsArgWith(3, null, true) + @HttpController.updateDoc @req, @res, @next + + it "should update the document", -> + @DocManager.updateDoc + .calledWith(@project_id, @doc_id, @lines) .should.equal true - it "should return the doc as JSON", -> - @res.send - .calledWith(JSON.stringify(lines: @doc.lines)) + it "should return a modified status", -> + @res.json + .calledWith(modified: true) .should.equal true - describe "when the doc does not exist", -> + describe "when the doc lines exist and were not updated", -> beforeEach -> - @req.params = - project_id: @project_id - doc_id: @doc_id - @DocManager.getDoc = sinon.stub().callsArgWith(2, null, null) - @HttpController.getDoc @req, @res, @next + @req.body = + lines: @lines = ["hello", "world"] + @DocManager.updateDoc = sinon.stub().callsArgWith(3, null, false) + @HttpController.updateDoc @req, @res, @next - it "should return a 404", -> + it "should return a modified status", -> + @res.json + .calledWith(modified: false) + .should.equal true + + describe "when the doc lines are not provided", -> + beforeEach -> + @req.body = {} + @DocManager.updateDoc = sinon.stub().callsArgWith(3, null, false) + @HttpController.updateDoc @req, @res, @next + + it "should not update the document", -> + @DocManager.updateDoc.called.should.equal false + + it "should return a 400 (bad request) response", -> @res.send - .calledWith(404) - .should.equal true \ No newline at end of file + .calledWith(400) + .should.equal true