diff --git a/.gitignore b/.gitignore index fd783d6..ca31334 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ yarn-error.log /.idea /.vscode .direnv +.php_cs.cache +/storage/mysql diff --git a/app/Faker/ProductNameProvider.php b/app/Faker/ProductNameProvider.php index 22af1d6..0aa7a53 100644 --- a/app/Faker/ProductNameProvider.php +++ b/app/Faker/ProductNameProvider.php @@ -1,5 +1,7 @@ diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index a0a2a8a..ce1176d 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -9,5 +9,7 @@ use Illuminate\Routing\Controller as BaseController; class Controller extends BaseController { - use AuthorizesRequests, DispatchesJobs, ValidatesRequests; + use AuthorizesRequests; + use DispatchesJobs; + use ValidatesRequests; } diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php new file mode 100644 index 0000000..3ba38c2 --- /dev/null +++ b/app/Http/Controllers/ImageController.php @@ -0,0 +1,54 @@ +validate([ + // The server should make sure to serve SVG files with the correct CSP to prevent XSS + 'image' => 'required|image|mimes:jpg,png,jpeg,gif,svg,webp|max:5000', + ]); + /* + Once the image is validated , create the name on the image + */ + $image = new Image(); + + $image->uuid = Str::uuid(); + $image->path = $request->file('image')->store("uploads"); + + $image->save(); + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Image $image + * @return \Illuminate\Http\Response + */ + public function destroy(Image $image) + { + $image->delete(); + } +} diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php index ac6a126..5bf5ab4 100644 --- a/app/Http/Controllers/LoginController.php +++ b/app/Http/Controllers/LoginController.php @@ -9,12 +9,13 @@ use Illuminate\Support\Facades\Hash; class LoginController extends Controller { - public function authenticate(Request $request) { + public function authenticate(Request $request) + { $creds = $request->validate([ 'email' => ['required', 'email'], 'password' => ['required'] ]); - if(Auth::attempt($creds)) { + if (Auth::attempt($creds)) { $request->session()->regenerate(); return redirect()->intended(); } @@ -23,7 +24,8 @@ class LoginController extends Controller ]); } - public function register(Request $request) { + public function register(Request $request) + { Auth::logout(); $creds = $request->validate([ @@ -37,7 +39,8 @@ class LoginController extends Controller return redirect("login")->withSuccess("Success! Now login"); } - public function logout(Request $request) { + public function logout(Request $request) + { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerate(); @@ -45,7 +48,8 @@ class LoginController extends Controller return back(); } - public function view() { + public function view() + { return view("login", []); } } diff --git a/app/Http/Controllers/OrderController.php b/app/Http/Controllers/OrderController.php new file mode 100644 index 0000000..52f847b --- /dev/null +++ b/app/Http/Controllers/OrderController.php @@ -0,0 +1,114 @@ +validate([ + 'products' => 'required|array', + 'products.*' => 'exists:products,uuid' + ]); + + DB::transaction(function () use ($validated, $user) { + $products = array_map(fn ($v) => Product::query()->where("uuid", $v)->first(), $validated["products"]); + $order = new Order($products); + + $order->user()->associate($user); + $order->cost = array_reduce($products,fn ($c, $i) => $c+=$i->price,0); + + return $order->save(); + }); + } + + /** + * Display the specified resource. + * + * @return \Illuminate\Http\Response + */ + public function show(Order $order) + { + // + } + + /** + * Show the form for editing the specified resource. + * + * @return \Illuminate\Http\Response + */ + public function edit(Order $order) + { + // + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function update(Request $request, Order $order) + { + $validated = $request->validate([ + 'address' => 'string', + ]); + if(isset($validated["address"])) { + $address = $order->address()->withDefault(fn() => new Addres($validated)); + $address->address = $validated["address"]; + $order->save(); + } + } + + /** + * Remove the specified resource from storage. + * + * @return \Illuminate\Http\Response + */ + public function destroy(Order $order) + { + throw new Error("TODO"); + // + } +} diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 83b71e6..f8d140c 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -8,7 +8,6 @@ use Illuminate\Support\Facades\Log; class ProductController extends Controller { - public function index() { // TMP do something better at some point maybe idk @@ -91,7 +90,6 @@ class ProductController extends Controller */ public function destroy(Product $product) { - echo $product; $product->delete(); return redirect()->route("product.index"); } diff --git a/app/Http/Resources/order.php b/app/Http/Resources/order.php new file mode 100644 index 0000000..6222be1 --- /dev/null +++ b/app/Http/Resources/order.php @@ -0,0 +1,19 @@ +{self::uuidField()} = (string) Str::uuid(); - }); - } -} + static::creating(function ($model) { + $model->{self::uuidField()} = (string) Str::uuid(); + }); + } + } diff --git a/app/Models/Image.php b/app/Models/Image.php new file mode 100644 index 0000000..e922f4a --- /dev/null +++ b/app/Models/Image.php @@ -0,0 +1,21 @@ +path); + } +} diff --git a/app/Models/Order.php b/app/Models/Order.php new file mode 100644 index 0000000..7c70094 --- /dev/null +++ b/app/Models/Order.php @@ -0,0 +1,28 @@ +belongsTo(Addres::class, "id", "addressId"); + } + + public function user() + { + return $this->belongsTo(User::class, "userId", "uuid"); + } + + public function products() + { + return $this->belongsToMany("App\Product", "orderProduct","productId", "orderId"); + } +} diff --git a/app/Models/Product.php b/app/Models/Product.php index 0fa9e87..fb9ac22 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -7,10 +7,11 @@ use Illuminate\Database\Eloquent\Model; class Product extends Model { - use HasFactory, HasUUID; + use HasFactory; + use HasUUID; public $primaryKey = "uuid"; - + protected $fillable = [ 'name', 'description', diff --git a/app/Models/User.php b/app/Models/User.php index 95c5be9..4a0cff2 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,8 +7,12 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; -class User extends Authenticatable { - use HasApiTokens, HasFactory, HasUUID, Notifiable; +class User extends Authenticatable +{ + use HasApiTokens; + use HasFactory; + use HasUUID; + use Notifiable; public $primaryKey = "uuid"; /** diff --git a/app/Providers/FakerServiceProvider.php b/app/Providers/FakerServiceProvider.php index ba6ffef..af3c99a 100644 --- a/app/Providers/FakerServiceProvider.php +++ b/app/Providers/FakerServiceProvider.php @@ -5,7 +5,8 @@ namespace App\Providers; use App\Faker\ProductNameProvider; use Illuminate\Support\ServiceProvider; -use Faker\{Factory, Generator}; +use Faker\Factory; +use Faker\Generator; class FakerServiceProvider extends ServiceProvider { diff --git a/composer.json b/composer.json index 4f6a928..cf3b613 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,9 @@ "guzzlehttp/guzzle": "^7.0.1", "laravel/framework": "^8.54", "laravel/sanctum": "^2.11", - "laravel/tinker": "^2.5" + "laravel/tinker": "^2.5", + "php-decimal/laravel": "^1.1", + "php-decimal/php-decimal": "^1.1" }, "require-dev": { "facade/ignition": "^2.5", diff --git a/composer.lock b/composer.lock index b617769..6831466 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6cadd819a6415b190ae2704ced6e6c8b", + "content-hash": "844437f75eed2577e6c25d111a4ecd28", "packages": [ { "name": "asm89/stack-cors", @@ -1978,6 +1978,183 @@ }, "time": "2021-04-09T13:42:10+00:00" }, + { + "name": "php-decimal/globals", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-decimal/globals.git", + "reference": "589a296b471a3913ed41396d6fcae2a321788a78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-decimal/globals/zipball/589a296b471a3913ed41396d6fcae2a321788a78", + "reference": "589a296b471a3913ed41396d6fcae2a321788a78", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "php-decimal/php-decimal": "^1.1.0" + }, + "type": "library", + "autoload": { + "files": [ + "globals.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rudi Theunissen", + "email": "rtheunissen@php.net" + } + ], + "description": "Globals helpers for the decimal extension", + "support": { + "issues": "https://github.com/php-decimal/globals/issues", + "source": "https://github.com/php-decimal/globals/tree/v1.1.0" + }, + "time": "2018-11-20T21:18:50+00:00" + }, + { + "name": "php-decimal/laravel", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-decimal/laravel.git", + "reference": "fbc4137d40d1dfde1457ecc113dede57ac0de841" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-decimal/laravel/zipball/fbc4137d40d1dfde1457ecc113dede57ac0de841", + "reference": "fbc4137d40d1dfde1457ecc113dede57ac0de841", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "php-decimal/globals": "^1.1.0", + "php-decimal/php-decimal": "^1.1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Decimal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rudi Theunissen", + "email": "rtheunissen@php.net" + } + ], + "description": "Laravel integration package for decimal support", + "support": { + "issues": "https://github.com/php-decimal/laravel/issues", + "source": "https://github.com/php-decimal/laravel/tree/v1.0.2" + }, + "time": "2019-07-05T23:45:07+00:00" + }, + { + "name": "php-decimal/php-decimal", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-decimal/php-decimal.git", + "reference": "ee8d92648fd06e82f9bfe0406d3670ebcddd946a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-decimal/php-decimal/zipball/ee8d92648fd06e82f9bfe0406d3670ebcddd946a", + "reference": "ee8d92648fd06e82f9bfe0406d3670ebcddd946a", + "shasum": "" + }, + "require": { + "ext-decimal": "^1.1", + "php": ">=7.0.0", + "php-decimal/stubs": "^1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Decimal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rudi Theunissen", + "email": "rtheunissen@php.net" + } + ], + "description": "Correctly-rounded arbitrary precision decimal floating point", + "keywords": [ + "decimal", + "math", + "number", + "precision" + ], + "support": { + "issues": "https://github.com/php-decimal/php-decimal/issues", + "source": "https://github.com/php-decimal/php-decimal/tree/v1.1.0" + }, + "time": "2018-11-20T20:49:48+00:00" + }, + { + "name": "php-decimal/stubs", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-decimal/stubs.git", + "reference": "168bb0d58bb398d7da739da0da99df2a9e1fc773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-decimal/stubs/zipball/168bb0d58bb398d7da739da0da99df2a9e1fc773", + "reference": "168bb0d58bb398d7da739da0da99df2a9e1fc773", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Decimal\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rudi Theunissen", + "email": "rtheunissen@php.net" + } + ], + "description": "IDE and static analysis helper for the decimal extension", + "keywords": [ + "decimal", + "math", + "number", + "precision" + ], + "support": { + "issues": "https://github.com/php-decimal/stubs/issues", + "source": "https://github.com/php-decimal/stubs/tree/v1.1.0" + }, + "time": "2018-11-20T20:48:40+00:00" + }, { "name": "phpoption/phpoption", "version": "1.8.0", diff --git a/config/filesystems.php b/config/filesystems.php index 760ef97..ccbcd16 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -67,7 +67,7 @@ return [ */ 'links' => [ - public_path('storage') => storage_path('app/public'), + public_path('storage') => storage_path('app'), ], ]; diff --git a/database/factories/OrderFactory.php b/database/factories/OrderFactory.php new file mode 100644 index 0000000..8fa0260 --- /dev/null +++ b/database/factories/OrderFactory.php @@ -0,0 +1,28 @@ + $this->faker->unique()->uuid(), 'name' => $this->faker->productName(), + 'price' => $this->faker->numberBetween(1,32124), 'description' => $this->faker->text(10000), ]; } diff --git a/database/migrations/2021_09_07_092431_create_products_table.php b/database/migrations/2021_09_07_092431_create_products_table.php index e3218cd..5dfcfda 100644 --- a/database/migrations/2021_09_07_092431_create_products_table.php +++ b/database/migrations/2021_09_07_092431_create_products_table.php @@ -17,6 +17,7 @@ class CreateProductsTable extends Migration $table->uuid("uuid")->primary()->unique(); $table->string("name", 255); $table->string("description", 10000); + $table->decimal("price"); $table->timestamps(); }); } diff --git a/database/migrations/2021_10_28_093227_create_images_table.php b/database/migrations/2021_10_28_093227_create_images_table.php new file mode 100644 index 0000000..2c6c771 --- /dev/null +++ b/database/migrations/2021_10_28_093227_create_images_table.php @@ -0,0 +1,32 @@ +uuid("uuid")->unique()->primary; + $table->string('path')->unique(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('images'); + } +} diff --git a/database/migrations/2021_11_01_204536_create_orders_table.php b/database/migrations/2021_11_01_204536_create_orders_table.php new file mode 100644 index 0000000..097b6f5 --- /dev/null +++ b/database/migrations/2021_11_01_204536_create_orders_table.php @@ -0,0 +1,55 @@ +id('id'); + $table->foreignUuid("userId")->references("uuid")->on("users"); + + // Address validation for international users is basically impossible so just use a string + $table->string("addres"); + $table->timestamps(); + }); + + Schema::create('orders', function (Blueprint $table) { + $table->uuid("uuid")->unique()->primary(); + $table->boolean("fulfilled")->default(false); + + $table->foreignUuid("userId")->references("uuid")->on("users"); + $table->foreignId("addressId")->nullable()->references("id")->on("addresses"); + + $table->decimal("cost"); + $table->boolean("paid")->default(false); + + $table->timestamps(); + }); + + Schema::create("orderProduct", function (Blueprint $table) { + $table->foreignUuid("productId")->references("uuid")->on("products")->cascadeOnUpdate()->cascadeOnDelete(); + $table->foreignUuid("orderId")->references("uuid")->on("orders")->cascadeOnUpdate()->cascadeOnDelete(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('orderProduct'); + Schema::dropIfExists('orders'); + Schema::dropIfExists('addresses'); + } +} diff --git a/database/seeders/OrderSeeder.php b/database/seeders/OrderSeeder.php new file mode 100644 index 0000000..1a874a6 --- /dev/null +++ b/database/seeders/OrderSeeder.php @@ -0,0 +1,18 @@ + /etc/timezone + +RUN apt-get update \ + && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 \ + && mkdir -p ~/.gnupg \ + && chmod 600 ~/.gnupg \ + && echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf \ + && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys E5267A6C \ + && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C300EE8C \ + && echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu hirsute main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ + && apt-get update \ + && apt-get install -y php8.0-cli php8.0-dev \ + php8.0-pgsql php8.0-sqlite3 php8.0-gd \ + php8.0-curl php8.0-memcached \ + php8.0-imap php8.0-mysql php8.0-mbstring \ + php8.0-xml php8.0-zip php8.0-bcmath php8.0-soap \ + php8.0-intl php8.0-readline php8.0-pcov \ + php8.0-msgpack php8.0-igbinary php8.0-ldap \ + php8.0-redis php8.0-swoole php8.0-xdebug \ + php-decimal \ + && php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \ + && curl -sL https://deb.nodesource.com/setup_16.x | bash - \ + && apt-get install -y nodejs \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ + && apt-get update \ + && apt-get install -y yarn \ + && apt-get install -y mysql-client \ + && apt-get install -y postgresql-client \ + && apt-get -y autoremove \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.0 + +RUN groupadd --force -g $WWWGROUP sail +RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail + +COPY start-container /usr/local/bin/start-container +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY php.ini /etc/php/8.0/cli/conf.d/99-sail.ini +RUN chmod +x /usr/local/bin/start-container + +EXPOSE 8000 + +ENTRYPOINT ["start-container"] diff --git a/docker/8.0/php.ini b/docker/8.0/php.ini new file mode 100644 index 0000000..66d04d5 --- /dev/null +++ b/docker/8.0/php.ini @@ -0,0 +1,4 @@ +[PHP] +post_max_size = 100M +upload_max_filesize = 100M +variables_order = EGPCS diff --git a/docker/8.0/start-container b/docker/8.0/start-container new file mode 100644 index 0000000..39e5270 --- /dev/null +++ b/docker/8.0/start-container @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if [ ! -z "$WWWUSER" ]; then + usermod -u $WWWUSER sail +fi + +if [ ! -d /.composer ]; then + mkdir /.composer +fi + +chmod -R ugo+rw /.composer + +if [ $# -gt 0 ];then + exec gosu $WWWUSER "$@" +else + /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf +fi diff --git a/docker/8.0/supervisord.conf b/docker/8.0/supervisord.conf new file mode 100644 index 0000000..9d28479 --- /dev/null +++ b/docker/8.0/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:php] +command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80 +user=sail +environment=LARAVEL_SAIL="1" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/resources/views/order/create.blade.php b/resources/views/order/create.blade.php new file mode 100644 index 0000000..0bab395 --- /dev/null +++ b/resources/views/order/create.blade.php @@ -0,0 +1,24 @@ + + + + + + Document + + + +@if(Session::has('success')) +
+ {{Session::get('success')}} +
+@endif +
+
+ + + @csrf +
+
+ + diff --git a/resources/views/product/view.blade.php b/resources/views/product/view.blade.php index 3da9375..03d547e 100644 --- a/resources/views/product/view.blade.php +++ b/resources/views/product/view.blade.php @@ -12,9 +12,9 @@

{{ $product }}

-
- - @method('DELETE') + + + @method('DELETE') @csrf
diff --git a/resources/views/uploadImage.blade.php b/resources/views/uploadImage.blade.php new file mode 100644 index 0000000..ce84ffd --- /dev/null +++ b/resources/views/uploadImage.blade.php @@ -0,0 +1,24 @@ + + + + + + Document + + + +@if(Session::has('success')) +
+ {{Session::get('success')}} +
+@endif +
+
+ + + @csrf +
+
+ + diff --git a/routes/console.php b/routes/console.php index 0f9b53b..643ec19 100644 --- a/routes/console.php +++ b/routes/console.php @@ -18,4 +18,3 @@ use Illuminate\Support\Facades\Artisan; Artisan::command('inspire', function () { $this->comment(Inspiring::quote()); })->purpose('Display an inspiring quote'); - diff --git a/routes/web.php b/routes/web.php index 994683b..daedb5d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,6 +1,8 @@ $user]); })->middleware("auth"); @@ -32,3 +34,5 @@ Route::post('/register', [LoginController::class, "register"])->name("register") Route::any("/logout", [LoginController::class, "logout"])->name("logout"); Route::resource("product", ProductController::class); +Route::resource("order", OrderController::class); +Route::resource("image", ImageController::class)->only(["store", "delete", "create"]); diff --git a/shell.nix b/shell.nix index 7ec5e08..52931ef 100644 --- a/shell.nix +++ b/shell.nix @@ -1,5 +1,5 @@ { pkgs ? import { } }: pkgs.mkShell { # nativeBuildInputs is usually what you want -- tools you need to run - nativeBuildInputs = with pkgs; [ php80Packages.composer php80 yarn docker-compose docker]; + nativeBuildInputs = with pkgs; [ php80Packages.php-cs-fixer php80Packages.composer php80 yarn docker-compose docker]; } diff --git a/storage/mysql/.gitignore b/storage/mysql/.gitignore deleted file mode 100755 index d6b7ef3..0000000 --- a/storage/mysql/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/tests/Feature/RegistrationTest.php b/tests/Feature/RegistrationTest.php index 3d4974b..20b69f2 100644 --- a/tests/Feature/RegistrationTest.php +++ b/tests/Feature/RegistrationTest.php @@ -15,6 +15,5 @@ class RegistrationTest extends TestCase { $this->post("/register", ["email" => "a@a.com", "username" => "aaa", "password" => "password"])->assertRedirect(); $this->post("/login", ["email" => "a@a.com", "username" => "aaa", "password" => "password"])->assertRedirect("/"); - } }