How to test a Symfony BinaryFileResponse with php-unit - php

I have written an action which create a temporary file and returned it with a BinaryFileResponse and delete it after.
Something like this :
$response = new BinaryFileResponse($this->getFile($filename) );
$response->deleteFileAfterSend(true);
$response->headers->set('Content-Type', $this->getFileMimeType($filename));
return $response;
Where $filename refers to a temporary file.
It's working great, my file is sent and deleted after. But I cannot test it.
Here the summary of my test :
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/post');
$response = $this->client->getResponse();
if($response instanceof \Symfony\Component\HttpFoundation\BinaryFileResponse )
{
$fullpath = '/some/path/filename';
$this->assertFileEquals($fullpath, $response->getFile()->getPathname());
}
}
But during the time of the test the file has already been deleted...
jerome#api $ phpunit -c .
PHPUnit 5.7.27 by Sebastian Bergmann and contributors.
F
Time: 3.08 seconds, Memory: 30.25MB
There was 1 failure:
1) Tests\CoreBundle\Controller\ExperienceControllerTest::testAPICall
Failed asserting that file "/Users/jerome/Developpement/api/app/../var/cache/experience_file_15b5ae9bc7f668" exists.
I have found bug request on the symfony github but no solution yet.
Any idea of how I can achieve this ?
My ideas so far :
1 Remove deleteFileAfterSenddepending on the environment, but I found this solution quit ugly.
2 Stop using WebTestCase and start using cURL but I don't want to lose code coverage and it seems to be a lot of work.

I finally found a solution, I just send the file without using BinaryFileResponse.
$headers = array(
'Content-Type' => 'image/png',
'Content-Disposition' => 'inline; filename="image.png"');
return new Response(file_get_contents('myFile.png'), 200, $headers);
To delete the file after sending, just save the result of file_get_contents in a variable and delete the file, then send the response.
For testing, it's really easy :
$fileContent = file_get_contents($filepath);
$this->assertEquals($response->getContent(),$fileContent,"File content doesn't match requirement");

Related

How to write the basic test message on PHPUnit

I am a quite newbee for phpunit, so it might be stupid though ....
I google around but not found.
This is my code and I have multiple API and URL to test.
namespace Acme\TopBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class DefaultControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
echo ("first test");
$crawler = $client->request('GET', '/api/getplaceinfo');
$this->assertTrue($client->getResponse()->isSuccessful());
echo ("second test");
echo('test :' + '/api/getmetainfo/kibichuo');
$crawler = $client->request('GET', '/api/getcat');
$this->assertTrue($client->getResponse()->isSuccessful());
echo ("third test");
$crawler = $client->request('GET', '/admin/dashboard');
$this->assertTrue($crawler->filter('html:contains("My Server")')->count() > 0);
}
}
then I test like this (I am using symfony2 framework)
whitebear$ phpunit -c app/
PHPUnit 4.8.35 by Sebastian Bergmann and contributors.
.0
Time: 3.69 seconds, Memory: 109.25MB
OK (1 test, 7 assertions)
There is no message I expected by echo("first test").
So, even error happens, I can't tell which url shows the error.
My basic idea is wrong??
You should write one test for each test and in assertTrue you can put a message there.
Example:
public function testThirdTest() {
$client = static::createClient();
$crawler = $client->request('GET', '/admin/dashboard');
$this->assertTrue($crawler->filter('html:contains("My Server")')->count() > 0, 'third test goes wrong, put message here');
}
In your test you can now see the test what goes wrong (the message in assertTrue) and see, what test is failed (the name of the test).
Hope, this helps....

Mock Slim endpoint POST requests with PHPUnit

