Symfony 3: headers set in WebTestCase are not present when retrieving request via Request::createFromGlobals() - symfony

I'm trying to write a functional test to validate a header sent along with a request. Using Symfony's WebTestCase i have:
$this->client->request(
'POST',
'my-endpoint',
[],
[],
[
'HTTP_My-custom-header' => 'some_value'
],
$this->data);
Within a controller, i can see the headers as expected form the $request argument passed in to an action method.
However, the code i am testing is not a controller, and retrieves the current request (and therefore headers) via Request::createFromGlobals(). From this context, the header is missing.
Why is the header missing?
thanks

Related

lumen - Why does PHPUnit register test returns 405?

I want to make phpunit tests for lumen app, like :
public function testRegisterUser()
{
$newUserData = [
'name' => 'test_user',
'email' => 'test_user#mail.com',
'password' => Hash::make('111111'),
'status' => 'A',
'has_debts' => false
];
$response = $this->get('/api/v1/register', $newUserData); // http://localhost:8000/api/v1/register
$this->assertResponseOk();
}
But running tests I got 405 error :
1) PagesTest::testRegisterUser
Expected response status code [200] but received 405.
Failed asserting that 200 is identical to 405.
/ProjectPath/vendor/illuminate/testing/TestResponse.php:177
/ProjectPath/vendor/illuminate/testing/TestResponse.php:99
/ProjectPath/vendor/laravel/lumen-framework/src/Testing/Concerns/MakesHttpRequests.php:415
/ProjectPath/tests/PagesTest.php:27
Why I got 405 Method Not Allowed ? I postman I check my method : https://prnt.sc/207bu03
Method /api/v1/register in postman has no any token protection.
How to make my test working ?
UPDATED BLOCK :
I got error :
Error: Call to undefined method PagesTest::getJson()
If I modify :
$response = $this->getJson('/api/v1/register', $newUserData);
method getJson is mentioned here : https://laravel.com/docs/8.x/http-tests
But on this page I see in test file header:
namespace Tests\Feature;
But not in my generated lumen test file.
In my routes/web.php I have :
$router->group(['prefix'=>'api/v1'], function() use($router){
$router->post('/register','AuthController#register');
$router->post('/login', 'AuthController#login');
$router->group(['middleware' => 'auth'], function () use ($router) {
$router->get('/profile', 'UserProfileController#index');
What is wrong ?
Thanks!
Because you maybe have to use $this->getJson instead of $this->get.
It is not http://localhost:8000 as you are testing, you are not literally accessing the URL. It is simulating that.
Also share your api.php or routes file and the controller please (also the middlewares working on that URL).
Looking at the Lumen's documentation I can see that there is no getJson, my bad. You have to use $this->json('GET' instead.

How to accept a single, default field in a Symfony form?

I have a REST API written in Symfony3, with help from the FOSRestBundle.
It makes use of Symfony form classes for data input (POST, PATCH, PUT actions), which works great for almost all endpoints.
However I have a child endpoint that sets up relations using a form with a single collectionType. A POST request body looks like this:
curl http://localhost/documents/100/related -d #- <<REQUEST_BODY
{
"related": [
{"id": 14},
{"id": 23}
]
}
REQUEST_BODY
However I would like to ommit the "related" field name as this information is already in the URI and seems redundant here. I would like to adjust the form to accept data like this:
curl http://localhost/documents/100/related -d #- <<REQUEST_BODY
[
{"id": 14},
{"id": 23}
]
REQUEST_BODY
But cannot see how to make a Symfony form behave in this way?
To clarify, I want to accept a single form field without having to specify the name of that field in a JSON request.
I had a similar problem a while back. What worked for me, was to use the form factory to create a named form with an empty name, like so (example when inside a typical controller):
/** #var $formFactory FormFactory */
$formFactory = $this->get('form.factory');
$form = $formFactory->createNamed('', $type, $data, $options);
Be aware that a form configured like this will consume all POST (or GET) data. So, if you can't ensure that only the required data will be present, you might need to use allow_extra_fields

Drupal 8, http request to server and append to the site

I have a Drupal 8 site and I need to make a http request to another server (for content) and append it into the page like footer. I can't do this after DOM is loaded because of SEO issues.
I'm familiar with WordPress and so easy to do it with WP. However, I'm confused about how to do this with .twig, Drupal 8. Any suggestions would be great. Thanks.
If you want the content to be part of the DOM when it is sent to the browser this is not something you want to do in Twig, you should have the content loaded earlier in the process.
You can create a module that defines custom block and place that block in the correct region of your theme.
The block plugin class requires you to write a build() method that returns a render array for your block. Within build() you can do whatever you need to acquire the content, including making an HTTP Request using Symfony's Guzzle client:
public function build() {
$url = 'https://www.example.com/remote/service';
$client = \Drupal::httpClient();
$request = $client->createRequest('GET', $url);
// Do whatever's needed to extract the data you need from the request...
$build = ['my_remote_block' => [
'#theme' => 'my_custom_theme_function',
'#attributes' => [
//An array of variables to pass to the theme
],
'#cache' => [
//Some appropriate cache settings
],
],
];
If you are getting HTML back from your request you could skip the custom theme function and return an array with '#type' => 'markup' and then a field for the markup. The rest of this example assumes you get data back and want to render it yourself.
In your module's .module file you can define the custom theme function (so you can use a twig file of your own design).
function my_module_theme($existing, $type, $theme, $path) {
return [
'my_custom_theme_function' => [
'variables'=> [
// defaults for variables used in this block.
],
],
];
}
Then finally you can create a twig file named my-custom-theme-function.html.twig to render the output.
Often these kinds of setups are quite slow (since the browser's request then triggers another HTTP request + processing time) so you should consider either caching the block as much as possible or using a technique like BigPipe (which is probably not an option for you based on your question but seemed worth pointing out).

HTTP client Cakephp 3 ignores json body

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

Extjs 4 - Retrieve data in json format and load a Store. It sends OPTION request

I'm developing an app with Spring MVC and the view in extjs 4. At this point, i have to create a Grid which shows a list of users.
In my Spring MVC controller i have a Get method which returns the list of users in a jsonformat with "items" as a root.
#RequestMapping(method=RequestMethod.GET, value="/getUsers")
public #ResponseBody Users getUsersInJSON(){
Users users = new Users();
users.setItems(userService.getUsers());
return users;
}
If i try to access it with the browser i can see the jsondata correctly.
{"items":[{"username":"name1",".....
But my problem is relative to request of the Ext.data.Store
My Script is the following:
Ext.onReady(function(){
Ext.define('UsersList', {
extend: 'Ext.data.Model',
fields: [
{name:'username', type:'string'},
{name:'firstname', type:'string'}
]
});
var store = Ext.create('Ext.data.Store', {
storeId: 'users',
model: 'UsersList',
autoLoad: 'true',
proxy: {
type: 'ajax',
url : 'http://localhost:8080/MyApp/getUsers.html',
reader: {type: 'json', root: 'items'}
}
});
Ext.create('Ext.grid.Panel',{
store :store,
id : 'user',
title: 'Users',
columns : [
{header : 'Username', dataIndex : 'username'},
{header : 'Firstname', dataIndex: 'firstname'}
],
height :300,
width: 400,
renderTo:'center'
});
});
When the store tries to retrieve the data and launchs the http request, in my firebug console appears OPTIONS getUsers.html while the request in the browser launchs GET getUsers.html
As a result, Ext.data.Store has not elements and the grid appears with the columnames but without data. Maybe i've missed something
Thank you
You can change the HTTP methods that are used by the proxy for the different CRUD operations using actionMethods.
But, as you can see in the doc (and as should obviously be the case), GET is the default for read operations. So the OPTIONS request you are observing is quite puzzling. Are you sure that there's not another part of your code that overrides the default application-wide? Maybe do a search for 'OPTIONS' in all your project's JS files, to try and find a possible suspect. Apparently there's no match in the whole Ext code, so that probably doesn't come from the framework.
Edit:
Ok, I think I've got it. If your page is not accessed from the same domain (i.e. localhost:8080, the port is taken into account), the XHR object seems to resort to an OPTIONS request.
So, to fix your problem, either omit the domain name completely, using:
url: '/MyApp/getUsers.html'
Or double check that your using the same domain and port to access the page and make the requests.

Resources