From f0feee719b28850a078a3c802d29b62023dd5c3d Mon Sep 17 00:00:00 2001 From: Jordan Partridge Date: Sun, 14 Dec 2025 21:40:46 -0700 Subject: [PATCH] Add User model with authentication support and tests --- app/Models/User.php | 43 +++++++++++++++++ database/factories/UserFactory.php | 38 +++++++++++++++ .../2025_12_15_043947_create_users_table.php | 28 +++++++++++ tests/Feature/UserTest.php | 46 +++++++++++++++++++ 4 files changed, 155 insertions(+) create mode 100644 app/Models/User.php create mode 100644 database/factories/UserFactory.php create mode 100644 database/migrations/2025_12_15_043947_create_users_table.php create mode 100644 tests/Feature/UserTest.php diff --git a/app/Models/User.php b/app/Models/User.php new file mode 100644 index 0000000..64f5073 --- /dev/null +++ b/app/Models/User.php @@ -0,0 +1,43 @@ + */ + use HasFactory, Notifiable; + + /** + * @var list + */ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + /** + * @var list + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * @return array + */ + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; + } +} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 0000000..023894d --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,38 @@ + + */ +class UserFactory extends Factory +{ + protected static ?string $password; + + /** + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/database/migrations/2025_12_15_043947_create_users_table.php b/database/migrations/2025_12_15_043947_create_users_table.php new file mode 100644 index 0000000..fe2e91e --- /dev/null +++ b/database/migrations/2025_12_15_043947_create_users_table.php @@ -0,0 +1,28 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('users'); + } +}; diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php new file mode 100644 index 0000000..a0f1d8b --- /dev/null +++ b/tests/Feature/UserTest.php @@ -0,0 +1,46 @@ +create(); + + expect($user)->toBeInstanceOf(User::class); + expect($user->name)->toBeString(); + expect($user->email)->toBeString(); + }); + + it('hides password and remember_token in serialization', function () { + $user = User::factory()->create(); + $array = $user->toArray(); + + expect($array)->not->toHaveKey('password'); + expect($array)->not->toHaveKey('remember_token'); + }); + + it('casts email_verified_at to datetime', function () { + $user = User::factory()->create([ + 'email_verified_at' => now(), + ]); + + expect($user->email_verified_at)->toBeInstanceOf(\Illuminate\Support\Carbon::class); + }); + + it('hashes password automatically', function () { + $user = User::factory()->create([ + 'password' => 'plain-text-password', + ]); + + expect($user->password)->not->toBe('plain-text-password'); + expect(password_verify('plain-text-password', $user->password))->toBeTrue(); + }); + + it('can create unverified user', function () { + $user = User::factory()->unverified()->create(); + + expect($user->email_verified_at)->toBeNull(); + }); +});