I want to test the endpoints of my Slim application with PHPUnit. I'm struggling to mock POST requests, as the request body is always empty.
I've tried the approach as described here: Slim Framework endpoint unit testing. (adding the environment variable slim-input)
I've tried writing to php://input directly, but I've found out php://input is read only (the hard way)
The emulation of the environment works correctly as for example the REQUEST_URI is always as expected. I've found out that the body of the request is read out in Slim\Http\RequestBody from php://input.
Notes:
I want to avoid calling the controller methods directly, so I can test everything, including endpoints.
I want to avoid guzzle because it sends an actual request. I do not want to have a server running while testing the application.
my test code so far:
//inherits from Slim/App
$this->app = new SyncApiApp();
// write json to //temp, does not work
$tmp_handle = fopen('php://temp', 'w+');
fwrite($tmp_handle, $json);
rewind($tmp_handle);
fclose($tmp_handle);
//override environment
$this->app->container["environment"] =
Environment::mock(
[
'REQUEST_METHOD' => 'POST',
'REQUEST_URI' => '/1.0/' . $relativeLink,
'slim.input' => $json,
'SERVER_NAME' => 'localhost',
'CONTENT_TYPE' => 'application/json;charset=utf8'
]
);
//run the application
$response = $this->app->run();
//result: the correct endpoint is reached, but $request->getBody() is empty
Whole project (be aware that I've simplified the code on stackoverflow):
https://github.com/famoser/SyncApi/blob/master/Famoser.SyncApi.Webpage/tests/Famoser/SyncApi/Tests/
Note 2:
I've asked at the slimframework forum, link:
http://discourse.slimframework.com/t/mock-slim-endpoint-post-requests-with-phpunit/973. I'll keep both stackoverflow and discourse.slimframework up to date what is happening.
Note 3:
There is a currently open pull request of mine for this feature: https://github.com/slimphp/Slim/pull/2086
There was help over at http://discourse.slimframework.com/t/mock-slim-endpoint-post-requests-with-phpunit/973/7, the solution was to create the Request from scratch, and write to the request body.
//setup environment vals to create request
$env = Environment::mock();
$uri = Uri::createFromString('/1.0/' . $relativeLink);
$headers = Headers::createFromEnvironment($env);
$cookies = [];
$serverParams = $env->all();
$body = new RequestBody();
$uploadedFiles = UploadedFile::createFromEnvironment($env);
$request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles);
//write request data
$request->write(json_encode([ 'key' => 'val' ]));
$request->getBody()->rewind();
//set method & content type
$request = $request->withHeader('Content-Type', 'application/json');
$request = $request->withMethod('POST');
//execute request
$app = new App();
$resOut = $app($request, new Response());
$resOut->getBody()->rewind();
$this->assertEquals('full response text', $resOut->getBody()->getContents());
The original blog post which helped to answer was at http://glenneggleton.com/page/slim-unit-testing

Using value from previous test case in PHPUnit

I am trying to assign a value to a variable inside the first testing function and then use it in other testing functions inside the class.
right now in my code the second function fails due to this error:
1) ApiAdTest::testApiAd_postedAdCreated
GuzzleHttp\Exception\ClientException: Client error: 404
and i dont know why. this is how the code looks like:
class ApiAdTest extends PHPUnit_Framework_TestCase
{
protected $adId;
private static $base_url = 'http://10.0.0.38/adserver/src/public/';
private static $path = 'api/ad/';
//start of expected flow
public function testApiAd_postAd()
{
$client = new Client(['base_uri' => self::$base_url]);
$response = $client->post(self::$path, ['form_params' => [
'name' => 'bellow content - guzzle testing'
]]);
$data = json_decode($response->getBody());
$this->adId = $data->id;
$code = $response->getStatusCode();
$this->assertEquals($code, 200);
}
public function testApiAd_postedAdCreated()
{
$client = new Client(['base_uri' => self::$base_url]);
$response = $client->get(self::$path.$this->adId);
$code = $response->getStatusCode();
$data = json_decode($response->getBody());
$this->assertEquals($code, 200);
$this->assertEquals($data->id, $this->adId);
$this->assertEquals($data->name, 'bellow content - guzzle testing');
}
in the phpunit doumintation https://phpunit.de/manual/current/en/fixtures.html i see i can define a
a variable inside the setUp method and then use it as i want but in my case i only know the value after the first post executes. any idea how can i use $this->adId in the second function??
Unit tests by definition should not rely on one another. You will end up with unstable and fragile tests which are then hard to debug the moment they start failing, since the cause is in another test case.
There is no guarantee in which order the tests execute in PHPUnit by default.
PHPUnit supports the #depends annotation to achieve what you want, the docs have the same warning though.

How to download a file with RESTFUL cakePHP 2.3

I am using CakePHP 2.3 and I have two apps on 2 different servers. I am required to download a file from the first server using REST. I have made my applications RestFul and have configured the routes. I am able to post, get, put and delete but I cannot get it done to download the file.
Below is a sample code for a get
public function view($id) {
$object = $this->Imodel->find('first', array('conditions' => array('Imodel.id' => $id), 'contain' => array()));
$this->set(array(
'object' => $object,
'_serialize' => array('object')
));
}
I would appreciate any help to download a file with REST, complying with the Restful architecture that I already have in place.
Edit
After some time, I finally got it to work. In case someone else runs into the same problem, the whole thing was about understanding cakePHP HttpSocket better.
So first on the server where the webservice is registered (where we download the file from), below is my function; its response is a file as explained (here)
public function getpdffile($id = NULL){
$filepath = APP. 'Files/file.pdf'; //path to the file of interest
$this->response->file($filepath);
return $this->response;
}
Since the file was not public (not in webroot), i had to use MediaView. Then after setting this, I'd retrieve it for download using HttpSocket as shown below:
public function download($id = NULL, $fileMine = 'pdf', $fileName = 'file', $download = TRUE){
$httpSocket = new HttpSocket();
$filepath = APP. 'Files/myfile.pdf';
$file = fopen($filepath, 'w');
$httpSocket->setContentResource($file);
$link = MAIN_SERVER."rest_models/getpdffile/".$id.".json";
$httpSocket->get($link);
fclose($file);
$this->response->file($filepath);
return $this->response;
}
What I did there was copy the file to the App folder of my server and render it in a view.
I hope it helps someone :-)
On the server which call the file to download :
$file = file_get_contents(urlwhereyoudownload) ;
And on the server where webservice is register :
header('Content-type: $mimetypeoffile');
header('Content-Disposition: attachment; filename=".$fileName."');
readfile("$pathtofile");exit;

