Working on my first Laravel packag . It is a Flysystem Adapter. It is the Amazon S3 adapter, but one I want to improve to deal with 503 rate limiting errors when storing Laravel Spatie Backups on Digital Ocean Spaces.
I also added my first PHP Unit Test:
<?php
namespace Smart48\FlysystemDigitalOcean\Test;
use GuzzleHttp\Psr7\Response;
use League\Flysystem\Config;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Smart48\FlysystemDigitalOcean\DigitalOceanAdapter;
class DigitalOceanAdapterTest extends TestCase
{
/** @var \Aws\S3\S3Client\Client|\Prophecy\Prophecy\ObjectProphecy */
protected $client;
/** @var \Smart48\FlysystemDigitalOcean\DigitalOceanAdapter */
protected $digitalOceanAdapter;
/**
* @before
*/
public function setUpTest()
{
$this->client = $this->prophesize(Client::class);
$this->digitalOceanAdapter = new DigitalOceanAdapter($this->client->reveal(), 'prefix');
}
/** @test */
public function it_can_write()
{
$this->client->upload(Argument::any(), Argument::any(), Argument::any())->willReturn([
'server_modified' => '2015-05-12T15:50:38Z',
'path_display' => '/prefix/something',
'.tag' => 'file',
]);
$result = $this->digitalOceanAdapter->write('something', 'contents', new Config());
$this->assertIsArrayType($result);
$this->assertArrayHasKey('type', $result);
$this->assertEquals('file', $result['type']);
}
/** @test */
public function it_can_update()
{
$this->client->upload(Argument::any(), Argument::any(), Argument::any())->willReturn([
'server_modified' => '2015-05-12T15:50:38Z',
'path_display' => '/prefix/something',
'.tag' => 'file',
]);
$result = $this->digitalOceanAdapter->update('something', 'contents', new Config());
$this->assertIsArrayType($result);
$this->assertArrayHasKey('type', $result);
$this->assertEquals('file', $result['type']);
}
/** @test */
public function it_can_write_a_stream()
{
$this->client->upload(Argument::any(), Argument::any(), Argument::any())->willReturn([
'server_modified' => '2015-05-12T15:50:38Z',
'path_display' => '/prefix/something',
'.tag' => 'file',
]);
$result = $this->digitalOceanAdapter->writeStream('something', tmpfile(), new Config());
$this->assertIsArrayType($result);
$this->assertArrayHasKey('type', $result);
$this->assertEquals('file', $result['type']);
}
/** @test */
public function it_can_upload_using_a_stream()
{
$this->client->upload(Argument::any(), Argument::any(), Argument::any())->willReturn([
'server_modified' => '2015-05-12T15:50:38Z',
'path_display' => '/prefix/something',
'.tag' => 'file',
]);
$result = $this->digitalOceanAdapter->updateStream('something', tmpfile(), new Config());
$this->assertIsArrayType($result);
$this->assertArrayHasKey('type', $result);
$this->assertEquals('file', $result['type']);
}
/**
* @test
*
* @dataProvider metadataProvider
*/
public function it_has_calls_to_get_meta_data($method)
{
$this->client = $this->prophesize(Client::class);
$this->client->getMetadata('/one')->willReturn([
'.tag' => 'file',
'server_modified' => '2015-05-12T15:50:38Z',
'path_display' => '/one',
]);
$this->digitalOceanAdapter = new DigitalOceanAdapter($this->client->reveal());
$this->assertIsArrayType($this->digitalOceanAdapter->{$method}('one'));
}
public function metadataProvider(): array
{
return [
['getMetadata'],
['getTimestamp'],
['getSize'],
['has'],
];
}
/** @test */
public function it_will_not_hold_metadata_after_failing()
{
$this->client = $this->prophesize(Client::class);
$this->client->getMetadata('/one')->willThrow(new BadRequest(new Response(409)));
$this->digitalOceanAdapter = new DigitalOceanAdapter($this->client->reveal());
$this->assertFalse($this->digitalOceanAdapter->has('one'));
}
/** @test */
public function it_can_read()
{
$stream = tmpfile();
fwrite($stream, 'something');
$this->client->download(Argument::any(), Argument::any())->willReturn($stream);
$this->assertIsArrayType($this->digitalOceanAdapter->read('something'));
}
/** @test */
public function it_can_read_using_a_stream()
{
$stream = tmpfile();
fwrite($stream, 'something');
$this->client->download(Argument::any(), Argument::any())->willReturn($stream);
$this->assertIsArrayType($this->digitalOceanAdapter->readStream('something'));
fclose($stream);
}
/** @test */
public function it_can_delete_stuff()
{
$this->client->delete('/prefix/something')->willReturn(['.tag' => 'file']);
$this->assertTrue($this->digitalOceanAdapter->delete('something'));
$this->assertTrue($this->digitalOceanAdapter->deleteDir('something'));
}
/** @test */
public function it_can_create_a_directory()
{
$this->client->createFolder('/prefix/fail/please')->willThrow(new BadRequest(new Response(409)));
$this->client->createFolder('/prefix/pass/please')->willReturn([
'.tag' => 'folder',
'path_display' => '/prefix/pass/please',
]);
$this->assertFalse($this->digitalOceanAdapter->createDir('fail/please', new Config()));
$expected = ['path' => 'pass/please', 'type' => 'dir'];
$this->assertEquals($expected, $this->digitalOceanAdapter->createDir('pass/please', new Config()));
}
/** @test */
public function it_can_list_a_single_page_of_contents()
{
$this->client->listFolder(Argument::type('string'), Argument::any())->willReturn(
[
'entries' => [
['.tag' => 'folder', 'path_display' => 'dirname'],
['.tag' => 'file', 'path_display' => 'dirname/file'],
],
'has_more' => false,
]
);
$result = $this->digitalOceanAdapter->listContents('', true);
$this->assertCount(2, $result);
}
/** @test */
public function it_can_list_multiple_pages_of_contents()
{
$cursor = 'cursor';
$this->client->listFolder(Argument::type('string'), Argument::any())->willReturn(
[
'entries' => [
['.tag' => 'folder', 'path_display' => 'dirname'],
['.tag' => 'file', 'path_display' => 'dirname/file'],
],
'has_more' => true,
'cursor' => $cursor,
]
);
$this->client->listFolderContinue(Argument::exact($cursor))->willReturn(
[
'entries' => [
['.tag' => 'folder', 'path_display' => 'dirname2'],
['.tag' => 'file', 'path_display' => 'dirname2/file2'],
],
'has_more' => false,
]
);
$result = $this->digitalOceanAdapter->listContents('', true);
$this->assertCount(4, $result);
}
/** @test */
public function it_can_rename_stuff()
{
$this->client->move(Argument::type('string'), Argument::type('string'))->willReturn(['.tag' => 'file', 'path' => 'something']);
$this->assertTrue($this->digitalOceanAdapter->rename('something', 'something'));
}
/** @test */
public function it_will_return_false_when_a_rename_has_failed()
{
$this->client->move('/prefix/something', '/prefix/something')->willThrow(new BadRequest(new Response(409)));
$this->assertFalse($this->digitalOceanAdapter->rename('something', 'something'));
}
/** @test */
public function it_can_copy_a_file()
{
$this->client->copy(Argument::type('string'), Argument::type('string'))->willReturn(['.tag' => 'file', 'path' => 'something']);
$this->assertTrue($this->digitalOceanAdapter->copy('something', 'something'));
}
/** @test */
public function it_will_return_false_when_the_copy_process_has_failed()
{
$this->client->copy(Argument::any(), Argument::any())->willThrow(new BadRequest(new Response(409)));
$this->assertFalse($this->digitalOceanAdapter->copy('something', 'something'));
}
/** @test */
public function it_can_get_a_client()
{
$this->assertInstanceOf(Client::class, $this->digitalOceanAdapter->getClient());
}
private function assertIsArrayType($input)
{
if (method_exists(TestCase::class, 'assertIsArray')) {
$this->assertIsArray($input);
} else {
$this->assertInternalType('array', $input);
}
}
}
But running it I get this error
➜ flysystem-digital-ocean-spaces git:(main) ✗ composer test tests/DigitalOceanAdapterTest.php
> vendor/bin/phpunit 'tests/DigitalOceanAdapterTest.php'
PHPUnit 9.5.4 by Sebastian Bergmann and contributors.
EEEEEEEEEEEEEEEEEEEE 20 / 20 (100%)
Time: 00:00.048, Memory: 6.00 MB
There were 20 errors:
1) Smart48\FlysystemDigitalOcean\Test\DigitalOceanAdapterTest::it_can_write
TypeError: Argument 1 passed to Smart48\FlysystemDigitalOcean\DigitalOceanAdapter::__construct() must be an instance of Aws\S3\S3ClientInterface, instance of Double\stdClass\P1 given, called in /Users/jasperfrumau/code/flysystem-digital-ocean-spaces/tests/DigitalOceanAdapterTest.php on line 28
....
I thought the actual new adapter was setup just fine so… S3ClientInterface
should be loaded and used. But the test says otherwise
_construct() must be an instance of Aws\S3\S3ClientInterface, instance of Double\stdClass\P1 given
But how can I debug this? First time working on a package, but also first time unit testing. So any pointers would be great.