Completed
Push — 2.x ( 598746...6ada3b )
by Frank
01:17
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\S3\S3Client;
8
use Aws\S3\S3ClientInterface;
9
use Generator;
10
use League\Flysystem\AdapterTestUtilities\FilesystemAdapterTestCase;
11
use League\Flysystem\Config;
12
use League\Flysystem\FileAttributes;
13
use League\Flysystem\FilesystemAdapter;
14
use League\Flysystem\StorageAttributes;
15
use League\Flysystem\UnableToDeleteFile;
16
use League\Flysystem\UnableToMoveFile;
17
18
/**
19
 * @group aws
20
 */
21
class AwsS3V3AdapterTest extends FilesystemAdapterTestCase
22
{
23
    /**
24
     * @var bool
25
     */
26
    private $shouldCleanUp = false;
27
28
    /**
29
     * @var string
30
     */
31
    private static $adapterPrefix = 'test-prefix';
32
33
    /**
34
     * @var S3ClientInterface|null
35
     */
36
    private $s3Client;
37
38
    /**
39
     * @var S3ClientStub
40
     */
41
    private $stubS3Client;
42
43
    public static function setUpBeforeClass(): void
44
    {
45
        static::$adapterPrefix = 'travis-ci/' . bin2hex(random_bytes(10));
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...
46
    }
47
48
    protected function tearDown(): void
49
    {
50
        if ( ! $this->shouldCleanUp) {
51
            return;
52
        }
53
54
        $adapter = $this->adapter();
55
        /** @var StorageAttributes[] $listing */
56
        $listing = $adapter->listContents('', false);
57
58
        foreach ($listing as $item) {
59
            if ($item->isFile()) {
60
                $adapter->delete($item->path());
61
            } else {
62
                $adapter->deleteDirectory($item->path());
63
            }
64
        }
65
    }
66
67
    private function s3Client(): S3ClientInterface
68
    {
69
        if ($this->s3Client instanceof S3ClientInterface) {
70
            return $this->s3Client;
71
        }
72
73
        $key = getenv('FLYSYSTEM_AWS_S3_KEY');
74
        $secret = getenv('FLYSYSTEM_AWS_S3_SECRET');
75
        $bucket = getenv('FLYSYSTEM_AWS_S3_BUCKET');
76
        $region = getenv('FLYSYSTEM_AWS_S3_REGION') ?: 'eu-central-1';
77
78
        if ( ! $key || ! $secret || ! $bucket) {
79
            $this->markTestSkipped('No AWS credentials present for testing.');
80
        }
81
82
        $this->shouldCleanUp = true;
83
        $options = ['version' => 'latest', 'credentials' => compact('key', 'secret'), 'region' => $region];
84
85
        return $this->s3Client = new S3Client($options);
86
    }
87
88
    /**
89
     * @test
90
     */
91
    public function writing_with_a_specific_mime_type(): void
92
    {
93
        $adapter = $this->adapter();
94
        $adapter->write('some/path.txt', 'contents', new Config(['ContentType' => 'text/plain+special']));
95
        $mimeType = $adapter->mimeType('some/path.txt')->mimeType();
96
        $this->assertEquals('text/plain+special', $mimeType);
97
    }
98
99
    /**
100
     * @test
101
     */
102
    public function listing_contents_recursive(): void
103
    {
104
        $adapter = $this->adapter();
105
        $adapter->write('something/0/here.txt', 'contents', new Config());
106
        $adapter->write('something/1/also/here.txt', 'contents', new Config());
107
108
        $contents = iterator_to_array($adapter->listContents('', true));
109
110
        $this->assertCount(2, $contents);
111
        $this->assertContainsOnlyInstancesOf(FileAttributes::class, $contents);
112
        /** @var FileAttributes $file */
113
        $file = $contents[0];
114
        $this->assertEquals('something/0/here.txt', $file->path());
115
        /** @var FileAttributes $file */
116
        $file = $contents[1];
117
        $this->assertEquals('something/1/also/here.txt', $file->path());
118
    }
119
120
    /**
121
     * @test
122
     */
123
    public function failing_to_delete_while_moving(): void
124
    {
125
        $this->expectException(UnableToMoveFile::class);
126
127
        $adapter = $this->adapter();
128
        $adapter->write('source.txt', 'contents to be copied', new Config());
129
        $this->stubS3Client->throwExceptionWhenExecutingCommand('CopyObject');
130
131
        $adapter->move('source.txt', 'destination.txt', new Config());
132
    }
133
134
    /**
135
     * @test
136
     */
137
    public function failing_to_delete_a_file(): void
138
    {
139
        $this->expectException(UnableToDeleteFile::class);
140
141
        $adapter = $this->adapter();
142
        $this->stubS3Client->throwExceptionWhenExecutingCommand('DeleteObject');
143
144
        $adapter->delete('path.txt');
145
    }
146
147
    /**
148
     * @test
149
     * @dataProvider casesWhereHttpStreamingInfluencesSeekability
150
     */
151
    public function streaming_reads_are_not_seekable_and_non_streaming_are(bool $streaming, bool $seekable): void
152
    {
153
        $adapter = $this->useAdapter($this->createFilesystemAdapter($streaming));
154
        $this->givenWeHaveAnExistingFile('path.txt');
155
156
        $resource = $adapter->readStream('path.txt');
157
        $metadata = stream_get_meta_data($resource);
158
        fclose($resource);
159
160
        $this->assertEquals($seekable, $metadata['seekable']);
161
    }
162
163
    public function casesWhereHttpStreamingInfluencesSeekability(): Generator
164
    {
165
        yield "not streaming reads have seekable stream" => [false, true];
166
        yield "streaming reads have non-seekable stream" => [true, false];
167
    }
168
169
    /**
170
     * @test
171
     * @dataProvider casesWhereHttpStreamingInfluencesSeekability
172
     */
173
    public function configuring_http_streaming_via_options(bool $streaming): void
174
    {
175
        $adapter = $this->useAdapter($this->createFilesystemAdapter($streaming, ['@http' => ['stream' => false]]));
176
        $this->givenWeHaveAnExistingFile('path.txt');
177
178
        $resource = $adapter->readStream('path.txt');
179
        $metadata = stream_get_meta_data($resource);
180
        fclose($resource);
181
182
        $this->assertTrue($metadata['seekable']);
183
    }
184
185
    protected function createFilesystemAdapter(bool $streaming = true, array $options = []): FilesystemAdapter
186
    {
187
        $this->stubS3Client = new S3ClientStub($this->s3Client());
188
        /** @var string $bucket */
189
        $bucket = getenv('FLYSYSTEM_AWS_S3_BUCKET');
190
        $prefix = getenv('FLYSYSTEM_AWS_S3_PREFIX') ?: static::$adapterPrefix;
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...
191
192
        return new AwsS3V3Adapter($this->stubS3Client, $bucket, $prefix, null, null, $options, $streaming);
193
    }
194
}
195