Related
OK, I'm going nuts here. I've spent the better part of a week working on this with Fiddler, Rest API Log, Apache logs, Postman, etc. and am no closer to a solution. Hopefully this will provide someone with a good laugh (as it's easy) and me with a solution (for my sanity).
I have a Wordpress 4.9.8 install hosted on hostgator. I have the WP REST API - OAuth 1.0a Server plugin installed and am attempting to authenticate with OAuth from a Perl script. I've been successful connecting with Postman using the credential generated by registering an application in the UI (Wordpress > Users > Applications) but cannot seem to crack the code using curl or a perl script (or php or anything else I've tried). As Postman works (whenever it does not generate a space in the OAuth signature) I believe the plugin does, in fact, work. Below is my code where I've attempted to follow the spec regarding parameters, sorting, URI encoding, utf8 encoding, etc. Everything 'looks' ok when examining headers/query params in either Fiddle or the REST API Log plugin (pretty great little tool, that) and yet, no love.
Thanks for any advice or direction.
Client code:
#!/usr/bin/perl
use strict;
use warnings;
use LWP::UserAgent;
use JSON;
use URI::Escape;
use Digest::HMAC_SHA1 qw( hmac_sha1 );
use MIME::Base64 qw( encode_base64 );
use Encode;
use Data::Dumper;
my $timestamp = time();
my $method = 'GET';
my $url = "https://hoppingmadmonkey.com/wp-json/myapiplugin/v2/greeting";
my $client_secret = "GelrnAAr1nCe5fzmTqzrU82PsfKCmKlDOZrLPakQRkH4sizJ";
my $token_secret = "mdeVuJnrs8VSDJNJfMNPQRqBkG8xadfK0jAYjDGhmKOeaY7O";
#my $nonce = $ARGV[0]; # test with Postman generated parameters
#$timestamp = $ARGV[1]; # test with Postman generated parameters
#my $signature = $ARGV[2]; # test with Postman generated parameters
my %params = (
oauth_consumer_key => "NCo8bflKU9LI",
oauth_signature_method => "HMAC-SHA1",
oauth_realm => "https://hoppingmadmonkey.com",
oauth_timestamp => $timestamp,
oauth_token => "2GeFG7MkXliq2OBOSSCSRBPX",
oauth_version => "1.0",
);
$params{oauth_nonce} = create_nonce();
#$params{oauth_nonce} = $nonce; # test with Postman generated parameters
$params{oauth_timestamp} = $timestamp;
my $key = create_key($client_secret,$token_secret);
my ($params,$base) = build_base_string($method, $url, \%params);
my $signature = create_signature($base, $key);
#for (sort keys %params) { print "$_=$params{$_}", "\n"; }
print "params: $params\n\n";
print "basestring: $base\n\n";
print "key: $key\n\n";
print "signature [$signature]\n\n";
$params{oauth_signature} = $signature; # -- set signature for GET query string
my $request_string = build_request_string($url,\%params);
print "\nrequest_string [$request_string]\n\n";
my $ua = LWP::UserAgent->new();
$ua->default_header("Authorization", "Basic user:pass");
my $response = $ua->get($request_string);
print Dumper $response, "\n";
exit;
my $curlcmd = qq{/usr/bin/curl -i -X GET "$request_string"};
#`$curlcmd 2>&1`;
sub create_signature {
my ($t,$k) = #_;
my $str = encode_base64(hmac_sha1($t,$k));
chomp $str;
return $str;
}
# Create unique nonce
#
sub create_nonce {
my $str = `/bin/cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 11 | head -n 1`;
chomp $str;
return $str;
}
# Create oauth key for generating hmac-sha1 signature
#
sub create_key {
my ($cs,$ts) = #_;
$cs = encode('utf8',uri_escape($cs));
$ts = encode('utf8',uri_escape($ts));
return "$cs&$ts";
}
# Build basestring for generating hmac-sha1 signature
#
sub build_request_string {
my ($u,$p) = #_;
my %params = %$p;
my $str = $u . '?';
my #tmp;
for (sort keys %params) {
$p = uri_escape($_) . '=' . uri_escape($params{$_});
push #tmp, $p;
}
$str .= join '&', #tmp;
return $str;
}
# build the base string parameter for creating the signature
#
sub build_base_string {
my ($m,$u,$phash) = #_;
my %params = %$phash;
my $str = $method;
$str .= '&' . uri_escape($u) . '&';
my #tmp;
for (sort keys %params) {
push #tmp, uri_escape("$_=$params{$_}");
}
$params = join '&', #tmp;
$str .= join '&', #tmp;
return $params,$str;
}
1;
The result of running this from the command line is:
dnu [test] ::>./oauth-perl.pl
params: oauth_consumer_key%3DNCo8bflKU9LI&oauth_nonce%3DJrGdlVLXpB4&oauth_realm%3Dhttps%3A%2F%2Fhoppingmadmonkey.com&oauth_signature_method%3DHMAC-SHA1&oauth_timestamp%3D1542134239&oauth_token%3D2GeFG7MkXliq2OBOSSCSRBPX&oauth_version%3D1.0
basestring: GET&https%3A%2F%2Fhoppingmadmonkey.com%2Fwp-json%2Fmyapiplugin%2Fv2%2Fgreeting&oauth_consumer_key%3DNCo8bflKU9LI&oauth_nonce%3DJrGdlVLXpB4&oauth_realm%3Dhttps%3A%2F%2Fhoppingmadmonkey.com&oauth_signature_method%3DHMAC-SHA1&oauth_timestamp%3D1542134239&oauth_token%3D2GeFG7MkXliq2OBOSSCSRBPX&oauth_version%3D1.0
key: GelrnAAr1nCe5fzmTqzrU82PsfKCmKlDOZrLPakQRkH4sizJ&mdeVuJnrs8VSDJNJfMNPQRqBkG8xadfK0jAYjDGhmKOeaY7O
signature [2pJRR1fz0uUdUWHlUjHFWlXFbL4=]
request_string [https://hoppingmadmonkey.com/wp-json/myapiplugin/v2/greeting?oauth_consumer_key=NCo8bflKU9LI&oauth_nonce=JrGdlVLXpB4&oauth_realm=https%3A%2F%2Fhoppingmadmonkey.com&oauth_signature=2pJRR1fz0uUdUWHlUjHFWlXFbL4%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1542134239&oauth_token=2GeFG7MkXliq2OBOSSCSRBPX&oauth_version=1.0]
$VAR1 = bless( {
'_protocol' => 'HTTP/1.1',
'_content' => '
{"code":"json_oauth1_signature_mismatch","message":"OAuth signature does not match","data":{"status":401}}',
'_rc' => '401',
'_headers' => bless( {
'connection' => 'close',
'cache-control' => 'no-cache, must-revalidate, max-age=0',
'date' => 'Tue, 13 Nov 2018 18:37:19 GMT',
'client-ssl-cert-issuer' => '/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA',
'client-ssl-cipher' => 'ECDHE-RSA-AES256-GCM-SHA384',
'client-peer' => '192.185.236.193:443',
'access-control-expose-headers' => 'X-WP-Total, X-WP-TotalPages',
'x-robots-tag' => 'noindex',
'client-warning' => 'Missing Authenticate header',
'client-date' => 'Tue, 13 Nov 2018 18:37:22 GMT',
'client-ssl-warning' => 'Peer certificate not verified',
'content-type' => 'application/json; charset=UTF-8',
'client-transfer-encoding' => [
'chunked'
],
'server' => 'Apache',
'x-endurance-cache-level' => '2',
'client-ssl-socket-class' => 'IO::Socket::SSL',
'link' => '<https://hoppingmadmonkey.com/index.php/wp-json/>; rel="https://api.w.org/"',
'access-control-allow-headers' => 'Authorization, Content-Type',
'client-response-num' => 1,
'x-content-type-options' => 'nosniff',
'client-ssl-cert-subject' => '/OU=Domain Control Validated/OU=Hosted by HostGator.com, LLC./OU=PositiveSSL Wildcard/CN=*.hostgator.com',
'expires' => 'Wed, 11 Jan 1984 05:00:00 GMT'
}, 'HTTP::Headers' ),
'_msg' => 'Unauthorized',
'_request' => bless( {
'_content' => '',
'_uri' => bless( do{\(my $o = 'https://hoppingmadmonkey.com/wp-json/myapiplugin/v2/greeting?oauth_consumer_key=NCo8bflKU9LI&oauth_nonce=JrGdlVLXpB4&oauth_realm=https%3A%2F%2Fhoppingmadmonkey.com&oauth_signature=2pJRR1fz0uUdUWHlUjHFWlXFbL4%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1542134239&oauth_token=2GeFG7MkXliq2OBOSSCSRBPX&oauth_version=1.0')}, 'URI::https' ),
'_headers' => bless( {
'user-agent' => 'libwww-perl/5.833',
'authorization' => 'Basic user:pass'
}, 'HTTP::Headers' ),
'_method' => 'GET',
'_uri_canonical' => bless( do{\(my $o = 'https://hoppingmadmonkey.com/wp-json/myapiplugin/v2/greeting?oauth_consumer_key=NCo8bflKU9LI&oauth_nonce=JrGdlVLXpB4&oauth_realm=https%3A%2F%2Fhoppingmadmonkey.com&oauth_signature=2pJRR1fz0uUdUWHlUjHFWlXFbL4%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1542134239&oauth_token=2GeFG7MkXliq2OBOSSCSRBPX&oauth_version=1.0')}, 'URI::https' )
}, 'HTTP::Request' )
}, 'HTTP::Response' );
$VAR2 = '
';
In the interests of expedience, I've included my real secrets and website. It's all test at the moment. Feel free to try a solution if you have that kind of time on your hands. I can always invalidate the tokens if/when they get abused and will obviously change once/if I can understand the problem.
The obvious thought is that I am not generating the signature correctly (Big Hint:
{"code":"json_oauth1_signature_mismatch","message":"OAuth signature does not match","data":{"status":401}}',
)
I think I've ready everything on the entire Internet to no avail. Thanks in advance for any thoughts. And if you are near Atlanta, GA and help me out, the beer's on me.
Cheers.
Try using Net::OAuth
If you really need to fix your implementation then try removing all uri_escape and testing with data that doesn't require any escaping.
It looks like you have some unnecessary or double escaping. Start with build_base_string function.
I thinks that = sign should be unescaped.
I could not get things working with Net::OAuth so I tried writing my code again from scratch and explicitly following the guidelines at http://lti.tools/oauth/ until my code output matched theirs. Hallelujah! It's now working.
One thing I had to add, and Postman fails to do this as well, is to url encode the signature. Makes sense, of course, but odd that Postman misses it and sends an occasional unescaped '+'.
I'm currently writing a RESTful API in Cakephp 3 whereby I need to test a POST operation through http://host.com/api/pictures. The code for the test:
<?php
namespace App\Test\TestCase\Controller;
use App\Controller\Api\UsersController;
use Cake\TestSuite\IntegrationTestCase;
use Cake\Network\Http\Client;
use Cake\Network\Http\FormData;
class ApiPicturesControllerTest extends IntegrationTestCase{
public $fixtures = [
'app.users',
'app.comments',
'app.albums',
'app.users_albums'
];
public function testAdd(){
// $data = new FormData();
$accessToken ='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjksImV4cCI6MTQ1NzYyOTU3NH0.NnjXWEQCno3PUiwHhnUCBjiknR-NlmT42oPLA5KhuYo';
$http = new Client([
'headers' => ['Authorization' => 'Bearer ' . $accessToken, 'Content-Type' => 'application/json']
]);
$data = [
"album_id" => 1,
"link" => "http://www.google.com",
"description" => "testtesttest",
"favorite" => true
];
$result = $http->post('http://vecto.app/api/pictures/add.json', $data, ['type'=>'json']);
// $this->assertResponseOk();
// debug($result);
}
}
When I try to debug the result I get a 'cannot add or update child row' while I'm sure the responding id does exists
(the fixtures does have the id's too). Additionally, the log indicates that it only tries to insert the create/update rows. Therefore, I'm pretty sure the data is ignored but however I can't find a solution. I already tried different combination of headers like only application/json for Accept, application/json for Content-Type etc. I'm using the CRUD plugin for Cakephp to pass the data to an add function.
Postman output
Furthermore, I tried the Postman Chrome plugin to save the data and that actually does work. Does anyone know what I'm doing wrong in the test?
That's not how the integration test case is ment to be used. You are dispatching an external, real request, which will leave the test environment, while you should use the request dispatching tools that the integration test case supplies, that is
IntegrationTestCase::get()
IntegrationTestCase::post()
IntegrationTestCase::put()
etc...
These methods will dispatch simulated requests that do not leave the test environment, which is crucial for things to work properly, as you want to use test connections, inspect possible exceptions, have access to the used session, etc...
ie, you should do something along the lines of
$accessToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjksImV4cCI6MTQ1NzYyOTU3NH0.NnjXWEQCno3PUiwHhnUCBjiknR-NlmT42oPLA5KhuYo';
$this->configRequest([
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'Content-Type' => 'application/json'
]
]);
$data = [
"album_id" => 1,
"link" => "http://www.google.com",
"description" => "testtesttest",
"favorite" => true
];
$this->post('/api/pictures/add.json', json_encode($data));
Note that a content type of application/json will require you to send raw JSON data! If you don't actually need/want to test parsing of raw input, then you could skip that header, and pass the array as data instead.
See also
Cookbook > Testing > Controller Integration Testing
API > \Cake\TestSuite\IntegrationTestCase
I'm using measurement protocol for my desktop application.
With this following URL I able send single request to Google Analytics(GA).
https://www.google-analytics.com/collect?v=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0
But I want to send multiple request to GA.
According to the documentation, with /batch we can send multiple requests.
I have tried this URL,
https://www.google-analytics.com/batch?
v=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test1&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0
&v=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test2&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0
&v=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test3&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0
But in report only 3rd event getting recorded.
Please help me to fix this issue.
You should send the payload/data in the body as raw text and on separate lines. Also, make sure you make a POST request. That worked for me. Here is an image showing how this looks in Postman:
From Postman you can then generate the code for the language you use. E.g for PHP Curl it looks like this.
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://www.google-analytics.com/batch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "v=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test1&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0\r\nv=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test2&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0\r\nv=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test3&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0",
CURLOPT_HTTPHEADER => array(
"cache-control: no-cache",
"content-type: text/html",
"postman-token: de143f21-c12e-d268-32a0-9e5101541a07"
),
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
Useful post from #Søren about Postman, which I've never used before. Unfortunately, it still took me some time to figure out why my call to https://www.google-analytics.com/batch wasn't working in Javascript, citing a CORS 403 error as the issue. In Postman it was working fine, but the JS output from Postman wasn't.
var settings = {
"async": true,
"crossDomain": true,
"url": "https://www.google-analytics.com/batch",
"method": "POST",
"headers": {
"cache-control": "no-cache",
"postman-token": "bec425da-11af-ec17-f702-fd7d01133ee4"
},
"data": "v=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test1&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0\r\nv=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test2&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0\r\nv=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test3&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0"
}
$.ajax(settings).done(function (response) {
console.log(response);
});
So using Fiddler and comparing the call Postman makes, the only real difference I could see in Raw view was Postman was using POST https://www.google-analytics.com/batch where as JS was using OPTIONS https://www.google-analytics.com/batch. By executing the raw script and changing it from OPTIONS to POST it worked fine. So why wasn't mine sending as POST? I then read something about the headers need to match otherwise it won't be executed as POST. So the solution? Remove the headers...
var settings = {
"async": true,
"crossDomain": true,
"url": "https://www.google-analytics.com/batch",
"method": "POST",
"data": "v=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test1&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0\r\nv=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test2&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0\r\nv=1&tid=UA-XXXXXX-1&cid=754654B98786B&t=event&ec=Test3&ea=click&cd=XYZ&an=XYZ&aid=123&av=3.0&aiid=1.0"
}
$.ajax(settings).done(function (response) {
console.log(response);
});
It took me a considerable amount if time to get this to work, for something so simple, and hope this will help someone else out.
According to the firebase docs, https://www.firebase.com/docs/rest-api.html, it states:
PATCH - Updating Data
You can update specific children at a location without overwriting existing data
with a PATCH request. Named children in the data being written with PATCH will be
written, but omitted children will not be deleted. This is equivalent to the
update( ) function.
curl -X PATCH -d '{"last":"Jones"}' \
https://SampleChat.firebaseIO-demo.com/users/jack/name/.json
A successful request will be indicated by a 200 OK HTTP status code.
The response will contain the data written:
{"last":"Jones"}
Now my understanding of this, is that if I wish to update only parts of a resource, then I can use a PATCH request.
My simplified firebase database is as follows:
"exchange-rates" : {
"eur" : {
"fx" : 1.2,
"currency_symbol" : "€",
"updated_at" : "2014-06-13T22:49:23+0100",
},
"usd" : {
"fx" : 1.6,
"currency_symbol" : "$",
"updated_at" : "2014-06-13T22:49:23+0100",
},
"gbp" : {
"fx" : 1,
"currency_symbol" : "£",
"updated_at" : "2014-06-16T15:43:15+0100",
}
}
However If I omit the currency_symbol and updated_at from the payload in my patch request, then Firebase removes these attributes from the database.
$auth = 'SUPER_SECRET_CODE';
$guzzleClient = new GuzzleHttp\Client();
$url = https://DATABASE.firebaseio.com/.json;
$data['exchange-rates']['gbp']['fx'] = (float) 1;
$data['exchange-rates']['usd']['fx'] = (float) 1.66;
$data['exchange-rates']['eur']['fx'] = (float) 1.22;
$payload =
[
'query' => [ 'auth' => $auth ],
'json' => $data
];
$response = $guzzleClient->patch($url, $payload);
As such, the PATCH request is not working as it should, or I have misunderstood what Firebase should do with this PATCH request - or I am missing something. Any thoughts?
Also, If I wish to add an object to the exchange-rates object, I should be able to do so.
$data['exchange-rates']['chf']['fx'] = 2.13;
$payload =
[
'query' => [ 'auth' => $auth ],
'json' => $data
];
$response = $guzzleClient->patch($url, $payload);
However all this does is just overwrite all the existing exchange-rates, and now I only have 1 exchange rate in the db.
The update/PATCH operations are not recursive. They only observe keys for the direct children of your update. Example:
// assume data: { foo: 'bar', baz: {uno: 1, dos: 2}, zeta: {hello: 'world'} };
curl -X PATCH -d '{"tres": 3}' $URL
// baz is now: {uno: 1, dos: 2, tres: 3}
curl -X PATCH -d '{"foo": "barr", baz: {"tres": 3}}' $URL
// baz is now {tres: 3}
So the update is only one level deep. If one of the child records you provide is an object, it replaces the child at that path rather than trying to merge the two.
In the Symfony2 documentation it gives the simple example of:
$client->request('POST', '/submit', array('name' => 'Fabien'), array('photo' => '/path/to/photo'));
To simulate a file upload.
However in all my tests I am getting nothing in the $request object in the app and nothing in the $_FILES array.
Here is a simple WebTestCase which is failing. It is self contained and tests the request that the $client constructs based on the parameters you pass in. It's not testing the app.
class UploadTest extends WebTestCase {
public function testNewPhotos() {
$client = $this->createClient();
$client->request(
'POST',
'/submit',
array('name' => 'Fabien'),
array('photo' => __FILE__)
);
$this->assertEquals(1, count($client->getRequest()->files->all()));
}
}
Just to be clear. This is not a question about how to do file uploads, that I can do. It is about how to test them in Symfony2.
Edit
I'm convinced I'm doing it right. So I've created a test for the Framework and made a pull request.
https://github.com/symfony/symfony/pull/1891
This was an error in the documentation.
Fixed here:
use Symfony\Component\HttpFoundation\File\UploadedFile;
$photo = new UploadedFile('/path/to/photo.jpg', 'photo.jpg', 'image/jpeg', 123);
// or
$photo = array('tmp_name' => '/path/to/photo.jpg', 'name' => 'photo.jpg', 'type' => 'image/jpeg', 'size' => 123, 'error' => UPLOAD_ERR_OK);
$client = static::createClient();
$client->request('POST', '/submit', array('name' => 'Fabien'), array('photo' => $photo));
Documentation here
Here is a code which works with Symfony 2.3 (I didn't tried with another version):
I created an photo.jpg image file and put it in Acme\Bundle\Tests\uploads.
Here is an excerpt from Acme\Bundle\Tests\Controller\AcmeTest.php:
function testUpload()
{
// Open the page
...
// Select the file from the filesystem
$image = new UploadedFile(
// Path to the file to send
dirname(__FILE__).'/../uploads/photo.jpg',
// Name of the sent file
'filename.jpg',
// MIME type
'image/jpeg',
// Size of the file
9988
);
// Select the form (adapt it for your needs)
$form = $crawler->filter('input[type=submit]...')->form();
// Put the file in the upload field
$form['... name of your field ....']->upload($image);
// Send it
$crawler = $this->client->submit($form);
// Check that the file has been successfully sent
// (in my case the filename is displayed in a <a> link so I check
// that it appears on the page)
$this->assertEquals(
1,
$crawler->filter('a:contains("filename.jpg")')->count()
);
}
Even if the question is related to Symfony2, it appears in the top results when searching for Symfony4 in Google.
Instancing an UploadedFile works, but the shortest way I found was also actually in the official documentation:
$crawler = $client->request('GET', '/post/hello-world');
$buttonCrawlerNode = $crawler->selectButton('submit');
$form = $buttonCrawlerNode->form();
$form['photo']->upload('/path/to/lucas.jpg');
https://symfony.com/doc/current/testing.html#forms
I found this article, https://coderwall.com/p/vp52ew/symfony2-unit-testing-uploaded-file, and works perfect.
Regards
if you want to mock a UploadedFile without a client request, you can use:
$path = 'path/to/file.test';
$originalName = 'original_name.test';
$file = new UploadedFile($path, $originalName, null, UPLOAD_ERR_OK, true);
$testSubject->method($file);