thread_post.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. process.env.NODE_ENV = 'test'
  2. let chai = require('chai')
  3. let server = require('../server')
  4. let should = chai.should()
  5. let expect = chai.expect
  6. let { sequelize } = require('../models')
  7. const Errors = require('../lib/errors.js')
  8. chai.use(require('chai-http'))
  9. chai.use(require('chai-things'))
  10. describe('Thread and post', () => {
  11. let userAgent, replyAgent
  12. //Wait for app to start before commencing
  13. before((done) => {
  14. if(server.locals.appStarted) mockData()
  15. server.on('appStarted', () => {
  16. mockData()
  17. })
  18. function mockData() {
  19. userAgent = chai.request.agent(server)
  20. replyAgent = chai.request.agent(server)
  21. userAgent
  22. .post('/api/v1/user')
  23. .set('content-type', 'application/json')
  24. .send({
  25. username: 'username',
  26. password: 'password',
  27. admin: true
  28. })
  29. .then(() => {
  30. userAgent
  31. .post('/api/v1/category')
  32. .set('content-type', 'application/json')
  33. .send({ name: 'category_name' })
  34. .then(() => { done() })
  35. .catch(done)
  36. })
  37. .catch(done)
  38. }
  39. })
  40. //Delete all rows in table after
  41. //tests completed
  42. after(() => {
  43. sequelize.sync({ force: true })
  44. })
  45. describe('POST /thread', () => {
  46. it('should create a thread if logged in', async () => {
  47. let res = await userAgent
  48. .post('/api/v1/thread')
  49. .set('content-type', 'application/json')
  50. .send({
  51. name: 'thread',
  52. category: 'category_name'
  53. })
  54. res.should.have.status(200)
  55. res.should.be.json
  56. res.body.should.have.property('name', 'thread')
  57. res.body.should.have.property('slug', 'thread')
  58. res.body.should.have.deep.property('User.username', 'username')
  59. res.body.should.have.deep.property('Category.name', 'category_name')
  60. })
  61. it('should add a slug from the thread name', async () => {
  62. let res = await userAgent
  63. .post('/api/v1/thread')
  64. .set('content-type', 'application/json')
  65. .send({
  66. name: ' à long thrËad, with lØts of àccents!!! ',
  67. category: 'category_name'
  68. })
  69. res.should.have.status(200)
  70. res.should.be.json
  71. res.body.should.have.property('name', ' à long thrËad, with lØts of àccents!!! ')
  72. res.body.should.have.property('slug', 'a-long-thread-with-lots-of-accents')
  73. res.body.should.have.deep.property('User.username', 'username')
  74. res.body.should.have.deep.property('Category.name', 'category_name')
  75. })
  76. it('should return an error if not logged in', async () => {
  77. try {
  78. let res = await chai.request(server)
  79. .post('/api/v1/thread')
  80. .set('content-type', 'application/json')
  81. .send({
  82. name: 'thread',
  83. category: 'category_name'
  84. })
  85. res.should.be.json
  86. res.should.have.status(401)
  87. res.body.errors.should.contain.something.that.deep.equals(Errors.requestNotAuthorized)
  88. } catch (res) {
  89. res.should.have.status(401)
  90. JSON.parse(res.response.text).errors.should.contain.something.that.deep.equals(Errors.requestNotAuthorized)
  91. }
  92. })
  93. it('should return an error if missing parameters', async () => {
  94. try {
  95. let res = await userAgent
  96. .post('/api/v1/thread')
  97. res.should.be.json
  98. res.should.have.status(400)
  99. res.body.errors.should.contain.something.that.deep.equals(Errors.missingParameter('name'))
  100. res.body.errors.should.contain.something.that.deep.equals(Errors.missingParameter('category'))
  101. } catch (res) {
  102. let body = JSON.parse(res.response.text)
  103. res.should.have.status(400)
  104. body.errors.should.contain.something.that.deep.equals(Errors.missingParameter('name'))
  105. body.errors.should.contain.something.that.deep.equals(Errors.missingParameter('category'))
  106. }
  107. })
  108. it('should return an error if invalid types', async () => {
  109. try {
  110. let res = await userAgent
  111. .post('/api/v1/thread')
  112. .set('content-type', 'application/json')
  113. .send({
  114. name: 123,
  115. category: 123
  116. })
  117. res.should.be.json
  118. res.should.have.status(400)
  119. res.body.errors.should.contain.something.that.deep.equals(Errors.invalidParameterType('name', 'string'))
  120. res.body.errors.should.contain.something.that.deep.equals(Errors.invalidParameterType('category', 'string'))
  121. } catch (res) {
  122. let body = JSON.parse(res.response.text)
  123. res.should.have.status(400)
  124. body.errors.should.contain.something.that.deep.equals(Errors.invalidParameterType('name', 'string'))
  125. body.errors.should.contain.something.that.deep.equals(Errors.invalidParameterType('category', 'string'))
  126. }
  127. })
  128. it('should return an error if category does not exist', async () => {
  129. try {
  130. let res = await userAgent
  131. .post('/api/v1/thread')
  132. .set('content-type', 'application/json')
  133. .send({
  134. name: 'thread1',
  135. category: 'non-existent'
  136. })
  137. res.should.be.json
  138. res.should.have.status(400)
  139. res.body.errors.should.contain.something.that.deep.equals(Errors.invalidCategory)
  140. } catch (res) {
  141. res.should.have.status(400)
  142. JSON.parse(res.response.text).errors.should.contain.something.that.deep.equals(Errors.invalidCategory)
  143. }
  144. })
  145. })
  146. describe('POST /post', () => {
  147. it('should create a post if logged in', async () => {
  148. let res = await userAgent
  149. .post('/api/v1/post')
  150. .set('content-type', 'application/json')
  151. .send({
  152. content: 'content',
  153. threadId: 1
  154. })
  155. res.should.be.json
  156. res.should.have.status(200)
  157. res.body.should.have.property('content', '<p>content</p>\n')
  158. res.body.should.have.deep.property('User.username', 'username')
  159. res.body.should.have.deep.property('Thread.name', 'thread')
  160. })
  161. it('should return an error if not logged in', async () => {
  162. try {
  163. let res = await chai.request(server)
  164. .post('/api/v1/post')
  165. .set('content-type', 'application/json')
  166. .send({
  167. content: 'content',
  168. threadId: 1
  169. })
  170. res.should.be.json
  171. res.should.have.status(401)
  172. res.body.errors.should.contain.something.that.deep.equals(Errors.requestNotAuthorized)
  173. } catch (res) {
  174. res.should.have.status(401)
  175. JSON.parse(res.response.text).errors.should.contain.something.that.deep.equals(Errors.requestNotAuthorized)
  176. }
  177. })
  178. it('should return an error if missing parameters', async () => {
  179. try {
  180. let res = await userAgent
  181. .post('/api/v1/post')
  182. res.should.be.json
  183. res.should.have.status(400)
  184. res.body.errors.should.contain.something.that.deep.equals(Errors.missingParameter('content'))
  185. res.body.errors.should.contain.something.that.deep.equals(Errors.missingParameter('threadId'))
  186. } catch (res) {
  187. let body = JSON.parse(res.response.text)
  188. res.should.have.status(400)
  189. body.errors.should.contain.something.that.deep.equals(Errors.missingParameter('content'))
  190. body.errors.should.contain.something.that.deep.equals(Errors.missingParameter('threadId'))
  191. }
  192. })
  193. it('should return an error if invalid types', async () => {
  194. try {
  195. let res = await userAgent
  196. .post('/api/v1/post')
  197. .set('content-type', 'application/json')
  198. .send({
  199. content: 123,
  200. threadId: 'string',
  201. replyingToId: 'string'
  202. })
  203. res.should.be.json
  204. res.should.have.status(400)
  205. res.body.errors.should.contain.something.that.deep.equals(Errors.invalidParameterType('content', 'string'))
  206. res.body.errors.should.contain.something.that.deep.equals(Errors.invalidParameterType('threadId', 'integer'))
  207. res.body.errors.should.contain.something.that.deep.equals(Errors.invalidParameterType('replyingToId', 'integer'))
  208. } catch (res) {
  209. let body = JSON.parse(res.response.text)
  210. res.should.have.status(400)
  211. body.errors.should.contain.something.that.deep.equals(Errors.invalidParameterType('content', 'string'))
  212. body.errors.should.contain.something.that.deep.equals(Errors.invalidParameterType('threadId', 'integer'))
  213. body.errors.should.contain.something.that.deep.equals(Errors.invalidParameterType('replyingToId', 'integer'))
  214. }
  215. })
  216. it('should return an error if thread id does not exist', async () => {
  217. try {
  218. let res = await userAgent
  219. .post('/api/v1/post')
  220. .set('content-type', 'application/json')
  221. .send({
  222. content: 'content',
  223. threadId: 10
  224. })
  225. res.should.be.json
  226. res.should.have.status(400)
  227. res.body.errors.should.include.something.that.deep.equals(Errors.invalidParameter('threadId', 'thread does not exist'))
  228. } catch (res) {
  229. let body = JSON.parse(res.response.text)
  230. res.should.have.status(400)
  231. body.errors.should.include.something.that.deep.equals(Errors.invalidParameter('threadId', 'thread does not exist'))
  232. }
  233. })
  234. it('should be able to reply to a post', async () => {
  235. await replyAgent
  236. .post('/api/v1/user')
  237. .set('content-type', 'application/json')
  238. .send({
  239. username: 'username1',
  240. password: 'password'
  241. })
  242. let res = await replyAgent
  243. .post('/api/v1/post')
  244. .set('content-type', 'application/json')
  245. .send({
  246. content: 'another post',
  247. threadId: 1,
  248. replyingToId: 1
  249. })
  250. res.should.be.json
  251. res.should.have.status(200)
  252. res.body.should.have.property('content', '<p>another post</p>\n')
  253. res.body.should.have.deep.property('User.username', 'username1')
  254. res.body.should.have.deep.property('Thread.name', 'thread')
  255. res.body.should.have.property('replyingToUsername', 'username')
  256. res.body.should.have.property('Replies').that.deep.equals([])
  257. })
  258. it('should return any replies to a post', async () => {
  259. let res = await replyAgent.get('/api/v1/post/1')
  260. res.should.be.json
  261. res.should.have.status(200)
  262. res.body.should.have.deep.property('replyingToUsername', null)
  263. res.body.should.have.deep.property('Replies.0.content', '<p>another post</p>\n')
  264. })
  265. it('should return an error if reply id does not exist', async () => {
  266. try {
  267. let res = await replyAgent
  268. .post('/api/v1/post')
  269. .set('content-type', 'application/json')
  270. .send({
  271. content: 'yet another post',
  272. threadId: 1,
  273. replyingToId: 10
  274. })
  275. res.should.have.status(400)
  276. res.body.errors.should.contain.something.that.deep.equals(Errors.invalidParameter('replyingToId', 'post does not exist'))
  277. } catch (res) {
  278. let body = JSON.parse(res.response.text)
  279. res.should.have.status(400)
  280. body.errors.should.contain.something.that.deep.equals(Errors.invalidParameter('replyingToId', 'post does not exist'))
  281. }
  282. })
  283. it('should return an error if post reply not in same thread', async () => {
  284. try {
  285. let threadId = (await replyAgent
  286. .post('/api/v1/thread')
  287. .set('content-type', 'application/json')
  288. .send({
  289. name: 'another thread',
  290. category: 'category_name'
  291. })).body.id
  292. let res = await replyAgent
  293. .post('/api/v1/post')
  294. .set('content-type', 'application/json')
  295. .send({
  296. content: 'yet another post',
  297. threadId: threadId,
  298. replyingToId: 1
  299. })
  300. res.should.have.status(400)
  301. res.body.errors.should.contain.something.that.deep.equals(Errors.invalidParameter('replyingToId', 'replies must be in same thread'))
  302. } catch (res) {
  303. let body = JSON.parse(res.response.text)
  304. res.should.have.status(400)
  305. body.errors.should.contain.something.that.deep.equals(Errors.invalidParameter('replyingToId', 'replies must be in same thread'))
  306. }
  307. })
  308. })
  309. describe('GET /thread/:id', () => {
  310. it('should return the thread and corresponding posts', async () => {
  311. let res = await chai.request(server).get('/api/v1/thread/1')
  312. res.should.have.status(200)
  313. res.should.be.json
  314. res.body.should.have.property('name', 'thread')
  315. res.body.should.have.deep.property('Category.name', 'category_name')
  316. res.body.should.have.deep.property('User.username', 'username')
  317. res.body.should.have.property('Posts')
  318. res.body.Posts.should.have.property('length', 2)
  319. res.body.Posts.should.contain.something.that.has.property('content', '<p>content</p>\n')
  320. res.body.Posts.should.contain.something.that.has.deep.property('User.username', 'username')
  321. res.body.Posts.should.contain.something.that.has.property('content', '<p>another post</p>\n')
  322. res.body.Posts.should.contain.something.that.has.deep.property('User.username', 'username1')
  323. })
  324. it('should allow pagination', async () => {
  325. let thread = await userAgent
  326. .post('/api/v1/thread')
  327. .set('content-type', 'application/json')
  328. .send({ category: 'category_name', name: 'pagination' })
  329. for(var i = 0; i < 30; i++) {
  330. await userAgent
  331. .post('/api/v1/post')
  332. .set('content-type', 'application/json')
  333. .send({ threadId: thread.body.id, content: `POST ${i}` })
  334. }
  335. let pageOne = await userAgent.get('/api/v1/thread/' + thread.body.id)
  336. let pageTwo = await userAgent.get(pageOne.body.meta.nextURL)
  337. let pageThree = await userAgent.get(pageTwo.body.meta.nextURL)
  338. let pageInvalid = await userAgent.get('/api/v1/thread/' + thread.body.id + '?lastId=' + 100)
  339. pageOne.body.Posts.should.have.length(10)
  340. pageOne.body.Posts[0].should.have.property('content', '<p>POST 0</p>\n')
  341. pageTwo.body.Posts.should.have.length(10)
  342. pageTwo.body.Posts[0].should.have.property('content', '<p>POST 10</p>\n')
  343. pageThree.body.Posts.should.have.length(10)
  344. pageThree.body.Posts[0].should.have.property('content', '<p>POST 20</p>\n')
  345. pageThree.body.Posts[9].should.have.property('content', '<p>POST 29</p>\n')
  346. expect(pageThree.body.meta.nextURL).to.be.null
  347. pageInvalid.body.Posts.should.have.length(0)
  348. })
  349. it('should return an error if :id is invalid', async () => {
  350. try {
  351. let res = await chai.request(server).get('/api/v1/thread/invalid')
  352. res.should.have.status(400)
  353. res.body.errors.should.contain.something.that.deep.equals(Errors.invalidParameter('id', 'thread does not exist'))
  354. } catch (res) {
  355. let body = JSON.parse(res.response.text)
  356. res.should.have.status(400)
  357. body.errors.should.contain.something.that.deep.equals(Errors.invalidParameter('id', 'thread does not exist'))
  358. }
  359. })
  360. })
  361. describe('GET /post/:id', () => {
  362. it('should return the post', async () => {
  363. let res = await chai.request(server).get('/api/v1/post/1')
  364. res.should.have.status(200)
  365. res.should.be.json
  366. res.body.should.have.property('content', '<p>content</p>\n')
  367. res.body.should.have.deep.property('User.username', 'username')
  368. res.body.should.have.deep.property('Thread.name', 'thread')
  369. res.body.should.have.deep.property('Thread.Category.name', 'category_name')
  370. res.body.should.have.deep.property('Replies.0.User.username', 'username1')
  371. })
  372. it('should return an error if invalid post id', async () => {
  373. try {
  374. let res = await chai.request(server).get('/api/v1/post/invalid')
  375. res.should.have.status(400)
  376. res.body.errors.should.contain.something.that.deep.equals(Errors.invalidParameter('id', 'post does not exist'))
  377. } catch (res) {
  378. let body = JSON.parse(res.response.text)
  379. res.should.have.status(400)
  380. body.errors.should.contain.something.that.deep.equals(Errors.invalidParameter('id', 'post does not exist'))
  381. }
  382. })
  383. })
  384. })