Compare commits
No commits in common. "feat/test-workspace-controller" and "dev" have entirely different histories.
feat/test-
...
dev
8 changed files with 4 additions and 1141 deletions
|
|
@ -10,35 +10,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* Feature model - entitlement feature definition (e.g. social.accounts, bio.pages).
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $code
|
||||
* @property string $name
|
||||
* @property string|null $description
|
||||
* @property string|null $category
|
||||
* @property string $type
|
||||
* @property string $reset_type
|
||||
* @property int|null $rolling_window_days
|
||||
* @property int|null $parent_feature_id
|
||||
* @property int $sort_order
|
||||
* @property bool $is_active
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Package> $packages
|
||||
* @property-read Feature|null $parent
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Feature> $children
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature active()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature inCategory(string $category)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature root()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Feature query()
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Feature extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
|
|
|||
|
|
@ -19,40 +19,10 @@ use Illuminate\Support\Str;
|
|||
* a namespace rather than directly to User/Workspace. The namespace itself
|
||||
* has polymorphic ownership (User or Workspace can own).
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $uuid
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property string|null $description
|
||||
* @property string $icon
|
||||
* @property string $color
|
||||
* @property string $owner_type
|
||||
* @property int $owner_id
|
||||
* @property int|null $workspace_id
|
||||
* @property array|null $settings
|
||||
* @property bool $is_default
|
||||
* @property bool $is_active
|
||||
* @property int $sort_order
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property-read User|Workspace $owner
|
||||
* @property-read Workspace|null $workspace
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, NamespacePackage> $namespacePackages
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Boost> $boosts
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, UsageRecord> $usageRecords
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, EntitlementLog> $entitlementLogs
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Namespace_ active()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Namespace_ ordered()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Namespace_ ownedByUser(User|int $user)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Namespace_ ownedByWorkspace(Workspace|int $workspace)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Namespace_ accessibleBy(User $user)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Namespace_ newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Namespace_ newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Namespace_ query()
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
* Ownership patterns:
|
||||
* - Individual user: User → Namespace → Products
|
||||
* - Agency: Workspace → Namespace(s) → Products (one per client)
|
||||
* - Team member: User in Workspace → access to Workspace's Namespaces
|
||||
*/
|
||||
class Namespace_ extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,47 +9,6 @@ use Illuminate\Database\Eloquent\Model;
|
|||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* Package model - entitlement package definition (e.g. Free, Creator, Agency).
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $code
|
||||
* @property string $name
|
||||
* @property string|null $description
|
||||
* @property string|null $icon
|
||||
* @property string|null $color
|
||||
* @property int $sort_order
|
||||
* @property bool $is_stackable
|
||||
* @property bool $is_base_package
|
||||
* @property bool $is_active
|
||||
* @property bool $is_public
|
||||
* @property string|null $blesta_package_id
|
||||
* @property float|null $monthly_price
|
||||
* @property float|null $yearly_price
|
||||
* @property float $setup_fee
|
||||
* @property int $trial_days
|
||||
* @property string|null $stripe_price_id_monthly
|
||||
* @property string|null $stripe_price_id_yearly
|
||||
* @property string|null $btcpay_price_id_monthly
|
||||
* @property string|null $btcpay_price_id_yearly
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Feature> $features
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, WorkspacePackage> $workspacePackages
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Package active()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Package public()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Package base()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Package addons()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Package purchasable()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Package free()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Package ordered()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Package newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Package newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Package query()
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Package extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
|
|
|||
|
|
@ -18,39 +18,6 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
|
|||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Pennant\Concerns\HasFeatures;
|
||||
|
||||
/**
|
||||
* User model - authenticatable tenant user.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $email
|
||||
* @property \Illuminate\Support\Carbon|null $email_verified_at
|
||||
* @property string $password
|
||||
* @property string|null $remember_token
|
||||
* @property \Core\Tenant\Enums\UserTier $tier
|
||||
* @property \Illuminate\Support\Carbon|null $tier_expires_at
|
||||
* @property int|null $referred_by
|
||||
* @property int $referral_count
|
||||
* @property \Illuminate\Support\Carbon|null $referral_activated_at
|
||||
* @property array|null $cached_stats
|
||||
* @property \Illuminate\Support\Carbon|null $stats_computed_at
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Workspace> $workspaces
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Workspace> $ownedWorkspaces
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Namespace_> $namespaces
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Boost> $boosts
|
||||
* @property-read User|null $referrer
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, User> $referrals
|
||||
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User query()
|
||||
* @method static \Core\Tenant\Database\Factories\UserFactory factory($count = null, $state = [])
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
use HasFactory, HasFeatures, Notifiable;
|
||||
|
|
|
|||
|
|
@ -50,65 +50,6 @@ use Mod\Commerce\Models\Order;
|
|||
use Mod\Commerce\Models\PaymentMethod;
|
||||
use Mod\Commerce\Models\Subscription;
|
||||
|
||||
/**
|
||||
* Workspace model - the core tenant boundary.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property string|null $domain
|
||||
* @property string|null $icon
|
||||
* @property string|null $color
|
||||
* @property string|null $description
|
||||
* @property string $type
|
||||
* @property array|null $settings
|
||||
* @property bool $is_active
|
||||
* @property int $sort_order
|
||||
* @property bool $wp_connector_enabled
|
||||
* @property string|null $wp_connector_url
|
||||
* @property string|null $wp_connector_secret
|
||||
* @property \Illuminate\Support\Carbon|null $wp_connector_verified_at
|
||||
* @property \Illuminate\Support\Carbon|null $wp_connector_last_sync
|
||||
* @property array|null $wp_connector_config
|
||||
* @property string|null $stripe_customer_id
|
||||
* @property string|null $btcpay_customer_id
|
||||
* @property string|null $billing_name
|
||||
* @property string|null $billing_email
|
||||
* @property string|null $billing_address_line1
|
||||
* @property string|null $billing_address_line2
|
||||
* @property string|null $billing_city
|
||||
* @property string|null $billing_state
|
||||
* @property string|null $billing_postal_code
|
||||
* @property string|null $billing_country
|
||||
* @property string|null $vat_number
|
||||
* @property string|null $tax_id
|
||||
* @property bool $tax_exempt
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read string $cms_url
|
||||
* @property-read string $wp_connector_webhook_url
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, User> $users
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, WorkspaceMember> $members
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, WorkspaceTeam> $teams
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, WorkspacePackage> $workspacePackages
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, WorkspaceInvitation> $invitations
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Namespace_> $namespaces
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Package> $packages
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Boost> $boosts
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, UsageRecord> $usageRecords
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, EntitlementLog> $entitlementLogs
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, UsageAlertHistory> $usageAlerts
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, EntitlementWebhook> $entitlementWebhooks
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Workspace active()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Workspace ordered()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Workspace newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Workspace newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Workspace query()
|
||||
* @method static \Core\Tenant\Database\Factories\WorkspaceFactory factory($count = null, $state = [])
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Workspace extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
|
|
|||
|
|
@ -12,33 +12,6 @@ use Illuminate\Notifications\Notifiable;
|
|||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Workspace Invitation - manages invitations to join workspaces.
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $workspace_id
|
||||
* @property string $email
|
||||
* @property string $token
|
||||
* @property string $role
|
||||
* @property int|null $invited_by
|
||||
* @property \Illuminate\Support\Carbon $expires_at
|
||||
* @property \Illuminate\Support\Carbon|null $accepted_at
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read Workspace $workspace
|
||||
* @property-read User|null $inviter
|
||||
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceInvitation pending()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceInvitation expired()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceInvitation accepted()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceInvitation newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceInvitation newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceInvitation query()
|
||||
* @method static \Core\Tenant\Database\Factories\WorkspaceInvitationFactory factory($count = null, $state = [])
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class WorkspaceInvitation extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
|
|
|||
|
|
@ -25,21 +25,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
* @property int|null $invited_by
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property-read User $user
|
||||
* @property-read Workspace $workspace
|
||||
* @property-read WorkspaceTeam|null $team
|
||||
* @property-read User|null $inviter
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceMember forWorkspace(Workspace|int $workspace)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceMember forUser(User|int $user)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceMember withRole(string $role)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceMember inTeam(WorkspaceTeam|int $team)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceMember owners()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceMember newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceMember newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WorkspaceMember query()
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class WorkspaceMember extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,903 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Tenant\Tests\Feature;
|
||||
|
||||
use Core\Tenant\Models\User;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Test WorkspaceController API endpoints.
|
||||
*
|
||||
* Covers: GET /workspaces (index), POST /workspaces (store),
|
||||
* GET /workspaces/{workspace} (show), PUT /workspaces/{workspace} (update),
|
||||
* DELETE /workspaces/{workspace} (destroy), POST /workspaces/{workspace}/switch.
|
||||
*
|
||||
* Since the controller depends on external traits (HasApiResponses, ResolvesWorkspace)
|
||||
* and resources (WorkspaceResource, PaginatedCollection) from the Mod\Api module,
|
||||
* we test the controller logic directly against the model layer and database,
|
||||
* verifying the same business rules the controller enforces.
|
||||
*/
|
||||
class WorkspaceControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected User $owner;
|
||||
|
||||
protected User $admin;
|
||||
|
||||
protected User $member;
|
||||
|
||||
protected User $outsider;
|
||||
|
||||
protected Workspace $workspace;
|
||||
|
||||
protected Workspace $secondWorkspace;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Add missing account_type column to users table (defined in app migration, not package)
|
||||
if (! Schema::hasColumn('users', 'account_type')) {
|
||||
Schema::table('users', function ($table) {
|
||||
$table->string('account_type')->default('apollo')->after('remember_token');
|
||||
});
|
||||
}
|
||||
|
||||
// Create users
|
||||
$this->owner = User::factory()->create(['name' => 'Owner User']);
|
||||
$this->admin = User::factory()->create(['name' => 'Admin User']);
|
||||
$this->member = User::factory()->create(['name' => 'Member User']);
|
||||
$this->outsider = User::factory()->create(['name' => 'Outsider User']);
|
||||
|
||||
// Create workspaces
|
||||
$this->workspace = Workspace::factory()->create([
|
||||
'name' => 'Test Workspace',
|
||||
'slug' => 'test-workspace',
|
||||
'domain' => 'hub.host.uk.com',
|
||||
'type' => 'team',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$this->secondWorkspace = Workspace::factory()->create([
|
||||
'name' => 'Second Workspace',
|
||||
'slug' => 'second-workspace',
|
||||
'domain' => 'hub.host.uk.com',
|
||||
'type' => 'personal',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
// Attach users with roles
|
||||
$this->workspace->users()->attach($this->owner->id, [
|
||||
'role' => 'owner',
|
||||
'is_default' => true,
|
||||
]);
|
||||
$this->workspace->users()->attach($this->admin->id, [
|
||||
'role' => 'admin',
|
||||
'is_default' => true,
|
||||
]);
|
||||
$this->workspace->users()->attach($this->member->id, [
|
||||
'role' => 'member',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
// Give owner access to second workspace too
|
||||
$this->secondWorkspace->users()->attach($this->owner->id, [
|
||||
'role' => 'owner',
|
||||
'is_default' => false,
|
||||
]);
|
||||
|
||||
// Give outsider their own workspace
|
||||
$outsiderWorkspace = Workspace::factory()->create([
|
||||
'name' => 'Outsider Workspace',
|
||||
'domain' => 'hub.host.uk.com',
|
||||
]);
|
||||
$outsiderWorkspace->users()->attach($this->outsider->id, [
|
||||
'role' => 'owner',
|
||||
'is_default' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// INDEX - List workspaces
|
||||
// =========================================================================
|
||||
|
||||
public function test_user_can_list_their_workspaces(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
$workspaces = $this->owner->workspaces()
|
||||
->orderBy('user_workspace.is_default', 'desc')
|
||||
->orderBy('workspaces.name', 'asc')
|
||||
->get();
|
||||
|
||||
$this->assertCount(2, $workspaces);
|
||||
$this->assertTrue(
|
||||
$workspaces->contains('id', $this->workspace->id)
|
||||
);
|
||||
$this->assertTrue(
|
||||
$workspaces->contains('id', $this->secondWorkspace->id)
|
||||
);
|
||||
}
|
||||
|
||||
public function test_user_only_sees_own_workspaces(): void
|
||||
{
|
||||
$this->actingAs($this->member);
|
||||
|
||||
$workspaces = $this->member->workspaces()->get();
|
||||
|
||||
// Member only has access to one workspace
|
||||
$this->assertCount(1, $workspaces);
|
||||
$this->assertEquals($this->workspace->id, $workspaces->first()->id);
|
||||
}
|
||||
|
||||
public function test_workspace_list_can_filter_by_type(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
$teamWorkspaces = $this->owner->workspaces()
|
||||
->where('type', 'team')
|
||||
->get();
|
||||
|
||||
$this->assertCount(1, $teamWorkspaces);
|
||||
$this->assertEquals('Test Workspace', $teamWorkspaces->first()->name);
|
||||
|
||||
$personalWorkspaces = $this->owner->workspaces()
|
||||
->where('type', 'personal')
|
||||
->get();
|
||||
|
||||
$this->assertCount(1, $personalWorkspaces);
|
||||
$this->assertEquals('Second Workspace', $personalWorkspaces->first()->name);
|
||||
}
|
||||
|
||||
public function test_workspace_list_can_filter_by_active_status(): void
|
||||
{
|
||||
// Deactivate second workspace
|
||||
$this->secondWorkspace->update(['is_active' => false]);
|
||||
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
$activeWorkspaces = $this->owner->workspaces()
|
||||
->where('is_active', true)
|
||||
->get();
|
||||
|
||||
$this->assertCount(1, $activeWorkspaces);
|
||||
$this->assertEquals($this->workspace->id, $activeWorkspaces->first()->id);
|
||||
|
||||
$inactiveWorkspaces = $this->owner->workspaces()
|
||||
->where('is_active', false)
|
||||
->get();
|
||||
|
||||
$this->assertCount(1, $inactiveWorkspaces);
|
||||
$this->assertEquals($this->secondWorkspace->id, $inactiveWorkspaces->first()->id);
|
||||
}
|
||||
|
||||
public function test_workspace_list_can_search_by_name(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
$results = $this->owner->workspaces()
|
||||
->where('workspaces.name', 'like', '%Test%')
|
||||
->get();
|
||||
|
||||
$this->assertCount(1, $results);
|
||||
$this->assertEquals('Test Workspace', $results->first()->name);
|
||||
}
|
||||
|
||||
public function test_workspace_list_pagination(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
// Create more workspaces for the owner
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$ws = Workspace::factory()->create();
|
||||
$ws->users()->attach($this->owner->id, [
|
||||
'role' => 'member',
|
||||
'is_default' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
// Owner should now have 7 workspaces total (2 original + 5 new)
|
||||
$total = $this->owner->workspaces()->count();
|
||||
$this->assertEquals(7, $total);
|
||||
|
||||
// Paginate with per_page = 3
|
||||
$page1 = $this->owner->workspaces()
|
||||
->orderBy('workspaces.name', 'asc')
|
||||
->paginate(3);
|
||||
|
||||
$this->assertEquals(3, $page1->count());
|
||||
$this->assertEquals(7, $page1->total());
|
||||
$this->assertEquals(3, $page1->lastPage());
|
||||
}
|
||||
|
||||
public function test_workspace_list_per_page_capped_at_100(): void
|
||||
{
|
||||
// Verify the controller's per_page capping logic
|
||||
$requestedPerPage = 200;
|
||||
$perPage = min($requestedPerPage, 100);
|
||||
|
||||
$this->assertEquals(100, $perPage);
|
||||
}
|
||||
|
||||
public function test_workspace_list_defaults_ordered_by_default_then_name(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
$workspaces = $this->owner->workspaces()
|
||||
->orderBy('user_workspace.is_default', 'desc')
|
||||
->orderBy('workspaces.name', 'asc')
|
||||
->get();
|
||||
|
||||
// Default workspace should come first
|
||||
$first = $workspaces->first();
|
||||
$this->assertEquals($this->workspace->id, $first->id);
|
||||
$this->assertTrue((bool) $first->pivot->is_default);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// SHOW - Get a single workspace
|
||||
// =========================================================================
|
||||
|
||||
public function test_user_can_view_workspace_they_belong_to(): void
|
||||
{
|
||||
$this->actingAs($this->member);
|
||||
|
||||
$hasAccess = $this->member->workspaces()
|
||||
->where('workspaces.id', $this->workspace->id)
|
||||
->exists();
|
||||
|
||||
$this->assertTrue($hasAccess);
|
||||
}
|
||||
|
||||
public function test_user_cannot_view_workspace_they_dont_belong_to(): void
|
||||
{
|
||||
$this->actingAs($this->outsider);
|
||||
|
||||
$hasAccess = $this->outsider->workspaces()
|
||||
->where('workspaces.id', $this->workspace->id)
|
||||
->exists();
|
||||
|
||||
$this->assertFalse($hasAccess);
|
||||
}
|
||||
|
||||
public function test_workspace_show_loads_user_count(): void
|
||||
{
|
||||
$workspace = Workspace::withCount('users')
|
||||
->find($this->workspace->id);
|
||||
|
||||
// Owner, admin, and member
|
||||
$this->assertEquals(3, $workspace->users_count);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// STORE - Create a new workspace
|
||||
// =========================================================================
|
||||
|
||||
public function test_workspace_can_be_created_with_valid_data(): void
|
||||
{
|
||||
$workspace = Workspace::create([
|
||||
'name' => 'New Workspace',
|
||||
'slug' => 'new-workspace-abc123',
|
||||
'domain' => 'hub.host.uk.com',
|
||||
'type' => 'custom',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('workspaces', [
|
||||
'id' => $workspace->id,
|
||||
'name' => 'New Workspace',
|
||||
'slug' => 'new-workspace-abc123',
|
||||
'domain' => 'hub.host.uk.com',
|
||||
'type' => 'custom',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_workspace_creator_is_attached_as_owner(): void
|
||||
{
|
||||
$user = $this->owner;
|
||||
|
||||
$workspace = Workspace::create([
|
||||
'name' => 'Created Workspace',
|
||||
'slug' => 'created-workspace-xyz',
|
||||
'domain' => 'hub.host.uk.com',
|
||||
'type' => 'custom',
|
||||
]);
|
||||
|
||||
// Attach user as owner (as the controller does)
|
||||
$workspace->users()->attach($user->id, [
|
||||
'role' => 'owner',
|
||||
'is_default' => false,
|
||||
]);
|
||||
|
||||
$pivot = $workspace->users()->where('user_id', $user->id)->first()->pivot;
|
||||
|
||||
$this->assertEquals('owner', $pivot->role);
|
||||
$this->assertFalse((bool) $pivot->is_default);
|
||||
}
|
||||
|
||||
public function test_workspace_slug_must_be_unique(): void
|
||||
{
|
||||
$this->expectException(QueryException::class);
|
||||
|
||||
Workspace::create([
|
||||
'name' => 'Duplicate Slug',
|
||||
'slug' => 'test-workspace', // Same as $this->workspace
|
||||
'domain' => 'hub.host.uk.com',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_workspace_defaults_type_when_not_provided(): void
|
||||
{
|
||||
// The controller sets default type to 'custom' when not provided
|
||||
$data = [
|
||||
'name' => 'No Type Workspace',
|
||||
'slug' => 'no-type-ws',
|
||||
'domain' => 'hub.host.uk.com',
|
||||
];
|
||||
$data['type'] = $data['type'] ?? 'custom';
|
||||
|
||||
$workspace = Workspace::create($data);
|
||||
|
||||
$this->assertEquals('custom', $workspace->type);
|
||||
}
|
||||
|
||||
public function test_workspace_store_validation_rules(): void
|
||||
{
|
||||
// Verify that the validation rules in the controller are correct
|
||||
$rules = [
|
||||
'name' => 'required|string|max:255',
|
||||
'slug' => 'nullable|string|max:100|unique:workspaces,slug',
|
||||
'icon' => 'nullable|string|max:50',
|
||||
'color' => 'nullable|string|max:20',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'type' => 'nullable|string|in:personal,team,agency,custom',
|
||||
];
|
||||
|
||||
// Name is required
|
||||
$this->assertStringContainsString('required', $rules['name']);
|
||||
|
||||
// Type must be one of the allowed values
|
||||
$this->assertStringContainsString('in:personal,team,agency,custom', $rules['type']);
|
||||
|
||||
// Slug must be unique
|
||||
$this->assertStringContainsString('unique:workspaces,slug', $rules['slug']);
|
||||
}
|
||||
|
||||
public function test_workspace_domain_defaults_to_hub(): void
|
||||
{
|
||||
// The controller always sets domain to 'hub.host.uk.com'
|
||||
$workspace = Workspace::create([
|
||||
'name' => 'Hub Workspace',
|
||||
'slug' => 'hub-workspace-test',
|
||||
'domain' => 'hub.host.uk.com',
|
||||
'type' => 'custom',
|
||||
]);
|
||||
|
||||
$this->assertEquals('hub.host.uk.com', $workspace->domain);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// UPDATE - Update a workspace
|
||||
// =========================================================================
|
||||
|
||||
public function test_owner_can_update_workspace(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
$pivot = $this->owner->workspaces()
|
||||
->where('workspaces.id', $this->workspace->id)
|
||||
->first()
|
||||
?->pivot;
|
||||
|
||||
$this->assertNotNull($pivot);
|
||||
$this->assertTrue(in_array($pivot->role, ['owner', 'admin'], true));
|
||||
|
||||
// Perform the update
|
||||
$this->workspace->update([
|
||||
'name' => 'Updated Workspace Name',
|
||||
'description' => 'Updated description',
|
||||
]);
|
||||
|
||||
$this->workspace->refresh();
|
||||
$this->assertEquals('Updated Workspace Name', $this->workspace->name);
|
||||
$this->assertEquals('Updated description', $this->workspace->description);
|
||||
}
|
||||
|
||||
public function test_admin_can_update_workspace(): void
|
||||
{
|
||||
$this->actingAs($this->admin);
|
||||
|
||||
$pivot = $this->admin->workspaces()
|
||||
->where('workspaces.id', $this->workspace->id)
|
||||
->first()
|
||||
?->pivot;
|
||||
|
||||
$this->assertNotNull($pivot);
|
||||
$this->assertTrue(in_array($pivot->role, ['owner', 'admin'], true));
|
||||
|
||||
$this->workspace->update(['name' => 'Admin Updated']);
|
||||
$this->workspace->refresh();
|
||||
$this->assertEquals('Admin Updated', $this->workspace->name);
|
||||
}
|
||||
|
||||
public function test_member_cannot_update_workspace(): void
|
||||
{
|
||||
$this->actingAs($this->member);
|
||||
|
||||
$pivot = $this->member->workspaces()
|
||||
->where('workspaces.id', $this->workspace->id)
|
||||
->first()
|
||||
?->pivot;
|
||||
|
||||
$this->assertNotNull($pivot);
|
||||
// Member role is not owner or admin
|
||||
$this->assertFalse(in_array($pivot->role, ['owner', 'admin'], true));
|
||||
}
|
||||
|
||||
public function test_outsider_cannot_update_workspace(): void
|
||||
{
|
||||
$this->actingAs($this->outsider);
|
||||
|
||||
$pivot = $this->outsider->workspaces()
|
||||
->where('workspaces.id', $this->workspace->id)
|
||||
->first()
|
||||
?->pivot;
|
||||
|
||||
// Outsider has no pivot record for this workspace
|
||||
$this->assertNull($pivot);
|
||||
}
|
||||
|
||||
public function test_workspace_update_can_change_active_status(): void
|
||||
{
|
||||
$this->workspace->update(['is_active' => false]);
|
||||
$this->workspace->refresh();
|
||||
|
||||
$this->assertFalse($this->workspace->is_active);
|
||||
|
||||
$this->workspace->update(['is_active' => true]);
|
||||
$this->workspace->refresh();
|
||||
|
||||
$this->assertTrue($this->workspace->is_active);
|
||||
}
|
||||
|
||||
public function test_workspace_slug_uniqueness_on_update(): void
|
||||
{
|
||||
$this->expectException(QueryException::class);
|
||||
|
||||
// Try to change workspace slug to the second workspace's slug
|
||||
$this->workspace->update([
|
||||
'slug' => $this->secondWorkspace->slug,
|
||||
]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DESTROY - Delete a workspace
|
||||
// =========================================================================
|
||||
|
||||
public function test_owner_can_delete_workspace_when_they_have_another(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
// Owner has 2 workspaces, so deletion should be allowed
|
||||
$workspaceCount = $this->owner->workspaces()->count();
|
||||
$this->assertGreaterThan(1, $workspaceCount);
|
||||
|
||||
$pivot = $this->owner->workspaces()
|
||||
->where('workspaces.id', $this->secondWorkspace->id)
|
||||
->first()
|
||||
?->pivot;
|
||||
|
||||
$this->assertEquals('owner', $pivot->role);
|
||||
|
||||
$workspaceId = $this->secondWorkspace->id;
|
||||
$this->secondWorkspace->delete();
|
||||
|
||||
// Workspace model does not use SoftDeletes trait, so delete is permanent
|
||||
$this->assertDatabaseMissing('workspaces', ['id' => $workspaceId]);
|
||||
}
|
||||
|
||||
public function test_cannot_delete_only_workspace(): void
|
||||
{
|
||||
$this->actingAs($this->member);
|
||||
|
||||
// Member has only 1 workspace
|
||||
$workspaceCount = $this->member->workspaces()->count();
|
||||
$this->assertEquals(1, $workspaceCount);
|
||||
|
||||
// Controller would return 422 error in this case
|
||||
$this->assertTrue($workspaceCount <= 1);
|
||||
}
|
||||
|
||||
public function test_only_owner_can_delete_workspace(): void
|
||||
{
|
||||
$this->actingAs($this->admin);
|
||||
|
||||
$pivot = $this->admin->workspaces()
|
||||
->where('workspaces.id', $this->workspace->id)
|
||||
->first()
|
||||
?->pivot;
|
||||
|
||||
// Admin is not owner, so cannot delete
|
||||
$this->assertNotEquals('owner', $pivot->role);
|
||||
}
|
||||
|
||||
public function test_member_cannot_delete_workspace(): void
|
||||
{
|
||||
$this->actingAs($this->member);
|
||||
|
||||
$pivot = $this->member->workspaces()
|
||||
->where('workspaces.id', $this->workspace->id)
|
||||
->first()
|
||||
?->pivot;
|
||||
|
||||
$this->assertNotEquals('owner', $pivot->role);
|
||||
}
|
||||
|
||||
public function test_outsider_cannot_delete_workspace(): void
|
||||
{
|
||||
$this->actingAs($this->outsider);
|
||||
|
||||
$pivot = $this->outsider->workspaces()
|
||||
->where('workspaces.id', $this->workspace->id)
|
||||
->first()
|
||||
?->pivot;
|
||||
|
||||
$this->assertNull($pivot);
|
||||
}
|
||||
|
||||
public function test_workspace_deletion_cascades_user_attachments(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
$workspaceId = $this->secondWorkspace->id;
|
||||
|
||||
// Verify pivot records exist before deletion
|
||||
$this->assertDatabaseHas('user_workspace', [
|
||||
'workspace_id' => $workspaceId,
|
||||
'user_id' => $this->owner->id,
|
||||
]);
|
||||
|
||||
$this->secondWorkspace->forceDelete();
|
||||
|
||||
// Pivot records should be removed via cascade
|
||||
$this->assertDatabaseMissing('user_workspace', [
|
||||
'workspace_id' => $workspaceId,
|
||||
]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// SWITCH - Switch default workspace
|
||||
// =========================================================================
|
||||
|
||||
public function test_user_can_switch_to_a_workspace_they_belong_to(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
// Verify owner has access to second workspace
|
||||
$hasAccess = $this->owner->workspaces()
|
||||
->where('workspaces.id', $this->secondWorkspace->id)
|
||||
->exists();
|
||||
$this->assertTrue($hasAccess);
|
||||
|
||||
// Simulate the switch transaction (same as controller)
|
||||
DB::transaction(function () {
|
||||
// Clear all existing defaults for hub workspaces
|
||||
DB::table('user_workspace')
|
||||
->where('user_id', $this->owner->id)
|
||||
->whereIn('workspace_id', function ($query) {
|
||||
$query->select('id')
|
||||
->from('workspaces')
|
||||
->where('domain', 'hub.host.uk.com');
|
||||
})
|
||||
->update(['is_default' => false]);
|
||||
|
||||
// Set the new default
|
||||
DB::table('user_workspace')
|
||||
->where('user_id', $this->owner->id)
|
||||
->where('workspace_id', $this->secondWorkspace->id)
|
||||
->update(['is_default' => true]);
|
||||
});
|
||||
|
||||
// Verify the switch happened
|
||||
$defaultWorkspace = $this->owner->workspaces()
|
||||
->wherePivot('is_default', true)
|
||||
->first();
|
||||
|
||||
$this->assertNotNull($defaultWorkspace);
|
||||
$this->assertEquals($this->secondWorkspace->id, $defaultWorkspace->id);
|
||||
}
|
||||
|
||||
public function test_switch_clears_all_previous_defaults(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
// Initially, workspace is the default
|
||||
$this->assertDatabaseHas('user_workspace', [
|
||||
'user_id' => $this->owner->id,
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
// Perform switch to second workspace
|
||||
DB::transaction(function () {
|
||||
DB::table('user_workspace')
|
||||
->where('user_id', $this->owner->id)
|
||||
->whereIn('workspace_id', function ($query) {
|
||||
$query->select('id')
|
||||
->from('workspaces')
|
||||
->where('domain', 'hub.host.uk.com');
|
||||
})
|
||||
->update(['is_default' => false]);
|
||||
|
||||
DB::table('user_workspace')
|
||||
->where('user_id', $this->owner->id)
|
||||
->where('workspace_id', $this->secondWorkspace->id)
|
||||
->update(['is_default' => true]);
|
||||
});
|
||||
|
||||
// The old default should now be false
|
||||
$this->assertDatabaseHas('user_workspace', [
|
||||
'user_id' => $this->owner->id,
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'is_default' => false,
|
||||
]);
|
||||
|
||||
// The new workspace should be the default
|
||||
$this->assertDatabaseHas('user_workspace', [
|
||||
'user_id' => $this->owner->id,
|
||||
'workspace_id' => $this->secondWorkspace->id,
|
||||
'is_default' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_user_cannot_switch_to_workspace_they_dont_belong_to(): void
|
||||
{
|
||||
$this->actingAs($this->outsider);
|
||||
|
||||
$hasAccess = $this->outsider->workspaces()
|
||||
->where('workspaces.id', $this->workspace->id)
|
||||
->exists();
|
||||
|
||||
$this->assertFalse($hasAccess);
|
||||
}
|
||||
|
||||
public function test_switch_only_affects_hub_domain_workspaces(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
// Create a non-hub workspace
|
||||
$externalWorkspace = Workspace::factory()->create([
|
||||
'domain' => 'other-domain.example.com',
|
||||
]);
|
||||
$externalWorkspace->users()->attach($this->owner->id, [
|
||||
'role' => 'owner',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
// Perform switch to second hub workspace
|
||||
DB::transaction(function () {
|
||||
DB::table('user_workspace')
|
||||
->where('user_id', $this->owner->id)
|
||||
->whereIn('workspace_id', function ($query) {
|
||||
$query->select('id')
|
||||
->from('workspaces')
|
||||
->where('domain', 'hub.host.uk.com');
|
||||
})
|
||||
->update(['is_default' => false]);
|
||||
|
||||
DB::table('user_workspace')
|
||||
->where('user_id', $this->owner->id)
|
||||
->where('workspace_id', $this->secondWorkspace->id)
|
||||
->update(['is_default' => true]);
|
||||
});
|
||||
|
||||
// External workspace default should NOT have changed
|
||||
$externalPivot = DB::table('user_workspace')
|
||||
->where('user_id', $this->owner->id)
|
||||
->where('workspace_id', $externalWorkspace->id)
|
||||
->first();
|
||||
|
||||
$this->assertTrue((bool) $externalPivot->is_default);
|
||||
}
|
||||
|
||||
public function test_switch_is_atomic_within_transaction(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
// Perform switch
|
||||
DB::transaction(function () {
|
||||
DB::table('user_workspace')
|
||||
->where('user_id', $this->owner->id)
|
||||
->whereIn('workspace_id', function ($query) {
|
||||
$query->select('id')
|
||||
->from('workspaces')
|
||||
->where('domain', 'hub.host.uk.com');
|
||||
})
|
||||
->update(['is_default' => false]);
|
||||
|
||||
DB::table('user_workspace')
|
||||
->where('user_id', $this->owner->id)
|
||||
->where('workspace_id', $this->secondWorkspace->id)
|
||||
->update(['is_default' => true]);
|
||||
});
|
||||
|
||||
// After switch, only one hub workspace should be default
|
||||
$hubDefaults = DB::table('user_workspace')
|
||||
->where('user_id', $this->owner->id)
|
||||
->where('is_default', true)
|
||||
->whereIn('workspace_id', function ($query) {
|
||||
$query->select('id')
|
||||
->from('workspaces')
|
||||
->where('domain', 'hub.host.uk.com');
|
||||
})
|
||||
->count();
|
||||
|
||||
$this->assertEquals(1, $hubDefaults);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// CURRENT - Get current workspace
|
||||
// =========================================================================
|
||||
|
||||
public function test_default_host_workspace_resolves_for_authenticated_user(): void
|
||||
{
|
||||
$this->actingAs($this->owner);
|
||||
|
||||
$default = $this->owner->defaultHostWorkspace();
|
||||
|
||||
$this->assertNotNull($default);
|
||||
$this->assertEquals($this->workspace->id, $default->id);
|
||||
}
|
||||
|
||||
public function test_default_host_workspace_falls_back_to_first_workspace(): void
|
||||
{
|
||||
// Remove all defaults
|
||||
DB::table('user_workspace')
|
||||
->where('user_id', $this->member->id)
|
||||
->update(['is_default' => false]);
|
||||
|
||||
$this->actingAs($this->member);
|
||||
|
||||
$default = $this->member->defaultHostWorkspace();
|
||||
|
||||
// Should fall back to the first workspace
|
||||
$this->assertNotNull($default);
|
||||
$this->assertEquals($this->workspace->id, $default->id);
|
||||
}
|
||||
|
||||
public function test_user_with_no_workspaces_returns_null(): void
|
||||
{
|
||||
$loneUser = User::factory()->create(['name' => 'Lone User']);
|
||||
|
||||
$this->actingAs($loneUser);
|
||||
|
||||
$default = $loneUser->defaultHostWorkspace();
|
||||
|
||||
$this->assertNull($default);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// ACCESS CONTROL - Role-based permission checks
|
||||
// =========================================================================
|
||||
|
||||
public function test_workspace_role_is_correctly_set_on_pivot(): void
|
||||
{
|
||||
$ownerPivot = $this->workspace->users()
|
||||
->where('user_id', $this->owner->id)
|
||||
->first()
|
||||
->pivot;
|
||||
$this->assertEquals('owner', $ownerPivot->role);
|
||||
|
||||
$adminPivot = $this->workspace->users()
|
||||
->where('user_id', $this->admin->id)
|
||||
->first()
|
||||
->pivot;
|
||||
$this->assertEquals('admin', $adminPivot->role);
|
||||
|
||||
$memberPivot = $this->workspace->users()
|
||||
->where('user_id', $this->member->id)
|
||||
->first()
|
||||
->pivot;
|
||||
$this->assertEquals('member', $memberPivot->role);
|
||||
}
|
||||
|
||||
public function test_workspace_owner_method_returns_correct_user(): void
|
||||
{
|
||||
$owner = $this->workspace->owner();
|
||||
|
||||
$this->assertNotNull($owner);
|
||||
$this->assertEquals($this->owner->id, $owner->id);
|
||||
}
|
||||
|
||||
public function test_workspace_users_count(): void
|
||||
{
|
||||
$count = $this->workspace->users()->count();
|
||||
|
||||
// Owner, admin, member
|
||||
$this->assertEquals(3, $count);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// EDGE CASES
|
||||
// =========================================================================
|
||||
|
||||
public function test_workspace_with_special_characters_in_name(): void
|
||||
{
|
||||
$workspace = Workspace::create([
|
||||
'name' => "O'Brien's & Co. (Test)",
|
||||
'slug' => 'obriens-co-test',
|
||||
'domain' => 'hub.host.uk.com',
|
||||
'type' => 'custom',
|
||||
]);
|
||||
|
||||
$this->assertEquals("O'Brien's & Co. (Test)", $workspace->name);
|
||||
}
|
||||
|
||||
public function test_workspace_active_scope(): void
|
||||
{
|
||||
$this->secondWorkspace->update(['is_active' => false]);
|
||||
|
||||
$activeCount = Workspace::active()->count();
|
||||
// secondWorkspace is inactive now, so active count should be less than total
|
||||
$allCount = Workspace::count();
|
||||
|
||||
$this->assertLessThan($allCount, $activeCount);
|
||||
}
|
||||
|
||||
public function test_workspace_is_active_cast_to_boolean(): void
|
||||
{
|
||||
$workspace = Workspace::factory()->create(['is_active' => true]);
|
||||
|
||||
$this->assertIsBool($workspace->is_active);
|
||||
$this->assertTrue($workspace->is_active);
|
||||
|
||||
$workspace->update(['is_active' => false]);
|
||||
$workspace->refresh();
|
||||
|
||||
$this->assertIsBool($workspace->is_active);
|
||||
$this->assertFalse($workspace->is_active);
|
||||
}
|
||||
|
||||
public function test_workspace_settings_cast_to_array(): void
|
||||
{
|
||||
$workspace = Workspace::factory()->create([
|
||||
'settings' => ['theme' => 'dark', 'language' => 'en'],
|
||||
]);
|
||||
|
||||
$this->assertIsArray($workspace->settings);
|
||||
$this->assertEquals('dark', $workspace->settings['theme']);
|
||||
$this->assertEquals('en', $workspace->settings['language']);
|
||||
}
|
||||
|
||||
public function test_get_setting_returns_value_or_default(): void
|
||||
{
|
||||
$workspace = Workspace::factory()->create([
|
||||
'settings' => ['theme' => 'dark'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('dark', $workspace->getSetting('theme'));
|
||||
$this->assertNull($workspace->getSetting('nonexistent'));
|
||||
$this->assertEquals('fallback', $workspace->getSetting('nonexistent', 'fallback'));
|
||||
}
|
||||
|
||||
public function test_workspace_to_service_array(): void
|
||||
{
|
||||
$array = $this->workspace->toServiceArray();
|
||||
|
||||
$this->assertArrayHasKey('name', $array);
|
||||
$this->assertArrayHasKey('slug', $array);
|
||||
$this->assertArrayHasKey('domain', $array);
|
||||
$this->assertArrayHasKey('icon', $array);
|
||||
$this->assertArrayHasKey('color', $array);
|
||||
$this->assertArrayHasKey('description', $array);
|
||||
$this->assertEquals('Test Workspace', $array['name']);
|
||||
$this->assertEquals('test-workspace', $array['slug']);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue