HttpErrorHandlerTests.js 11 KB

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