extensions/http_api/definitions.js

  1. 'use strict'
  2. const { expect } = require('chai')
  3. const _ = require('lodash')
  4. const Cast = require('../../cast')
  5. const Helper = require('../../helper')
  6. const { STATUS_CODES } = require('http')
  7. const STATUS_MESSAGES = _.values(STATUS_CODES).map(_.lowerCase)
  8. /**
  9. * Ensures there's a response available and returns it.
  10. *
  11. * @param {Object} client
  12. */
  13. const mustGetResponse = client => {
  14. const response = client.getResponse()
  15. expect(response, 'No response available').to.not.be.empty
  16. return response
  17. }
  18. module.exports = ({ baseUrl = '' } = {}) => ({ Given, When, Then }) => {
  19. /**
  20. * Setting http headers
  21. */
  22. Given(/^(?:I )?set request headers$/, function(step) {
  23. this.httpApiClient.setHeaders(Cast.object(this.state.populateObject(step.rowsHash())))
  24. })
  25. /**
  26. * Setting a single http header
  27. */
  28. Given(/^(?:I )?set ([a-zA-Z0-9-]+) request header to (.+)$/, function(key, value) {
  29. this.httpApiClient.setHeader(key, Cast.value(this.state.populate(value)))
  30. })
  31. /**
  32. * Clearing headers
  33. */
  34. Given(/^(?:I )?clear request headers/, function() {
  35. this.httpApiClient.clearHeaders()
  36. })
  37. /**
  38. * Setting json payload
  39. */
  40. Given(/^(?:I )?set request json body$/, function(step) {
  41. this.httpApiClient.setJsonBody(Cast.object(this.state.populateObject(step.rowsHash())))
  42. })
  43. /**
  44. * Setting json payload from fixture file
  45. */
  46. Given(/^(?:I )?set request json body from (.+)$/, function(fixture) {
  47. return this.fixtures.load(fixture).then(data => {
  48. this.httpApiClient.setJsonBody(data)
  49. })
  50. })
  51. /**
  52. * Setting form data
  53. */
  54. Given(/^(?:I )?set request form body$/, function(step) {
  55. this.httpApiClient.setFormBody(Cast.object(this.state.populateObject(step.rowsHash())))
  56. })
  57. /**
  58. * Setting form data from fixture file
  59. */
  60. Given(/^(?:I )?set request form body from (.+)$/, function(fixture) {
  61. return this.fixtures.load(fixture).then(data => {
  62. this.httpApiClient.setFormBody(data)
  63. })
  64. })
  65. /**
  66. * Clearing body
  67. */
  68. Given(/^(?:I )?clear request body$/, function() {
  69. this.httpApiClient.clearBody()
  70. })
  71. /**
  72. * Setting query parameters
  73. */
  74. Given(/^(?:I )?set request query$/, function(step) {
  75. this.httpApiClient.setQuery(Cast.object(this.state.populateObject(step.rowsHash())))
  76. })
  77. Given(/^(?:I )?pick response json (.+) as (.+)$/, function(path, key) {
  78. const response = this.httpApiClient.getResponse()
  79. const body = response.body
  80. this.state.set(key, _.get(body, path))
  81. })
  82. /**
  83. * Enabling cookies
  84. */
  85. Given(/^(?:I )?enable cookies$/, function() {
  86. this.httpApiClient.enableCookies()
  87. })
  88. /**
  89. * Disabling cookies
  90. */
  91. Given(/^(?:I )?disable cookies$/, function() {
  92. this.httpApiClient.disableCookies()
  93. })
  94. /**
  95. * Setting a cookie from fixture file
  96. */
  97. Given(/^(?:I )?set cookie from (.+)$/, function(fixture) {
  98. return this.fixtures.load(fixture).then(cookie => {
  99. this.httpApiClient.setCookie(cookie)
  100. })
  101. })
  102. /**
  103. * Clearing client request cookies
  104. */
  105. Given(/^(?:I )?clear request cookies$/, function() {
  106. this.httpApiClient.clearRequestCookies()
  107. })
  108. /**
  109. * Resetting the client's state
  110. */
  111. When(/^(?:I )?reset http client$/, function() {
  112. this.httpApiClient.reset()
  113. })
  114. /**
  115. * Performing a request
  116. */
  117. When(/^(?:I )?(GET|POST|PUT|DELETE) (.+)$/, function(method, path) {
  118. return this.httpApiClient.makeRequest(method, this.state.populate(path), baseUrl)
  119. })
  120. /**
  121. * Dumping response body
  122. */
  123. When(/^(?:I )?dump response body$/, function() {
  124. const response = mustGetResponse(this.httpApiClient)
  125. console.log(response.body) // eslint-disable-line no-console
  126. })
  127. /**
  128. * Dumping response headers
  129. */
  130. When(/^(?:I )?dump response headers$/, function() {
  131. const response = mustGetResponse(this.httpApiClient)
  132. console.log(response.headers) // eslint-disable-line no-console
  133. })
  134. /**
  135. * Dumping response cookies
  136. */
  137. When(/^(?:I )?dump response cookies$/, function() {
  138. mustGetResponse(this.httpApiClient)
  139. console.log(this.httpApiClient.getCookies()) // eslint-disable-line no-console
  140. })
  141. /**
  142. * Checking response status code
  143. */
  144. Then(/^response status code should be ([1-5][0-9][0-9])$/, function(statusCode) {
  145. const response = mustGetResponse(this.httpApiClient)
  146. expect(
  147. response.statusCode,
  148. `Expected status code to be: ${statusCode}, but found: ${response.statusCode}`
  149. ).to.equal(Number(statusCode))
  150. })
  151. /**
  152. * Checking response status by message
  153. */
  154. Then(/^response status should be (.+)$/, function(statusMessage) {
  155. if (!STATUS_MESSAGES.includes(_.lowerCase(statusMessage))) {
  156. throw new TypeError(`'${statusMessage}' is not a valid status message`)
  157. }
  158. const response = mustGetResponse(this.httpApiClient)
  159. const statusCode = _.findKey(STATUS_CODES, msg => _.lowerCase(msg) === statusMessage)
  160. const currentStatusMessage = STATUS_CODES[`${response.statusCode}`] || response.statusCode
  161. expect(
  162. response.statusCode,
  163. `Expected status to be: '${statusMessage}', but found: '${_.lowerCase(
  164. currentStatusMessage
  165. )}'`
  166. ).to.equal(Number(statusCode))
  167. })
  168. /**
  169. * Checking response cookie is present|absent
  170. */
  171. Then(/^response should (not )?have an? (.+) cookie$/, function(flag, key) {
  172. const cookie = this.httpApiClient.getCookie(key)
  173. if (flag === undefined) {
  174. expect(cookie, `No cookie found for key '${key}'`).to.not.be.null
  175. } else {
  176. expect(cookie, `A cookie exists for key '${key}'`).to.be.null
  177. }
  178. })
  179. /**
  180. * Checking response cookie is|isn't secure
  181. */
  182. Then(/^response (.+) cookie should (not )?be secure$/, function(key, flag) {
  183. const cookie = this.httpApiClient.getCookie(key)
  184. expect(cookie, `No cookie found for key '${key}'`).to.not.be.null
  185. if (flag === undefined) {
  186. expect(cookie.secure, `Cookie '${key}' is not secure`).to.be.true
  187. } else {
  188. expect(cookie.secure, `Cookie '${key}' is secure`).to.be.false
  189. }
  190. })
  191. /**
  192. * Checking response cookie httpOnly
  193. */
  194. Then(/^response (.+) cookie should (not )?be http only$/, function(key, flag) {
  195. const cookie = this.httpApiClient.getCookie(key)
  196. expect(cookie, `No cookie found for key '${key}'`).to.not.be.null
  197. if (flag === undefined) {
  198. expect(cookie.httpOnly, `Cookie '${key}' is not http only`).to.be.true
  199. } else {
  200. expect(cookie.httpOnly, `Cookie '${key}' is http only`).to.be.false
  201. }
  202. })
  203. /**
  204. * Checking response cookie domain
  205. */
  206. Then(/^response (.+) cookie domain should (not )?be (.+)$/, function(key, flag, domain) {
  207. const cookie = this.httpApiClient.getCookie(key)
  208. expect(cookie, `No cookie found for key '${key}'`).to.not.be.null
  209. if (flag === undefined) {
  210. expect(
  211. cookie.domain,
  212. `Expected cookie '${key}' domain to be '${domain}', found '${cookie.domain}'`
  213. ).to.equal(domain)
  214. } else {
  215. expect(cookie.domain, `Cookie '${key}' domain is '${domain}'`).to.not.equal(domain)
  216. }
  217. })
  218. /**
  219. * This definition can be used for checking an object response.
  220. * It check that the properties of this object match with the expected properties
  221. * The columns header are | field | matcher | value |
  222. * You can define severals matchers :
  223. * - equals
  224. * - contains
  225. */
  226. Then(/^(?:I )?json response should (fully )?match$/, function(fully, table) {
  227. const response = mustGetResponse(this.httpApiClient)
  228. const { body } = response
  229. const expectedProperties = table.hashes()
  230. // We check the response has json content-type
  231. expect(response.headers['content-type']).to.contain('application/json')
  232. // We check response properties correspond to the expected response
  233. expectedProperties.forEach(({ field, matcher, value }) => {
  234. const currentValue = _.get(body, field)
  235. const expectedValue = Cast.value(this.state.populate(value))
  236. switch (matcher) {
  237. case 'match':
  238. case 'matches':
  239. expect(
  240. currentValue,
  241. `Property '${field}' (${currentValue}) does not match '${expectedValue}'`
  242. ).to.match(new RegExp(expectedValue))
  243. break
  244. case 'contain':
  245. case 'contains':
  246. expect(
  247. currentValue,
  248. `Property '${field}' (${currentValue}) does not contain '${expectedValue}'`
  249. ).to.contain(expectedValue)
  250. break
  251. case 'defined':
  252. case 'present':
  253. expect(currentValue, `Property '${field}' is undefined`).to.not.be.undefined
  254. break
  255. case 'equal':
  256. case 'equals':
  257. default:
  258. expect(
  259. currentValue,
  260. `Expected property '${field}' to equal '${value}', but found '${currentValue}'`
  261. ).to.be.deep.equal(expectedValue)
  262. }
  263. })
  264. // We check we have exactly the same number of properties as expected
  265. if (fully) {
  266. const propertiesCount = Helper.countNestedProperties(body)
  267. expect(
  268. propertiesCount,
  269. 'Expected json response to fully match spec, but it does not'
  270. ).to.be.equal(table.hashes().length)
  271. }
  272. })
  273. /**
  274. * This definition verify that an array for a given path has the expected length
  275. */
  276. Then(/^(?:I )?should receive a collection of ([0-9]+) items?(?: for path )?(.+)?$/, function(
  277. size,
  278. path
  279. ) {
  280. const response = mustGetResponse(this.httpApiClient)
  281. const { body } = response
  282. const array = path !== undefined ? _.get(body, path) : body
  283. expect(array.length).to.be.equal(Number(size))
  284. })
  285. /**
  286. * Verifies that response matches a fixture.
  287. **/
  288. Then(/^response should match fixture (.+)$/, function(fixtureId) {
  289. const response = mustGetResponse(this.httpApiClient)
  290. return this.fixtures.load(fixtureId).then(snapshot => {
  291. expect(response.body).to.deep.equal(snapshot)
  292. })
  293. })
  294. /**
  295. * Checking response header.
  296. */
  297. Then(/^response header (.+) should (not )?(equal|contain|match) (.+)$/, function(
  298. key,
  299. flag,
  300. comparator,
  301. expectedValue
  302. ) {
  303. const response = mustGetResponse(this.httpApiClient)
  304. const header = response.headers[key.toLowerCase()]
  305. expect(header, `Header '${key}' does not exist`).to.not.be.undefined
  306. let expectFn = expect(
  307. header,
  308. `Expected header '${key}' to ${flag
  309. ? flag
  310. : ''}${comparator} '${expectedValue}', but found '${header}' which does${flag
  311. ? ''
  312. : ' not'}`
  313. ).to
  314. if (flag !== undefined) {
  315. expectFn = expectFn.not
  316. }
  317. expectFn[comparator](comparator === 'match' ? new RegExp(expectedValue) : expectedValue)
  318. })
  319. }