How can I test a class that uses the Storage Facade? - php

In a Laravel 5 package I am making there is a class FileSelector that uses the Storage-facade in a certain method.
public function filterFilesOnDate($files, DateTime $date)
{
return array_filter($files, function($file) use($date){
return Storage::lastModified($file) < $date->getTimeStamp();
});
}
This class takes a path (to some files) and a Storage::disk()in it's constructor.
Now I am trying to write some basic unit tests for this specific class using the Orchestra Testbench.
The setUp-function looks like this:
protected $fileSelector;
protected $date;
public function setUp()
{
parent::setUp();
$this->date = new DateTime();
$this->fileSelector = new fileSelector('tests/_data/backups', Storage::disk('local'));
}
The failing test is:
public function test_if_files_are_filtered_on_date()
{
$files = Storage::allFiles('tests/_data/backups');
$filteredFiles = $this->fileSelector->filterFilesOnDate($files, $this->date);
}
Storage::allFiles('tests/_data/backups') returns no files at all.
The path is correct because using the File-facade returns the needed files but this isn't compatible with the filterFilesOnDate()-method because it uses Storage.
Using the File-facade generates the following error:
League\Flysystem\FileNotFoundException: File not found at tests/_data/backups/ElvisPresley.zip
Am I using the Storage-methods wrong in the test or have I stumbled on a limitation of Orchestra/Testbench?

Ok, turns out I didn't completely understand how Storageand disks worked.
Using things like Storage::lastModified() calls the default Filesystem specified in the filesystem-config.
Since this is a test there is no config.
What Storage::disk() does, is create an instance of FilesystemAdapter using a Filesystem-object So a Storage object needs to be 'recreated'.
So:
$this->fileSelector = new FileSelector('tests/_data/backups', Storage::disk('local'));
Becomes:
$this->disk = new Illuminate\Filesystem\FilesystemAdapter(
new Filesystem(new Local($this->root))
);
$this->fileSelector = new FileSelector($this->disk, $this->path);
($this->pathis the path the where the files I use for testing are stored)
It was also pointed out to me that I should set the lastModified-timestamps manually everytime the test is run to avoid differing test results.
foreach (scandir($this->testFilesPath) as $file)
{
touch($this->testFilesPath . '/' . $file, time() - (60 * 60 * 24 * 5));
}
Using touch you can create files or set timestamps of files. In this case, they are set to 5 days.

Related

Test laravel Cache failed

I am trying to test via phpunit laravel cache (file driver). My test body:
namespace Tests\Feature;
use Illuminate\Support\Facades\Cache;
use Tests\TestCase;
use Closure;
class CacheTestTest extends TestCase
{
public function testCache()
{
Cache::shouldReceive('remember')
->once()
->with(md5(1), 120, Closure::class)
->andReturn('Closure');
}
}
When I am running test, I get error:
Method remember('c4ca4238a0b923820dcc509a6f75849b', 120, 'Closure')
from Mockery_0_Illuminate_Cache_CacheManager should be called exactly
1 times but called 0 times.
What am I doing wrong? Where to get docs about shouldReceive and what I must put to the andReturn ?
Thanks for replies!
EDIT:
The code creates Carbon object and then check if it exist.
public function testCache()
{
$key = md5(1);
$duration = 120;
$object = Cache::remember( $key, $duration, function () {
return Carbon::today();
});
Cache::shouldReceive('get')
->with($key, $duration, \Closure::class)
->andReturn($object);
}
So, I can give some tips based on the test you added to your question:
You are trying to test if Cache works
You want to check if calling Cache with the right parameters works
So:
Your test makes no sense at all, because you are testing if Cache works, and it does! It is shipped by the framework itself, so of course it is working. If you want to test a specific caching part of your code, that is fine, but just calling Cache and then checking if it cached, that test adds no value at all
Instead of having Cache::shouldReceive(...)->... after the Cache::remember (the part you want to test), you have to move that BEFORE you call it, because you are literally caching the value on your test environment, but then you are saying "if something calls Cache->get with $key, $duration and \Closure::class, then return $object.
To have 2. fixed, you have to use this code (instead of using a Mock, use a Spy, this is what Laravel says about Spies):
use WithFaker;
public function testCache()
{
$key = $this->faker->uuid();
$duration = $this->faker->numberBetween(1, 120);
$spy = Cache::spy();
Cache::remember(
$key,
$duration,
function () {
return today();
}
);
$spy->shouldHaveReceived('remember')
->once()
->with($key, $duration, \Mockery::type('callable'));
}
You can read more about the difference between a Mock and a Spy, but the long story short is that a Mock will mock what it will return when a certain method of a certain class with more conditions (or not) is met what to return or do, but a Spy will spy (assert) if those conditions were met AFTER you run your code.
The code creates Carbon object and then check if it exist.
public function testCache()
{
$key = md5(1);
$duration = 120;
$object = Cache::remember( $key, $duration, function () {
return Carbon::today();
});
Cache::shouldReceive('get')
->with($key, $duration, \Closure::class)
->andReturn($object);
}

