I'm working with Laravel and my code is like that
$this->actingAs($user)
->json('post', '/graphql/admin', ['query' => $query])
->assertStatus(200)
->assertJsonStructure($expected);
I would like to print the variable $query if this test fails.
My ideal is add in the end of code some like:
if ($this->isFail()) {
echo $query;
}
In the file TestCase.php add this function
// tests/TestCase.php
public function assertJsonStructure(
string $url,
array $params,
array $expected
) {
$response = $this->json('post', $url, $params);
try {
$response->assertStatus(200)->assertJsonStructure($expected);
} catch (\Exception $ex) {
$this->printDie($params, $expected, $ex, $response);
}
}
And this function:
// tests/TestCase.php
private function printDie($params, $expected, \Exception $ex, $response, $user_id = null)
{
$content = substr($response->getContent(), 0, 1500);
$trace = debug_backtrace();
$error = [
'class' => static::class.'::'.$trace[2]['function'],
'params' => $params,
'expected' => $expected,
'user' => $user_id,
'error' => $ex->toString(),
'content' => $content,
];
dd($error);
}
Now, you can test your query:
$this->assertJsonStructure(
'/graphql',
[
'query' => $query,
],
$expected
);
And, if it fail, it will print the error info, and it will easier to debug. You can see a preview here
Related
I use laravel 8 & have 3 table:
Products, ProductPrice & ProductsPublisher:
this is my Products model for this relationship:
public function lastPrice(){
return $this->hasMany(ProductPrice::class)->where('status','active')->orderBy('created_at','DESC')->distinct('publisher_id');
}
and this is my productsPrice model for publisher relationship:
public function getPublisher(){
return $this->belongsTo(ProductsPublisher::class,'publisher_id');
}
now, i want to use laravel resource for my api, i wrote products resource:
public function toArray($request)
{
return [
'id' => $this->id,
'price' => lastPrice::make($this->lastPrice),
'status' => $this->status,
'slug' => $this->slug,
'title' => $this->title,
'description' => $this->description,
'txt' => $this->txt,
'lang' => $this->lang,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
but in lastPrice resource, when i wrote like this:
return [
'id' => $this->id,
'main_price' => $this->main_price
];
it give me this error:
Property [id] does not exist on this collection instance.
when i use this code:
return parent::toArray($request);
get response but because i need to use another relationship in my lastPirce for publishers, i cant use that code and should return separately my data.
What i should to do?
thanks
Edit 1:
this is my Controller Code:
$products = Product::where('id',$id)->where('slug',$slug)->where('status','confirm')->first();
if(!$products){
return $this->sendError('Post does not exist.');
}else{
return $this->sendResponse(new \App\Http\Resources\Products\Products($products), 'Posts fetched.');
}
and this is sendResponse & sendError:
public function sendResponse($result, $message)
{
$response = [
'success' => true,
'data' => $result,
'message' => $message,
];
return response()->json($response, 200);
}
public function sendError($error, $errorMessages = [], $code = 404)
{
$response = [
'success' => false,
'message' => $error,
];
if(!empty($errorMessages)){
$response['data'] = $errorMessages;
}
return response()->json($response, $code);
}
thanks.
Edit 2:
i change my lastPrice Resource toArray function to this and my problem solved, but i think this isn't a clean way, any better idea?
$old_data = parent::toArray($request);
$co = 0;
$new_data = [];
foreach ($old_data as $index){
$publisher_data = Cache::remember('publisher'.$index['publisher_id'], env('CACHE_TIME_LONG') , function () use ($index) {
return ProductsPublisher::where('id' , $index['publisher_id'])->first();
});
$new_data[$co]['main_prices'] = $index['main_price'];
$new_data[$co]['off_prices'] = $index['off_price'];
$new_data[$co]['publisher'] = SinglePublisher::make($publisher_data);
$new_data[$co]['created_at'] = $index['created_at'];
$co++;
}
return $new_data;
I am using Laravel-8 and Maatwebsite-3.1 package to import Excel into the DB using Laravel API as the endpoint.
Trait:
trait ApiResponse {
public
function coreResponse($message, $data = null, $statusCode, $isSuccess = true) {
if (!$message) return response() - > json(['message' => 'Message is required'], 500);
// Send the response
if ($isSuccess) {
return response() - > json([
'message' => $message,
'error' => false,
'code' => $statusCode,
'results' => $data
], $statusCode);
} else {
return response() - > json([
'message' => $message,
'error' => true,
'code' => $statusCode,
], $statusCode);
}
}
public
function success($message, $data, $statusCode = 200) {
return $this - > coreResponse($message, $data, $statusCode);
}
public
function error($message, $statusCode = 500) {
return $this - > coreResponse($message, null, $statusCode, false);
}
}
Import:
class EmployeeImport extends DefaultValueBinder implements OnEachRow, WithStartRow, SkipsOnError, WithValidation, SkipsOnFailure
{
use Importable, SkipsErrors, SkipsFailures;
public function onRow(Row $row)
{
$rowIndex = $row->getIndex();
if($rowIndex >= 1000)
return; // Not more than 1000 rows at a time
$row = $row->toArray();
$employee = Employee::create([
'first_name' => $row[0],
'other_name' => $row[1] ?? '',
'last_name' => $row[2],
'email' => preg_replace('/\s+/', '', strtolower($row[3])),
'created_at' => date("Y-m-d H:i:s"),
'created_by' => Auth::user()->id,
]);
public function startRow(): int
{
return 2;
}
}
Controller:
public function importEmployee(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'document' => 'file|mimes:xls,xlsx|max:5000',
]);
if ($request->hasFile('document'))
{
if($validator->passes()) {
$import = new EmployeeImport;
$file = $request->file('document');
$file->move(public_path('storage/file_imports/employee_imports'), $file->getClientOriginalName());
Excel::import($import, public_path('storage/file_imports/employee_imports/' . $file->getClientOriginalName() ));
foreach ($import->failures() as $failure) {
$importerror = new ImportError();
$importerror->data_row = $failure->row(); // row that went wrong
$importerror->data_attribute = $failure->attribute(); // either heading key (if using heading row concern) or column index
$importerror->data_errors = $failure->errors()[0]; // Actual error messages from Laravel validator
$importerror->data_values = json_encode($failure->values());
$importerror->created_by = Auth::user()->id;
$importerror->created_at = date("Y-m-d H:i:s");
$importerror->save();
}
return $this->success('Employees Successfully Imported.', [
'file' => $file
]);
}else{
return $this->error($validator->errors(), 422);
}
}
} catch(\Throwable $e) {
Log::error($e);
return $this->error($e->getMessage(), $e->getCode());
}
}
I made it to SkipOnError and SkipOnFailure.
If there's error, it saves the error into the DB. This is working.
However, there is issue, if some rows fail it still display success (Employees Successfully Imported) based on this:
return $this->success('Employees Successfully Imported.
When there is partial upload, or all the rows or some of the rows have issues, I want to display this to the user. So that it will be interactive.
How do I achieve this?
Thanks
The amount I'm passing seems correct, but I got always an error.
As I checkout the amount is invalid, even if I'm passing a float, and this error is shown on the page after submit:
The payment function is as following:
public function payment(Request $request) {
$data = $request->all();
// dd($data['price']);
$gateway = new Braintree\Gateway([
'environment' => config('services.braintree.environment'),
'merchantId' => config('services.braintree.merchantId'),
'publicKey' => config('services.braintree.publicKey'),
'privateKey' => config('services.braintree.privateKey')
]);
$amount = Sponsorship::where('price', $data['price'])->first();
// dd($amount);c
$nonce = $request->payment_method_nonce;
$result = $gateway->transaction()->sale([
'amount' => $amount,
'paymentMethodNonce' => $nonce,
'customer' => [
'firstName' => 'Tony',
'lastName' => 'Stark',
'email' => 'tony#avengers.com',
],
'options' => [
'submitForSettlement' => true
]
]);
if ($result->success) {
$transaction = $result->transaction;
// header("Location: transaction.php?id=" . $transaction->id);
return back()->with('success_message', 'Transaction successful. The ID is:'. $transaction->id);
} else {
$errorString = "";
foreach ($result->errors->deepAll() as $error) {
$errorString .= 'Error: ' . $error->code . ": " . $error->message . "\n";
}
// $_SESSION["errors"] = $errorString;
// header("Location: index.php");
return back()->withErrors('An error occurred with the message: '.$result->message);
}
}
I want to import csv file with matlab/Excel at laravel 5.2,
the price should be integer, but when someone input price as string, i need to validate it.
This is my code:
Excel::filter( 'chunk' )->load( $path . $filename )->chunk( 1000, function( $results ) {
foreach ( $results as $import_data ) {
$validation = Validator::make( $import_data->toArray(), [
'code' => 'required|alpha',
'name' => 'alpha',
'number' => 'alpha',
'price' => 'numeric',
'pages' => 'numeric',
'delivery_id' => 'numeric'
] );
if( $validation->fails() ) {
return back();
}
if( $binder = Binder::where( 'code', $import_data->code )->first() ) {
$binder->update( [
'price' => $import_data->price,
'pages' => $import_data->pages
] );
} else {
Binder::create( [
'code' => $import_data->code,
'name' => $import_data->name,
'number' => $import_data->number,
'price' => $import_data->price,
'pages' => $import_data->pages,
'delivery_id' => $import_data->delivery_id
] );
}
}
});
After import csv, my return back() is not working at all, the controller still doing the foreach, how to enable my validation?
You're returning from the chunk() method, and then it goes on to call the next iteration of the same chunk() method, and so on. I would suggest wrapping the entire thing in a try-catch-block and throwing an exception instead.
Edit: like this:
try {
Excel::filter( 'chunk' )->load( $path . $filename )->chunk( 1000, function( $results ) {
foreach ( $results as $import_data ) {
// ...
if( $validation->fails() ) {
throw new Exception();
}
// ...
}
)};
} catch (Exception $e) {
return back();
}
I need to filter different authorization roles for different HTTP verbs. Attaching one filter in the controller is no problem, as the example shows:
public function __construct()
{
$this->beforeFilter('roleOne', ['on' => ['get', 'post', 'put', 'delete']]);
}
However, attaching multiple filters, as the next example shows, hides all the routes from the caller.
public function __construct()
{
$this->beforeFilter('roleOne', ['on' => ['get', 'post', 'put', 'delete']]);
$this->beforeFilter('roleTwo', ['on' => ['get']]);
}
And combining the filters lets all traffic through.
public function __construct()
{
$this->beforeFilter('roleOne|roleTwo', ['on' => ['get', 'post', 'put', 'delete']]);
}
Both fails.
I've considered using user levels instead of role names, or attaching level values to the user roles. But the best scenario for me would be to avoid having to change the structure.
Edit, here's the general structure for my filters.
Route::filter('roleOne', function ($route, $request){
$token = $request->header('X-Auth-Token');
// If the token doesn't exist the user isn't authenticated
if ($token === null) {
$data = ['Not authenticated'];
$code = 401;
$response = Response::json([
'error' => true,
'data' => $data,
'code' => $code
], $code
);
return $response;
}
// If the token is invalid the user isn't authenticated
else if ( ! isAuthenticated($token) ) {
$data = ['Not authenticated'];
$code = 401;
$response = Response::json([
'error' => true,
'data' => $data,
'code' => $code
], $code
);
return $response;
}
// If the user is not a member of group s/he doesn't have access
if( ! isAuthorized($token, 'roleOne') ){
$data = ['Not authorized'];
$code = 401;
$response = Response::json([
'error' => true,
'data' => $data,
'code' => $code
], $code
);
return $response;
}
});
I ended up introducing a level parameter in the authorized groups. Then I only needed one single beforeFilter.
public function __construct()
{
$this->beforeFilter('userRole', ['on' => ['get', 'post', 'put', 'delete']]);
}
And the filter now lookes something like...
Route::filter('userRole', function ($route, $request){
$token = $request->header('X-Auth-Token');
// If the token doesn't exist the user isn't authenticated
if ($token === null) {
$data = ['Not authenticated'];
$code = 401;
$response = Response::json([
'error' => true,
'data' => $data,
'code' => $code
], $code
);
return $response;
}
// If the token is invalid the user isn't authenticated
else if ( ! isAuthenticated($token) ) {
$data = ['Not authenticated'];
$code = 401;
$response = Response::json([
'error' => true,
'data' => $data,
'code' => $code
], $code
);
return $response;
}
// If the user is not a member of group Admin s/he doesn't have access
if( ! isAuthorized($token, 'Admin') ){
$data = ['Not authorized'];
$code = 401;
$response = Response::json([
'error' => true,
'data' => $data,
'code' => $code
], $code
);
return $response;
}
});
And the isAuthorized method looks something like this.
public function isAuthorized($token, $group) {
$hasPermission = false;
$user = false;
$requiredGroup = false;
// Attempting to find the user will automatically throw errors if unsuccessful
try {
if( $group !== '*' ){
// Get the needed group
$requiredGroup = $this->groupProvider->findByName($group);
}
// Get the user, including the memberships
$user = $this->userProvider->findByToken($token);
} catch (InvalidTokenException $e) {
$hasPermission = false;
}
// If the group is '*' it means everyone has permission, but has to be authenticated!
// That's why we do this check after the token-check
if ( $group === '*' ) {
return true;
}
if( !$user || !$requiredGroup ) {
$hasPermission = false;
} else {
// Compare user permissions to required
foreach ($user['groups'] as $group) {
if( $group['level'] >= $requiredGroup['level'] ){
$hasPermission = true;
}
}
}
return $hasPermission;
}