Laravel S3 file upload test errors - php

I am trying to write a test to mock an S3 upload which generates a pre-signed url.
Here is my test:
public function test_it_uploads_to_s3()
{
Storage::fake('s3');
$response = $this->json('POST', route('api.presignedUpload', 1), [
'name' => 'file.txt',
]);
$response->assertStatus(200)
->assertJsonStructure(['url']);
}
I'm getting the following error:
Call to undefined method League\Flysystem\Adapter\Local::getClient()
I thought I only needed to add the Storage::fake('s3'); part and it should mock S3 or am I mistaken?
Edit:
The controller function code:
$s3 = Storage::disk('s3');
$client = $s3->getDriver()->getAdapter()->getClient();
$cmd = $client->getCommand('PutObject', [
'Bucket' => \Config::get('s3.bucket'),
'Key' => 'files/' . $request->name,
'ACL' => 'private',
]);
$request = $client->createPresignedRequest($cmd, $this->expiry);
$presignedUrl = (string)$request->getUri();
return response()->json(['url' => $presignedUrl], 201);

I am not sure about the error you're getting with only this context but it seems that you're missing some code according to the Laravel 8.x docs for this:
public function test_it_uploads_to_s3() {
Storage::fake('s3');
$response = $this->json('POST', route('api.presignedUpload', 1), [
UploadedFile::fake()->file('file.txt')
]);
Storage::disk('s3')->assertExists('file.txt');
$response->assertStatus(200)->assertJsonStructure(['url']);
}

You can mock the filesystem and test this out.
use Illuminate\Support\Facades\Storage;
use Mockery;
protected static function mockPresignedUrl()
{
$storage = Mockery::mock('League\Flysystem\Adapter\Local');
$storage->shouldReceive('getAdapter')->andReturn($storage)
->shouldReceive('getClient')->andReturn($storage)
->shouldReceive('getBucket')->andReturn('s3')
->shouldReceive('getCommand')->andReturn($storage)
->shouldReceive('createPresignedRequest')->andReturn($storage)
->shouldReceive('getUri')->andReturn('https://some-presigned-url');
return $storage;
}
public function test_it_uploads_to_s3()
{
$storage = $this->mockPresignedUrl();
Storage::set('s3', $storage);
$this->putJson(route('api.presignedUpload', 1), [
'name' => 'file.txt',
])
->assertStatus(200)
->assertJson([
'url' => 'https://some-presigned-url'
]);
}

Related

I am uploading files to google drive subfolder but it's uploading files only to the root directory

here is my code in controller
constructor code in
private $drive;
public function __construct(\Google_Client $client)
{
$this->middleware(function ($request, $next) use ($client) {
$accessToken = [
'access_token' => auth()->user()->token,
'created' => auth()->user()->created_at->timestamp,
'expires_in' => auth()->user()->expires_in,
'refresh_token' => auth()->user()->refresh_token
];
$client->setAccessToken($accessToken);
if ($client->isAccessTokenExpired()) {
if ($client->getRefreshToken()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
}
Auth::user()->update([
'token' => $client->getAccessToken()['access_token'],
'expires_in' => $client->getAccessToken()['expires_in'],
'created_at' => $client->getAccessToken()['created'],
]);
}
$client->refreshToken(auth()->user()->refresh_token);
$this->drive = new \Google_Service_Drive($client);
return $next($request);
});
}
the method that uploading files to drive I think the problem is this method
function createFile($file, $parent_id = null){
$fileName = FileStorage::find($file)->first();
$name = pathinfo(asset('uploadedfiles/' . $fileName->filenames));
$meta = new \Google_Service_Drive_DriveFile([
'name' => $name['basename'],
'parent' => '1GJ3KC-vsBrLAtlwUYgOvm7AjrtIXb4t-',// Parent Folder ID
]);
$content = File::get('uploadedfiles/' . $fileName->UniqueFileName);
$mime = File::mimeType('uploadedfiles/' . $fileName->UniqueFileName);
$file = $this->drive->files->create($meta, [
'data' => $content,
'mimeType' => $mime,
'uploadType' => 'multipart',
'fields' => 'id',
]);
}
where is the problem in my code? Any help would be appreciated.
The reason it is going to root is that you are not setting a parent folder.
You are using the parameter parent when in fact the parameter you should be using is parents and the value should be an array. parent is just getting ignore since its not a valid parameter
change
$meta = new \Google_Service_Drive_DriveFile([
'name' => $name['basename'],
'parent' => '1GJ3KC-vsBrLAtlwUYgOvm7AjrtIXb4t-',// Parent Folder ID
]);
to
$meta = new Drive\DriveFile(array(
'name' => $name['basename'],
'parents' => array('1GJ3KC-vsBrLAtlwUYgOvm7AjrtIXb4t-')
));

