blob: 7ba0e153418b0543652bc3755dc2ff34df3824c3 [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3namespace LdapRecord\Testing;
4
5use Exception;
6use LdapRecord\DetailedError;
7use LdapRecord\DetectsErrors;
8use LdapRecord\HandlesConnection;
9use LdapRecord\LdapInterface;
10use LdapRecord\Support\Arr;
11use PHPUnit\Framework\Assert as PHPUnit;
12use PHPUnit\Framework\Constraint\Constraint;
13
14class LdapFake implements LdapInterface
15{
16 use HandlesConnection, DetectsErrors;
17
18 /**
19 * The expectations of the LDAP fake.
20 *
21 * @var array
22 */
23 protected $expectations = [];
24
25 /**
26 * The default fake error number.
27 *
28 * @var int
29 */
30 protected $errNo = 1;
31
32 /**
33 * The default fake last error string.
34 *
35 * @var string
36 */
37 protected $lastError = '';
38
39 /**
40 * The default fake diagnostic message string.
41 *
42 * @var string
43 */
44 protected $diagnosticMessage = '';
45
46 /**
47 * Create a new expected operation.
48 *
49 * @param string $method
50 *
51 * @return LdapExpectation
52 */
53 public static function operation($method)
54 {
55 return new LdapExpectation($method);
56 }
57
58 /**
59 * Set the user that will pass binding.
60 *
61 * @param string $dn
62 *
63 * @return $this
64 */
65 public function shouldAuthenticateWith($dn)
66 {
67 return $this->expect(
68 static::operation('bind')->with($dn, PHPUnit::anything())->andReturn(true)
69 );
70 }
71
72 /**
73 * Add an LDAP method expectation.
74 *
75 * @param LdapExpectation|array $expectations
76 *
77 * @return $this
78 */
79 public function expect($expectations = [])
80 {
81 $expectations = Arr::wrap($expectations);
82
83 foreach ($expectations as $key => $expectation) {
84 // If the key is non-numeric, we will assume
85 // that the string is the method name and
86 // the expectation is the return value.
87 if (! is_numeric($key)) {
88 $expectation = static::operation($key)->andReturn($expectation);
89 }
90
91 if (! $expectation instanceof LdapExpectation) {
92 $expectation = static::operation($expectation);
93 }
94
95 $this->expectations[$expectation->getMethod()][] = $expectation;
96 }
97
98 return $this;
99 }
100
101 /**
102 * Determine if the method has any expectations.
103 *
104 * @param string $method
105 *
106 * @return bool
107 */
108 public function hasExpectations($method)
109 {
110 return count($this->getExpectations($method)) > 0;
111 }
112
113 /**
114 * Get expectations by method.
115 *
116 * @param string $method
117 *
118 * @return LdapExpectation[]|mixed
119 */
120 public function getExpectations($method)
121 {
122 return $this->expectations[$method] ?? [];
123 }
124
125 /**
126 * Remove an expectation by method and key.
127 *
128 * @param string $method
129 * @param int $key
130 *
131 * @return void
132 */
133 public function removeExpectation($method, $key)
134 {
135 unset($this->expectations[$method][$key]);
136 }
137
138 /**
139 * Set the error number of a failed bind attempt.
140 *
141 * @param int $number
142 *
143 * @return $this
144 */
145 public function shouldReturnErrorNumber($number = 1)
146 {
147 $this->errNo = $number;
148
149 return $this;
150 }
151
152 /**
153 * Set the last error of a failed bind attempt.
154 *
155 * @param string $message
156 *
157 * @return $this
158 */
159 public function shouldReturnError($message = '')
160 {
161 $this->lastError = $message;
162
163 return $this;
164 }
165
166 /**
167 * Set the diagnostic message of a failed bind attempt.
168 *
169 * @param string $message
170 *
171 * @return $this
172 */
173 public function shouldReturnDiagnosticMessage($message = '')
174 {
175 $this->diagnosticMessage = $message;
176
177 return $this;
178 }
179
180 /**
181 * Return a fake error number.
182 *
183 * @return int
184 */
185 public function errNo()
186 {
187 return $this->errNo;
188 }
189
190 /**
191 * Return a fake error.
192 *
193 * @return string
194 */
195 public function getLastError()
196 {
197 return $this->lastError;
198 }
199
200 /**
201 * @inheritdoc
202 */
203 public function getDiagnosticMessage()
204 {
205 return $this->diagnosticMessage;
206 }
207
208 /**
209 * Return a fake detailed error.
210 *
211 * @return DetailedError
212 */
213 public function getDetailedError()
214 {
215 return new DetailedError(
216 $this->errNo(),
217 $this->getLastError(),
218 $this->getDiagnosticMessage()
219 );
220 }
221
222 /**
223 * @inheritdoc
224 */
225 public function getEntries($searchResults)
226 {
227 return $searchResults;
228 }
229
230 /**
231 * @inheritdoc
232 */
233 public function isUsingSSL()
234 {
235 return $this->hasExpectations('isUsingSSL')
236 ? $this->resolveExpectation('isUsingSSL')
237 : $this->useSSL;
238 }
239
240 /**
241 * @inheritdoc
242 */
243 public function isUsingTLS()
244 {
245 return $this->hasExpectations('isUsingTLS')
246 ? $this->resolveExpectation('isUsingTLS')
247 : $this->useTLS;
248 }
249
250 /**
251 * @inheritdoc
252 */
253 public function isBound()
254 {
255 return $this->hasExpectations('isBound')
256 ? $this->resolveExpectation('isBound')
257 : $this->bound;
258 }
259
260 /**
261 * @inheritdoc
262 */
263 public function setOption($option, $value)
264 {
265 return $this->hasExpectations('setOption')
266 ? $this->resolveExpectation('setOption', func_get_args())
267 : true;
268 }
269
270 /**
271 * @inheritdoc
272 */
273 public function getOption($option, &$value = null)
274 {
275 return $this->resolveExpectation('getOption', func_get_args());
276 }
277
278 /**
279 * @inheritdoc
280 */
281 public function startTLS()
282 {
283 return $this->resolveExpectation('startTLS', func_get_args());
284 }
285
286 /**
287 * @inheritdoc
288 */
289 public function connect($hosts = [], $port = 389)
290 {
291 $this->bound = false;
292
293 $this->host = $this->makeConnectionUris($hosts, $port);
294
295 return $this->connection = $this->hasExpectations('connect')
296 ? $this->resolveExpectation('connect', func_get_args())
297 : true;
298 }
299
300 /**
301 * @inheritdoc
302 */
303 public function close()
304 {
305 $this->connection = null;
306 $this->bound = false;
307 $this->host = null;
308
309 return $this->hasExpectations('close')
310 ? $this->resolveExpectation('close')
311 : true;
312 }
313
314 /**
315 * @inheritdoc
316 */
317 public function bind($username, $password)
318 {
319 return $this->bound = $this->resolveExpectation('bind', func_get_args());
320 }
321
322 /**
323 * @inheritdoc
324 */
325 public function search($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = null, $serverControls = [])
326 {
327 return $this->resolveExpectation('search', func_get_args());
328 }
329
330 /**
331 * @inheritdoc
332 */
333 public function listing($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = null, $serverControls = [])
334 {
335 return $this->resolveExpectation('listing', func_get_args());
336 }
337
338 /**
339 * @inheritdoc
340 */
341 public function read($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = null, $serverControls = [])
342 {
343 return $this->resolveExpectation('read', func_get_args());
344 }
345
346 /**
347 * @inheritdoc
348 */
349 public function parseResult($result, &$errorCode, &$dn, &$errorMessage, &$referrals, &$serverControls = [])
350 {
351 return $this->resolveExpectation('parseResult', func_get_args());
352 }
353
354 /**
355 * @inheritdoc
356 */
357 public function add($dn, array $entry)
358 {
359 return $this->resolveExpectation('add', func_get_args());
360 }
361
362 /**
363 * @inheritdoc
364 */
365 public function delete($dn)
366 {
367 return $this->resolveExpectation('delete', func_get_args());
368 }
369
370 /**
371 * @inheritdoc
372 */
373 public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false)
374 {
375 return $this->resolveExpectation('rename', func_get_args());
376 }
377
378 /**
379 * @inheritdoc
380 */
381 public function modify($dn, array $entry)
382 {
383 return $this->resolveExpectation('modify', func_get_args());
384 }
385
386 /**
387 * @inheritdoc
388 */
389 public function modifyBatch($dn, array $values)
390 {
391 return $this->resolveExpectation('modifyBatch', func_get_args());
392 }
393
394 /**
395 * @inheritdoc
396 */
397 public function modAdd($dn, array $entry)
398 {
399 return $this->resolveExpectation('modAdd', func_get_args());
400 }
401
402 /**
403 * @inheritdoc
404 */
405 public function modReplace($dn, array $entry)
406 {
407 return $this->resolveExpectation('modReplace', func_get_args());
408 }
409
410 /**
411 * @inheritdoc
412 */
413 public function modDelete($dn, array $entry)
414 {
415 return $this->resolveExpectation('modDelete', func_get_args());
416 }
417
418 /**
419 * @inheritdoc
420 */
421 public function controlPagedResult($pageSize = 1000, $isCritical = false, $cookie = '')
422 {
423 return $this->resolveExpectation('controlPagedResult', func_get_args());
424 }
425
426 /**
427 * @inheritdoc
428 */
429 public function controlPagedResultResponse($result, &$cookie)
430 {
431 return $this->resolveExpectation('controlPagedResultResponse', func_get_args());
432 }
433
434 /**
435 * @inheritdoc
436 */
437 public function freeResult($result)
438 {
439 return $this->resolveExpectation('freeResult', func_get_args());
440 }
441
442 /**
443 * @inheritdoc
444 */
445 public function err2Str($number)
446 {
447 return $this->resolveExpectation('err2Str', func_get_args());
448 }
449
450 /**
451 * Resolve the methods expectations.
452 *
453 * @param string $method
454 * @param array $args
455 *
456 * @throws Exception
457 *
458 * @return mixed
459 */
460 protected function resolveExpectation($method, array $args = [])
461 {
462 foreach ($this->getExpectations($method) as $key => $expectation) {
463 $this->assertMethodArgumentsMatch($method, $expectation->getExpectedArgs(), $args);
464
465 $expectation->decrementCallCount();
466
467 if ($expectation->getExpectedCount() === 0) {
468 $this->removeExpectation($method, $key);
469 }
470
471 if (! is_null($exception = $expectation->getExpectedException())) {
472 throw $exception;
473 }
474
475 if ($expectation->isReturningError()) {
476 $this->applyExpectationError($expectation);
477 }
478
479 return $expectation->getExpectedValue();
480 }
481
482 throw new Exception("LDAP method [$method] was unexpected.");
483 }
484
485 /**
486 * Apply the expectation error to the fake.
487 *
488 * @param LdapExpectation $expectation
489 *
490 * @return void
491 */
492 protected function applyExpectationError(LdapExpectation $expectation)
493 {
494 $this->shouldReturnError($expectation->getExpectedErrorMessage());
495 $this->shouldReturnErrorNumber($expectation->getExpectedErrorCode());
496 $this->shouldReturnDiagnosticMessage($expectation->getExpectedErrorDiagnosticMessage());
497 }
498
499 /**
500 * Assert that the expected arguments match the operations arguments.
501 *
502 * @param string $method
503 * @param Constraint[] $expectedArgs
504 * @param array $methodArgs
505 *
506 * @return void
507 */
508 protected function assertMethodArgumentsMatch($method, array $expectedArgs = [], array $methodArgs = [])
509 {
510 foreach ($expectedArgs as $key => $constraint) {
511 $argNumber = $key + 1;
512
513 PHPUnit::assertArrayHasKey(
514 $key,
515 $methodArgs,
516 "LDAP method [$method] argument #{$argNumber} does not exist."
517 );
518
519 $constraint->evaluate(
520 $methodArgs[$key],
521 "LDAP method [$method] expectation failed."
522 );
523 }
524 }
525}