HttpErrorHandlerTests.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. const { expect } = require('chai')
  2. const sinon = require('sinon')
  3. const MockResponse = require('../helpers/MockResponse')
  4. const MockRequest = require('../helpers/MockRequest')
  5. const SandboxedModule = require('sandboxed-module')
  6. const modulePath = '../../../../app/src/Features/Errors/HttpErrorHandler.js'
  7. describe('HttpErrorHandler', function() {
  8. beforeEach(function() {
  9. this.req = new MockRequest()
  10. this.res = new MockResponse()
  11. this.logger = {
  12. warn() {},
  13. error() {}
  14. }
  15. this.HttpErrorHandler = SandboxedModule.require(modulePath, {
  16. globals: { console },
  17. requires: {
  18. 'logger-sharelatex': this.logger
  19. }
  20. })
  21. })
  22. describe('handleErrorByStatusCode', function() {
  23. it('returns the http status code of 400 errors', function() {
  24. const err = new Error()
  25. this.HttpErrorHandler.handleErrorByStatusCode(
  26. this.req,
  27. this.res,
  28. err,
  29. 400
  30. )
  31. expect(this.res.statusCode).to.equal(400)
  32. })
  33. it('returns the http status code of 500 errors', function() {
  34. const err = new Error()
  35. this.HttpErrorHandler.handleErrorByStatusCode(
  36. this.req,
  37. this.res,
  38. err,
  39. 500
  40. )
  41. expect(this.res.statusCode).to.equal(500)
  42. })
  43. it('returns the http status code of any 5xx error', function() {
  44. const err = new Error()
  45. this.HttpErrorHandler.handleErrorByStatusCode(
  46. this.req,
  47. this.res,
  48. err,
  49. 588
  50. )
  51. expect(this.res.statusCode).to.equal(588)
  52. })
  53. it('returns the http status code of any 4xx error', function() {
  54. const err = new Error()
  55. this.HttpErrorHandler.handleErrorByStatusCode(
  56. this.req,
  57. this.res,
  58. err,
  59. 488
  60. )
  61. expect(this.res.statusCode).to.equal(488)
  62. })
  63. it('returns 500 for http status codes smaller than 400', function() {
  64. const err = new Error()
  65. this.HttpErrorHandler.handleErrorByStatusCode(
  66. this.req,
  67. this.res,
  68. err,
  69. 302
  70. )
  71. expect(this.res.statusCode).to.equal(500)
  72. })
  73. it('returns 500 for http status codes larger than 600', function() {
  74. const err = new Error()
  75. this.HttpErrorHandler.handleErrorByStatusCode(
  76. this.req,
  77. this.res,
  78. err,
  79. 302
  80. )
  81. expect(this.res.statusCode).to.equal(500)
  82. })
  83. it('returns 500 when the error has no http status code', function() {
  84. const err = new Error()
  85. this.HttpErrorHandler.handleErrorByStatusCode(this.req, this.res, err)
  86. expect(this.res.statusCode).to.equal(500)
  87. })
  88. it('uses the conflict() error handler', function() {
  89. const err = new Error()
  90. this.HttpErrorHandler.handleErrorByStatusCode(
  91. this.req,
  92. this.res,
  93. err,
  94. 409
  95. )
  96. expect(this.res.body).to.equal('conflict')
  97. })
  98. it('uses the forbidden() error handler', function() {
  99. const err = new Error()
  100. this.HttpErrorHandler.handleErrorByStatusCode(
  101. this.req,
  102. this.res,
  103. err,
  104. 403
  105. )
  106. expect(this.res.body).to.equal('restricted')
  107. })
  108. it('uses the notFound() error handler', function() {
  109. const err = new Error()
  110. this.HttpErrorHandler.handleErrorByStatusCode(
  111. this.req,
  112. this.res,
  113. err,
  114. 404
  115. )
  116. expect(this.res.body).to.equal('not found')
  117. })
  118. it('uses the unprocessableEntity() error handler', function() {
  119. const err = new Error()
  120. err.httpStatusCode = 422
  121. this.HttpErrorHandler.handleErrorByStatusCode(
  122. this.req,
  123. this.res,
  124. err,
  125. 422
  126. )
  127. expect(this.res.body).to.equal('unprocessable entity')
  128. })
  129. })
  130. describe('badRequest', function() {
  131. it('returns 400', function() {
  132. this.HttpErrorHandler.badRequest(this.req, this.res)
  133. expect(this.res.statusCode).to.equal(400)
  134. })
  135. it('should print a message when no content-type is included', function() {
  136. this.HttpErrorHandler.badRequest(this.req, this.res)
  137. expect(this.res.body).to.equal('client error')
  138. })
  139. it("should render a template including the error message when content-type is 'html'", function() {
  140. this.req.accepts = () => 'html'
  141. this.HttpErrorHandler.badRequest(this.req, this.res, 'an error')
  142. expect(this.res.renderedTemplate).to.equal('general/400')
  143. expect(this.res.renderedVariables).to.deep.equal({
  144. title: 'Client Error',
  145. message: 'an error'
  146. })
  147. })
  148. it("should render a default template when content-type is 'html' and no message is provided", function() {
  149. this.req.accepts = () => 'html'
  150. this.HttpErrorHandler.badRequest(this.req, this.res)
  151. expect(this.res.renderedTemplate).to.equal('general/400')
  152. expect(this.res.renderedVariables).to.deep.equal({
  153. title: 'Client Error',
  154. message: undefined
  155. })
  156. })
  157. it("should return a json object when content-type is 'json'", function() {
  158. this.req.accepts = () => 'json'
  159. this.HttpErrorHandler.badRequest(this.req, this.res, 'an error', {
  160. foo: 'bar'
  161. })
  162. expect(JSON.parse(this.res.body)).to.deep.equal({
  163. message: 'an error',
  164. foo: 'bar'
  165. })
  166. })
  167. it("should return an empty json object when content-type is 'json' and no message and info are provided", function() {
  168. this.req.accepts = () => 'json'
  169. this.HttpErrorHandler.badRequest(this.req, this.res)
  170. expect(JSON.parse(this.res.body)).to.deep.equal({})
  171. })
  172. })
  173. describe('conflict', function() {
  174. it('returns 409', function() {
  175. this.HttpErrorHandler.conflict(this.req, this.res)
  176. expect(this.res.statusCode).to.equal(409)
  177. })
  178. it('should print a message when no content-type is included', function() {
  179. this.HttpErrorHandler.conflict(this.req, this.res)
  180. expect(this.res.body).to.equal('conflict')
  181. })
  182. it("should render a template including the error message when content-type is 'html'", function() {
  183. this.req.accepts = () => 'html'
  184. this.HttpErrorHandler.unprocessableEntity(this.req, this.res, 'an error')
  185. expect(this.res.renderedTemplate).to.equal('general/400')
  186. expect(this.res.renderedVariables).to.deep.equal({
  187. title: 'Client Error',
  188. message: 'an error'
  189. })
  190. })
  191. it("should return a json object when content-type is 'json'", function() {
  192. this.req.accepts = () => 'json'
  193. this.HttpErrorHandler.unprocessableEntity(
  194. this.req,
  195. this.res,
  196. 'an error',
  197. {
  198. foo: 'bar'
  199. }
  200. )
  201. expect(JSON.parse(this.res.body)).to.deep.equal({
  202. message: 'an error',
  203. foo: 'bar'
  204. })
  205. })
  206. })
  207. describe('forbidden', function() {
  208. it('returns 403', function() {
  209. this.HttpErrorHandler.forbidden(this.req, this.res)
  210. expect(this.res.statusCode).to.equal(403)
  211. })
  212. it('should print a message when no content-type is included', function() {
  213. this.HttpErrorHandler.forbidden(this.req, this.res)
  214. expect(this.res.body).to.equal('restricted')
  215. })
  216. it("should render a template when content-type is 'html'", function() {
  217. this.req.accepts = () => 'html'
  218. this.HttpErrorHandler.forbidden(this.req, this.res)
  219. expect(this.res.renderedTemplate).to.equal('user/restricted')
  220. expect(this.res.renderedVariables).to.deep.equal({
  221. title: 'restricted'
  222. })
  223. })
  224. it("should return a json object when content-type is 'json'", function() {
  225. this.req.accepts = () => 'json'
  226. this.HttpErrorHandler.forbidden(this.req, this.res, 'an error', {
  227. foo: 'bar'
  228. })
  229. expect(JSON.parse(this.res.body)).to.deep.equal({
  230. message: 'an error',
  231. foo: 'bar'
  232. })
  233. })
  234. })
  235. describe('notFound', function() {
  236. it('returns 404', function() {
  237. this.HttpErrorHandler.notFound(this.req, this.res)
  238. expect(this.res.statusCode).to.equal(404)
  239. })
  240. it('should print a message when no content-type is included', function() {
  241. this.HttpErrorHandler.notFound(this.req, this.res)
  242. expect(this.res.body).to.equal('not found')
  243. })
  244. it("should render a template when content-type is 'html'", function() {
  245. this.req.accepts = () => 'html'
  246. this.HttpErrorHandler.notFound(this.req, this.res)
  247. expect(this.res.renderedTemplate).to.equal('general/404')
  248. expect(this.res.renderedVariables).to.deep.equal({
  249. title: 'page_not_found'
  250. })
  251. })
  252. it("should return a json object when content-type is 'json'", function() {
  253. this.req.accepts = () => 'json'
  254. this.HttpErrorHandler.notFound(this.req, this.res, 'an error', {
  255. foo: 'bar'
  256. })
  257. expect(JSON.parse(this.res.body)).to.deep.equal({
  258. message: 'an error',
  259. foo: 'bar'
  260. })
  261. })
  262. })
  263. describe('unprocessableEntity', function() {
  264. it('returns 422', function() {
  265. this.HttpErrorHandler.unprocessableEntity(this.req, this.res)
  266. expect(this.res.statusCode).to.equal(422)
  267. })
  268. it('should print a message when no content-type is included', function() {
  269. this.HttpErrorHandler.unprocessableEntity(this.req, this.res)
  270. expect(this.res.body).to.equal('unprocessable entity')
  271. })
  272. it("should render a template including the error message when content-type is 'html'", function() {
  273. this.req.accepts = () => 'html'
  274. this.HttpErrorHandler.unprocessableEntity(this.req, this.res, 'an error')
  275. expect(this.res.renderedTemplate).to.equal('general/400')
  276. expect(this.res.renderedVariables).to.deep.equal({
  277. title: 'Client Error',
  278. message: 'an error'
  279. })
  280. })
  281. it("should return a json object when content-type is 'json'", function() {
  282. this.req.accepts = () => 'json'
  283. this.HttpErrorHandler.unprocessableEntity(
  284. this.req,
  285. this.res,
  286. 'an error',
  287. {
  288. foo: 'bar'
  289. }
  290. )
  291. expect(JSON.parse(this.res.body)).to.deep.equal({
  292. message: 'an error',
  293. foo: 'bar'
  294. })
  295. })
  296. describe('legacyInternal', function() {
  297. it('returns 500', function() {
  298. this.HttpErrorHandler.legacyInternal(this.req, this.res, new Error())
  299. expect(this.res.statusCode).to.equal(500)
  300. })
  301. it('should send the error to the logger', function() {
  302. const error = new Error('message')
  303. this.logger.error = sinon.stub()
  304. this.HttpErrorHandler.legacyInternal(
  305. this.req,
  306. this.res,
  307. 'message',
  308. error
  309. )
  310. expect(this.logger.error).to.have.been.calledWith(error)
  311. })
  312. it('should print a message when no content-type is included', function() {
  313. this.HttpErrorHandler.legacyInternal(this.req, this.res, new Error())
  314. expect(this.res.body).to.equal('internal server error')
  315. })
  316. it("should render a template when content-type is 'html'", function() {
  317. this.req.accepts = () => 'html'
  318. this.HttpErrorHandler.legacyInternal(this.req, this.res, new Error())
  319. expect(this.res.renderedTemplate).to.equal('general/500')
  320. expect(this.res.renderedVariables).to.deep.equal({
  321. title: 'Server Error'
  322. })
  323. })
  324. it("should return a json object with a static message when content-type is 'json'", function() {
  325. this.req.accepts = () => 'json'
  326. this.HttpErrorHandler.legacyInternal(
  327. this.req,
  328. this.res,
  329. 'a message',
  330. new Error()
  331. )
  332. expect(JSON.parse(this.res.body)).to.deep.equal({
  333. message: 'a message'
  334. })
  335. })
  336. })
  337. })
  338. })