Too few arguments to function App\Http\Controllers\FileController::store(), 0 passed and exactly 1 expected using laravel 6

I want to insert data files used (array $ data) in store method like that of RegisterController.php create method with validation system but it gives me error
Too few arguments to function App \ Http \ Controllers \ FileController :: store ( ), 0 passed and exactly 1 expected.
FileController.php
public function store(array $data)
{
$file = new File();
$jdate = Carbon::now();
$request = app('request');
if ($request->hasFile('image')) {
$image = $request->file('image');
$imagee = Crypt::encryptString($image);
$image->storeAs("public\profiles\\".$jdate->format('F').$jdate->year,$imagee.'.'.$image->extension());
$file->image = "profiles\\".$jdate->format('F').$jdate->year."\\".$imagee.'.'.$image->extension();
}
$im = $file->image;
$validator = Validator::make($data, [
'category_id'=> ['bail','required'],
'titre' => ['bail','exclude_unless:category_id,1', 'string', 'min:3', 'max:255'],
'name' => ['bail','exclude_unless:category_id,1', 'string', 'min:3', 'max:255'],
'last' => ['bail','exclude_unless:category_id,1', 'string', 'min:2', 'max:255'],
'image' => ['bail','mimes:jpeg,jpg,png,gif,svg','exclude_unless:category_id,1','max:2048'],
]);
return File::create([
'category_id' => $data['category_id'],
'titre' => $data['titre'],
'name' => $data['name'],
'last' => $data['last'],
'image' => $im
]);
return Redirect::to("/")
->withSuccess('Great! file has been successfully uploaded.');
}
Getting data from a form you need to instantiate the Request:
use \Illuminate\Http\Request;
public function store(Request $request)
{
......
In RegisterController there is a trait called RegistersUsers that process the request values and then send them to create method of RegisterController to create new User. In your case your store method expects a parameter but you are not passing that parameter from any where. You have to use Request class to get get or post request data or you can use request() global helper.
if you use class then it is like
use \Illuminate\Http\Request;
public function store(Request $request)
{
$a = $request->a;
}
or if you use helper function
public function store()
{
$a = request('a');
}

How add parameter in FormRequest Laravel

I'm doing a login with passport (in API) I'm trying to get the tokens generated by the authentication server. However, I can't add extra parameters to the request which is an instance of FormRequest.
On the other hand, if I change my request to an instance of Request, it works.
So, my question how I can add parameters to my query $loginRequest (which is instance of FormRequest)
$loginRequest->request->add($params);
Here my code:
class AuthController extends Controller
{
use ThrottlesLogins;
public function store(LoginRequest $loginRequest)
{
$loginRequest->validated();
if ($this->hasTooManyLoginAttempts($loginRequest)) {
$this->fireLockoutEvent($loginRequest);
return $this->sendLockoutResponse($loginRequest);
}
if (Auth::attempt($this->credentials($loginRequest))){
$client = $this->getClient($loginRequest->name);
$params = [
'grant_type' => 'password',
'client_id' => $client->id,
'client_secret' => $client->secret,
'username' => $loginRequest->email,
'password' => $loginRequest->password,
'scopes' => 'fd',
];
$loginRequest->request->add($params);
$req = Request::create('oauth/token', 'POST');
$response = Route::dispatch($req)->getContent();
return $response;
}
$this->incrementLoginAttempts($loginRequest);
$this->sendFailedLoginResponse($loginRequest);
}
}
To append properties to an instance of FormRequest you can use the merge() method.
public function store(LoginRequest $loginRequest) {
$params = [
'foo' => 'bar',
];
$loginRequest->merge($params);
}

What is the best design pattern to consume REST API

I want to consume a Rest API in Laravel (an MVC framework) but I resort to use __call and was wonder if there is a better design pattern for this.
I know this a bad choice and I'm looking for an alternative pattern but here is my Repository class:
namespace App\Repositories;
use App\Models\OnlinePayment;
use App\Models\Order;
use App\Models\Transaction;
use App\Models\User;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use SoapClient;
class Bank
{
protected $http;
protected $user;
public function __construct()
{
$this->http = new Client;
}
protected function index()
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/report';
$data = [
'user_gender' => $this->user->gender ?? 1,
'user_name' => $this->user->name,
'user_family' => $this->user->family ?? 'خالی',
'user_mobile' => $this->user->mobile,
'user_type' => $this->user->type->name,
];
$options = $this->options($data);
$res = $this->http->request('GET', $url, $options);
$response = json_decode($res->getBody(), true);
return $response;
}
protected function indexData($request)
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers';
$options = $this->options($request->all());
$res = $this->http->request('GET', $url, $options);
$response = response()->json(json_decode($res->getBody(), true), $res->getStatusCode());
return $response;
}
protected function show($national_id)
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers/' . $national_id;
$options = $this->options([]);
$res = $this->http->request('GET', $url, $options);
if ($res->getStatusCode() == 404) {
abort(404);
}
$response = json_decode($res->getBody(), true);
return $response;
}
protected function store($request)
{
$http = new Client;
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers';
$this->user = auth()->user();
$data = array_merge(
[
'customer_national_id' => $request->national_id,
'customer_gender' => $request->gender,
'customer_name' => $request->name,
'customer_family' => $request->family,
'customer_phone' => $request->phone,
'customer_mobile' => $request->mobile,
'customer_city_id' => $request->city_id,
], [
'user_name' => $this->user->nanfig() is a hidden dependency. The settings should also be passed via the construcme,
'user_family' => $this->user->family ?? 'خالی',
'user_mobile' => $this->user->mobile,
'user_type' => $this->user->type->name,
'user_gender' => $this->user->gender ?? 1,
]
);
$res = $http->request('POST', $url, [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . config('Bank.token'),
],
'json' => $data,
'http_errors' => false
]);
if (! in_array($res->getStatusCode(), [200, 422])) {
$error = ValidationException::withMessages([
'name' => 'خطای ' . $res->getStatusCode() . ' در تعویض کالا'
]);
throw $error;
}
$response = response()->json(json_decode($res->getBody(), true), $res->getStatusCode());
return $response;
}
protected function options($data)
{
$options = [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . config('Bank.token'),
],
'json' => $data,
'http_errors' => false
];
return $options;
}
public function __call($method, $arguments) {
if (method_exists($this, $method)) {
if (! isset($arguments[0]) || ! $arguments[0] instanceof User) {
$this->user = auth()->user();
} else {
$this->user = $arguments[0];
unset($arguments[0]);
}
return call_user_func_array(array($this, $method), $arguments);
}
}
}
then create an instance of it in controller constructor:
public function __construct()
{
$this->Bank = new Bank();
}
and use it in controller like this:
$response = $this->Bank->indexData($user, $request);
or this:
$response = $this->Bank->indexData($request);
I think the shown class is not a Repository class because a Repository is only responsible for reading and writing the date from a data source. Your class does too much and violates all basic MVC principles.
Some thinks I would fix:
A repository is not responsible for creating the response view data (like JSON)
A repository is not responsible for creating a response object
A repository is independent of the request/response
The method name index makes no sense because a repository is not a Controller action. Don't mix the model layer with the controller layer.
config() is a hidden dependency. The settings should also be passed via the constructor.
Instead use better separation:
Create a class BankApiClient
Dont use magic methods like __call
Instead use public methods like: getUserByNationalId(int $nationalId): UserData
and so on...
Let the controller action create / render the json response with the results of the BankApiClient.
__call is an magic method of php which allow to execute protected method outside of the object instance, this is a rupture of the class visibility.
If you want to call a method from outside it must be public
public function __construct()
{
$this->bank = new Bank()
}
Use Auto injection of the dependency
public function __construct(Bank $bank)
{
$this->bank = $bank;
}

