Mountebank::saveImposters()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 8
cts 9
cp 0.8889
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 4
nop 0
crap 4.0218
1
<?php
2
3
namespace Codeception\Module;
4
5
use Codeception\Configuration;
6
use Codeception\Exception\ConfigurationException;
7
use Codeception\Exception\ModuleConfigException;
8
use Codeception\Module;
9
use Codeception\TestInterface;
10
use Exception;
11
use Meare\Juggler\Imposter\Imposter;
12
use Meare\Juggler\Juggler;
13
14
class Mountebank extends Module
15
{
16
    /**
17
     * @var array
18
     */
19
    protected $requiredFields = [
20
        'host',
21
    ];
22
23
    /**
24
     * @var array
25
     */
26
    protected $config = [
27
        'port'      => Juggler::DEFAULT_PORT,
28
        'imposters' => [],
29
    ];
30
31
    /**
32
     * Mountebank client
33
     *
34
     * @var Juggler
35
     */
36
    private $juggler;
37
38
    /**
39
     * Per-test imposters cache
40
     *
41
     * @var Imposter[]
42
     */
43
    private $cachedImposters;
44
45
    /**
46
     * Map; imposter alias (key) to imposter port (value)
47
     *
48
     * @var array
49
     */
50
    private $imposterAliasToPort = [];
51
52
    /**
53
     * List of imposters that were replaced during test
54
     * Contains imposter aliases as keys and dummy values
55
     *
56
     * @var array
57
     */
58
    private $replacedImposters = [];
59
60
    /**
61
     * @throws ConfigurationException
62
     * @throws ModuleConfigException
63
     */
64 4
    public function _initialize()
65
    {
66 4
        $this->juggler = $this->createJuggler();
67
68 4
        $this->initializeImposters();
69 4
    }
70
71
    /**
72
     * @return Juggler
73
     */
74
    protected function createJuggler()
75
    {
76
        return new Juggler($this->config['host'], $this->config['port']);
77
    }
78
79
    /**
80
     * @throws ConfigurationException
81
     * @throws Exception
82
     */
83 4
    private function initializeImposters()
84
    {
85 4
        $this->juggler->deleteImposters();
86 4
        $this->postConfiguredImposters();
87 4
    }
88
89 4
    private function postConfiguredImposters()
90
    {
91 4
        foreach ($this->config['imposters'] as $alias => $imposter_config) {
92 3
            $port = $this->juggler->postImposterFromFile($imposter_config['contract']);
93 3
            $this->imposterAliasToPort[$alias] = $port;
94 4
        }
95 4
    }
96
97
    /**
98
     * @return Juggler
99
     */
100
    public function _getJuggler()
101
    {
102
        return $this->juggler;
103
    }
104
105
    /**
106
     * @param TestInterface $test
107
     */
108 1
    public function _before(TestInterface $test)
109
    {
110 1
        foreach ($this->config['imposters'] as $alias => $imposter_config) {
111 1
            if ($this->imposterShouldBeRestored($alias)) {
112 1
                $this->restoreImposter($alias);
113 1
            }
114 1
        }
115 1
    }
116
117
    /**
118
     * Imposter should be restored if it was replaced during test or it was specified as 'mock' in module configuration
119
     *
120
     * @param string $alias
121
     *
122
     * @return bool
123
     */
124 1
    private function imposterShouldBeRestored($alias)
125
    {
126 1
        return isset($this->replacedImposters[$alias])
127 1
        || (isset($this->config['imposters'][$alias]['mock']) && true === $this->config['imposters'][$alias]['mock']);
128
    }
129
130
    /**
131
     * Restores imposter to initial state by posting contract from configuration
132
     *
133
     * @param string $alias
134
     */
135 1
    public function restoreImposter($alias)
136
    {
137 1
        $this->debug("Restoring imposter '$alias'");
138 1
        $this->silentlyReplaceImposter(
139 1
            $alias,
140 1
            $this->config['imposters'][$alias]['contract']
141 1
        );
142 1
    }
143
144
    /**
145
     * Replaces imposter without queueing it for restoration;
146
     * Expects new imposter to have the same port as replaced imposter had
147
     *
148
     * @param string          $alias
149
     * @param string|Imposter $imposter_or_path
150
     */
151 1
    private function silentlyReplaceImposter($alias, $imposter_or_path)
152 1
    {
153 1
        $port = $this->resolveImposterPort($alias);
154
155 1
        if (is_string($imposter_or_path)) {
156
            // Assume path to imposter contract given
157 1
            $this->juggler->deleteImposterIfExists($port);
158 1
            $new_port = $this->juggler->postImposterFromFile($imposter_or_path);
159 1
        } else {
160
            // Assume Imposter instance given
161
            $new_port = $this->juggler->replaceImposter($imposter_or_path);
162
        }
163
164 1
        if ($new_port !== $port) {
165
            $this->fail("Failed to replace imposter '$alias' at port $port - new imposter port does not match ($new_port)");
166
        }
167 1
    }
168
169
    /**
170
     * @param string $alias
171
     *
172
     * @return int Port
173
     */
174 2
    private function resolveImposterPort($alias)
175
    {
176 2
        if (!isset($this->imposterAliasToPort[$alias])) {
177
            $this->fail("Imposter '$alias' is not presented in configuration");
178
        }
179
180 2
        return $this->imposterAliasToPort[$alias];
181
    }
182
183
    /**
184
     * @throws Exception
185
     */
186 1
    public function _afterSuite()
187
    {
188 1
        $this->saveImposters();
189 1
    }
190
191
    /**
192
     * Saves imposter contracts to files if 'save' param was set in imposter config
193
     *
194
     * @throws Exception
195
     */
196 1
    private function saveImposters()
197
    {
198 1
        foreach ($this->config['imposters'] as $alias => $imposter_config) {
199 1
            if (isset($imposter_config['save'])) {
200
                try {
201 1
                    $this->juggler->retrieveAndSaveContract($this->resolveImposterPort($alias), $imposter_config['save']);
202 1
                } catch (Exception $e) {
203
                    $this->debug(sprintf('Failed to save imposters: %s %s', get_class($e), $e->getMessage()));
204
                }
205 1
            }
206 1
        }
207 1
    }
208
209
    /**
210
     * Fetches imposter from mountebank or returns cached Imposter instance.
211
     * Imposter instance gets cached for current test after fetching
212
     *
213
     * @param string $alias
214
     *
215
     * @return Imposter
216
     * @see Mountebank::fetchImposter() To get Imposter without hitting cache
217
     */
218
    public function getImposter($alias)
219
    {
220
        if (isset($this->cachedImposters[$alias])) {
221
            return $this->cachedImposters[$alias];
222
        } else {
223
            return $this->fetchImposter($alias);
224
        }
225
    }
226
227
    /**
228
     * Retrieves imposter from mountebank
229
     * Does not looks in cache but caches fetched Imposter instance
230
     *
231
     * @param string $alias
232
     *
233
     * @return Imposter
234
     */
235
    public function fetchImposter($alias)
236
    {
237
        $port = $this->resolveImposterPort($alias);
238
239
        return $this->cachedImposters[$alias] = $this->juggler->getImposter($port);
240
    }
241
242
    /**
243
     * Asserts there is one or $exact_quantity of requests recorded in imposter that matches criteria
244
     *
245
     * @param string $alias
246
     * @param array  $criteria
247
     * @param int    $exact_quantity
248
     *
249
     * @see Imposter::findRequests()
250
     */
251
    public function seeImposterHasRequestsByCriteria($alias, array $criteria, $exact_quantity = 1)
252
    {
253
        $requests = $this->fetchImposter($alias)
254
            ->findRequests($criteria);
255
256
        if (sizeof($requests) > 0) {
257
            $this->debugSection('Matched requests', json_encode($requests, JSON_PRETTY_PRINT));
258
        }
259
260
        $this->assertNotEmpty(
261
            $requests,
262
            'Imposter has requests by criteria'
263
        );
264
        $this->assertSame(
265
            $exact_quantity,
266
            sizeof($requests),
267
            "Imposter has exactly $exact_quantity requests by criteria"
268
        );
269
    }
270
271
    /**
272
     * Asserts there is at least one request recorded in imposter
273
     *
274
     * @param string $alias
275
     */
276
    public function seeImposterHasRequests($alias)
277
    {
278
        $this->assertTrue(
279
            $this->fetchImposter($alias)->hasRequests(),
280
            'Imposter has requests recorded'
281
        );
282
    }
283
284
    /**
285
     * Asserts that there are no requests recorded in imposter
286
     *
287
     * @param string $alias
288
     */
289
    public function seeImposterHasNoRequests($alias)
290
    {
291
        $this->assertFalse(
292
            $this->fetchImposter($alias)->hasRequests(),
293
            'Imposter has no request recorded'
294
        );
295
    }
296
297
    /**
298
     * Replaces imposter with cached Imposter instance for current test
299
     * Imposter instance gets cached when retrieved with Mountebank::getImposter() or Mountebank::fetchImposter()
300
     * methods
301
     *
302
     * @param string $alias
303
     *
304
     * @see Mountebank::getImposter()
305
     * @see Mountebank::fetchImposter()
306
     */
307
    public function replaceImposterWithCached($alias)
308
    {
309
        if (!isset($this->cachedImposters[$alias])) {
310
            $this->fail("Unable to replace imposter '$alias' with cached instance - no cached instance found");
311
        }
312
        $imposter = $this->cachedImposters[$alias];
313
        $this->replaceImposter($alias, $imposter);
314
    }
315
316
    /**
317
     * Replaces imposter until next test
318
     *
319
     * @param string          $alias
320
     * @param string|Imposter $imposter_or_path Path to imposter contract or Imposter instance
321
     */
322
    public function replaceImposter($alias, $imposter_or_path)
323
    {
324
        $this->replacedImposters[$alias] = true;
325
        $this->silentlyReplaceImposter($alias, $imposter_or_path);
326
    }
327
328 8
    protected function validateConfig()
329
    {
330 8
        parent::validateConfig();
331 7
        $this->validateImpostersConfig();
332 6
    }
333
334 7
    protected function validateImpostersConfig()
335
    {
336 7
        foreach ($this->config['imposters'] as $alias => $imposter_config) {
337 5
            if (!isset($imposter_config['contract'])) {
338 1
                throw new ModuleConfigException($this, "Missing 'contract' field in imposter configuration ('$alias')");
339
            }
340 6
        }
341 6
    }
342
343
    /**
344
     * @param string $relative_path Path relative to project directory
345
     *
346
     * @return string
347
     */
348
    protected function getFullPath($relative_path)
349
    {
350
        return Configuration::projectDir() . DIRECTORY_SEPARATOR . $relative_path;
351
    }
352
}
353