Copy remote file using Guzzle

I'm trying to copy a remote file (image PNG, GIF, JPG ...) to my server. I use Guzzle since I sometimes get 404 with copy() even if the file exists and I also need to do a basic auth. This script is within a long script launched in command triggered by a cron job.
I'm pretty new to Guzzle and I successfully copy the image but my files have wrong mime type. I must be doing something wrong here. Please suggest me a good way to do this (including checking success/failure of copy and mime type check). If file has no mime type I would pop an error with details informations.
Here is the code:
$remoteFilePath = 'http://example.com/path/to/file.jpg';
$localFilePath = '/home/www/path/to/file.jpg';
try {
$client = new Guzzle\Http\Client();
$response = $client->send($client->get($remoteFilePath)->setAuth('login', 'password'));
if ($response->getBody()->isReadable()) {
if ($response->getStatusCode()==200) {
// is this the proper way to retrieve mime type?
//$mime = array_shift(array_values($response->getHeaders()->get('Content-Type')));
file_put_contents ($localFilePath , $response->getBody()->getStream());
return true;
}
}
} catch (Exception $e) {
return $e->getMessage();
}
When I do this my mime type is set to application/x-empty
Also it looks like when status is different from 200 Guzzle will automatically throw an exception. How can I stop this behaviour and check status myself so I can custom error message?
EDIT: This was for Guzzle 3.X
Now this is how you can do it using Guzzle v 4.X (works as well with Guzzle 6)
$client = new \GuzzleHttp\Client();
$client->get(
'http://path.to/remote.file',
[
'headers' => ['key'=>'value'],
'query' => ['param'=>'value'],
'auth' => ['username', 'password'],
'save_to' => '/path/to/local.file',
]);
Or using Guzzle stream:
use GuzzleHttp\Stream;
$original = Stream\create(fopen('https://path.to/remote.file', 'r'));
$local = Stream\create(fopen('/path/to/local.file', 'w'));
$local->write($original->getContents());
This looks great. Is there better/proper solution when using Guzzle 4?
Your code can be simplified a great deal. My example code below will stream the body of the response directly to the filesystem.
<?php
function copyRemote($fromUrl, $toFile) {
try {
$client = new Guzzle\Http\Client();
$response = $client->get($fromUrl)
->setAuth('login', 'password') // in case your resource is under protection
->setResponseBody($toFile)
->send();
return true;
} catch (Exception $e) {
// Log the error or something
return false;
}
}
When I do this my mime type is set to application/x-empty
A filesystem mimetype?
Also it looks like when status is different from 200 Guzzle will automatically throw an exception. How can I stop this behaviour and check status myself so I can custom error message?
Guzzle will throw an exception for bad responses like 4xx and 5xx. No need to disable this. Just catch an exception and deal with the error there.
Look at this with post:
$myFile = fopen('path/to/file', 'w') or die('Problems');
$client = new \Guzzle\Service\Client();
$request = $client->post('https://www.yourdocumentpage.com', array(), ['pagePostField' => 'data'], ['save_to' => $myFile]);
$client->send($request);
fclose($myFile);
here you must send the request of your "post"
and with get
$myFile = fopen('path/to/file', 'w') or die('Problems');
$client = new \GuzzleHttp\Client();
$request = $client->get('https://www.yourdocumentpage.com', ['save_to' => $myFile]);
and here you don't need to send the request,
and here you'll find a lot of documentation, you must have guzzle 6 for doing that, and if you are using GOUTTE at the same time you'll need goutte 3.1, update your require in your composer.json
using Guzzle 6 just use SINK option. see below detailed function
Extra:
use GuzzleHttp\Client; Guzzle namespace included
$access_token = if you need auth else simply remove this option
ReportFileDownloadException = custom exception
/**
* download report file and read data to database
* #param remote url
* #return N/A
* #throws ReportFileDownloadException
*/
protected function getReportFile($report_file_url)
{
$file = $this->tempDirectory . "/" . basename($report_file_url);
$fileHandle = fopen($file, "w+");
try {
$client = new Client();
$response = $client->get($report_file_url, [
RequestOptions::SINK => $fileHandle,
RequestOptions::HEADERS => [
"Authorization" => "Bearer $access_token"
]
]);
} catch (RequestException $e) {
throw new ReportFileDownloadException(
"Can't download report file $report_file_url"
);
} finally {
#fclose($fileHandle);
}
throw new ReportFileDownloadException(
"Can't download report file $report_file_url"
);
}

Categories