$s3->getObject returns private class (can't access properties)

I'm trying to wirte a function which is downloading content from my S3 bucket. The main problem I have is that, $s3->getObject returns class with private properties.
I'm using "aws/aws-sdk-php": "^3.54" via composer and this are my methods.
My main controller
public function download($idDocument = null) {
$document = $this->Documents->get(['where' => ['id_document' => $idDocument]]);
$document = $document[0];
// Download file from S3
$this->load->library('s3');
$response = $this->s3->downloadFromS3($document->path);
var_dump($response);
die();
}
This is my S3 library, which I'm calling in upper controller
public function authorize() {
$s3 = new Aws\S3\S3Client([
'version' => 'latest',
'region' => 'eu-central-1',
'credentials' => [
'key' => $this->config->config['s3_access_key'],
'secret' => $this->config->config['s3_secret_key']
]
]);
return $s3;
}
public function downloadFromS3($uniqueIdTypeAndName) {
$s3 = $this->authorize();
$object = $s3->getObject([
'Bucket' => $this->config->config['s3_bucket_name'],
'Key' => $uniqueIdTypeAndName
]);
return $object;
}
And this is the response if I var_dump($response); of my library function
So when I try to call $response->ContentType i get Message: Undefined property: Aws\Result::$ContentType
What can I do so my class will be public and the properties will be accessible? If you need any additional informations, please let me know and I will provide. Thank you
I figure it out. You have to access this object properties as you would access the array.
In my case if I try to access content type of $response I need to call
$response['ContentType']

Categories