I'm testing a method that uses a public key to encrypt a social security number before it is saved to a database. It looks like this:
public function setSsnAttribute($value)
{
// Load the public key
$public = file_get_contents(Config::get('certificates.public'));
// Attempt to encrypt the social security number using the public key
if (!openssl_public_encrypt($value, $crypted, $public))
{
throw new Exception('Could not encrypt data. Nothing was stored.');
}
// The value of $crypted returned by openssl_public_encrypt contains
// binary characters. Rather than storing the data in a BLOB, I'm
// electing to use base64 encoding to convert it to a string that is
// suitable for storage in the database.
$crypted = base64_encode($crypted);
$this->attributes['ssn'] = $crypted;
}
The issue is with the Config::get('certificates.public') call. I want to make sure that the appropriate exception is thrown if the encryption step fails. The value from Config::get('certificates.public') returns the path to the public certificate that is defined in a config file. My thinking is that the simplest way to test the exception would be to provide a bad path for the public certificate.
I could define an additional parameter in my config file. I'm thinking something along the lines of certificates.test.public.bad would return /dev/null or something like that.
What is the best practice for specifying alternate config parameters during unit testing? Loading the path to the certificate within the setSsnAttribute method seems suspect to me. Is there a more testing-friendly way to load config parameters?
After going back to the Laravel documentation, I realized that I can override any config parameters I need to by simply calling Config::set() on the parameter from within the unit test. For example:
/**
* #expectedException Exception
*/
public function testSsnDecryptionFailureThrowsException()
{
// Replace the private certificate with
Config::set('certificates.private', '/dev/null');
$application = FactoryMuff::create('Lease317\RentalApplication');
// I must access the attribute in order to trigger the decryption
$application->ssn;
}
This works as expected and now I have 100% code coverage on the model.
Related
I want to add additional cloud driver to my lumen app like this:
Storage::extend('s3_v2', static function ($app, array $config) {
return (new FilesystemManager($app))->createS3Driver($config);
});
So, it work's. And it's a problem. When i use Storage::put()/makedir() etc. it works, even if I've another cloud driver by default. Code in closure isn't working (Log::info()
for e.x.), may be cause i use another S3 cloud driver but if i delete this fragment of code, i'll have this error:
Credentials must be an instance of
Aws\Credentials\CredentialsInterface, an associative array that
contains "key", "secret", and an optional
"token" key-value pairs, a credentials provider function, or
false. (500 Internal Server Error)
If i change driver to current default it will work and all additional logic in callback execute:
Storage::extend('minio', static function ($app, array $config) {
Log::error('test'); // Log successful output-ed
return (new FilesystemManager($app))->createS3Driver($config);
});
Its works... and not? It's like it isn't entering into the closure if i use another driver, but it's registering that driver...
So if i'm extending current driver its will register it and will execute callback (???)
So i'm very confused.
Just to be clear, i don't have other Storage::extend anywhere more in my app. And if:
Storage::extend('ASDASDASD', static function ($app, array $config) {
return (new FilesystemManager($app))->createS3Driver($config);
});
Its also allows me to properly works with my current cloud driver, but callback don't executes. I can verify this by opening minio console and seeing the added files there
I've found answer to my question.
Method "extend" of storage facade will perform regardless of given driver name and its callback.
It's just adding given callback to array property of FilesystemManager instance with key from first argument.
It's also performs register of all drivers from configuration, not only added in the first argument.
Hello i need to decrypt value of cookie.
My code to create and destroy:
public function setSession($id){
Cookie::queue('userId', $id, 10000);
}
public function destroySession(){
Cookie::queue(Cookie::forget('userId'));
}
But i need to get value of cookie without encrypt.
In web request context cookies are usually automatically encrypted and decrypted by the EncryptCookies middleware. So easiest option would be just to enable this middleware (and it's enabled by default in Laravel).
If you need to decrypt any value manually, the following will do the trick:
// get the encrypter service
$encrypter = app(\Illuminate\Contracts\Encryption\Encrypter::class);
// decrypt
$decryptedString = $encrypter->decrypt($encryptedString);
Check the code of the EncryptCookies middleware to learn more about what it does internally.
By default Crypt::decrypt tries to deserialize the value, and yours is not serialized and that's why you get the error. You need to pass a second argument like:
Crypt::decrypt(Cookie::get('userId'), false);
I have about 500 possible paths to a particular page, and I need to test all of them. Each path to the that page looks similar to this (using PHP web driver; usually has about 10 steps):
// Navigate to form
$driver->get('http://www.domain.com');
$driver->get($driver->findElement(WebDriverBy::xpath("//a[contains(text(),'Foo 1')]"))->getAttribute('href'));
$driver->findElement(WebDriverBy::xpath("//div[#class='countryHeader']//a[contains(text(), 'Bar 1')]"))->click();
$driver->findElement(WebDriverBy::xpath("//form[#name='formDisclaimer']//input[contains(#class, 'button')]"))->click();
I don't want to have to write code for all the steps for all possible paths to the page. I do, however, have all the pertinent details of the steps (e.g. the XPath, the string the node may contain, etc.) in a database.
Is there a way for me to "dynamically" produce some sort of configuration file (either in XML or JSON) that I can feed to the driver as a set of instructions for it to follow?
A long time back at one of my project I had a similar requirement. I tried to create a Robot (or someone may call Web Crawler). As I started navigating through the pages I started maintaining the navigation paths in spreadsheet, so I don't have to click on the paths manually. Once I have the paths, next time whenever a Path changes I will be notified and if it is a valid change then make that change in s/s or raise it as a bug.
As you said you have all relevant details in the database then you just can simply read it and in a foreach loop pass to selenium driver.
or if you don't want to have a reference to the database in your test, just dump data to PHP array and add to your test class.
You just need to write a logic to transform your sql data into test. Don't need to write every test manually.
I don't know which testing framework you are using, but you can execute many tests from a single test for example in PHPUnit that would be something like:
class My_PathsTest extends PHPUnit_Framework_TestCase
{
public function setUp() {
// setup $this->tests here
}
public function testAll() {
// $this->tests would contain info about paths taken from database.
$failures = array();
foreach($this->tests as $paths_set) {
try {
/**
* $driver->get($paths_set['start_point']);
* foreach ($paths_set['paths'] as $path ) {
* $driver->findElement(WebDriverBy::xpath($path));
* }
*
* Important!!!
* If you didn't find something you expected
* just throw the PHPUnit_Framework_ExpectationFailedException exception
* throw new PHPUnit_Framework_ExpectationFailedException('Element missing add some info here about which is missing etc..');
*/
}
catch(PHPUnit_Framework_ExpectationFailedException $e) {
$failures[] = $e->getMessage();
}
}
if (!empty($failures)) {
throw new PHPUnit_Framework_ExpectationFailedException(count($failures) . " assertions failed:\n\t" . implode("\n\t", $failures));
}
}
}
best is to get data from db with odbc as a list (array) xpath locators and then loop over it.
If you don't have a direct access to the db, export the query results as a .csv file (MS db has an option save as, not sure about the others) and then read the file and loop over the array
I'm using Symfony 2.3 to save a file uploaded by a form POST.
This is the code I use in the controller:
$fileDir = '/home2/divine/Symfony/src/App/Bundle/Resources/public/files';
$form['my_file']->getData()->move($fileDir, 'book.pdf');
Under water, Symfony executes this code to move the file:
move_uploaded_file("/tmp/phpBM9kw8", "/home2/divine/Symfony/src/App/Bundle/Resources/public/files/book.pdf");
The public directory has 777 permissions.
This is the error I get:
"Could not move the file "/tmp/phpBM9kw8" to "/home2/divine/Symfony/src/App/Bundle/Resources/public/files/book.pdf"
(move_uploaded_file() expects parameter 2 to be valid path, object given)"
I'm using PHP 5.3.
Update:
This is the code snipped that executes the move_uploaded_file():
// Class: Symfony\Component\HttpFoundation\File\UploadedFile
$target = $this->getTargetFile($directory, $name);
if (!#move_uploaded_file($this->getPathname(), $target)) {
// etc...
The $target" variable is created here:
protected function getTargetFile($directory, $name = null) {
// Some error handling here...
$target = $directory.DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
return new File($target, false);
}
The $target variable is therefor a File class. It does have a __toString() method, inherited from SplFileInfo:
/**
* Returns the path to the file as a string
* #link http://php.net/manual/en/splfileinfo.tostring.php
* #return string the path to the file.
* #since 5.1.2
*/
public function __toString () {}
But somehow that __toString method is not working.
But somehow that __toString method is not working
It is one of the “magic methods”, it gets called automatically when the object is used in a string context – so for example if you had 'foo' . $object.
But I don’t think it is supposed to work in this situation here. Because PHP is loosely typed, you can pass anything into move_uploaded_file. No automatic conversion to string will happen at this point. And then internally, the function only checks if the parameter is a string, but doesn’t try to convert it into one – because that would make little sense, it could be any kind of object, and there is no way of telling if calling __toString would result in a valid file path.
You might wonder now, why in the error message we do get to see the path:
Could not move the file "/tmp/phpBM9kw8" to "/home2/divine/Symfony/src/App/Bundle/Resources/public/files/book.pdf"
My guess is, that when that error message is assembled, there is string concatenation going on, so that __toString does get called at this specific point.
If you are willing to modify the Symfony source code, I think this should work as an easy fix, if you just change this line
if (!#move_uploaded_file($this->getPathname(), $target)) {
to
if (!#move_uploaded_file($this->getPathname(), ''.$target)) {
– then you have the situation again, where __toString will be called, because the object is transferred into a string context by concatenating it with a string (an empty one, because we don’t want to tamper with the resulting value.)
Of course modifying a framework’s files directly is not the most recommendable way of dealing with this – after the next update, our change might be lost again. I’d recommend that you check the Symfony bugtracker (they should have something like that) to see if this is a known issue already and if maybe an official patch file exists; and otherwise report it as a bug, so that it can be fixed in a future version.
I defined a test which tests the creation of a user. The controller is set to redirect back to the same page on error (using validation through a generated App\Http\Requests\Request). This works correctly when manually clicking in a browser, but fails during a test. Instead of being redirected to:
http://localhost/account/create
The test redirects to (missing a slash):
http://localhostaccount/create
Neither of these urls are what I have setup in the .htaccess or in the $url variable in config/app.php. Which is (On OSX Yosemite):
http://~username/laravel_projects/projectname/public
I finally pinpointed the issue to have something to do with how the result of Request::root() is generated. Making a call to this outside of a test results in the expected value defined in .htaccess and $url. Inside the test it results in:
http://localhost
What configuration needs to change in order to get this function to return the correct value in both contexts?
I should also mention I made the painful upgrade from Laravel 4 to the current version 5.0.27.
****** UPDATE *******
I was able to figure out an acceptable solution/workaround to this issue!
In Laravel 5, FormRequests were introduced to help move validation logic out of controllers. Once a request is mapped to the controller, if a FormRequest (or just Request) is specified, this is executed before hitting the controller action.
This FormRequest by default handles the response if the validation fails. It attempts to construct a redirect based on the route you posted the form data to. In my case, possibly related to an error of mine updating from Laravel 4 to 5, this default redirect was being constructed incorrectly. The Laravel System code for handling the response looks like this:
/**
* Get the proper failed validation response for the request.
*
* #param array $errors
* #return \Symfony\Component\HttpFoundation\Response
*/
public function response(array $errors)
{
if ($this->ajax() || $this->wantsJson())
{
return new JsonResponse($errors, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
}
Notice how the returned redirect is NOT the same as calling Redirect::route('some_route'). You can override this response function by including use Response in your Request class.
After using Redirect::route() to create the redirect, the logic in my tests passed with the expected results. Here is my Request code that worked:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Http\Requests\Request;
use Response;
class AccountRequest extends FormRequest {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'email' => 'required|max:50|email|unique:users',
'password' => 'required|min:6',
'password_confirmation' => 'required|same:password'
];
}
public function response(array $errors){
return \Redirect::route('account_create');
}
}
The important part is that I called Redirect::route instead of letting the default response code execute.
Override the response function in the FormRequest validation handler to force the redirect to be constructed with Redirect::route('named_route') instead of allowing the default redirect.
You need to change config/app.php file's url value. Default value is http://localhost
Doc from config/app.php
This URL is used by the console to properly generate URLs when using the Artisan command line tool. You should set this to the root of your application so that it is used when running Artisan tasks.
I know this isn't an exact answer to your question since it is not a configuration update that solves the problem. But I was struggling with a related problem and this seems to be the only post on the internet of someone dealing with something similar - I thought I'd put in my two cents for anyone that wants a different fix.
Please note that I'm using Laravel 4.2 at the moment, so this might have changed in Laravel 5 (although I doubt it).
You can specify the HTTP_HOST header when you're testing a controller using the function:
$response = $this->call($method, $uri, $parameters, $files, $server, $content);
To specify the header just provided the $server variable as an array like so:
array('HTTP_HOST' => 'testing.mydomain.com');
When I did the above, the value produced for my Request::root() was http://testing.mydomain.com.
Again, I know this isn't a configuration update to solve you're issue, but hopefully this can help someone struggling with a semi-related issue.
If you tried changine config/app.php and it did not help.
it is better to use $_ENV - global variable in phpunit.
say, you want Request::root() to return 'my.site'
but you cannot touch phpunit.xml
you can simply set an env param like so
$_ENV['APP_URL'] = 'my.site';
and call $this->refreshApplication(); in your unittest.
viola, your request()->root() is giving you my.site now.