create(); $this->assertDatabaseMissing('permission_assignments', [ 'assignee_id' => $user->id, 'assignee_type' => $user->getMorphClass(), 'permission' => 'global:admin', 'scope_id' => null, 'scope_type' => null, ]); } public function test_user_avatar_attribute_returns_configured_storage_url(): void { config([ 'filesystems.avatar_disk' => 'avatar-test', 'filesystems.avatar_urls_temporary' => false, ]); Storage::fake('avatar-test'); $user = User::factory()->create([ 'avatar_path' => 'avatars/generated/example.svg', 'avatar_color' => '#F97316', 'avatar_is_generated' => true, ]); $this->assertSame( Storage::disk('avatar-test')->url('avatars/generated/example.svg'), $user->avatar, ); $this->assertArrayNotHasKey('avatar_path', $user->toArray()); } public function test_user_avatar_attribute_returns_temporary_url_for_private_storage(): void { config([ 'filesystems.avatar_disk' => 's3', 'filesystems.avatar_urls_temporary' => true, 'filesystems.avatar_url_expiration_minutes' => 10, ]); $disk = Mockery::mock(FilesystemAdapter::class); $disk->shouldReceive('temporaryUrl') ->once() ->with( 'avatars/uploads/private.png', Mockery::on(fn (mixed $expiration): bool => $expiration instanceof DateTimeInterface), ) ->andReturn('https://object-storage.test/signed-avatar'); Storage::shouldReceive('disk')->once()->with('s3')->andReturn($disk); $user = User::factory()->create([ 'avatar_path' => 'avatars/uploads/private.png', 'avatar_is_generated' => false, ]); $this->assertSame('https://object-storage.test/signed-avatar', $user->avatar); } public function test_user_can_identify_uploaded_avatars(): void { $generated = User::factory()->make([ 'avatar_path' => 'avatars/generated/example.svg', 'avatar_is_generated' => true, ]); $uploaded = User::factory()->make([ 'avatar_path' => 'avatars/uploads/example.png', 'avatar_is_generated' => false, ]); $missing = User::factory()->make([ 'avatar_path' => null, 'avatar_is_generated' => false, ]); $this->assertFalse($generated->hasUploadedAvatar()); $this->assertTrue($uploaded->hasUploadedAvatar()); $this->assertFalse($missing->hasUploadedAvatar()); } public function test_avatar_migration_backfills_existing_users(): void { Storage::fake('public'); Schema::table('users', function (Blueprint $table): void { $table->dropColumn([ 'avatar_path', 'avatar_color', 'avatar_is_generated', ]); }); DB::table('users')->insert([ 'name' => 'Legacy User', 'email' => 'legacy@example.com', 'email_verified_at' => now(), 'password' => 'password', 'must_change_password_on_next_login' => false, 'remember_token' => null, 'created_at' => now(), 'updated_at' => now(), ]); $avatarFieldsMigration = require database_path('migrations/2026_06_24_114831_add_avatar_fields_to_users_table.php'); $avatarFieldsMigration->up(); $backfillMigration = require database_path('migrations/2026_06_24_123911_backfill_existing_user_avatars.php'); $backfillMigration->up(); $user = DB::table('users')->where('email', 'legacy@example.com')->first(); $this->assertNotNull($user->avatar_path); $this->assertNotNull($user->avatar_color); $this->assertTrue((bool) $user->avatar_is_generated); Storage::disk('public')->assertExists($user->avatar_path); $this->assertStringContainsString('LU', Storage::disk('public')->get($user->avatar_path)); } /** * Ensure the admin factory state creates admin users. */ public function test_admin_factory_state_creates_admin_users(): void { $user = User::factory()->admin()->create(); $this->assertDatabaseHas('permission_assignments', [ 'assignee_id' => $user->id, 'assignee_type' => $user->getMorphClass(), 'permission' => 'global:admin', 'scope_id' => null, 'scope_type' => null, ]); } /** * Ensure the user seeder creates both admin and regular users. */ public function test_user_seeder_creates_admin_and_regular_users(): void { Storage::fake('public'); $this->seed(UserSeeder::class); $admin = User::query()->where('email', 'admin@kanbangit.test')->firstOrFail(); $member = User::query()->where('email', 'test@example.com')->firstOrFail(); $this->assertDatabaseHas('permission_assignments', [ 'assignee_id' => $admin->id, 'assignee_type' => $admin->getMorphClass(), 'permission' => 'global:admin', 'scope_id' => null, 'scope_type' => null, ]); $this->assertDatabaseHas('permission_assignments', [ 'assignee_id' => $member->id, 'assignee_type' => $member->getMorphClass(), 'permission' => 'project:create', 'scope_id' => null, 'scope_type' => null, ]); User::query() ->get() ->each(function (User $user): void { $this->assertTrue($user->avatar_is_generated); $this->assertNotNull($user->avatar_path); $this->assertNotNull($user->avatar_color); Storage::disk('public')->assertExists($user->avatar_path); }); } /** * Ensure users can belong to teams. */ public function test_users_can_belong_to_teams(): void { $user = User::factory()->create(); $teams = Team::factory()->count(2)->create(); $user->teams()->sync($teams->modelKeys()); $this->assertCount(2, $user->fresh()->teams); } }