Im trying to test an upload API but it fails every time:
Test Code :
$JSONResponse = $this->call('POST', '/upload', [], [], [
'photo' => new UploadedFile(base_path('public/uploads/test') . '/34610974.jpg', '34610974.jpg')
]);
$this->assertResponseOk();
$this->seeJsonStructure(['name']);
$response = json_decode($JSONResponse);
$this->assertTrue(file_exists(base_path('public/uploads') . '/' . $response['name']));
file path is /public/uploads/test/34610974.jpg
Here is My Upload code in a controller :
$this->validate($request, [
'photo' => 'bail|required|image|max:1024'
]);
$name = 'adummyname' . '.' . $request->file('photo')->getClientOriginalExtension();
$request->file('photo')->move('/uploads', $name);
return response()->json(['name' => $name]);
How should I test file upload in Laravel 5.2? How to use call method to upload a file?
When you create an instance of UploadedFile set the last parameter $test to true.
$file = new UploadedFile($path, $name, filesize($path), 'image/png', null, true);
^^^^
Here is a quick example of a working test. It expects that you have a stub test.png file in tests/stubs folder.
class UploadTest extends TestCase
{
public function test_upload_works()
{
$stub = __DIR__.'/stubs/test.png';
$name = str_random(8).'.png';
$path = sys_get_temp_dir().'/'.$name;
copy($stub, $path);
$file = new UploadedFile($path, $name, filesize($path), 'image/png', null, true);
$response = $this->call('POST', '/upload', [], [], ['photo' => $file], ['Accept' => 'application/json']);
$this->assertResponseOk();
$content = json_decode($response->getContent());
$this->assertObjectHasAttribute('name', $content);
$uploaded = 'uploads'.DIRECTORY_SEPARATOR.$content->name;
$this->assertFileExists(public_path($uploaded));
#unlink($uploaded);
}
}
➔ phpunit tests/UploadTest.php
PHPUnit 4.8.24 by Sebastian Bergmann and contributors.
.
Time: 2.97 seconds, Memory: 14.00Mb
OK (1 test, 3 assertions)
In Laravel 5.4 you can also use \Illuminate\Http\UploadedFile::fake(). A simple example below:
/**
* #test
*/
public function it_should_allow_to_upload_an_image_attachment()
{
$this->post(
action('AttachmentController#store'),
['file' => UploadedFile::fake()->image('file.png', 600, 600)]
);
/** #var \App\Attachment $attachment */
$this->assertNotNull($attachment = Attachment::query()->first());
$this->assertFileExists($attachment->path());
#unlink($attachment->path());
}
If you want to fake a different file type you can use
UploadedFile::fake()->create($name, $kilobytes = 0)
More information directly on Laravel Documentation.
I think this is the easiest way to do it
$file=UploadedFile::fake()->image('file.png', 600, 600)];
$this->post(route("user.store"),["file" =>$file));
$user= User::first();
//check file exists in the directory
Storage::disk("local")->assertExists($user->file);
and I think the best way to delete uploaded files in the test is by using tearDownAfterClass static method,
this will delete all uploaded files
use Illuminate\Filesystem\Filesystem;
public static function tearDownAfterClass():void{
$file=new Filesystem;
$file->cleanDirectory("storage/app/public/images");
}
The laravel documentation has an answer for when you want to test a fake file. When you want to test using a real file in laravel 6 you can do the following:
namespace Tests\Feature;
use Illuminate\Http\UploadedFile;
use Tests\TestCase;
class UploadsTest extends TestCase
{
// This authenticates a user, useful for authenticated routes
public function setUp(): void
{
parent::setUp();
$user = User::first();
$this->actingAs($user);
}
public function testUploadFile()
{
$name = 'file.xlsx';
$path = 'absolute_directory_of_file/' . $name;
$file = new UploadedFile($path, $name, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', null, true);
$route = 'route_for_upload';
// Params contains any post parameters
$params = [];
$response = $this->call('POST', $route, $params, [], ['upload' => $file]);
$response->assertStatus(200);
}
}
You can find this code at this link
Setup
/**
* #param $fileName
* #param $stubDirPath
* #param null $mimeType
* #param null $size
*
* #return \Illuminate\Http\UploadedFile
*/
public static function getTestingFile($fileName, $stubDirPath, $mimeType = null, $size = null)
{
$file = $stubDirPath . $fileName;
return new \Illuminate\Http\UploadedFile\UploadedFile($file, $fileName, $mimeType, $size, $error = null, $testMode = true);
}
Usage
$fileName = 'orders.csv';
$filePath = __DIR__ . '/Stubs/';
$file = $this->getTestingFile($fileName, $filePath, 'text/csv', 2100);
Folder Structure:
- MyTests
- TestA.php
- Stubs
- orders.csv
Related
Im saving a request to my database from my vue js via;
public function store(Request $request)
{
//validate
$this->validate($request, [
'name' => 'required',
'description' => 'required',
'price' => 'required'
]);
//get image
$exploded = explode(',', $request->cover_image);
$decoded = base64_decode($exploded[1]);
if(str_contains($exploded[0],'jpeg'))
$extension = 'jpg';
else
$extension = 'png';
$fileName = str_random().'.'.$extension;
$path = public_path().'/cover_images/'.$fileName;
file_put_contents($path, $decoded);
//save
$product = new Product;
$product->name = $request->input('name');
$product->description = $request->input('description');
$product->price = $request->input('price');
$product->cover_image = $fileName;
if($product->save()) {
return new ProductsResource($product);
}
}
How can I validate the base64 image? Is my procedure in saving the image coming from vue js is in correct way or is there a better way? please let me know. Thanks im just new to laravel and vue js hoping to learn more
You should add this function to your custom helper :
if (!function_exists('validate_base64')) {
/**
* Validate a base64 content.
*
* #param string $base64data
* #param array $allowedMime example ['png', 'jpg', 'jpeg']
* #return bool
*/
function validate_base64($base64data, array $allowedMime)
{
// strip out data uri scheme information (see RFC 2397)
if (strpos($base64data, ';base64') !== false) {
list(, $base64data) = explode(';', $base64data);
list(, $base64data) = explode(',', $base64data);
}
// strict mode filters for non-base64 alphabet characters
if (base64_decode($base64data, true) === false) {
return false;
}
// decoding and then reeconding should not change the data
if (base64_encode(base64_decode($base64data)) !== $base64data) {
return false;
}
$binaryData = base64_decode($base64data);
// temporarily store the decoded data on the filesystem to be able to pass it to the fileAdder
$tmpFile = tempnam(sys_get_temp_dir(), 'medialibrary');
file_put_contents($tmpFile, $binaryData);
// guard Against Invalid MimeType
$allowedMime = array_flatten($allowedMime);
// no allowedMimeTypes, then any type would be ok
if (empty($allowedMime)) {
return true;
}
// Check the MimeTypes
$validation = Illuminate\Support\Facades\Validator::make(
['file' => new Illuminate\Http\File($tmpFile)],
['file' => 'mimes:' . implode(',', $allowedMime)]
);
return !$validation->fails();
}
}
Then extend the base64_image validation in your AppServiceProvider in boot() method :
use Illuminate\Support\Facades\Validator;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
...
Validator::extend('base64_image', function ($attribute, $value, $parameters, $validator) {
return validate_base64($value, ['png', 'jpg', 'jpeg', 'gif']);
});
}
Now you can use it in your validation rules like this :
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'photo' => 'required|base64_image'
];
}
There is a crazybooot/base64-validation package that handles base64 validation.
For installation instructions and more details see:
https://github.com/crazybooot/base64-validation
I have 2 methods in my Controller and I need to validate it but I don't know how.
1st method which should allow all image extensions:
public function testing(Request $request) {
if($request->hasFile('img')) {
$image = Input::file('img');
$filename = time() . '.' . $image->getClientOriginalExtension();
$path = public_path('images/' . $filename);
Image::make($image->getRealPath())->resize(200, 200)->save($path);
$file = $request->file('img');
return ['url' => url('images/' . $filename)];
}
}
2nd method which should only allow 1 word and if there is space, trim it into 1 word:
public function postDB(Request $request) {
$newName = $request->input('newName');
$websites = new Website();
$websites->name = $newName;
$websites->save();
return redirect('template')->with('status', 'Website has been saved successfully!');
}
First write new Request for your data
php artisan make:request ImageRequest
Than write in ImageRequest:
public function authorize()
{
return true;
}
public function rules()
{
return [
'img' => 'file|image',
]
}
If you want to customize error messages:
public function messages()
{
return [
'img.image' => 'Some custom message ...',
];
}
Last inject request to your method (don`t forget about use App\Http\Requests):
public function testing(Requests\ImageRequest $request) {
//for retrieving validation errors use:
$imgErrors = $errors->first('img');
}
More information about Form Request Validation
Or you can use Validator facade (don`t forget about use Validator):
$validator = Validator::make(
$image, [
'img' => 'file|image',
]
);
More information about A Note On Optional Fields
I'm trying to test my api that's made with JWT_auth: https://github.com/tymondesigns/jwt-auth
class UpdateTest extends TestCase
{
use DatabaseTransactions;
public $token;
public function signIn($data = ['email'=>'mail#gmail.com', 'password'=>'secret'])
{
$this->post('api/login', $data);
$content = json_decode($this->response->getContent());
$this->assertObjectHasAttribute('token', $content);
$this->token = $content->token;
return $this;
}
/** #test */
public function a_user_updates_his_account()
{
factory(User::class)->create([
'name' => 'Test',
'last_name' => 'Test',
'email' => 'mail#gmail.com',
'mobile' => '062348383',
'function' => 'ceo',
'about' => 'About me.....',
'corporation_id' => 1
]);
$user = User::first();
$user->active = 2;
$user->save();
$this->signIn();
$url = '/api/user/' . $user->slug . '?token=' . $this->token;
$result = $this->json('GET', $url);
dd($result);
}
}
Result is always:
The token could not be parsed from the request
How do I get this t work!?
Source (https://github.com/tymondesigns/jwt-auth/issues/206)
One way to test your API in this situation is to bypass the actual token verification, but still log your user in (if you need to identify the user). Here is a snippet of the helper method we used in our recent API-based application.
/**
* Simulate call api
*
* #param string $endpoint
* #param array $params
* #param string $asUser
*
* #return mixed
*/
protected function callApi($endpoint, $params = [], $asUser = 'user#example.org')
{
$endpoint = starts_with($endpoint, '/')
? $endpoint
: '/' . $endpoint;
$headers = [];
if (!is_null($asUser)) {
$token = auth()->guard('api')
->login(\Models\User::whereEmail($asUser)->first());
$headers['Authorization'] = 'Bearer ' . $token;
}
return $this->json(
'POST',
'http://api.dev/' . $endpoint,
$params,
$headers
);
}
And is used like this:
$this->callApi('orders/list', [
'type' => 'customers'
])
->seeStatusOk()
Basically, there is not really a way for now. The fake request that is created during testing and is passed to Laravel to handle, somehow drops the token data.
It has alredy been reported in an issue (https://github.com/tymondesigns/jwt-auth/issues/852) but as far as I know, there is no solution yet.
I am using Ionic framework for mobile app development. The Yii2 API code below can be used for file upload, but it doesn't work. It shows the following errors:
i) Undefined offset: 0.
ii) yii\db\BaseActiveRecord->save()
public function actionNew() {
$model = new Apiprofile();
$userid = $_REQUEST['user_id'];
$photo = $_FILES['photo'];
$model->user_id = $userid;
$model->photo = $photo;
$name = $model->user_id;
$model->file = UploadedFile::getInstance($model, 'photo');
if($model->file) {
$model->file->saveAs('uploads/photos/'.$name.'.'.$model->file->extension);
$model->photo = $name.'.'.$model->file->extension;
$model->save();
}
$name = $model->user_id;
if($model->save()) {
echo json_encode(array('status'=>1,'data'=>$model->attributes),JSON_PRETTY_PRINT);
} else {
echo json_encode(array('status'=>0,'error_code'=>400,'errors'=>$model->errors),JSON_PRETTY_PRINT);
}
}
Hi if you want I will share my helper class that I used for working with images in Yii2 REST.
In the base folder of your application create folder components and inside of that folder create two folders helpers and objects.
-->assets
-->componests
----->helpers
----->objects
-->config
-->...
after that create class FileUpload inside of objects folder and put this code inside.
<?php
namespace app\components\objects;
class FileUpload
{
public $error=[];
public $isSuccess=false;
public $file;
public $ready_path;
public $file_type;
public $file_size;
public $file_extension;
public $file_tmp_name;
public $file_name;
public $save_path;
public function __construct($file,$save_path,$ready_path,$required=false)
{
if(!isset($_FILES[$file])){
if($required){
$this->error=['message'=>$file.' is required'];
}
return $this;
}
$this->save_path=$save_path;
$this->ready_path=$ready_path;
$this->file_type = strtolower($_FILES[$file]['type']);
$this->file_name = $_FILES[$file]['name'];
$this->file_tmp_name=$_FILES[$file]['tmp_name'];
$this->file_size = $_FILES[$file]['size'];
$this->file_extension=pathinfo($this->file_name, PATHINFO_EXTENSION);
}
public function setError($error){
if(empty($this->error)){
$this->error=$error;
}
}
}
Then inside of helpers folder create class FileUploadHelper and then put this code inside.
<?php
namespace app\components\helpers;
use app\components\objects\FileUpload;
use Yii;
class FileUploadHelper
{
private $allowed_files;
private $file_size;
const IMAGE='image';
const FILE='file';
/** File Upload
* #param string $file_name
* #param string $save_path
* #param string $ready_path
* #param string $type
* #param bool $required
* #return \app\components\objects\FileUpload
*/
public static function fileUpload($file_name,$save_path,$ready_path,$type,$required=false){
$image=new FileUpload($file_name,$save_path,$ready_path,$required);
if($type==self::FILE){
$allowed_files=Yii::$app->params['allowed_files'];
$file_size=Yii::$app->params['file_max_size'];
}else{
$allowed_files=Yii::$app->params['allowed_files'];
$file_size=Yii::$app->params['file_max_size'];
}
$dir = realpath(Yii::$app->basePath);
if(in_array($image->file_type,$allowed_files)
&&$image->file_size<$file_size){
$filename = $file_name.'_'.md5(uniqid(time()).time() . '_' . date('YmdHis')) . '.' . $image->file_extension;
$file = $dir . $image->save_path . $filename;
if(move_uploaded_file($image->file_tmp_name, $file)){
$image->file=$image->ready_path. $filename;
$image->isSuccess=true;
$image->setError(['message'=>'file_upload_success']);
}else{
$image->setError(['message'=>'error_try_again']);
}
}else{
$image->setError(['message'=>'file_should_be_no_more_than_given_size']);
}
return $image;
}
/** Delete File
* #param string $ready_file
*/
public static function deleteImage($ready_file){
$dir = realpath(Yii::$app->basePath);
if (strpos($ready_file, 'default') === false){
if(is_file($dir.'/web/'.$ready_file)){
unlink($dir.'/web/'.$ready_file);
}
}
}
}
That's all needed. Below I will give you example
public function actionEditAvatar($id)
{
$product = Product::findOne($id);
if( $product ){
$old_avatar=$product->avatar;
$image=FileUploadHelper::fileUpload(ProductForm::AVATAR,Yii::$app->params['product_path'],Yii::$app->params['product_ready_path'],FileUploadHelper::IMAGE);
if($image->isSuccess) {
$product->avatar = $image->file;
if($product->save()){
FileUploadHelper::deleteImage($old_avatar);
return $product->avatar;
}
}
return $image->error;
}
throw new NotFoundHttpException;
}
The code above from a real project. FileUploadHelper has two static classes which are "fileUpload" and "deleteImage".
FileUploadHelper requires fileUpload('file_name','save_path','ready_path','type')
'save_path' is where the file will be saved.
'ready_path' is how the ready URL should be like.
they are in Yii::$app->params[];
You can check if image succeeded or not by attribute isSuccess. If you have the error you can access them by attribute error. The ready file can be accessed by attribute file. You can delete image via static function deleteImage('saved_image_url'); All of them are used at above action. Please see it.
By the way, here the params that I used. Do not forget to to create folders inside web/uploads and change names of the folders like in config file.
return [
'adminEmail' => 'admin#example.com',
'allowed_images' => [ 'image/jpeg', 'image/gif', 'image/png' ],
'allowed_files' => [ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document','application/msword', 'application/pdf','image/jpeg', 'image/gif', 'image/png'],
'image_max_size' => 2097152,
'file_max_size' => 8388608,
'owner_document_path' => '/web/uploads/owner_document/',
'owner_document_ready_path' => 'uploads/owner_document/',
'product_path' => '/web/uploads/product/',
'product_ready_path' => 'uploads/product/',
'complain_photo_path' => '/web/uploads/complain_photo/',
'complain_photo_ready_path' => 'uploads/complain_photo/',
'owner_path' => '/web/uploads/owner/',
'owner_ready_path' => 'uploads/owner/',
'staff_path' => '/web/uploads/staff/',
'staff_ready_path' => 'uploads/staff/',
];
I want to get image from the user and then rename it and after that i want to save the renamed image name into the database. here is my controller codes. im using intervention package. i can save the photo to the destination folder correctly after renaming it but i cant save the name of the photo into my database after renaming. what will be the code?
public function store(UserRequest $request)
{
$farmer = User::create([
'name' => $request->name,
'phone' => $request->phone,
'address' => $request->address,
'nid' => $request->nid,
'dob' => $request->dob,
'remarks' => $request->remarks,
'division_id' => $request->division_id,
'district_id' => $request->district_id,
'upazila_id' => $request->upazila_id,
'farmer_point_id' => $request->farmer_point_id,
'user_type_id' => 3 // 3 is for farmer
]);
$image = Image::make($request->profile_picture);
$image->resize(250, 272);
$image->save(public_path("uploads/Farmers/farmer_$farmer->id.jpg"));
return redirect("farmer/{$farmer->id}");
}
The ideal thing to do is to upload the image first and then save the file path to database.
Ideally, it will be better to extract your upload logic into a separate stand-alone class. You can use use the below as a guide.
<?php
Class UploadImage
{
/**
* UploadPostImage constructor.
* .
* #param UploadedFile $postedImage
*
*/
public function __construct(UploadedFile $postedImage)
{
$this->postedImage = $postedImage;
}
/**
* Create the filename
*
*/
public function getFilename()
{
$dt = Carbon::now();
$timestamp = $dt->getTimestamp();
$this->filename = $timestamp . '_' . $this->postedImage->getClientOriginalName();
}
/**
* Create the image and return the path
*
* #param $path
* #param int $width
* #return mixed
*/
public function createImage($path, $width = 400)
{
// Upload the image
$image = Image::make($this->postedImage)
->resize(250, 272);
$image->save(public_path($path . $this->filename, 60));
return $path . $this->filename;
}
}
In your controller, you can then call this class
$uploadImage = new Image(UploadedFile $file);
$uploadImage->getFilename();
$data['image'] = uploadImage->createImage('your-upload-path');
// Add other data into the $data array, then save to database.
$data['phone'] = $request->name,
$data['address'] = $request->address
// Add other data then pass it into User::create()
When you make a call to createImage() in your controller, the path is returned to you and you can save this in your database.
I hope this helps!