api.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import test from 'ava'
  2. import request from 'supertest'
  3. import fs from 'fs-extra'
  4. import path from 'path'
  5. import express from 'express'
  6. import serveStatic from 'serve-static'
  7. import { apiPrefix, imageServedUrl, imagesPath } from '../../config'
  8. import routes from '../../server/routes'
  9. // Path to `test` directory
  10. const testDir = path.resolve(__dirname, '..', '..', 'test')
  11. // Pretty-print a JSON object
  12. const json = obj => 'JSON DATA : ' + (JSON.stringify(obj, null, 2) || obj)
  13. /**
  14. * Uses supertest to open an Express server on an ephemeral port.
  15. * The server serves images in `test/images`, all api routes and
  16. * uses a custom error handler (no logging to stdout).
  17. *
  18. * Using `request` (supertest) on this object will start the server
  19. *
  20. * @returns {object} an Express server
  21. */
  22. const serve = () => {
  23. const app = express()
  24. app.use(imageServedUrl, serveStatic(imagesPath))
  25. app.use(apiPrefix, routes)
  26. app.use((err, req, res, next) => {
  27. res.status(err.output.payload.statusCode).json({
  28. message: err.message || err.output.payload.message,
  29. data: err.data || undefined
  30. })
  31. })
  32. return app
  33. }
  34. // Before starting all the tests, copy `test/images_test` to `test/images`
  35. test.before(async t => {
  36. await fs.remove(path.resolve(testDir, 'images'))
  37. await fs.copy(path.resolve(testDir, 'images_test'), path.resolve(testDir, 'images'))
  38. })
  39. // Before each tests, start a server
  40. test.beforeEach(async t => {
  41. t.context.server = serve()
  42. })
  43. // After finishing all the tests, remove `test/images`
  44. test.after.always(async t => {
  45. await fs.remove(path.resolve(testDir, 'images'))
  46. })
  47. // ROUTE /listScenes
  48. test('GET /listScenes', async t => {
  49. const res = await request(t.context.server)
  50. .get(`${apiPrefix}/listScenes`)
  51. t.is(res.status, 200, json(res))
  52. t.deepEqual(res.body.data, ['bathroom', 'contemporary'], json(res.body))
  53. })
  54. // ROUTE /listSceneQualities
  55. test('GET /listSceneQualities', async t => {
  56. const res = await request(t.context.server)
  57. .get(`${apiPrefix}/listSceneQualities`)
  58. t.is(res.status, 400, json(res))
  59. t.true(res.body.message.includes('Missing parameter'), json(res.body))
  60. t.true(res.body.message.includes('sceneName'), json(res.body))
  61. })
  62. test('GET /listSceneQualities?sceneName=invalid/../scene', async t => {
  63. const res = await request(t.context.server)
  64. .get(`${apiPrefix}/listSceneQualities?sceneName=invalid/../scene`)
  65. t.is(res.status, 409, json(res))
  66. t.truthy(res.body.message.match(/The requested scene name.*is not valid/), json(res.body))
  67. })
  68. test('GET /listSceneQualities?sceneName=unknown-scene-name', async t => {
  69. const res = await request(t.context.server)
  70. .get(`${apiPrefix}/listSceneQualities?sceneName=unknown-scene-name`)
  71. t.is(res.status, 500, json(res))
  72. t.truthy(res.body.message.match(/Can't access.*scene dir.*Check it exist.*and you have read permission/), json(res.body))
  73. })
  74. test('GET /listSceneQualities?sceneName=bathroom', async t => {
  75. const res = await request(t.context.server)
  76. .get(`${apiPrefix}/listSceneQualities?sceneName=bathroom`)
  77. t.is(res.status, 200, json(res))
  78. t.true(Array.isArray(res.body.data) && res.body.data.every(x => Number.isInteger(x)), json(res.body))
  79. })
  80. // ROUTE /getImage
  81. test('GET /getImage', async t => {
  82. const res = await request(t.context.server)
  83. .get(`${apiPrefix}/getImage`)
  84. t.is(res.status, 400, json(res))
  85. t.true(res.body.message.includes('Missing parameter'), json(res.body))
  86. t.true(res.body.message.includes('sceneName'), json(res.body))
  87. t.true(res.body.message.includes('imageQuality'), json(res.body))
  88. })
  89. test('GET /getImage?sceneName=invalid/../scene&imageQuality=aaaa', async t => {
  90. const res = await request(t.context.server)
  91. .get(`${apiPrefix}/getImage?sceneName=invalid/../scene&imageQuality=aaaa`)
  92. t.is(res.status, 400, json(res))
  93. t.true(res.body.message.includes('Invalid query parameter'), json(res.body))
  94. t.truthy(res.body.data.find(x => x.match(/The requested scene name.*is not valid/)), json(res.body))
  95. t.truthy(res.body.data.find(x => x.includes('The specified quality is not an integer')), json(res.body))
  96. })
  97. test('GET /getImage?sceneName=unknown-scene-name&imageQuality=10', async t => {
  98. const res = await request(t.context.server)
  99. .get(`${apiPrefix}/getImage?sceneName=unknown-scene-name&imageQuality=10`)
  100. t.is(res.status, 500, json(res))
  101. t.truthy(res.body.message.match(/Can't access.*scene dir.*Check it exist.*and you have read permission/), json(res.body))
  102. })
  103. test('GET /getImage?sceneName=bathroom&imageQuality=999999', async t => {
  104. const res = await request(t.context.server)
  105. .get(`${apiPrefix}/getImage?sceneName=bathroom&imageQuality=999999`)
  106. t.is(res.status, 404, json(res))
  107. t.truthy(res.body.message.match(/requested quality.*not found for.*scene/), json(res.body))
  108. })
  109. test('GET /getImage?sceneName=bathroom&imageQuality=10', async t => {
  110. const res = await request(t.context.server)
  111. .get(`${apiPrefix}/getImage?sceneName=bathroom&imageQuality=10`)
  112. t.is(res.status, 200, json(res))
  113. t.is(res.body.data, `${imageServedUrl}/bathroom/bathroom_00010.png`, json(res.body))
  114. // Check link is accessible and is an image
  115. const res2 = await request(t.context.server)
  116. .get(`${imageServedUrl}/bathroom/bathroom_00010.png`)
  117. t.is(res2.status, 200, json(res2))
  118. t.is(res2.header['content-type'], 'image/png', json(res2))
  119. })
  120. // ROUTE /getImageExtracts
  121. test('GET /getImageExtracts', async t => {
  122. const res = await request(t.context.server)
  123. .get(`${apiPrefix}/getImageExtracts`)
  124. t.is(res.status, 400, json(res))
  125. t.true(res.body.message.includes('Missing parameter'), json(res.body))
  126. t.true(res.body.message.includes('sceneName'), json(res.body))
  127. t.true(res.body.message.includes('imageQuality'), json(res.body))
  128. t.true(res.body.message.includes('horizontalExtractCount'), json(res.body))
  129. t.true(res.body.message.includes('verticalExtractCount'), json(res.body))
  130. })
  131. test('GET /getImageExtracts?sceneName=/../&imageQuality=a&horizontalExtractCount=a&verticalExtractCount=a', async t => {
  132. const res = await request(t.context.server)
  133. .get(`${apiPrefix}/getImageExtracts?sceneName=/../&imageQuality=a&horizontalExtractCount=a&verticalExtractCount=a`)
  134. t.is(res.status, 400, json(res))
  135. t.true(res.body.message.includes('Invalid query parameter'), json(res.body))
  136. t.truthy(res.body.data.find(x => x.match(/The requested scene name.*is not valid/)), json(res.body))
  137. t.truthy(res.body.data.find(x => x.includes('The specified quality is not an integer')), json(res.body))
  138. t.truthy(res.body.data.find(x => x.includes('horizontal axis is not an integer')), json(res.body))
  139. t.truthy(res.body.data.find(x => x.includes('vertical axis is not an integer')), json(res.body))
  140. })
  141. test('GET /getImageExtracts?sceneName=unknown-scene-name&imageQuality=10&horizontalExtractCount=5&verticalExtractCount=2', async t => {
  142. const res = await request(t.context.server)
  143. .get(`${apiPrefix}/getImageExtracts?sceneName=unknown-scene-name&imageQuality=10&horizontalExtractCount=5&verticalExtractCount=2`)
  144. t.is(res.status, 500, json(res))
  145. t.truthy(res.body.message.match(/Can't access.*scene dir.*Check it exist.*and you have read permission/), json(res.body))
  146. })
  147. test('GET /getImageExtracts?sceneName=bathroom&imageQuality=99999&horizontalExtractCount=5&verticalExtractCount=2', async t => {
  148. const res = await request(t.context.server)
  149. .get(`${apiPrefix}/getImageExtracts?sceneName=bathroom&imageQuality=99999&horizontalExtractCount=5&verticalExtractCount=2`)
  150. t.is(res.status, 404, json(res))
  151. t.truthy(res.body.message.match(/requested quality.*not found for.*scene/), json(res.body))
  152. })
  153. test('GET /getImageExtracts?sceneName=bathroom&imageQuality=10&horizontalExtractCount=0&verticalExtractCount=9999', async t => {
  154. const res = await request(t.context.server)
  155. .get(`${apiPrefix}/getImageExtracts?sceneName=bathroom&imageQuality=10&horizontalExtractCount=0&verticalExtractCount=9999`)
  156. t.is(res.status, 400, json(res))
  157. t.true(res.body.message.includes('Invalid query parameter'), json(res.body))
  158. t.truthy(res.body.data.find(x => x.includes('Incompatible number of horizontal extracts')), json(res.body))
  159. t.truthy(res.body.data.find(x => x.includes('Incompatible number of vertical extracts')), json(res.body))
  160. })
  161. test.serial('GET /getImageExtracts?sceneName=bathroom&imageQuality=10&horizontalExtractCount=5&verticalExtractCount=2', async t => {
  162. const res = await request(t.context.server)
  163. .get(`${apiPrefix}/getImageExtracts?sceneName=bathroom&imageQuality=10&horizontalExtractCount=5&verticalExtractCount=2`)
  164. t.is(res.status, 200, json(res))
  165. t.true(Array.isArray(res.body.data), json(res.body))
  166. t.is(res.body.data[0], `${imageServedUrl}/bathroom/extracts/x5_y2/zone00001/bathroom_zone00001_10.png`, json(res.body))
  167. // Check link is accessible and is an image
  168. const res2 = await request(t.context.server)
  169. .get(`${imageServedUrl}/bathroom/extracts/x5_y2/zone00001/bathroom_zone00001_10.png`)
  170. t.is(res2.status, 200, json(res2))
  171. t.is(res2.header['content-type'], 'image/png', json(res2))
  172. })
  173. test.serial('Extracts were successfully generated', async t => {
  174. // Check the extract on the file system
  175. const extracts = path.resolve(imagesPath, 'bathroom', 'extracts')
  176. const aBathroomConfig = path.resolve(extracts, 'x5_y2')
  177. const aBathroomConfigZone = path.resolve(aBathroomConfig, 'zone00001')
  178. const aBathroomConfigZoneImg = path.resolve(aBathroomConfigZone, 'bathroom_zone00001_10.png')
  179. const fsp = fs.promises
  180. // Check `bathroom/extracts`
  181. t.true(await fs.pathExists(extracts))
  182. t.deepEqual(await fsp.readdir(extracts), ['x5_y2'])
  183. // Check `bathroom/extracts/x5_y2`
  184. t.true(await fs.pathExists(aBathroomConfig), aBathroomConfig)
  185. t.is((await fsp.readdir(aBathroomConfig)).length, 10)
  186. // Check `bathroom/extracts/x5_y2/zone00001`
  187. t.true(await fs.pathExists(aBathroomConfigZone))
  188. t.is((await fsp.readdir(aBathroomConfigZone)).length, 1)
  189. // Check `bathroom/extracts/x5_y2/zone00001/bathroom_zone00001_10.png`
  190. t.true(await fs.pathExists(aBathroomConfigZoneImg))
  191. })