QueueFake.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. <?php
  2. namespace Illuminate\Support\Testing\Fakes;
  3. use BadMethodCallException;
  4. use Closure;
  5. use Illuminate\Contracts\Queue\Queue;
  6. use Illuminate\Queue\CallQueuedClosure;
  7. use Illuminate\Queue\QueueManager;
  8. use Illuminate\Support\Collection;
  9. use Illuminate\Support\Traits\ReflectsClosures;
  10. use PHPUnit\Framework\Assert as PHPUnit;
  11. class QueueFake extends QueueManager implements Fake, Queue
  12. {
  13. use ReflectsClosures;
  14. /**
  15. * The original queue manager.
  16. *
  17. * @var \Illuminate\Contracts\Queue\Queue
  18. */
  19. public $queue;
  20. /**
  21. * The job types that should be intercepted instead of pushed to the queue.
  22. *
  23. * @var \Illuminate\Support\Collection
  24. */
  25. protected $jobsToFake;
  26. /**
  27. * The job types that should be pushed to the queue and not intercepted.
  28. *
  29. * @var \Illuminate\Support\Collection
  30. */
  31. protected $jobsToBeQueued;
  32. /**
  33. * All of the jobs that have been pushed.
  34. *
  35. * @var array
  36. */
  37. protected $jobs = [];
  38. /**
  39. * Indicates if items should be serialized and restored when pushed to the queue.
  40. *
  41. * @var bool
  42. */
  43. protected bool $serializeAndRestore = false;
  44. /**
  45. * Create a new fake queue instance.
  46. *
  47. * @param \Illuminate\Contracts\Foundation\Application $app
  48. * @param array $jobsToFake
  49. * @param \Illuminate\Queue\QueueManager|null $queue
  50. * @return void
  51. */
  52. public function __construct($app, $jobsToFake = [], $queue = null)
  53. {
  54. parent::__construct($app);
  55. $this->jobsToFake = Collection::wrap($jobsToFake);
  56. $this->jobsToBeQueued = Collection::make();
  57. $this->queue = $queue;
  58. }
  59. /**
  60. * Specify the jobs that should be queued instead of faked.
  61. *
  62. * @param array|string $jobsToBeQueued
  63. * @return $this
  64. */
  65. public function except($jobsToBeQueued)
  66. {
  67. $this->jobsToBeQueued = Collection::wrap($jobsToBeQueued)->merge($this->jobsToBeQueued);
  68. return $this;
  69. }
  70. /**
  71. * Assert if a job was pushed based on a truth-test callback.
  72. *
  73. * @param string|\Closure $job
  74. * @param callable|int|null $callback
  75. * @return void
  76. */
  77. public function assertPushed($job, $callback = null)
  78. {
  79. if ($job instanceof Closure) {
  80. [$job, $callback] = [$this->firstClosureParameterType($job), $job];
  81. }
  82. if (is_numeric($callback)) {
  83. return $this->assertPushedTimes($job, $callback);
  84. }
  85. PHPUnit::assertTrue(
  86. $this->pushed($job, $callback)->count() > 0,
  87. "The expected [{$job}] job was not pushed."
  88. );
  89. }
  90. /**
  91. * Assert if a job was pushed a number of times.
  92. *
  93. * @param string $job
  94. * @param int $times
  95. * @return void
  96. */
  97. protected function assertPushedTimes($job, $times = 1)
  98. {
  99. $count = $this->pushed($job)->count();
  100. PHPUnit::assertSame(
  101. $times, $count,
  102. "The expected [{$job}] job was pushed {$count} times instead of {$times} times."
  103. );
  104. }
  105. /**
  106. * Assert if a job was pushed based on a truth-test callback.
  107. *
  108. * @param string $queue
  109. * @param string|\Closure $job
  110. * @param callable|null $callback
  111. * @return void
  112. */
  113. public function assertPushedOn($queue, $job, $callback = null)
  114. {
  115. if ($job instanceof Closure) {
  116. [$job, $callback] = [$this->firstClosureParameterType($job), $job];
  117. }
  118. $this->assertPushed($job, function ($job, $pushedQueue) use ($callback, $queue) {
  119. if ($pushedQueue !== $queue) {
  120. return false;
  121. }
  122. return $callback ? $callback(...func_get_args()) : true;
  123. });
  124. }
  125. /**
  126. * Assert if a job was pushed with chained jobs based on a truth-test callback.
  127. *
  128. * @param string $job
  129. * @param array $expectedChain
  130. * @param callable|null $callback
  131. * @return void
  132. */
  133. public function assertPushedWithChain($job, $expectedChain = [], $callback = null)
  134. {
  135. PHPUnit::assertTrue(
  136. $this->pushed($job, $callback)->isNotEmpty(),
  137. "The expected [{$job}] job was not pushed."
  138. );
  139. PHPUnit::assertTrue(
  140. collect($expectedChain)->isNotEmpty(),
  141. 'The expected chain can not be empty.'
  142. );
  143. $this->isChainOfObjects($expectedChain)
  144. ? $this->assertPushedWithChainOfObjects($job, $expectedChain, $callback)
  145. : $this->assertPushedWithChainOfClasses($job, $expectedChain, $callback);
  146. }
  147. /**
  148. * Assert if a job was pushed with an empty chain based on a truth-test callback.
  149. *
  150. * @param string $job
  151. * @param callable|null $callback
  152. * @return void
  153. */
  154. public function assertPushedWithoutChain($job, $callback = null)
  155. {
  156. PHPUnit::assertTrue(
  157. $this->pushed($job, $callback)->isNotEmpty(),
  158. "The expected [{$job}] job was not pushed."
  159. );
  160. $this->assertPushedWithChainOfClasses($job, [], $callback);
  161. }
  162. /**
  163. * Assert if a job was pushed with chained jobs based on a truth-test callback.
  164. *
  165. * @param string $job
  166. * @param array $expectedChain
  167. * @param callable|null $callback
  168. * @return void
  169. */
  170. protected function assertPushedWithChainOfObjects($job, $expectedChain, $callback)
  171. {
  172. $chain = collect($expectedChain)->map(fn ($job) => serialize($job))->all();
  173. PHPUnit::assertTrue(
  174. $this->pushed($job, $callback)->filter(fn ($job) => $job->chained == $chain)->isNotEmpty(),
  175. 'The expected chain was not pushed.'
  176. );
  177. }
  178. /**
  179. * Assert if a job was pushed with chained jobs based on a truth-test callback.
  180. *
  181. * @param string $job
  182. * @param array $expectedChain
  183. * @param callable|null $callback
  184. * @return void
  185. */
  186. protected function assertPushedWithChainOfClasses($job, $expectedChain, $callback)
  187. {
  188. $matching = $this->pushed($job, $callback)->map->chained->map(function ($chain) {
  189. return collect($chain)->map(function ($job) {
  190. return get_class(unserialize($job));
  191. });
  192. })->filter(function ($chain) use ($expectedChain) {
  193. return $chain->all() === $expectedChain;
  194. });
  195. PHPUnit::assertTrue(
  196. $matching->isNotEmpty(), 'The expected chain was not pushed.'
  197. );
  198. }
  199. /**
  200. * Assert if a closure was pushed based on a truth-test callback.
  201. *
  202. * @param callable|int|null $callback
  203. * @return void
  204. */
  205. public function assertClosurePushed($callback = null)
  206. {
  207. $this->assertPushed(CallQueuedClosure::class, $callback);
  208. }
  209. /**
  210. * Assert that a closure was not pushed based on a truth-test callback.
  211. *
  212. * @param callable|null $callback
  213. * @return void
  214. */
  215. public function assertClosureNotPushed($callback = null)
  216. {
  217. $this->assertNotPushed(CallQueuedClosure::class, $callback);
  218. }
  219. /**
  220. * Determine if the given chain is entirely composed of objects.
  221. *
  222. * @param array $chain
  223. * @return bool
  224. */
  225. protected function isChainOfObjects($chain)
  226. {
  227. return ! collect($chain)->contains(fn ($job) => ! is_object($job));
  228. }
  229. /**
  230. * Determine if a job was pushed based on a truth-test callback.
  231. *
  232. * @param string|\Closure $job
  233. * @param callable|null $callback
  234. * @return void
  235. */
  236. public function assertNotPushed($job, $callback = null)
  237. {
  238. if ($job instanceof Closure) {
  239. [$job, $callback] = [$this->firstClosureParameterType($job), $job];
  240. }
  241. PHPUnit::assertCount(
  242. 0, $this->pushed($job, $callback),
  243. "The unexpected [{$job}] job was pushed."
  244. );
  245. }
  246. /**
  247. * Assert the total count of jobs that were pushed.
  248. *
  249. * @param int $expectedCount
  250. * @return void
  251. */
  252. public function assertCount($expectedCount)
  253. {
  254. $actualCount = collect($this->jobs)->flatten(1)->count();
  255. PHPUnit::assertSame(
  256. $expectedCount, $actualCount,
  257. "Expected {$expectedCount} jobs to be pushed, but found {$actualCount} instead."
  258. );
  259. }
  260. /**
  261. * Assert that no jobs were pushed.
  262. *
  263. * @return void
  264. */
  265. public function assertNothingPushed()
  266. {
  267. PHPUnit::assertEmpty($this->jobs, 'Jobs were pushed unexpectedly.');
  268. }
  269. /**
  270. * Get all of the jobs matching a truth-test callback.
  271. *
  272. * @param string $job
  273. * @param callable|null $callback
  274. * @return \Illuminate\Support\Collection
  275. */
  276. public function pushed($job, $callback = null)
  277. {
  278. if (! $this->hasPushed($job)) {
  279. return collect();
  280. }
  281. $callback = $callback ?: fn () => true;
  282. return collect($this->jobs[$job])->filter(
  283. fn ($data) => $callback($data['job'], $data['queue'], $data['data'])
  284. )->pluck('job');
  285. }
  286. /**
  287. * Determine if there are any stored jobs for a given class.
  288. *
  289. * @param string $job
  290. * @return bool
  291. */
  292. public function hasPushed($job)
  293. {
  294. return isset($this->jobs[$job]) && ! empty($this->jobs[$job]);
  295. }
  296. /**
  297. * Resolve a queue connection instance.
  298. *
  299. * @param mixed $value
  300. * @return \Illuminate\Contracts\Queue\Queue
  301. */
  302. public function connection($value = null)
  303. {
  304. return $this;
  305. }
  306. /**
  307. * Get the size of the queue.
  308. *
  309. * @param string|null $queue
  310. * @return int
  311. */
  312. public function size($queue = null)
  313. {
  314. return collect($this->jobs)->flatten(1)->filter(
  315. fn ($job) => $job['queue'] === $queue
  316. )->count();
  317. }
  318. /**
  319. * Push a new job onto the queue.
  320. *
  321. * @param string|object $job
  322. * @param mixed $data
  323. * @param string|null $queue
  324. * @return mixed
  325. */
  326. public function push($job, $data = '', $queue = null)
  327. {
  328. if ($this->shouldFakeJob($job)) {
  329. if ($job instanceof Closure) {
  330. $job = CallQueuedClosure::create($job);
  331. }
  332. $this->jobs[is_object($job) ? get_class($job) : $job][] = [
  333. 'job' => $this->serializeAndRestore ? $this->serializeAndRestoreJob($job) : $job,
  334. 'queue' => $queue,
  335. 'data' => $data,
  336. ];
  337. } else {
  338. is_object($job) && isset($job->connection)
  339. ? $this->queue->connection($job->connection)->push($job, $data, $queue)
  340. : $this->queue->push($job, $data, $queue);
  341. }
  342. }
  343. /**
  344. * Determine if a job should be faked or actually dispatched.
  345. *
  346. * @param object $job
  347. * @return bool
  348. */
  349. public function shouldFakeJob($job)
  350. {
  351. if ($this->shouldDispatchJob($job)) {
  352. return false;
  353. }
  354. if ($this->jobsToFake->isEmpty()) {
  355. return true;
  356. }
  357. return $this->jobsToFake->contains(
  358. fn ($jobToFake) => $job instanceof ((string) $jobToFake) || $job === (string) $jobToFake
  359. );
  360. }
  361. /**
  362. * Determine if a job should be pushed to the queue instead of faked.
  363. *
  364. * @param object $job
  365. * @return bool
  366. */
  367. protected function shouldDispatchJob($job)
  368. {
  369. if ($this->jobsToBeQueued->isEmpty()) {
  370. return false;
  371. }
  372. return $this->jobsToBeQueued->contains(
  373. fn ($jobToQueue) => $job instanceof ((string) $jobToQueue)
  374. );
  375. }
  376. /**
  377. * Push a raw payload onto the queue.
  378. *
  379. * @param string $payload
  380. * @param string|null $queue
  381. * @param array $options
  382. * @return mixed
  383. */
  384. public function pushRaw($payload, $queue = null, array $options = [])
  385. {
  386. //
  387. }
  388. /**
  389. * Push a new job onto the queue after (n) seconds.
  390. *
  391. * @param \DateTimeInterface|\DateInterval|int $delay
  392. * @param string|object $job
  393. * @param mixed $data
  394. * @param string|null $queue
  395. * @return mixed
  396. */
  397. public function later($delay, $job, $data = '', $queue = null)
  398. {
  399. return $this->push($job, $data, $queue);
  400. }
  401. /**
  402. * Push a new job onto the queue.
  403. *
  404. * @param string $queue
  405. * @param string|object $job
  406. * @param mixed $data
  407. * @return mixed
  408. */
  409. public function pushOn($queue, $job, $data = '')
  410. {
  411. return $this->push($job, $data, $queue);
  412. }
  413. /**
  414. * Push a new job onto a specific queue after (n) seconds.
  415. *
  416. * @param string $queue
  417. * @param \DateTimeInterface|\DateInterval|int $delay
  418. * @param string|object $job
  419. * @param mixed $data
  420. * @return mixed
  421. */
  422. public function laterOn($queue, $delay, $job, $data = '')
  423. {
  424. return $this->push($job, $data, $queue);
  425. }
  426. /**
  427. * Pop the next job off of the queue.
  428. *
  429. * @param string|null $queue
  430. * @return \Illuminate\Contracts\Queue\Job|null
  431. */
  432. public function pop($queue = null)
  433. {
  434. //
  435. }
  436. /**
  437. * Push an array of jobs onto the queue.
  438. *
  439. * @param array $jobs
  440. * @param mixed $data
  441. * @param string|null $queue
  442. * @return mixed
  443. */
  444. public function bulk($jobs, $data = '', $queue = null)
  445. {
  446. foreach ($jobs as $job) {
  447. $this->push($job, $data, $queue);
  448. }
  449. }
  450. /**
  451. * Get the jobs that have been pushed.
  452. *
  453. * @return array
  454. */
  455. public function pushedJobs()
  456. {
  457. return $this->jobs;
  458. }
  459. /**
  460. * Specify if jobs should be serialized and restored when being "pushed" to the queue.
  461. *
  462. * @param bool $serializeAndRestore
  463. * @return $this
  464. */
  465. public function serializeAndRestore(bool $serializeAndRestore = true)
  466. {
  467. $this->serializeAndRestore = $serializeAndRestore;
  468. return $this;
  469. }
  470. /**
  471. * Serialize and unserialize the job to simulate the queueing process.
  472. *
  473. * @param mixed $job
  474. * @return mixed
  475. */
  476. protected function serializeAndRestoreJob($job)
  477. {
  478. return unserialize(serialize($job));
  479. }
  480. /**
  481. * Get the connection name for the queue.
  482. *
  483. * @return string
  484. */
  485. public function getConnectionName()
  486. {
  487. //
  488. }
  489. /**
  490. * Set the connection name for the queue.
  491. *
  492. * @param string $name
  493. * @return $this
  494. */
  495. public function setConnectionName($name)
  496. {
  497. return $this;
  498. }
  499. /**
  500. * Override the QueueManager to prevent circular dependency.
  501. *
  502. * @param string $method
  503. * @param array $parameters
  504. * @return mixed
  505. *
  506. * @throws \BadMethodCallException
  507. */
  508. public function __call($method, $parameters)
  509. {
  510. throw new BadMethodCallException(sprintf(
  511. 'Call to undefined method %s::%s()', static::class, $method
  512. ));
  513. }
  514. }