create(); $member = User::factory()->create(); $workspace = Workspace::factory()->create(); $workspace->users()->attach($owner->id, ['role' => 'owner']); $workspace->users()->attach($member->id, ['role' => 'member']); $workspace->transferOwnership($member, $owner); // Previous owner should now be admin $this->assertEquals( 'admin', $workspace->members()->where('user_id', $owner->id)->first()->role ); // New owner should now be owner $this->assertEquals( 'owner', $workspace->members()->where('user_id', $member->id)->first()->role ); Event::assertDispatched(WorkspaceOwnershipTransferred::class, function ($event) use ($workspace, $owner, $member) { return $event->workspace->id === $workspace->id && $event->previousOwner->id === $owner->id && $event->newOwner->id === $member->id; }); } public function test_owner_can_transfer_ownership_to_admin(): void { Event::fake(); $owner = User::factory()->create(); $admin = User::factory()->create(); $workspace = Workspace::factory()->create(); $workspace->users()->attach($owner->id, ['role' => 'owner']); $workspace->users()->attach($admin->id, ['role' => 'admin']); $workspace->transferOwnership($admin, $owner); $this->assertEquals( 'admin', $workspace->members()->where('user_id', $owner->id)->first()->role ); $this->assertEquals( 'owner', $workspace->members()->where('user_id', $admin->id)->first()->role ); } public function test_transfer_without_acting_user_skips_auth_check(): void { Event::fake(); $owner = User::factory()->create(); $member = User::factory()->create(); $workspace = Workspace::factory()->create(); $workspace->users()->attach($owner->id, ['role' => 'owner']); $workspace->users()->attach($member->id, ['role' => 'member']); // No acting user passed - should succeed (for programmatic use) $workspace->transferOwnership($member); $this->assertEquals( 'owner', $workspace->members()->where('user_id', $member->id)->first()->role ); } public function test_non_owner_cannot_transfer_ownership(): void { $owner = User::factory()->create(); $admin = User::factory()->create(); $member = User::factory()->create(); $workspace = Workspace::factory()->create(); $workspace->users()->attach($owner->id, ['role' => 'owner']); $workspace->users()->attach($admin->id, ['role' => 'admin']); $workspace->users()->attach($member->id, ['role' => 'member']); $this->expectException(WorkspaceOwnershipException::class); $this->expectExceptionCode(403); $workspace->transferOwnership($member, $admin); } public function test_cannot_transfer_to_non_member(): void { $owner = User::factory()->create(); $nonMember = User::factory()->create(); $workspace = Workspace::factory()->create(); $workspace->users()->attach($owner->id, ['role' => 'owner']); $this->expectException(WorkspaceOwnershipException::class); $this->expectExceptionCode(422); $workspace->transferOwnership($nonMember, $owner); } public function test_cannot_transfer_to_self(): void { $owner = User::factory()->create(); $workspace = Workspace::factory()->create(); $workspace->users()->attach($owner->id, ['role' => 'owner']); $this->expectException(WorkspaceOwnershipException::class); $this->expectExceptionCode(422); $workspace->transferOwnership($owner, $owner); } public function test_transfer_updates_team_assignments(): void { Event::fake(); $owner = User::factory()->create(); $member = User::factory()->create(); $workspace = Workspace::factory()->create(); // Create system teams $ownerTeam = WorkspaceTeam::create([ 'workspace_id' => $workspace->id, 'name' => 'Owner', 'slug' => WorkspaceTeam::TEAM_OWNER, 'is_system' => true, 'colour' => 'violet', 'sort_order' => 1, ]); $adminTeam = WorkspaceTeam::create([ 'workspace_id' => $workspace->id, 'name' => 'Admin', 'slug' => WorkspaceTeam::TEAM_ADMIN, 'is_system' => true, 'colour' => 'blue', 'sort_order' => 2, ]); $memberTeam = WorkspaceTeam::create([ 'workspace_id' => $workspace->id, 'name' => 'Member', 'slug' => WorkspaceTeam::TEAM_MEMBER, 'is_system' => true, 'is_default' => true, 'colour' => 'emerald', 'sort_order' => 3, ]); // Attach users with teams $workspace->users()->attach($owner->id, [ 'role' => 'owner', 'team_id' => $ownerTeam->id, ]); $workspace->users()->attach($member->id, [ 'role' => 'member', 'team_id' => $memberTeam->id, ]); $workspace->transferOwnership($member, $owner); // Previous owner should be in admin team $ownerMembership = $workspace->members()->where('user_id', $owner->id)->first(); $this->assertEquals('admin', $ownerMembership->role); $this->assertEquals($adminTeam->id, $ownerMembership->team_id); // New owner should be in owner team $memberMembership = $workspace->members()->where('user_id', $member->id)->first(); $this->assertEquals('owner', $memberMembership->role); $this->assertEquals($ownerTeam->id, $memberMembership->team_id); } public function test_transfer_dispatches_event(): void { Event::fake(); $owner = User::factory()->create(); $member = User::factory()->create(); $workspace = Workspace::factory()->create(); $workspace->users()->attach($owner->id, ['role' => 'owner']); $workspace->users()->attach($member->id, ['role' => 'member']); $workspace->transferOwnership($member, $owner); Event::assertDispatched(WorkspaceOwnershipTransferred::class, 1); } public function test_failed_transfer_does_not_dispatch_event(): void { Event::fake(); $owner = User::factory()->create(); $nonMember = User::factory()->create(); $workspace = Workspace::factory()->create(); $workspace->users()->attach($owner->id, ['role' => 'owner']); try { $workspace->transferOwnership($nonMember, $owner); } catch (WorkspaceOwnershipException) { // Expected } Event::assertNotDispatched(WorkspaceOwnershipTransferred::class); } public function test_transfer_returns_workspace_instance(): void { Event::fake(); $owner = User::factory()->create(); $member = User::factory()->create(); $workspace = Workspace::factory()->create(); $workspace->users()->attach($owner->id, ['role' => 'owner']); $workspace->users()->attach($member->id, ['role' => 'member']); $result = $workspace->transferOwnership($member, $owner); $this->assertInstanceOf(Workspace::class, $result); $this->assertEquals($workspace->id, $result->id); } }