create(['role' => UserRole::SuperAdmin])); $id = $this->postJson('/api/v1/organizations', [ 'name' => 'Acme Learning', 'slug' => 'acme-learning', 'defaultLocale' => 'fa', 'timezone' => 'Asia/Tehran', ])->assertCreated()->assertJsonMissingPath('data.learningAnalytics')->json('data.id'); $this->getJson('/api/v1/organizations')->assertOk()->assertJsonPath('meta.total', 2)->assertJsonFragment(['slug' => 'acme-learning']); $this->patchJson("/api/v1/organizations/{$id}", ['status' => 'disabled'])->assertOk()->assertJsonPath('data.status', 'disabled'); } public function test_tenant_roles_cannot_access_platform_organization_administration(): void { Sanctum::actingAs(User::factory()->create(['role' => UserRole::CourseDesigner])); $this->getJson('/api/v1/organizations')->assertForbidden(); } public function test_on_premise_mode_hides_multi_organization_endpoints(): void { config()->set('deployment.mode', 'on_premise'); $this->app->forgetInstance(DeploymentCapabilities::class); Sanctum::actingAs(User::factory()->create(['role' => UserRole::SuperAdmin])); $this->getJson('/api/v1/organizations')->assertNotFound(); } public function test_organization_slug_is_unique(): void { Organization::factory()->create(['slug' => 'taken']); Sanctum::actingAs(User::factory()->create(['role' => UserRole::SuperAdmin])); $this->postJson('/api/v1/organizations', ['name' => 'Duplicate', 'slug' => 'taken', 'defaultLocale' => 'en', 'timezone' => 'UTC']) ->assertUnprocessable()->assertJsonValidationErrors('slug'); } public function test_platform_list_supports_search_filters_sorting_and_complete_pagination_metadata(): void { $alpha = Organization::factory()->create(['name' => 'Alpha Learning', 'slug' => 'alpha', 'status' => 'active']); Organization::factory()->create(['name' => 'Beta Academy', 'slug' => 'beta', 'status' => 'disabled']); User::factory()->count(2)->for($alpha)->create(); Subscription::query()->create(['organization_id' => $alpha->getKey(), 'plan_key' => 'enterprise', 'status' => 'active', 'starts_at' => now()->subDay(), 'expires_at' => now()->addMonth(), 'seat_limit' => 50, 'storage_quota_bytes' => 1000, 'ai_credit_quota' => 500]); DB::table('audit_logs')->insert(['id' => (string) str()->ulid(), 'organization_id' => $alpha->getKey(), 'actor_id' => null, 'action' => 'organization.activity', 'entity_type' => 'organization', 'entity_id' => $alpha->getKey(), 'metadata' => json_encode(['schemaVersion' => 1]), 'ip_address' => '127.0.0.1', 'created_at' => now()]); Sanctum::actingAs(User::factory()->create(['organization_id' => null, 'role' => UserRole::SuperAdmin])); $this->getJson('/api/v1/organizations?search=alpha&status=active&plan=enterprise&sort=users&direction=desc&perPage=1') ->assertOk() ->assertJsonPath('meta.currentPage', 1) ->assertJsonPath('meta.lastPage', 1) ->assertJsonPath('meta.perPage', 1) ->assertJsonPath('meta.total', 1) ->assertJsonPath('data.0.id', $alpha->getKey()) ->assertJsonPath('data.0.planKey', 'enterprise') ->assertJsonPath('data.0.usersCount', 2) ->assertJsonPath('data.0.aiCreditsUsed', 0) ->assertJsonStructure(['data' => [['lastActivityAt', 'subscriptionExpiresAt', 'storageUsedBytes', 'storageQuotaBytes']]]); } public function test_organization_detail_is_platform_scoped_and_exposes_real_operational_metadata(): void { $organization = Organization::factory()->create(['name' => 'Detail Org']); User::factory()->for($organization)->create(['name' => 'Member One']); Subscription::query()->create(['organization_id' => $organization->getKey(), 'plan_key' => 'growth', 'status' => 'active', 'starts_at' => now()->subDay(), 'seat_limit' => 20, 'storage_quota_bytes' => 2048, 'storage_used_bytes' => 512, 'ai_credit_quota' => 100, 'ai_credits_used' => 12, 'enabled_features' => ['exports']]); DB::table('audit_logs')->insert(['id' => (string) str()->ulid(), 'organization_id' => $organization->getKey(), 'actor_id' => null, 'action' => 'organization.seeded', 'entity_type' => 'organization', 'entity_id' => $organization->getKey(), 'metadata' => json_encode(['schemaVersion' => 1]), 'ip_address' => '127.0.0.1', 'created_at' => now()]); Sanctum::actingAs(User::factory()->create(['organization_id' => null, 'role' => UserRole::SuperAdmin])); $this->getJson('/api/v1/organizations/'.$organization->getKey()) ->assertOk() ->assertJsonPath('data.subscription.planKey', 'growth') ->assertJsonPath('data.subscription.aiCreditsUsed', 12) ->assertJsonPath('data.recentUsers.0.name', 'Member One') ->assertJsonPath('data.recentActivity.0.action', 'organization.seeded') ->assertJsonStructure(['data' => ['storageUsedBytes', 'aiJobsCount', 'failedAiJobsCount', 'recentUsers', 'recentActivity']]); } public function test_organization_lifecycle_changes_are_audited(): void { Sanctum::actingAs(User::factory()->create(['organization_id' => null, 'role' => UserRole::SuperAdmin])); $id = $this->postJson('/api/v1/organizations', ['name' => 'Audited Org', 'slug' => 'audited-org', 'defaultLocale' => 'fa', 'timezone' => 'Asia/Tehran'])->assertCreated()->json('data.id'); $this->patchJson('/api/v1/organizations/'.$id, ['status' => 'disabled'])->assertOk(); $this->assertDatabaseHas('audit_logs', ['organization_id' => $id, 'action' => 'organization.created', 'entity_id' => $id]); $this->assertDatabaseHas('audit_logs', ['organization_id' => $id, 'action' => 'organization.updated', 'entity_id' => $id]); } }