Files
KanbanGit/tests/Feature/Models/UserTest.php
T
Twyco afe99ea6d4
CI/CD / test_and_check_app (push) Failing after 7m26s
CI/CD / Publish immutable image (push) Skipped
feat: add containerized deployment workflow
2026-08-22 08:57:18 +02:00

210 lines
6.9 KiB
PHP

<?php
namespace Tests\Feature\Models;
use App\Models\Team;
use App\Models\User;
use Database\Seeders\UserSeeder;
use DateTimeInterface;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
use Mockery;
use Tests\TestCase;
class UserTest extends TestCase
{
use RefreshDatabase;
/**
* Ensure the default user factory does not grant global admin access.
*/
public function test_user_factory_defaults_to_no_global_admin_access(): void
{
$user = User::factory()->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);
}
}