Test api returns 201 instead 200 - laravel

I do not understand what happens on an API test (laravel 8).
This call (a very simple put) returns a response 200 , using postman.
The same test using phpunit, returns 201 :
public function testPutOrganizationOk()
{
$organization = Organization::factory()->create();
$superAdmin = User::factory()->create([
'organization_id' => $organization->id,
'role_id' => 'SUPERADMIN'
]);
Sanctum::actingAs($superAdmin);
$organizationToModify = [
'name' => 'mon organization moif',
'contact' => 'contact name modif',
'comment' => 'comment comment comment modif',
'ads_max' => 12345,
'state_id' => 'VALIDATED'
];
$response = $this->putJson($this->getUrl() . '/organizations/' . $organization->id, $organizationToModify);
$response->assertStatus(200);
}
The error is :
Tests\Feature\OrganizationTest::testPutOrganizationOk Expected status code 200 but received 201. Failed asserting that 200 is
identical to 201.
I tried a lot of things , without success. I really do not understand what happens. Any suggestions will be appreciated. Thanks.
EDIT :my controller
public function update(StoreOrganizationRequest $request, Organization $organization)
{
$this->authorize('update', Organization::class);
$organizationUpdated = $this->organizationRepository->updateOrganization($organization, $request->only(['name', 'contact', 'comment', 'ads_max', 'state_id']));
return new OrganizationResource($organizationUpdated);
}
EDIT 7 hours later ;-)
When I replace , in the controller, the return of the resource by a return of a simple json, then I have the same behaviour between postman and phpunit . The api call receives a 200 for the update.
Strange, it means that the problem is around the resource ?
Why a different behavior between postman and phpunit ? Who is right : postman or phpunit ?

The http code 201, it mean created success.
see here developer.mozilla.org
and you able to customize the header code by:
return Response::json(new OrganizationResource($organizationUpdated), 200);

201 Status Code says that you just create an Instance, and
200 Status Code says that already existing Instance has been update
The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI."
I might be wrong but seems like you have created the instance first and trying to modifying it then

Finally, I give up!
I will write the response with a status code like that:
return (new OrganizationResource($organization))->response()->setStatusCode(200);
instead of:
return new OrganizationResource($organization);
it's longer to write, but at least my tests are OK.

Related

Laravel HTTP Client does not work with empty body but Postman works

So I'm having an interesting issue with Laravel HTTP Client while trying to hit an API endpoint for PayPal.
I can get Laravel HTTP Client working on all my endpoints that POST with data, but this one endpoint that only requires headers (no data is passed in the body) fails with an error.
{
"name":"INVALID_REQUEST",
"message":"Request is not well-formed, syntactically incorrect, or violates schema.",
"debug_id":"609388c4ddfe4",
"details":[
{
"field":"\/",
"location":"body",
"issue":"INVALID_SYNTAX",
"description":"MALFORMED_REQUEST_JSON"
}
],
"links":[
{
"href":"https:\/\/developer.paypal.com\/docs\/api\/orders\/v2\/#error-INVALID_SYNTAX",
"rel":"information_link",
"encType":"application\/json"
}
]
}
When I hit the same endpoint in Postman everything works fine
My method for hitting the endpoint looks like this
public static function capture($order)
{
$token = Paypal::fetchToken();
$api_url = config('services.paypal.api_url') . '/v2/checkout/orders/' . $order['id'] . '/capture';
$headers = [
'Content/Type' => 'application/json',
];
$response = Http::withToken($token)
->withHeaders($headers)
->post($api_url)
->json();
return $response;
}
I have tried passing an empty array in the post request like this ->post($api_url, []) but that did not work either.
I have hardcoded the $api_url just in case I made a mistake with my formatting with variables. Resulted in the same issue.
I have tried changing the 'Content/Type' in the header to 'none'. This did not work either and also doesn't make sense because I have this same header set in postman and it works fine (PayPal docs also says to pass this content/type)
Based on the error I am receiving I can only assume the request is hitting the endpoint correctly, but either the HTTP wrapper or guzzle itself is adding something to the body when I leave it blank and it is causing PayPal to throw the error. Don't really know what else I can try though.
Is there a parameter I am overlooking for specifying an empty body on a post request?
Any help is appreciated.
Looking at the source I found the following solution
$response = Http::withToken($token)
->withHeaders($headers)
->send("POST", $api_url)
->json();
I had the same issue, but I fixed it with a simple trick.
I found the solution on https://docs.guzzlephp.org/en/stable/request-options.html#json.
This code should work.
$response = Http::withToken($token)
->withHeaders($headers)
->post($api_url,['json' => []])
->json();
The empty array is now seen as an empty array/body in JSON.

