1 | 'use strict'
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | const got = require('got')
|
8 | const JSPath = require('jspath')
|
9 | const assert = require('assert').strict
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | function Test(url, options) {
|
19 | this.data = {
|
20 | url,
|
21 | options,
|
22 | response: {},
|
23 | error: null
|
24 | }
|
25 | this.promise = Promise.resolve(null)
|
26 | this.request()
|
27 | }
|
28 |
|
29 | const freddo = (url, options = null) => new Test(url, options)
|
30 |
|
31 | Test.prototype.extend = function(promise, that) {
|
32 | for (const key in that) {
|
33 | promise[key] = that[key]
|
34 | }
|
35 | }
|
36 |
|
37 | Test.prototype.next = function(what) {
|
38 | this.promise = this.promise.then(what)
|
39 | this.extend(this.promise, this)
|
40 | return this.promise
|
41 | }
|
42 |
|
43 | Test.prototype.request = function() {
|
44 | return this.next(async () => {
|
45 | let response
|
46 | if (this.data.options != undefined) {
|
47 | response = await got(this.data.url, this.data.options)
|
48 | } else {
|
49 | response = await got(this.data.url)
|
50 | }
|
51 | this.data.response = response
|
52 | return true
|
53 | })
|
54 | }
|
55 |
|
56 | Test.prototype.verify = async function(key, expected, isHeader) {
|
57 | let check = expected
|
58 | let value, location
|
59 | if (key instanceof Expression) {
|
60 | if (typeof this.data.response.body != 'string') {
|
61 | this.data.response.body = JSON.stringify(this.data.response.body)
|
62 | }
|
63 | value = key.apply(JSON.parse(this.data.response.body))
|
64 | location = `expression ${JSON.stringify(key.expression)}`
|
65 | } else {
|
66 | if (isHeader) {
|
67 | value = this.data.response.headers[key]
|
68 | } else {
|
69 | value = this.data.response[key]
|
70 | }
|
71 | if (typeof value === 'undefined') {
|
72 | throw new Error(`Key ${JSON.stringify(key)} does not exist`)
|
73 | }
|
74 | location = `key ${JSON.stringify(key)}`
|
75 | }
|
76 | if (typeof expected !== 'function') {
|
77 | check = (actual, location) => {
|
78 | try {
|
79 | assert.deepStrictEqual(actual, expected)
|
80 | } catch (e) {
|
81 | return {
|
82 | result: false,
|
83 | error: `Expected ${location} to be ${JSON.stringify(expected)}, but got ${JSON.stringify(actual)}`
|
84 | }
|
85 | }
|
86 | return true
|
87 | }
|
88 | }
|
89 | let result = check(value, location)
|
90 | let error = `Custom assertion failed: ${check}`
|
91 | if (typeof result !== 'boolean') {
|
92 | if (typeof result.result === 'undefined') {
|
93 | throw new Error('Custom assertion functions must return a boolean or a {result, error} object')
|
94 | }
|
95 | if (typeof result.error !== 'undefined') {
|
96 | error = result.error
|
97 | }
|
98 | result = result.result
|
99 | }
|
100 | if (!result) {
|
101 | this.data.error = error
|
102 | }
|
103 | return result
|
104 | }
|
105 |
|
106 | Test.prototype.expect = function(key, expected, isHeader = false) {
|
107 | return this.next((prev) => prev && this.verify(key, expected, isHeader))
|
108 | }
|
109 |
|
110 | Test.prototype.status = function(expected) {
|
111 | return this.expect('statusCode', expected)
|
112 | }
|
113 |
|
114 | Test.prototype.header = function(key, expected) {
|
115 | return this.expect(key, String(expected), true)
|
116 | }
|
117 |
|
118 | Test.prototype.body = function(expected, expression = null) {
|
119 | if (expression == null) {
|
120 | return this.expect('body', expected)
|
121 | }
|
122 | return this.expect(expression, expected)
|
123 | }
|
124 |
|
125 | Test.prototype.redirectsTo = function(url) {
|
126 | return this.status((code) => [301, 302, 303, 307, 308].includes(code))
|
127 | .header('location', url)
|
128 | }
|
129 |
|
130 | Test.prototype.ensure = async function() {
|
131 | if (!(await this)) {
|
132 | throw new Error(this.data.error)
|
133 | }
|
134 | }
|
135 |
|
136 | const exists = (actual, location) => {
|
137 | if (actual.length != 0) {
|
138 | return true
|
139 | }
|
140 | return {
|
141 | result: false,
|
142 | error: `Expected ${location} to contain a value, but it does not exist`
|
143 | }
|
144 | }
|
145 |
|
146 | class Expression {
|
147 | constructor(expression) {
|
148 | this.expression = expression
|
149 | }
|
150 | apply(haystack) {
|
151 | let result = JSPath.apply(this.expression, haystack)
|
152 | return result
|
153 | }
|
154 | }
|
155 |
|
156 | const expr = expression => new Expression(expression)
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | module.exports = { freddo, expr, exists } |
\ | No newline at end of file |