Using the mikehaertl\php-pdftk library for manipulating PDFs, chaining commands fails when getDataFields is called first

I'm attempting to create a wrapper class around the mikehaertl\php-pdftk\pdf object for the purposes of populating PDF form fields. When trying to chain commands via the documentation the pdf fails to correctly execute the second command (or any after the first). It looks as though this is an issue with the underlying temp file handling and the tmep file not being written out as I watch my temp folder. As I debug, the temp file is there, but of 0 size.
Sample code demonstrating the issue
use mikehaertl\pdftk\Pdf;
class PDFTKTest extends TestCase
{
public function testPdfTkOperations()
{
$cmdPath = 'D:\PDFtk\bin\pdftk.exe';
$formPath = 'D:\test\sample_files\test.pdf';
$options = ['command' => $cmdPath];
$pdf = new Pdf($formPath, $options);
$this->assertNotNull($pdf);
//Get fields from PDF
$fields = $pdf->getDataFields();
$this->assertNotNull($fields);
//Set some field Values
$values = ['full_name' => 'John Q. Programmer'];
$pdf2 = new Pdf($pdf, $options); //chaining broken
//$pdf2 = new Pdf($formPath, $options); //works fine creating a new Pdf object
$this->assertNotNull($pdf2);
$res = $pdf2->fillForm($values)->execute();
//Next assertion fails using chaining
$this->assertTrue($res, "Execute failed: \n". $pdf2->getError());
//Get fields with the updates
$fields = $pdf2->getDataFields();
$this->assertNotNull($fields);
//Next assertion fails, getDataFields fails on a chained command
$this->assertGreaterThan(0, count($fields));
}
}
I've got a work around where I use separate \Pdf objects for each action and manage my own temp file, I was just hoping to take advantage of the classes functionality a bit more and not have to do so much of the mundane. Is this functionality broken, or am I using it incorrectly?
After looking deeper in to the PDFTK library which mikehaertl\php-pdftk\pdf wraps and reading the documentation on the dump_data_fields option I came up with the folowing observations:
PDFTK doesn't produce an output file for the dump_data_fields command
The php-pdftk class does create the underlying temp file when calling getDataFields, but it is empty and remains that way.
When chaining another Pdf object, it references the empty temp file from the previous command. Here lies the rub.
Solution
When I call getFieldData I create a new Pdf object and chain it to the previous, however I don't save a reference to that. I only save the newly chained object if it is form a command that creates actual output.
Here's an exmaple to demonstate:
<?php
use mikehaertl\pdftk\Pdf;
class PDFTKFormService
{
protected $pdf = null;
/**
* #return array|bool|\mikehaertl\pdftk\DataFields
*/
public function getDataFields()
{
//get data fields doesn't output a new file
//so we need to use the existing instance or create a new one and
$pdf = $this->getNextPdf();
$fields = $pdf->getDataFields();
if ($fields === false)
return [];
return $fields;
}
/**
* #param array $data
*
* #return resource The stream resource
*/
public function setDataFieldValues($data = [])
{
$this->pdf = $this->getNextPdf();
$this->pdf->fillForm($data)->execute();
}
protected function getNextPdf()
{
$options = ['command' => 'Path\To\PDFTK\binary'];
if ($this->pdf === null) {
return new Pdf($this->getTemplatePath(), $options);
} else {
return new Pdf($this->pdf, $options);
}
}
}
Hopefully this can help someone else.

Track folder using php and inotify

I need help to understand how i can make inotify work with PHP.
I have a main file where i call a instance of a inotify class i created.
This works for 30 seconds and then php throws a timeout error. In that time window it can in fact print information from new files and deleted ones. It kinda works but...
My questions for you guys are:
how can i make it persistent and stable. I mean i can set timeout requests for unlimited time but that doesn't seem to be a good practice. How to deal with this?
It's supposed to work like this? I call the function and the php
hangs in that loop until a new change happens?
My index.php
$teste = new Inotify_service();
$teste->add_watch('files');
class Inotify_service
{
private $instance;
private $watch_id;
public function __construct()
{
$this->instance = inotify_init();
stream_set_blocking($this->instance, 0); # this is needed so inotify_read while operate in non blocking mode
}
/**
* [add_watch Adds a new watch or modify an existing watch for the file or directory specified in pathname]
* #param [string] $pathname [description]
*/
public function add_watch($pathname)
{
$this->watch_id = inotify_add_watch($this->instance, $pathname, IN_CREATE | IN_DELETE);
while(true){
// read events
$events = inotify_read($this->instance);
// if the event is happening within our 'Files directory'
if ($events[0]['wd'] === $this->watch_id){
// a file was created
if($events[0]['mask'] === IN_CREATE){
printf("Created file: %s in Files directory\n", $events[0]['name']);
// a file was deleted
} else if ($events[0]['mask'] === IN_DELETE){
printf("Deleted file: %s in Files directory\n", $events[0]['name']);
}
}
}
// stop watching our directories
inotify_rm_watch($this->instance, $this->watch_id);
// close our inotify instance
fclose($this->instance);
}

Sonata Media Bundle - How to extend FormatThumbnail.php

The Sonata Media Bundle you have the thumbnail property on a provider in the config where you can specify either
sonata.media.thumbnail.format
sonata.media.thumbnail.liip_imagine
This all fine and the sonata.media.thumbnail.format one works fine for everything I want to achieve. My problem comes in with what happens within these files.
In the FormatThumbnail.php there is a function called generatePublicUrl where they generate the url of the media file and also the name of the formatted file. They use the media id within the name or url. If you have private files not everyone must be able to see this causes a problem with it is easy to manipulate the id to another id.
I know the public files that will be served will always stay public so if the url can be guessed the user will access the file. For this specific reason I want to try and replace that id with the unique reference that the bundle uses before they create the actual formatted files as this will not be as easy to just change.
I am aware that there are still risks of leaking out data.
I want to change this
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getId(), $format, $this->getExtension($media));
}
return $path;
}
to this
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getProviderReference(), $format, $this->getExtension($media));
}
return $path;
}
How do I override the file that the bundle just picks up the change?
I have followed the steps on Sonata's site on how to install and set up the bundle using the easy extends bundle. I have my own Application\Sonata\MediaBundle folder that is extending the original Sonata\MediaBundle.
For installation related information have a look through the documentation(https://sonata-project.org/bundles/media/master/doc/reference/installation.html)
However I tried to create my own Thumbnail folder and creating a new FormatThumbnail.php as follows
<?php
namespace Application\Sonata\MediaBundle\Thumbnail;
use Sonata\MediaBundle\Model\MediaInterface;
use Sonata\MediaBundle\Provider\MediaProviderInterface;
use Sonata\MediaBundle\Thumbnail\FormatThumbnail as BaseFormatThumbnail;
class FormatThumbnail extends BaseFormatThumbnail
{
/**
* Overriding this to replace the id with the reference
*
* {#inheritdoc}
*/
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getProviderReference(), $format, $this->getExtension($media));
}
return $path;
}
}
But the bundle still generates everything using the id instead of the reference. Is there a more specific way to achieve extending this file and overriding the function?
After looking at a few different bundles and after looking in code I found that they physically have a parameter which is set to use Sonata\MediaBundle\Thumbnail\FormatThumbnail.
The solution is to override the parameter in the config aswell.
#As top level classification in app/config/config.yml
parameters:
sonata.media.thumbnail.format: Application\Sonata\MediaBundle\Thumbnail\FormatThumbnail
This way the custom FormatThumbnail class is injected everywhere it will be used within the bundle.

ZF2 - Job queue to create a PDF file using SlmQueueBeanstalkd and DOMPDFModule

I'm trying to run a job queue to create a PDF file using SlmQueueBeanstalkd and DOMPDFModule in ZF".
Here's what I'm doing in my controller:
public function reporteAction()
{
$job = new TareaReporte();
$queueManager = $this->serviceLocator->get('SlmQueue\Queue\QueuePluginManager');
$queue = $queueManager->get('myQueue');
$queue->push($job);
...
}
This is the job:
namespace Application\Job;
use SlmQueue\Job\AbstractJob;
use SlmQueue\Queue\QueueAwareInterface;
use SlmQueue\Queue\QueueInterface;
use DOMPDFModule\View\Model\PdfModel;
class TareaReporte extends AbstractJob implements QueueAwareInterface
{
protected $queue;
public function getQueue()
{
return $this->queue;
}
public function setQueue(QueueInterface $queue)
{
$this->queue = $queue;
}
public function execute()
{
$sm = $this->getQueue()->getJobPluginManager()->getServiceLocator();
$empresaTable = $sm->get('Application\Model\EmpresaTable');
$registros = $empresaTable->listadoCompleto();
$model = new PdfModel(array('registros' => $registros));
$model->setOption('paperSize', 'letter');
$model->setOption('paperOrientation', 'portrait');
$model->setTemplate('empresa/reporte-pdf');
$output = $sm->get('viewPdfrenderer')->render($model);
$filename = "/path/to/pdf/file.pdf";
file_put_contents($filename, $output);
}
}
The first time you run it, the file is created and the work is successful, however, if you run a second time, the task is buried and the file is not created.
It seems that stays in an endless cycle when trying to render the model a second time.
I've had a similar issue and it turned out it was because of the way ZendPdf\PdfDocument reuses it's object factory. Are you using ZendPdf\PdfDocument?
You might need to correctly close factory.
class MyDocument extends PdfDocument
{
public function __destruct()
{
$this->_objFactory->close();
}
}
Try to add this or something similar to the PdfDocument class...
update : it seem you are not using PdfDocument, however I suspect this is the issue is the same. Are you able to regenerate a second PDF in a normal http request? It is your job to make sure the environment is equal on each run.
If you are unable to overcome this problem a short-term quick solution would be to set max_runs configuration for SlmQueue to 1. That way the worker is stopped after each job and this reset to a vanilla state...

Categories