Can't access request data in production but can in local env

I'm working with a legacy Cakephp 2 app and need to create users via AJAX post on another domain.
I've got the whole thing working nicely in my local environment but have been battling with my prod environment.
I am using Postman to form a consistent Post request and setting the various headers as well as setting data values.
Locally:
I send a post request to a URL and var_dump the entire request object into the response. I can see that my data is populated. $this->request->data['email'] returns exactly what I expect.
Production:
I deploy the exact same code and my data array is completely empty.
I have set my Access-Control-Allow headers and I'm not getting any authisation issues. I can interact with the request within the application but I can not access any data. The request is the same request just a different endpoint.
I am running identical versions of PHP and exactly the same codebase.
Can anyone think of any environmental factors that might affect the request data?
This is my controller code in brief:
public function remoteadd() {
var_dump($this->request);
if ($this->request->is('ajax')) {
$this->disableCache();
$this->autoRender = false;
$this->response->type('json');
$this->User->create();
$gen_pass = $this->generatePassword();
$this->request->data['password'] = $gen_pass;
$emailAddr = $this->request->data['email'];
// Check if this email exists
$conditions = array(
'User.email' => $emailAddr,
);
if (!$this->User->hasAny($conditions)) {
if ($this->User->save($this->request->data)) {
$this->response->statusCode(200);
$this->response->body(json_encode(
array('status' => 'success', 'message' => 'New account successfully created')
));
}
} else {
$this->response->statusCode(500);
$this->response->body(json_encode(
array('status' => 'error', 'message' => 'Email address already exists')
));
}
$this->response->send();
$this->_stop();
}
}
It seems like the issue related to CORS preflight. Two requests are actually triggered. The first is a preflight which given my controller action is not returning any data as it's not actually a legitimate post request. The second request/response has the data appropriately loaded as expected.

Laravel HTTP tests and request attributes

