Skip to content
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
9 changes: 5 additions & 4 deletions src/queue/src/InteractsWithQueue.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ public function delete(): void
{
if ($this->job) {
$this->job->delete();
return;
}
}

Expand All @@ -64,7 +63,6 @@ public function release(DateInterval|DateTimeInterface|int $delay = 0): void

if ($this->job) {
$this->job->release($delay);
return;
}
}

Expand Down Expand Up @@ -195,10 +193,13 @@ public function assertReleased(DateInterval|DateTimeInterface|int|null $delay =
);

if (! is_null($delay)) {
// A fake job released directly can hold a date or interval instead of seconds.
$actual = $this->secondsUntil($this->job->releaseDelay); // @phpstan-ignore property.notFound (FakeJob test-double property)

PHPUnit::assertSame(
$delay,
$this->job->releaseDelay, // @phpstan-ignore property.notFound (FakeJob test-double property)
"Expected job to be released with delay of [{$delay}] seconds, but was released with delay of [{$this->job->releaseDelay}] seconds." // @phpstan-ignore property.notFound
$actual,
"Expected job to be released with delay of [{$delay}] seconds, but was released with delay of [{$actual}] seconds."
);
}

Expand Down
14 changes: 7 additions & 7 deletions src/queue/src/Jobs/FakeJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,27 @@
class FakeJob extends Job
{
/**
* The number of seconds the released job was delayed.
* The delay the job was released with.
*/
public int $releaseDelay = 0;
public DateInterval|DateTimeInterface|int $releaseDelay = 0;
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.

/**
* The number of attempts made to process the job.
*/
public int $attempts = 1;

/**
* The exception the job failed with.
*/
public ?Throwable $failedWith = null;

public function __construct(
public array $payload = [],
protected string $connectionName = 'sync',
protected string $queue = 'default',
) {
}

/**
* The exception the job failed with.
*/
public ?Throwable $failedWith = null;

/**
* Get the job identifier.
*/
Expand Down
195 changes: 83 additions & 112 deletions tests/Auth/AuthDatabaseUserProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,32 @@
use Hypervel\Contracts\Hashing\Hasher;
use Hypervel\Database\ConnectionInterface;
use Hypervel\Database\ConnectionResolverInterface;
use Hypervel\Database\PdoConnection;
use Hypervel\Database\Query\Builder;
use Hypervel\Database\SQLiteConnection;
use Hypervel\Hashing\BcryptHasher;
use Hypervel\Tests\TestCase;
use Mockery as m;
use stdClass;
use PDO;

use function Hypervel\Coroutine\parallel;

class AuthDatabaseUserProviderTest extends TestCase
{
public function testRetrieveByIDReturnsUserWhenUserIsFound(): void
{
$conn = m::mock(ConnectionInterface::class);
$query = m::mock(Builder::class);
$conn->expects('table')->with('foo')->andReturn($query);
$query->expects('find')->with(1)->andReturn(['id' => 1, 'name' => 'Dayle']);
$hasher = m::mock(Hasher::class);
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$user = $provider->retrieveById(1);
$provider = $this->newProvider();
$user = $provider->retrieveById(3);

$this->assertInstanceOf(GenericUser::class, $user);
$this->assertSame(1, $user->getAuthIdentifier());
$this->assertSame('Dayle', $user->name);
$this->assertSame(3, $user->getAuthIdentifier());
$this->assertSame('taylor', $user->name);
}

public function testRetrieveByIDReturnsNullWhenUserIsNotFound(): void
{
$conn = m::mock(ConnectionInterface::class);
$query = m::mock(Builder::class);
$conn->expects('table')->with('foo')->andReturn($query);
$query->expects('find')->with(1)->andReturn(null);
$hasher = m::mock(Hasher::class);
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$user = $provider->retrieveById(1);
$provider = $this->newProvider();
$user = $provider->retrieveById(99);

$this->assertNull($user);
}
Expand Down Expand Up @@ -126,105 +119,65 @@ public function testResolverContractTakesPrecedenceForObjectsImplementingBothCon

public function testRetrieveByTokenReturnsUser(): void
{
$mockUser = new stdClass;
$mockUser->remember_token = 'a';
$provider = $this->newProvider();
$user = $provider->retrieveByToken(3, 'a');

$conn = m::mock(ConnectionInterface::class);
$query = m::mock(Builder::class);
$conn->expects('table')->with('foo')->andReturn($query);
$query->expects('find')->with(1)->andReturn($mockUser);
$hasher = m::mock(Hasher::class);
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$user = $provider->retrieveByToken(1, 'a');

$this->assertEquals(new GenericUser((array) $mockUser), $user);
$this->assertInstanceOf(GenericUser::class, $user);
$this->assertSame(3, $user->getAuthIdentifier());
}

public function testRetrieveTokenWithBadIdentifierReturnsNull(): void
{
$conn = m::mock(ConnectionInterface::class);
$query = m::mock(Builder::class);
$conn->expects('table')->with('foo')->andReturn($query);
$query->expects('find')->with(1)->andReturn(null);
$hasher = m::mock(Hasher::class);
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$user = $provider->retrieveByToken(1, 'a');
$provider = $this->newProvider();
$user = $provider->retrieveByToken(99, 'a');

$this->assertNull($user);
}

public function testRetrieveByBadTokenReturnsNull(): void
{
$mockUser = new stdClass;
$mockUser->remember_token = null;

$conn = m::mock(ConnectionInterface::class);
$query = m::mock(Builder::class);
$conn->expects('table')->with('foo')->andReturn($query);
$query->expects('find')->with(1)->andReturn($mockUser);
$hasher = m::mock(Hasher::class);
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$provider = $this->newProvider();
$user = $provider->retrieveByToken(1, 'a');

$this->assertNull($user);
}

public function testRetrieveByCredentialsReturnsUserWhenUserIsFound(): void
{
$conn = m::mock(ConnectionInterface::class);
$query = m::mock(Builder::class);
$conn->expects('table')->with('foo')->andReturn($query);
$query->expects('where')->with('username', 'dayle');
$query->expects('whereIn')->with('group', ['one', 'two']);
$query->expects('first')->andReturn(['id' => 1, 'name' => 'taylor']);
$hasher = m::mock(Hasher::class);
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$provider = $this->newProvider();
$user = $provider->retrieveByCredentials(['username' => 'dayle', 'password' => 'foo', 'group' => ['one', 'two']]);

$this->assertInstanceOf(GenericUser::class, $user);
$this->assertSame(1, $user->getAuthIdentifier());
$this->assertSame(3, $user->getAuthIdentifier());
$this->assertSame('taylor', $user->name);
}

public function testRetrieveByCredentialsAcceptsCallback(): void
{
$conn = m::mock(ConnectionInterface::class);
$query = m::mock(Builder::class);
$conn->expects('table')->with('foo')->andReturn($query);
$query->expects('where')->with('username', 'dayle');
$query->expects('whereIn')->with('group', ['one', 'two']);
$query->expects('first')->andReturn(['id' => 1, 'name' => 'taylor']);
$hasher = m::mock(Hasher::class);
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$provider = $this->newProvider();

$user = $provider->retrieveByCredentials([function (Builder $builder): void {
$builder->where('username', 'dayle');
$builder->whereIn('group', ['one', 'two']);
}]);

$this->assertInstanceOf(GenericUser::class, $user);
$this->assertSame(1, $user->getAuthIdentifier());
$this->assertSame(3, $user->getAuthIdentifier());
$this->assertSame('taylor', $user->name);
}

public function testRetrieveByCredentialsReturnsNullWhenUserIsFound(): void
{
$conn = m::mock(ConnectionInterface::class);
$query = m::mock(Builder::class);
$conn->expects('table')->with('foo')->andReturn($query);
$query->expects('where')->with('username', 'dayle');
$query->expects('first')->andReturn(null);
$hasher = m::mock(Hasher::class);
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$user = $provider->retrieveByCredentials(['username' => 'dayle']);
$provider = $this->newProvider();
$user = $provider->retrieveByCredentials(['username' => 'nobody']);

$this->assertNull($user);
}

public function testRetrieveByCredentialsWithMultiplyPasswordsReturnsNull(): void
{
$conn = m::mock(ConnectionInterface::class);
$hasher = m::mock(Hasher::class);
$conn = new PdoConnection(new PDO('sqlite::memory:'));
$hasher = new BcryptHasher;
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$user = $provider->retrieveByCredentials([
'password' => 'dayle',
Expand All @@ -236,33 +189,29 @@ public function testRetrieveByCredentialsWithMultiplyPasswordsReturnsNull(): voi

public function testCredentialValidation(): void
{
$conn = m::mock(ConnectionInterface::class);
$hasher = m::mock(Hasher::class);
$hasher->expects('check')->with('plain', 'hash')->andReturn(true);
$conn = new PdoConnection(new PDO('sqlite::memory:'));
$hasher = new BcryptHasher;
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$user = m::mock(Authenticatable::class);
$user->expects('getAuthPassword')->andReturn('hash');
$user = new GenericUser(['password' => $hasher->make('plain')]);
$result = $provider->validateCredentials($user, ['password' => 'plain']);

$this->assertTrue($result);
}

public function testCredentialValidationFails(): void
{
$conn = m::mock(ConnectionInterface::class);
$hasher = m::mock(Hasher::class);
$hasher->expects('check')->with('plain', 'hash')->andReturn(false);
$conn = new PdoConnection(new PDO('sqlite::memory:'));
$hasher = new BcryptHasher;
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$user = m::mock(Authenticatable::class);
$user->expects('getAuthPassword')->andReturn('hash');
$result = $provider->validateCredentials($user, ['password' => 'plain']);
$user = new GenericUser(['password' => $hasher->make('plain')]);
$result = $provider->validateCredentials($user, ['password' => 'wrong']);

$this->assertFalse($result);
}

public function testCredentialValidationFailsGracefullyWithNullPassword(): void
{
$conn = m::mock(ConnectionInterface::class);
$conn = new PdoConnection(new PDO('sqlite::memory:'));
$hasher = m::mock(Hasher::class);
$hasher->shouldReceive('check')->never();
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
Expand All @@ -275,51 +224,73 @@ public function testCredentialValidationFailsGracefullyWithNullPassword(): void

public function testRehashPasswordIfRequired(): void
{
$hasher = m::mock(Hasher::class);
$hasher->expects('needsRehash')->with('hash')->andReturn(true);
$hasher->expects('make')->with('plain')->andReturn('rehashed');
$connection = $this->newConnection();
$connection->table('foo')->where('id', 3)->update(['password' => (new BcryptHasher(['rounds' => 4]))->make('plain')]);

$hasher = new BcryptHasher(['rounds' => 5]);
$provider = new DatabaseUserProvider($connection, $hasher, 'foo');
$provider->rehashPasswordIfRequired($provider->retrieveById(3), ['password' => 'plain']);

$hash = $connection->table('foo')->where('id', 3)->value('password');
$this->assertTrue($hasher->check('plain', $hash));
$this->assertFalse($hasher->needsRehash($hash));
}

$conn = m::mock(ConnectionInterface::class);
$table = m::mock(Builder::class);
$conn->expects('table')->with('foo')->andReturn($table);
$table->expects('where')->with('id', 1)->andReturnSelf();
$table->expects('update')->with(['password_attribute' => 'rehashed']);
public function testRehashPasswordIfRequiredWithCustomPasswordColumn(): void
{
$connection = $this->newConnection();
$connection->statement('alter table "foo" rename column "password" to "secret"');

$user = new class(['id' => 1, 'password_attribute' => 'hash']) extends GenericUser {
$user = new class(['id' => 3, 'secret' => (new BcryptHasher(['rounds' => 4]))->make('plain')]) extends GenericUser {
/**
* Get the custom password attribute name.
* Get the name of the password attribute for the user.
*/
public function getAuthPasswordName(): string
{
return 'password_attribute';
return 'secret';
}
};

$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$hasher = new BcryptHasher(['rounds' => 5]);
$provider = new DatabaseUserProvider($connection, $hasher, 'foo');
$provider->rehashPasswordIfRequired($user, ['password' => 'plain']);

$this->assertSame('rehashed', $user->getAuthPassword());
$hash = $connection->table('foo')->where('id', 3)->value('secret');
$this->assertTrue($hasher->check('plain', $hash));
$this->assertSame($hash, $user->getAuthPassword());
}

public function testDontRehashPasswordIfNotRequired(): void
{
$hasher = m::mock(Hasher::class);
$hasher->expects('needsRehash')->with('hash')->andReturn(false);
$hasher->shouldNotReceive('make');
$hasher = new BcryptHasher(['rounds' => 5]);
$connection = $this->newConnection();
$connection->table('foo')->where('id', 3)->update(['password' => $hash = $hasher->make('plain')]);

$conn = m::mock(ConnectionInterface::class);
$table = m::mock(Builder::class);
$conn->shouldNotReceive('table');
$table->shouldNotReceive('where');
$table->shouldNotReceive('update');
$provider = new DatabaseUserProvider($connection, $hasher, 'foo');
$provider->rehashPasswordIfRequired($provider->retrieveById(3), ['password' => 'plain']);

$user = m::mock(Authenticatable::class);
$user->expects('getAuthPassword')->andReturn('hash');
$user->shouldNotReceive('getAuthIdentifierName');
$user->shouldNotReceive('getAuthIdentifier');
$user->shouldNotReceive('getAuthPasswordName');
$this->assertSame($hash, $connection->table('foo')->where('id', 3)->value('password'));
}

$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
$provider->rehashPasswordIfRequired($user, ['password' => 'plain']);
/**
* Create an in-memory connection with seeded users.
*/
protected function newConnection(): SQLiteConnection
{
$pdo = new PDO('sqlite::memory:');
$pdo->exec('create table "foo" ("id" integer primary key, "username" text, "group" text, "name" text, "password" text, "remember_token" text)');
$pdo->exec("insert into \"foo\" values (1, 'dayle', 'three', 'other', null, null)");
$pdo->exec("insert into \"foo\" values (2, 'sam', 'one', 'third', null, null)");
$pdo->exec("insert into \"foo\" values (3, 'dayle', 'one', 'taylor', null, 'a')");

return new SQLiteConnection($pdo);
}

/**
* Create a provider backed by the seeded connection.
*/
protected function newProvider(): DatabaseUserProvider
{
return new DatabaseUserProvider($this->newConnection(), new BcryptHasher, 'foo');
}
}
Loading
Loading