Completed
Push — 2.x ( 44f724...01e849 )
by Frank
04:30
created

AwsS3V3AdapterTest.php (2 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
namespace League\Flysystem\AwsS3V3;
6
7
use Aws\Result;
8
use Aws\S3\S3Client;
9
use Aws\S3\S3ClientInterface;
10
use Generator;
11
use League\Flysystem\AdapterTestUtilities\FilesystemAdapterTestCase;
12
use League\Flysystem\Config;
13
use League\Flysystem\FileAttributes;
14
use League\Flysystem\FilesystemAdapter;
15
use League\Flysystem\StorageAttributes;
16
use League\Flysystem\UnableToCheckFileExistence;
17
use League\Flysystem\UnableToDeleteFile;
18
use League\Flysystem\UnableToMoveFile;
19
20
/**
21
 * @group aws
22
 */
23
class AwsS3V3AdapterTest extends FilesystemAdapterTestCase
24
{
25
    /**
26
     * @var bool
27
     */
28
    private $shouldCleanUp = false;
29
30
    /**
31
     * @var string
32
     */
33
    private static $adapterPrefix = 'test-prefix';
34
35
    /**
36
     * @var S3ClientInterface|null
37
     */
38
    private static $s3Client;
39
40
    /**
41
     * @var S3ClientStub
42
     */
43
    private static $stubS3Client;
44
45
    public static function setUpBeforeClass(): void
46
    {
47
        static::$adapterPrefix = 'ci/' . bin2hex(random_bytes(10));
48
    }
49
50
    protected function tearDown(): void
51
    {
52
        if ( ! $this->shouldCleanUp) {
53
            return;
54
        }
55
56
        $adapter = $this->adapter();
57
        $adapter->deleteDirectory('/');
58
        /** @var StorageAttributes[] $listing */
59
        $listing = $adapter->listContents('', false);
60
61
        foreach ($listing as $item) {
62
            if ($item->isFile()) {
63
                $adapter->delete($item->path());
64
            } else {
65
                $adapter->deleteDirectory($item->path());
66
            }
67
        }
68
    }
69
70
    private static function s3Client(): S3ClientInterface
71
    {
72
        if (static::$s3Client instanceof S3ClientInterface) {
73
            return static::$s3Client;
74
        }
75
76
        $key = getenv('FLYSYSTEM_AWS_S3_KEY');
77
        $secret = getenv('FLYSYSTEM_AWS_S3_SECRET');
78
        $bucket = getenv('FLYSYSTEM_AWS_S3_BUCKET');
79
        $region = getenv('FLYSYSTEM_AWS_S3_REGION') ?: 'eu-central-1';
80
81
        if ( ! $key || ! $secret || ! $bucket) {
82
            self::markTestSkipped('No AWS credentials present for testing.');
83
        }
84
85
        $options = ['version' => 'latest', 'credentials' => compact('key', 'secret'), 'region' => $region];
86
87
        return static::$s3Client = new S3Client($options);
88
    }
89
90
    /**
91
     * @test
92
     */
93
    public function writing_with_a_specific_mime_type(): void
94
    {
95
        $adapter = $this->adapter();
96
        $adapter->write('some/path.txt', 'contents', new Config(['ContentType' => 'text/plain+special']));
97
        $mimeType = $adapter->mimeType('some/path.txt')->mimeType();
98
        $this->assertEquals('text/plain+special', $mimeType);
99
    }
100
101
    /**
102
     * @test
103
     */
104
    public function listing_contents_recursive(): void
105
    {
106
        $adapter = $this->adapter();
107
        $adapter->write('something/0/here.txt', 'contents', new Config());
108
        $adapter->write('something/1/also/here.txt', 'contents', new Config());
109
110
        $contents = iterator_to_array($adapter->listContents('', true));
111
112
        $this->assertCount(2, $contents);
113
        $this->assertContainsOnlyInstancesOf(FileAttributes::class, $contents);
114
        /** @var FileAttributes $file */
115
        $file = $contents[0];
116
        $this->assertEquals('something/0/here.txt', $file->path());
117
        /** @var FileAttributes $file */
118
        $file = $contents[1];
119
        $this->assertEquals('something/1/also/here.txt', $file->path());
120
    }
121
122
    /**
123
     * @test
124
     */
125
    public function failing_to_delete_while_moving(): void
126
    {
127
        $adapter = $this->adapter();
128
        $adapter->write('source.txt', 'contents to be copied', new Config());
129
        static::$stubS3Client->failOnNextCopy();
130
131
        $this->expectException(UnableToMoveFile::class);
132
133
        $adapter->move('source.txt', 'destination.txt', new Config());
134
    }
135
136
    /**
137
     * @test
138
     */
139
    public function failing_to_delete_a_file(): void
140
    {
141
        $adapter = $this->adapter();
142
        static::$stubS3Client->throwExceptionWhenExecutingCommand('DeleteObject');
143
144
        $this->expectException(UnableToDeleteFile::class);
145
146
        $adapter->delete('path.txt');
147
    }
148
149
    /**
150
     * @test
151
     */
152
    public function fetching_unknown_mime_type_of_a_file(): void
153
    {
154
        $this->adapter();
155
        $result = new Result([
156
            'Key' => static::$adapterPrefix . '/unknown-mime-type.md5',
0 ignored issues
show
Since $adapterPrefix is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $adapterPrefix to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
157
        ]);
158
        static::$stubS3Client->stageResultForCommand('HeadObject', $result);
0 ignored issues
show
Since $stubS3Client is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $stubS3Client to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
159
160
        parent::fetching_unknown_mime_type_of_a_file();
161
    }
162
163
    /**
164
     * @test
165
     */
166
    public function failing_to_check_for_file_existence(): void
167
    {
168
        $adapter = $this->adapter();
169
170
        static::$stubS3Client->throw500ExceptionWhenExecutingCommand('HeadObject');
171
172
        $this->expectException(UnableToCheckFileExistence::class);
173
174
        $adapter->fileExists('something-that-does-exist.txt');
175
    }
176
177
    /**
178
     * @test
179
     * @dataProvider casesWhereHttpStreamingInfluencesSeekability
180
     */
181
    public function streaming_reads_are_not_seekable_and_non_streaming_are(bool $streaming, bool $seekable): void
182
    {
183
        if (getenv('COMPOSER_OPTS') === '--prefer-lowest') {
184
            $this->markTestSkipped('The SDK does not support streaming in low versions.');
185
        }
186
187
        $adapter = $this->useAdapter($this->createFilesystemAdapter($streaming));
188
        $this->givenWeHaveAnExistingFile('path.txt');
189
190
        $resource = $adapter->readStream('path.txt');
191
        $metadata = stream_get_meta_data($resource);
192
        fclose($resource);
193
194
        $this->assertEquals($seekable, $metadata['seekable']);
195
    }
196
197
    public function casesWhereHttpStreamingInfluencesSeekability(): Generator
198
    {
199
        yield "not streaming reads have seekable stream" => [false, true];
200
        yield "streaming reads have non-seekable stream" => [true, false];
201
    }
202
203
    /**
204
     * @test
205
     * @dataProvider casesWhereHttpStreamingInfluencesSeekability
206
     */
207
    public function configuring_http_streaming_via_options(bool $streaming): void
208
    {
209
        $adapter = $this->useAdapter($this->createFilesystemAdapter($streaming, ['@http' => ['stream' => false]]));
210
        $this->givenWeHaveAnExistingFile('path.txt');
211
212
        $resource = $adapter->readStream('path.txt');
213
        $metadata = stream_get_meta_data($resource);
214
        fclose($resource);
215
216
        $this->assertTrue($metadata['seekable']);
217
    }
218
219
    protected static function createFilesystemAdapter(bool $streaming = true, array $options = []): FilesystemAdapter
220
    {
221
        static::$stubS3Client = new S3ClientStub(static::s3Client());
222
        /** @var string $bucket */
223
        $bucket = getenv('FLYSYSTEM_AWS_S3_BUCKET');
224
        $prefix = getenv('FLYSYSTEM_AWS_S3_PREFIX') ?: static::$adapterPrefix;
225
226
        return new AwsS3V3Adapter(static::$stubS3Client, $bucket, $prefix, null, null, $options, $streaming);
227
    }
228
}
229