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