Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHPORM-167 Fix and test MongoDB failed queue provider #2838

Merged
merged 3 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.

* New aggregation pipeline builder by @GromNaN in [#2738](https://github.com/mongodb/laravel-mongodb/pull/2738)
* Drop support for Composer 1.x by @GromNaN in [#2784](https://github.com/mongodb/laravel-mongodb/pull/2784)
* Fix `artisan query:retry` command by @GromNaN in [#2838](https://github.com/mongodb/laravel-mongodb/pull/2838)

## [4.2.0] - 2024-03-14

Expand Down
41 changes: 37 additions & 4 deletions src/Queue/Failed/MongoFailedJobProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
namespace MongoDB\Laravel\Queue\Failed;

use Carbon\Carbon;
use DateTimeInterface;
use Exception;
use Illuminate\Queue\Failed\DatabaseFailedJobProvider;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\UTCDateTime;

use function array_map;
Expand Down Expand Up @@ -55,16 +57,16 @@ public function all()
/**
* Get a single failed job.
*
* @param mixed $id
* @param string $id
*
* @return object
* @return object|null
*/
public function find($id)
{
$job = $this->getTable()->find($id);

if (! $job) {
return;
return null;
}

$job['id'] = (string) $job['_id'];
Expand All @@ -75,12 +77,43 @@ public function find($id)
/**
* Delete a single failed job from storage.
*
* @param mixed $id
* @param string $id
*
* @return bool
*/
public function forget($id)
{
return $this->getTable()->where('_id', $id)->delete() > 0;
}

/**
* Get the IDs of all the failed jobs.
*
* @param string|null $queue
*
* @return list<ObjectId>
*/
public function ids($queue = null)
{
return $this->getTable()
->when($queue !== null, static fn ($query) => $query->where('queue', $queue))
->orderBy('_id', 'desc')
->pluck('_id')
->all();
}

/**
* Prune all failed jobs older than the given date.
*
* @param DateTimeInterface $before
*
* @return int
*/
public function prune(DateTimeInterface $before)
{
return $this
->getTable()
->where('failed_at', '<', $before)
->delete();
}
}
150 changes: 150 additions & 0 deletions tests/Queue/Failed/MongoFailedJobProviderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php

namespace MongoDB\Laravel\Tests\Queue\Failed;

use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Laravel\Queue\Failed\MongoFailedJobProvider;
use MongoDB\Laravel\Tests\TestCase;
use OutOfBoundsException;

use function array_map;
use function range;
use function sprintf;

class MongoFailedJobProviderTest extends TestCase
{
public function setUp(): void
{
parent::setUp();

DB::connection('mongodb')
->collection('failed_jobs')
->raw()
->insertMany(array_map(static fn ($i) => [
'_id' => new ObjectId(sprintf('%024d', $i)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are _id fields within jobs always an ObjectId? I saw that MongoFailedJobProvider made some assumptions about that by using string for type hints and casting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documents are added by the log method which doesn't specify _id. So we get an automatic ObjectId.

'connection' => 'mongodb',
'queue' => $i % 2 ? 'default' : 'other',
'failed_at' => new UTCDateTime(Date::now()->subHours($i)),
], range(1, 5)));
}

public function tearDown(): void
{
DB::connection('mongodb')
->collection('failed_jobs')
->raw()
->drop();

parent::tearDown();
}

public function testLog(): void
{
$provider = $this->getProvider();

$provider->log('mongodb', 'default', '{"foo":"bar"}', new OutOfBoundsException('This is the error'));

$ids = $provider->ids();

$this->assertCount(6, $ids);

$inserted = $provider->find($ids[0]);

$this->assertSame('mongodb', $inserted->connection);
$this->assertSame('default', $inserted->queue);
$this->assertSame('{"foo":"bar"}', $inserted->payload);
$this->assertStringContainsString('OutOfBoundsException: This is the error', $inserted->exception);
$this->assertInstanceOf(ObjectId::class, $inserted->_id);
$this->assertSame((string) $inserted->_id, $inserted->id);
}

public function testCount(): void
{
$provider = $this->getProvider();

$this->assertEquals(5, $provider->count());
$this->assertEquals(3, $provider->count('mongodb', 'default'));
$this->assertEquals(2, $provider->count('mongodb', 'other'));
}

public function testAll(): void
{
$all = $this->getProvider()->all();

$this->assertCount(5, $all);
$this->assertEquals(new ObjectId(sprintf('%024d', 5)), $all[0]->_id);
$this->assertEquals(sprintf('%024d', 5), $all[0]->id, 'id field is added for compatibility with DatabaseFailedJobProvider');
}

public function testFindAndForget(): void
{
$provider = $this->getProvider();

$id = sprintf('%024d', 2);
$found = $provider->find($id);

$this->assertIsObject($found, 'The job is found');
$this->assertEquals(new ObjectId($id), $found->_id);
$this->assertObjectHasProperty('failed_at', $found);

// Delete the job
$result = $provider->forget($id);

$this->assertTrue($result, 'forget return true when the job have been deleted');
$this->assertNull($provider->find($id), 'the job have been deleted');

// Delete the same job again
$result = $provider->forget($id);

$this->assertFalse($result, 'forget return false when the job does not exist');

$this->assertCount(4, $provider->ids(), 'Other jobs are kept');
}

public function testIds(): void
{
$ids = $this->getProvider()->ids();

$this->assertCount(5, $ids);
$this->assertEquals(new ObjectId(sprintf('%024d', 5)), $ids[0]);
}

public function testIdsFilteredByQuery(): void
{
$ids = $this->getProvider()->ids('other');

$this->assertCount(2, $ids);
$this->assertEquals(new ObjectId(sprintf('%024d', 4)), $ids[0]);
}

public function testFlush(): void
{
$provider = $this->getProvider();

$this->assertEquals(5, $provider->count());

$provider->flush(4);

$this->assertEquals(3, $provider->count());
}

public function testPrune(): void
{
$provider = $this->getProvider();

$this->assertEquals(5, $provider->count());

$result = $provider->prune(Date::now()->subHours(4));

$this->assertEquals(2, $result);
$this->assertEquals(3, $provider->count());
}

private function getProvider(): MongoFailedJobProvider
{
return new MongoFailedJobProvider(DB::getFacadeRoot(), '', 'failed_jobs');
}
}
Loading