I have a Laravel 8 app that uses Botman. When I call the endpoint from Postman, I get the correct content with the correct headers. But when I call the endpoint while testing I get empty content and thus the test doesn't pass. The response is handled by HttpFoundation JsonResponse. The weird thing is the content is printed in the test console (even when I don't dump) but, $response->dump() returns Null.
My test method
/** #test */
public function user_can_talk_to_coach()
{
Passport::actingAs($this->learner, $this->learnerScopes);
$response = $this->postJson(
'learner/v1/coach',
[
'driver' => 'mobile',
'userId' => 1,
'message' => null,
'intent' => 'intent_testing',
],
$this->headers
);
var_dump('dump');
$response->dump();
var_dump('headers');
$response->dumpHeaders();
$response->assertStatus(200)
->assertJson([
'data' => [
[
'type' => 'text',
'text' => 'Hey',
'attachment' => null,
'additionalParameters' => [
'node'=> 'testing',
],
],
],
]);
}
Message sending method
/**
* Send out message response.
*/
public function messagesHandled()
{
$messages = $this->buildReply($this->replies);
// Reset replies
$this->replies = [];
$response = new JsonResponse();
$response->setData(['data' => $messages]);
$response->send();
}
Test result capture
Any idea of what is happening here ?
Related
Quick question.
Would it be possible to changes the JSON validation response of laravel?
This is for a custom API that I am building in Laravel.
Validation process
$validation = $this->validate(
$request, [
'user_id' => 'required',
]);
The response shows up like this in json
{
"message": "The given data was invalid.",
"errors": {
"user_id": [
"The user id field is required."
],
}
}
Preferable it would become something like this.
{
"common:" [
"status": "invalid",
"message": "Param xxxx is required",
],
}
What would be the best way to changes this?
Is it even possible?
Thank you.
You can do this, and it will be reflected globally.
Navigate to below folder and use Controller.php
app/Http/Controllers
use Illuminate\Http\Request;
Write below method in Controller.php and change response as you want.
public function validate(
Request $request,
array $rules,
array $messages = [],
array $customAttributes = [])
{
$validator = $this->getValidationFactory()
->make(
$request->all(),
$rules, $messages,
$customAttributes
);
if ($validator->fails()) {
$errors = (new \Illuminate\Validation\ValidationException($validator))->errors();
throw new \Illuminate\Http\Exceptions\HttpResponseException(response()->json(
[
'status' => false,
'message' => "Some fields are missing!",
'error_code' => 1,
'errors' => $errors
], \Illuminate\Http\JsonResponse::HTTP_UNPROCESSABLE_ENTITY));
}
}
I have tried it with Laravel 5.6, maybe this is useful for you.
#Dev Ramesh solution is still perfectly valid for placing inline within your controller.
For those of you looking to abstract this logic out into a FormRequest, FormRequest has a handy override method called failedValidation. When this is hit, you can throw your own response exception, like so...
/**
* When we fail validation, override our default error.
*
* #param ValidatorContract $validator
*/
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
{
$errors = $this->validator->errors();
throw new \Illuminate\Http\Exceptions\HttpResponseException(
response()->json([
'errors' => $errors,
'message' => 'The given data was invalid.',
'testing' => 'Whatever custom data you want here...',
], 422)
);
}
I was searching for an answer to this and I think I found a better way. There is an exception handler in a default Laravel app - \App\Exceptions\Handler - and you can override the invalidJson method:
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
class Handler extends ExceptionHandler
{
// ...
protected function invalidJson($request, ValidationException $exception)
{
$errors = [];
foreach ($exception->errors() as $field => $messages) {
foreach ($messages as $message) {
$errors[] = [
'code' => $field,
'message' => $message,
];
}
}
return response()->json([
'error' => $errors,
], $exception->status);
}
}
I'm looking for a way to execute a little bit of JSON from my Symfony (2.6 btw) controller, moreover than an other action (post data into database)
In fact, there is an register page with a controller which put data into database and then, redirect user to another page. But i need that my controller execute too a little bit of JSON to use Mailchimp API.
I've found a lot of docs about how to render JSON response, but, it seems to me that it's not what i want to be.
There is my controller
public function registerAction(Request $request)
{
/** #var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$formFactory = $this->get('fos_user.registration.form.factory');
/** #var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
/** #var $dispatcher \Symfony\Component\EventDispatcher\EventDispatcherInterface */
$dispatcher = $this->get('event_dispatcher');
$user = $userManager->createUser();
$user->setEnabled(true);
$event = new GetResponseUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_INITIALIZE, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
$form = $formFactory->createForm();
$form->setData($user);
$form->handleRequest($request);
if ($form->isValid()) {
// Gestion du type d'utilisateur et ajout du role
$user_type = $form->get('user_profile')->get('type')->getData();
$new_role = $this->roles[$user_type];
$event = new FormEvent($form, $request);
$user = $event->getForm()->getData();
$user->addRole($new_role);
$user->getUserProfile()->setEmail($user->getEmail());
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_SUCCESS, $event);
$userManager->updateUser($user);
if (null === $response = $event->getResponse()) {
$url = $this->generateUrl('fos_user_registration_confirmed');
$response = new RedirectResponse($url);
}
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
return $response;
}
return $this->render('FOSUserBundle:Registration:register.html.twig', array(
'form' => $form->createView(),
));
}
There is my JSON request
{
"email_address": "$email",
"status": "subscribed",
"merge_fields": {
"FNAME": "$name",
"LNAME": "$lastname",
"DATE": "$date"
}
}
So, how can i do to execute this JSON with this controller ?
Thank you in advance for your help (and sorry for my excellent english)
You probably want to create the JSON from an array rather than try to pass variables. Try:
$data = [
'email_address' => $email,
'status' => 'subscribed',
'merge_fields' => [
'FNAME' => $name,
'LNAME' => $lastname,
'DATE' => $date,
],
];
$json = json_encode($data);
Then I'm assuming this data gets sent to MailChimp in a POST request? If so, you could use Guzzle to send the data to MailChimp:
First add the guzzle dependency in composer by running:
composer require guzzlehttp/guzzle
Then send the data:
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://MAILCHIMP_URL', ['body' => $data]);
To send JSON instead of raw data, do the following:
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://MAILCHIMP_URL', ['json' => $data]);
Depending on the response status, you can then handle the logic afterwards.
You can achieve this also using JsonResponse (Symfony\Component\HttpFoundation\JsonResponse)
use Symfony\Component\HttpFoundation\JsonResponse;
...
// if you know the data to send when creating the response
$data = [
'email_address' => $email,
'status' => 'subscribed',
'merge_fields' => [
'FNAME' => $name,
'LNAME' => $lastname,
'DATE' => $date,
]
];
$response = new JsonResponse($data);
return $response;
More details here https://symfony.com/doc/current/components/http_foundation.html
I made a Zend Framework 3 MVC application. I don't want a default router. My one controller RESTFUL and only returning JSON. I want to remove the default IndexController. I want / to just give a 404 error. I'd prefer not to call any route 'home' but will do that if necessary.
If I make my route config look like this:
'router' => [
'routes' => [
'myRoute' => [
'type' => Segment::class,
'options' => [
'route' => '/myThing[/:action]',
'defaults' => [
'controller' => Controller\MyThingController::class,
'action' => 'index',
],
],
],
],
],
I get the following exception when I connect to a route that worked when I kept the default index controller in my browser:
Fatal error: Uncaught Zend\Router\Exception\RuntimeException: Route with name "home" not found in /var/www/vendor/zendframework/zend-router/src/Http/TreeRouteStack.php on line 354
If I change 'myRoute' => [ to 'home' => [ It renders the default layout instead of the Json rendered by JsonViewModel.
I just put an IndexController with a default route that renders the defautl 404 page for now. I'm going to make it return JSON at some point when I figure out how.
class IndexController extends AbstractRestfulController
{
public function indexAction()
{
$this->response->setStatusCode(Response::STATUS_CODE_404);
}
}
I'm not sure if this is what your after but to return a json response from your controller use.
In your module config:
'view_manager' => array(
'strategies' => array(
'ViewJsonStrategy',
),
},
and in your controller:
use Zend\View\Model\JsonModel;
// ...
public function indexAction()
{
$this->response->setStatusCode(Response::STATUS_CODE_404);
$view = new JsonModel();
$view->jsonVariable = 'someValue';
return $view;
}
This will return a json response.
Hope this helps.
I have a route group that looks like this:
Route::group(['prefix' => 'recipe','middleware'=>['auth.basic']], function (){
//Some things to do
});
When credentials are invalid Laravel outputs "Invalid credentials." How do I override this response with my own JSON response?
In AuthController, try this :
public function postLogin(Request $request)
{
$this->validate($request, [
'email' => 'required', 'password' => 'required',
]);
$credentials = [
'email' => $request->input('email'),
'password' => $request->input('password')
];
if (Auth::attempt($credentials, $request->has('remember')))
{
return redirect()->intended($this->redirectPath())
->with('success', 'You are successfully logged in');
}
return Response::json(array(
'success' => false,
'errors' => $this->getFailedLoginMessage(),
));
}
I just had a look at the Illuminate\Auth\SessionGuard. The method getBasicResponse() seems to be responsible for the response on a failed login attempt (with basic auth).
protected function getBasicResponse()
{
$headers = ['WWW-Authenticate' => 'Basic'];
return new Response('Invalid credentials.', 401, $headers);
}
How to actually overwrite it seems a little tricky though. You probably need to extend the SessionGuard Class and implement your own getBasicResponse() method. Thats the easy part, how to actually instantiate your own guard instead of the default one, I don't know yet.
Bug or Feature? If I change request data with beforeMarshal and there is a validation error, the request data will not be given back modified.
This question may be related to How to use Trim() before validation NotEmpty?.
Modifying Request Data Before Building Entities
If you need to modify request data before it is converted into entities, you can use the Model.beforeMarshal event. This event lets you manipulate the request data just before entities are created. Source: CakePHP 3 Documentation
According to the book I would expect the request data is always changed, no matter if there is a validation error or not.
Example or test case:
// /src/Model/Table/UsersTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
// Required for beforeMarshal event:
use Cake\Event\Event;
use ArrayObject;
// Required for Validation:
use Cake\Validation\Validator;
class UsersTable extends Table {
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options) {
$data['firstname'] = trim($data['firstname']);
}
public function validationDefault(Validator $validator) {
$validator
->add('firstname', [
'minLength' => [ 'rule' => ['minLength', 2], 'message' => 'Too short.' ],
])
;
return $validator;
}
}
If I enter " d" (Space-d) the validation error is shown, but the space itself is not removed in the form. I would expact the form showing only "d" because the space is removed from the request data with the beforeMarshal event. So... bug or feature?
My solution would be to use the trim()-function in the controller instead of the beforeMarshal event:
// /src/Controller/UsersController.php
// ...
public function add() {
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
// Use trim() here instead of beforeMarshal?
$this->request->data['firstname'] = trim($this->request->data['firstname']);
$user = $this->Users->patchEntity($user, $this->request->data );
if ( $this->Users->save($user) ) {
$this->Flash->succeed('Saved');
return $this->redirect(['controller' => 'Users', 'action' => 'index']);
} else {
$this->Flash->error('Error');
}
}
$this->set('user', $user);
}
This way the space will be removed even if there is a validation error. Or did I miss another function similar to beforeMarshal which is really modifying the request data?
The main purpose of beforeMarshal is to assist the users to pass the validation process when simple mistakes can be automatically resolved, or when data needs to be restructured so it can be put into the right columns.
The beforMarshal event is triggered just at the start of the validation process, one of the reasons is that beforeMarshal is allowed to change the validation rules and the saving options, such as the field whitelist. Validation is triggered just after this event is finished.
As documentation explains, if a field does not pass validation it will automatically removed from the data array and not be copied into the entity. This is to prevent having inconsistent data in the entity object.
More over, the data in beforeMarshal is a copy of the request. This is because it is important to preserve the original user input, as it may be used elsewhere.
If you need to trim your columns and display the result of the trimming to your user, I recommend doing it in the controller:
$this->request->data = array_map(function ($d) {
return is_string($d) ? trim($d) : $d;
}, $this->request->data);
Not work. This is my beforeMarshal :
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
{
$schema = $this->schema();
foreach($schema->columns() as $idx => $field ) {
$sc = $schema->getColumn($field);
if (isset($data[$field]) && $data[$field] != null) {
if ($sc['type'] == 'date') {
$date = DateTime::createFromFormat('d/m/Y',$data[$field]);
if ($date)
$data[$field] = $date->format('Y-m-d');
}
if ($sc['type'] == 'datetime') {
debug($data[$field]);
$date = DateTime::createFromFormat('d/m/Y',$data[$field]);
if ($date)
$data[$field] = $date->format('Y-m-d H:i:s');
}
}
}
debug($data);
}
The date commission_approved_date is correctly modified in beforeMarshal:
/src/Model/Table/AccountsTable.php (line 265)
object(ArrayObject) {
_referer => 'http://localhost/gessin/Accounts/edit/ODc?filter=eyJBY2NvdW50cy51c2VyX2lkIjoiMTA4NSIsIjAiOiJNT05USChBY2NvdW50cy5jb21taXNzaW9uX2RhdGUpID4gMCIsIllFQVIoQWNjb3VudHMuY29tbWlzc2lvbl9kYXRlKSI6IjIwMjAifQ=='
description => 'Provvigione su attivazione prodotto vod002'
notes => 'asd'
totalpaid => '0'
commission_approved_date => '2020-02-23 18:34:22'
}
But the same date is not, after patchEntity:
/src/Controller/AccountsController.php (line 203)
object(App\Model\Entity\Account) {
'id' => (int) 87,
'identifier' => null,
'company_id' => null,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2020-02-29 14:01:50.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2020-02-29 18:30:24.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'notes' => 'asd',
'total' => null,
'totaltax' => null,
'invoice_id' => null,
'description' => 'Provvigione su attivazione prodotto vod002',
'in_out' => null,
'is_refund' => null,
'client_id' => null,
'contract_id' => (int) 32,
'totalpaid' => (float) 0,
'user_id' => (int) 1085,
'commission_date' => object(Cake\I18n\FrozenTime) {
'time' => '2020-02-04 00:00:00.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'commission_approved_date' => object(Cake\I18n\FrozenTime) {
'time' => '2028-08-12 00:00:00.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},