On my app I add attributes to the HTTP request so I can use it later. My app is a multi domain app (.co.uk, .dk, .de). I findout the domain in the RouteServiceProvider and add the detected language to the HTTP request so I can load the data according to the language and some other things.
I findout and add the website directly in the RouteServiceProvider:
$website = Website::where('domain', '=', request()->getHttpHost())->first();
request()->attributes->add(['website' => $website]);
Then in my controller or anywere else I just have to query the request
if (!$request->attributes->has('website')) {
\Log::error('Abort HTTP request: invalid website: ' . request()->getHttpHost());
abort('500');
}
$language = $request->attributes->get('website')->language();
When testing my app the code execute normally (website is found in the RouteServiceProvider) but then it break in the controller:
testing.ERROR: Abort HTTP request: invalid website
When looking at the attribute, the data are empty in controller but not in the RouteServiceProvider:
dump($request->attributes); // in RouteServiceProvider.php
Symfony\Component\HttpFoundation\ParameterBag {
#parameters: array:1 [
"website" => ...
dump($request->attributes); // in controller
Symfony\Component\HttpFoundation\ParameterBag {
#parameters: []
}
It looks like the request object in the controller is no longer the same. When dumping :
dump(['RouteServiceProvider' => request()]);
I get:
"RouteServiceProvider" => Illuminate\Http\Request {#385
And in controller:
dump(['Controller' => request()]);
"Controller" => Illuminate\Http\Request {#9379
How can I fix this?
First of all, I agree that this code should be in a middleware instead of the RouteServiceProvider.
If the language is you only concern here, I would suggest to just use app()->setLocale() instead of saving the website in your request. If you need other informations contained in the website object, I would suggest to store it in the session instead of the request because I think that this kind of information is more under the responsability of the session than the request, which is more designed to handle inputs, http verbs, headers, ...
This could solve your problem, if it is not the case, let us see more of your code and of the new dd() results

Http request and response in codeigniter

I am currently working in codeigniter and I am new to this.
I was wondering how to retrieve the JSON values using API call .
Can anyone suggest me where should I start.
Many Thanks in advance
Pass your array of row to json_encode();example for method of controller is below
public function getUserList() {
header('Content-Type: application/json');
$query = $this->db->get('mytable');
if(count($query) > 0) {
$message = array('status' => 'true' , 'message' => 'Record get successfully' , 'data' => $return );
}else{
$message = array('status' => 'false' , 'message' => 'Record not found.' );
}
echo json_encode($message);
}
Codeigniter does not have an inbuilt HTTP method so you need to use other things in php to achieve this.
There are 2 ways, you can use cURL, but honestly... it's convoluted... but read this: http://php.net/manual/en/book.curl.php
Another method is using stream_context_create() http://php.net/manual/en/function.stream-context-create.php
I strongly suggest using this 2nd one as its much easier to work with (in context with curl..
Much of how you setup your request depends on the API you are referencing with and the kind of requests it allows: GET, POST ... and what kind of header information it requires you to send over as well do they require oAuth header?
There is no 1 bunch of code fits all, I had to create a full custom library to integrate codeigniter into Magento, it took many hours.

laravel api with vue 2 js not returning data - could 'localhost:8000' (or '127.0.0.1:8000') be the issue?

I am using the repo https://github.com/mschwarzmueller/laravel-ng2-vue/tree/03-vue-frontend so I have 100% confidence in the reliability of the code. I can post through the laravel api endpoint through the very simple Vue client, and also through Postman. Through Postman I can retrieve the table data array, but not so in the client app. In POSTMAN:
localhost:8000/api/quotes
works just fine.
IN THE vue 2 js CLIENT APP:
methods: {
onGetQuotes() {
axios.get('http://localhost:8000/api/quotes')
.then(
response => {
this.quotes = (response.data.quotes);
}
)
.catch(
error => console.log(error)
);
}
returns nothing. returning the response to Console.log returns nothing. The Network/XHR tab shows the table data rows, but I am not sure what that means.
I know for sure that this code works for others with their unique api endpoints, which I assume may not use localhost or '127:0.0.1:1080.
Edit: in response to request for more info
public function getQuotes()
{
$quotes = Quote::all();
$response = [$quotes];
return response()->json($response, 200);
}
and the relevant route:
Route::get('/quotes', [
'uses' => 'QuoteController#getQuotes'
]);
Just to confirm: I am using verified github repo code in which the ONLY change is my api endpoint addressas mentioned in the first line of the body of this question. . Note that the Laravel back end is also derived from a related repo in Max's fine tutorial. The running code can be seen at
So I really don't think this is a coding error- but is it a configuration error due to me using local host??
EDIT: It WAS a coding error in the laravel controller as shown below
The reason your code isn't working if because you haven't provided a key for your $quotes in your controller but you're looking for it in your vue file (response.data.quotes).
[$quotes] is essentially [0 => $quotes] so when the json response comes through it be 0: [...] not quotes: [...].
To get this to work you just need to change:
$response = [$quotes];
to:
$response = ['quotes' => $quotes];
Furthermore, just an FYI, you don't need to provide the 200 in response->json() as it's the default and you can just return an array and Laravel automatically return the correct json response e.g.:
public function getQuotes()
{
$quotes = \App\Models\Artist::all();
return compact('quotes'); //<-- This is just another way of writting ['quotes' => $quotes]
}
Obviously, you don't have to if you don't want to.
Hope this helps!

Resources