create(); $workspace = Workspace::factory()->create(); $workspace->users()->attach($user->id, [ 'role' => $role, 'permissions' => $permissions, ]); session(['current_workspace_id' => $workspace->id]); return [$user, $workspace]; } // ── Team Index ────────────────────────────────────────────── test('owner can view team index page', function () { [$user, $workspace] = setupTeamUser(WorkspaceUserRole::Owner); $response = $this->actingAs($user)->get(route('team.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('team/Index') ->has('members') ->has('pendingInvitations') ->where('canManageTeam', true) ->has('inviteUrl') ->has('roles') ); }); test('manager with can_manage_team can view team index page', function () { [$user] = setupTeamUser(WorkspaceUserRole::Manager, [ Permission::CanManageTeam => true, ]); $response = $this->actingAs($user)->get(route('team.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('team/Index') ->where('canManageTeam', true) ); }); test('manager without can_manage_team can view team index but canManageTeam is false', function () { [$user] = setupTeamUser(WorkspaceUserRole::Manager, [ Permission::CanManageTeam => false, ]); $response = $this->actingAs($user)->get(route('team.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('team/Index') ->where('canManageTeam', false) ); }); test('worker receives 404 on team index', function () { [$user] = setupTeamUser(WorkspaceUserRole::Worker); $response = $this->actingAs($user)->get(route('team.index')); $response->assertNotFound(); }); // ── Invite Member ─────────────────────────────────────────── test('owner can invite a new member', function () { Mail::fake(); [$user, $workspace] = setupTeamUser(WorkspaceUserRole::Owner); $response = $this->actingAs($user)->post(route('team.invite'), [ 'email' => 'newmember@example.com', 'role' => 'worker', ]); $response->assertRedirect(); $response->assertSessionHas('success', 'Invitation envoyée'); expect(TeamInvitation::where('workspace_id', $workspace->id) ->where('email', 'newmember@example.com') ->exists() )->toBeTrue(); Mail::assertQueued(TeamInvitationMail::class, function ($mail) { return $mail->hasTo('newmember@example.com'); }); }); test('manager with can_manage_team can invite a new member', function () { Mail::fake(); [$user, $workspace] = setupTeamUser(WorkspaceUserRole::Manager, [ Permission::CanManageTeam => true, ]); $response = $this->actingAs($user)->post(route('team.invite'), [ 'email' => 'newmanager@example.com', 'role' => 'manager', ]); $response->assertRedirect(); $response->assertSessionHas('success', 'Invitation envoyée'); expect(TeamInvitation::where('workspace_id', $workspace->id) ->where('email', 'newmanager@example.com') ->where('role', 'manager') ->exists() )->toBeTrue(); }); test('manager without permission gets 404 on invite', function () { [$user] = setupTeamUser(WorkspaceUserRole::Manager, [ Permission::CanManageTeam => false, ]); $response = $this->actingAs($user)->post(route('team.invite'), [ 'email' => 'blocked@example.com', 'role' => 'worker', ]); $response->assertNotFound(); }); test('worker gets 404 on invite', function () { [$user] = setupTeamUser(WorkspaceUserRole::Worker); $response = $this->actingAs($user)->post(route('team.invite'), [ 'email' => 'blocked@example.com', 'role' => 'worker', ]); $response->assertNotFound(); }); test('cannot invite email already in workspace', function () { [$user, $workspace] = setupTeamUser(WorkspaceUserRole::Owner); $existingMember = User::factory()->create(['email' => 'existing@example.com']); $workspace->users()->attach($existingMember->id, ['role' => 'worker']); $response = $this->actingAs($user)->post(route('team.invite'), [ 'email' => 'existing@example.com', 'role' => 'worker', ]); $response->assertSessionHasErrors('email'); }); test('invitation creates team invitation record with correct data', function () { Mail::fake(); [$user, $workspace] = setupTeamUser(WorkspaceUserRole::Owner); $this->actingAs($user)->post(route('team.invite'), [ 'email' => 'record@example.com', 'role' => 'manager', ]); $invitation = TeamInvitation::where('email', 'record@example.com')->first(); expect($invitation)->not->toBeNull() ->and($invitation->workspace_id)->toBe($workspace->id) ->and($invitation->role)->toBe('manager') ->and($invitation->invited_by)->toBe($user->id) ->and($invitation->token)->not->toBeEmpty() ->and($invitation->expires_at)->not->toBeNull() ->and($invitation->accepted_at)->toBeNull(); }); test('invitation sends email', function () { Mail::fake(); [$user] = setupTeamUser(WorkspaceUserRole::Owner); $this->actingAs($user)->post(route('team.invite'), [ 'email' => 'mailto@example.com', 'role' => 'worker', ]); Mail::assertQueued(TeamInvitationMail::class, function ($mail) { return $mail->hasTo('mailto@example.com'); }); }); test('cannot send duplicate active invitation to same email', function () { Mail::fake(); [$user, $workspace] = setupTeamUser(WorkspaceUserRole::Owner); // Create an existing active invitation TeamInvitation::create([ 'workspace_id' => $workspace->id, 'email' => 'duplicate@example.com', 'role' => 'worker', 'invited_by' => $user->id, 'expires_at' => now()->addDays(7), ]); $response = $this->actingAs($user)->post(route('team.invite'), [ 'email' => 'duplicate@example.com', 'role' => 'manager', ]); $response->assertSessionHasErrors('email'); });