thomthom's avatar

Tests for "Unable to retrieve the mime_type" with Storage?

I'm doing some updates to a site of mine. I have some files I serve that used to be stored directly on the server but I'm making preparations to serve from S3 by refactoring into using the Storage facade.

My original code:

$file = storage_path('assets/'.$asset->filename);
return Response::download($file);

The new logic:

return Storage::disk('assets')->download($asset->filename);

In my tests I use Storage::fake('assets'); and add some test files with the data of a real file from a fixture I have.

The filetype is in reality a ZIP file, but with a custom file extension.

I have route tests that tests the download:

    public function test_download_latest(): void
    {
        $this->get('download/latest')
                ->assertStatus(200)
                ->assertDownload();
    }

My tests all passed after this refactoring, but when testing in local environment I got an error when testing the file download:

League \ Flysystem \ UnableToRetrieveMetadata
Unable to retrieve the mime_type for file at location: example.myext. 

The fix was to add Content-Type header explicitly with a known mime type:

$headers = ['Content-Type' => 'application/zip'];
return Storage::disk('assets')
    ->download($asset->filename, null, $headers);

However, I'd like to capture this in a test. But I'm not able to reproduce it via tests. Not sure why it behaves differently when I use the tests. Is it the fake Storage that's doing less than the real one? Is this something I'd have to turn into an integration test without fake Storage?

0 likes
2 replies
LaryAI's avatar
Level 58

It seems like you've encountered a discrepancy between how the fake storage and the actual S3 storage behave when it comes to retrieving the mime type of files. The fake storage likely doesn't simulate the mime type retrieval process as the real S3 storage does, which is why you're not seeing the error in your tests.

To capture this in a test, you can simulate the behavior of the S3 storage by manually throwing the exception when the mime type is attempted to be retrieved. However, since the Storage::fake() does not provide a direct way to simulate this behavior, you would need to use a mock or spy to achieve this.

Here's an example of how you might write a test to simulate the error and ensure that your code handles it correctly:

public function test_download_with_custom_mime_type_handling()
{
    Storage::fake('assets');

    // Manually create a file with a custom extension
    $filename = 'example.myext';
    Storage::disk('assets')->put($filename, 'Contents of your zip file');

    // Mock the mime type retrieval to throw an exception
    $storage = Storage::disk('assets');
    $filesystemAdapter = $storage->getDriver()->getAdapter();

    $reflection = new \ReflectionClass($filesystemAdapter);
    $reflectionProperty = $reflection->getProperty('mimeTypeDetector');
    $reflectionProperty->setAccessible(true);
    $mimeTypeDetector = $reflectionProperty->getValue($filesystemAdapter);

    $mimeTypeDetectorMock = \Mockery::mock($mimeTypeDetector);
    $mimeTypeDetectorMock->shouldReceive('detectMimeType')
        ->andThrow(new \League\Flysystem\UnableToRetrieveMetadata(
            'Unable to retrieve the mime_type for file at location: ' . $filename
        ));

    $reflectionProperty->setValue($filesystemAdapter, $mimeTypeDetectorMock);

    // Perform the download
    $response = $this->get('download/latest');

    // Assert the response headers contain the correct Content-Type
    $response->assertStatus(200)
             ->assertHeader('Content-Type', 'application/zip');
}

In this test, we're using Mockery to mock the mime type detector and force it to throw the UnableToRetrieveMetadata exception. Then, we assert that the response still has a status of 200 and contains the correct Content-Type header.

Please note that this test is quite advanced and uses reflection to manipulate the internals of the storage adapter, which is not typically recommended. However, it serves to demonstrate how you might test this specific scenario.

If you prefer not to use such an advanced technique, you could consider writing an integration test that interacts with the actual S3 storage. This would involve setting up test credentials and ensuring that your CI/CD pipeline can securely access the S3 bucket for testing purposes. Integration tests can be slower and more complex to set up, but they provide a more accurate representation of how your application will behave in production.

thomthom's avatar
thomthom
OP
Best Answer
Level 7

Turn out - that I'd corrupted the file on my local dev environment. Not the ones I used for tests, but when I was running the app in local dev. The file had been truncated to zero size and thus it wasn't possible to infer a mime type for it. 🤦‍♂️

Once I corrected that it all worked fine.

Please or to participate in this conversation.