diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index 16cef00d4d..dc87dc999d 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -115,7 +115,7 @@ class AssetsController extends Controller $allowed_columns[] = $field->db_column_name(); } - $assets = Company::scopeCompanyables(Asset::select('assets.*'), 'company_id', 'assets') + $assets = Asset::select('assets.*') ->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo', 'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users. @@ -480,7 +480,7 @@ class AssetsController extends Controller public function selectlist(Request $request) { - $assets = Company::scopeCompanyables(Asset::select([ + $assets = Asset::select([ 'assets.id', 'assets.name', 'assets.asset_tag', @@ -488,7 +488,7 @@ class AssetsController extends Controller 'assets.assigned_to', 'assets.assigned_type', 'assets.status_id', - ])->with('model', 'assetstatus', 'assignedTo')->NotArchived(), 'company_id', 'assets'); + ])->with('model', 'assetstatus', 'assignedTo')->NotArchived(); if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') { $assets = $assets->RTD(); @@ -1033,9 +1033,10 @@ class AssetsController extends Controller { $this->authorize('viewRequestable', Asset::class); - $assets = Company::scopeCompanyables(Asset::select('assets.*'), 'company_id', 'assets') + $assets = Asset::select('assets.*') ->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo', - 'model.category', 'model.manufacturer', 'model.fieldset', 'supplier')->requestableAssets(); + 'model.category', 'model.manufacturer', 'model.fieldset', 'supplier') + ->requestableAssets(); $offset = request('offset', 0); $limit = $request->input('limit', 50); diff --git a/app/Http/Controllers/Api/ComponentsController.php b/app/Http/Controllers/Api/ComponentsController.php index 24eb1044b8..8b51344424 100644 --- a/app/Http/Controllers/Api/ComponentsController.php +++ b/app/Http/Controllers/Api/ComponentsController.php @@ -44,9 +44,8 @@ class ComponentsController extends Controller 'notes', ]; - - $components = Company::scopeCompanyables(Component::select('components.*') - ->with('company', 'location', 'category', 'assets', 'supplier')); + $components = Component::select('components.*') + ->with('company', 'location', 'category', 'assets', 'supplier'); if ($request->filled('search')) { $components = $components->TextSearch($request->input('search')); diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php index bac9440dca..ba7e6fb302 100644 --- a/app/Http/Controllers/Api/ConsumablesController.php +++ b/app/Http/Controllers/Api/ConsumablesController.php @@ -45,11 +45,8 @@ class ConsumablesController extends Controller 'notes', ]; - - $consumables = Company::scopeCompanyables( - Consumable::select('consumables.*') - ->with('company', 'location', 'category', 'users', 'manufacturer') - ); + $consumables = Consumable::select('consumables.*') + ->with('company', 'location', 'category', 'users', 'manufacturer'); if ($request->filled('search')) { $consumables = $consumables->TextSearch(e($request->input('search'))); diff --git a/app/Http/Controllers/Api/LicensesController.php b/app/Http/Controllers/Api/LicensesController.php index df74b60895..e021fc3d3e 100644 --- a/app/Http/Controllers/Api/LicensesController.php +++ b/app/Http/Controllers/Api/LicensesController.php @@ -26,8 +26,8 @@ class LicensesController extends Controller public function index(Request $request) { $this->authorize('view', License::class); - $licenses = Company::scopeCompanyables(License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count')); + $licenses = License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count'); if ($request->filled('company_id')) { $licenses->where('company_id', '=', $request->input('company_id')); diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php index cd3e0f8391..0e0c3931d8 100644 --- a/database/factories/AssetFactory.php +++ b/database/factories/AssetFactory.php @@ -328,4 +328,14 @@ class AssetFactory extends Factory ]; }); } + + public function requestable() + { + return $this->state(['requestable' => true]); + } + + public function nonrequestable() + { + return $this->state(['requestable' => false]); + } } diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 8aa38d2325..586cb7186b 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -271,6 +271,15 @@ class UserFactory extends Factory }); } + public function viewDepartments() + { + return $this->state(function () { + return [ + 'permissions' => '{"departments.view":"1"}', + ]; + }); + } + public function viewLicenses() { return $this->state(function () { diff --git a/tests/Feature/Api/Assets/AssetIndexTest.php b/tests/Feature/Api/Assets/AssetIndexTest.php index 3618c6e01e..778483c1c9 100644 --- a/tests/Feature/Api/Assets/AssetIndexTest.php +++ b/tests/Feature/Api/Assets/AssetIndexTest.php @@ -3,9 +3,9 @@ namespace Tests\Feature\Api\Assets; use App\Models\Asset; +use App\Models\Company; use App\Models\User; use Illuminate\Testing\Fluent\AssertableJson; -use Laravel\Passport\Passport; use Tests\Support\InteractsWithSettings; use Tests\TestCase; @@ -17,14 +17,14 @@ class AssetIndexTest extends TestCase { Asset::factory()->count(3)->create(); - Passport::actingAs(User::factory()->superuser()->create()); - $this->getJson( - route('api.assets.index', [ - 'sort' => 'name', - 'order' => 'asc', - 'offset' => '0', - 'limit' => '20', - ])) + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson( + route('api.assets.index', [ + 'sort' => 'name', + 'order' => 'asc', + 'offset' => '0', + 'limit' => '20', + ])) ->assertOk() ->assertJsonStructure([ 'total', @@ -32,4 +32,50 @@ class AssetIndexTest extends TestCase ]) ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); } + + public function testAssetIndexAdheresToCompanyScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $assetA = Asset::factory()->for($companyA)->create(); + $assetB = Asset::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->viewAssets()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->viewAssets()->make()); + + $this->settings->disableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.assets.index')) + ->assertResponseContainsInRows($assetA, 'asset_tag') + ->assertResponseContainsInRows($assetB, 'asset_tag'); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.assets.index')) + ->assertResponseContainsInRows($assetA, 'asset_tag') + ->assertResponseContainsInRows($assetB, 'asset_tag'); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.assets.index')) + ->assertResponseContainsInRows($assetA, 'asset_tag') + ->assertResponseContainsInRows($assetB, 'asset_tag'); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.assets.index')) + ->assertResponseContainsInRows($assetA, 'asset_tag') + ->assertResponseContainsInRows($assetB, 'asset_tag'); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.assets.index')) + ->assertResponseContainsInRows($assetA, 'asset_tag') + ->assertResponseDoesNotContainInRows($assetB, 'asset_tag'); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.assets.index')) + ->assertResponseDoesNotContainInRows($assetA, 'asset_tag') + ->assertResponseContainsInRows($assetB, 'asset_tag'); + } } diff --git a/tests/Feature/Api/Assets/AssetsForSelectListTest.php b/tests/Feature/Api/Assets/AssetsForSelectListTest.php new file mode 100644 index 0000000000..cccae38d38 --- /dev/null +++ b/tests/Feature/Api/Assets/AssetsForSelectListTest.php @@ -0,0 +1,76 @@ +create(['asset_tag' => '0001']); + Asset::factory()->create(['asset_tag' => '0002']); + + $response = $this->actingAsForApi(User::factory()->create()) + ->getJson(route('assets.selectlist', ['search' => '000'])) + ->assertOk(); + + $results = collect($response->json('results')); + + $this->assertEquals(2, $results->count()); + $this->assertTrue($results->pluck('text')->contains(fn($text) => str_contains($text, '0001'))); + $this->assertTrue($results->pluck('text')->contains(fn($text) => str_contains($text, '0002'))); + } + + public function testAssetsAreScopedToCompanyWhenMultipleCompanySupportEnabled() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $assetA = Asset::factory()->for($companyA)->create(['asset_tag' => '0001']); + $assetB = Asset::factory()->for($companyB)->create(['asset_tag' => '0002']); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->viewAssets()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->viewAssets()->make()); + + $this->settings->disableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('assets.selectlist', ['search' => '000'])) + ->assertResponseContainsInResults($assetA) + ->assertResponseContainsInResults($assetB); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('assets.selectlist', ['search' => '000'])) + ->assertResponseContainsInResults($assetA) + ->assertResponseContainsInResults($assetB); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('assets.selectlist', ['search' => '000'])) + ->assertResponseContainsInResults($assetA) + ->assertResponseContainsInResults($assetB); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('assets.selectlist', ['search' => '000'])) + ->assertResponseContainsInResults($assetA) + ->assertResponseContainsInResults($assetB); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('assets.selectlist', ['search' => '000'])) + ->assertResponseContainsInResults($assetA) + ->assertResponseDoesNotContainInResults($assetB); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('assets.selectlist', ['search' => '000'])) + ->assertResponseDoesNotContainInResults($assetA) + ->assertResponseContainsInResults($assetB); + } +} diff --git a/tests/Feature/Api/Assets/RequestableAssetsTest.php b/tests/Feature/Api/Assets/RequestableAssetsTest.php new file mode 100644 index 0000000000..8649b1b00b --- /dev/null +++ b/tests/Feature/Api/Assets/RequestableAssetsTest.php @@ -0,0 +1,79 @@ +actingAsForApi(User::factory()->create()) + ->getJson(route('api.assets.requestable')) + ->assertForbidden(); + } + + public function testReturnsRequestableAssets() + { + $requestableAsset = Asset::factory()->requestable()->create(['asset_tag' => 'requestable']); + $nonRequestableAsset = Asset::factory()->nonrequestable()->create(['asset_tag' => 'non-requestable']); + + $this->actingAsForApi(User::factory()->viewRequestableAssets()->create()) + ->getJson(route('api.assets.requestable')) + ->assertOk() + ->assertResponseContainsInRows($requestableAsset, 'asset_tag') + ->assertResponseDoesNotContainInRows($nonRequestableAsset, 'asset_tag'); + } + + public function testRequestableAssetsAreScopedToCompanyWhenMultipleCompanySupportEnabled() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $assetA = Asset::factory()->requestable()->for($companyA)->create(['asset_tag' => '0001']); + $assetB = Asset::factory()->requestable()->for($companyB)->create(['asset_tag' => '0002']); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->viewRequestableAssets()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->viewRequestableAssets()->make()); + + $this->settings->disableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.assets.requestable')) + ->assertResponseContainsInRows($assetA, 'asset_tag') + ->assertResponseContainsInRows($assetB, 'asset_tag'); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.assets.requestable')) + ->assertResponseContainsInRows($assetA, 'asset_tag') + ->assertResponseContainsInRows($assetB, 'asset_tag'); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.assets.requestable')) + ->assertResponseContainsInRows($assetA, 'asset_tag') + ->assertResponseContainsInRows($assetB, 'asset_tag'); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.assets.requestable')) + ->assertResponseContainsInRows($assetA, 'asset_tag') + ->assertResponseContainsInRows($assetB, 'asset_tag'); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.assets.requestable')) + ->assertResponseContainsInRows($assetA, 'asset_tag') + ->assertResponseDoesNotContainInRows($assetB, 'asset_tag'); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.assets.requestable')) + ->assertResponseDoesNotContainInRows($assetA, 'asset_tag') + ->assertResponseContainsInRows($assetB, 'asset_tag'); + } +} diff --git a/tests/Feature/Api/Components/ComponentIndexTest.php b/tests/Feature/Api/Components/ComponentIndexTest.php new file mode 100644 index 0000000000..ee83b7a46d --- /dev/null +++ b/tests/Feature/Api/Components/ComponentIndexTest.php @@ -0,0 +1,60 @@ +count(2)->create(); + + $componentA = Component::factory()->for($companyA)->create(); + $componentB = Component::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->viewComponents()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->viewComponents()->make()); + + $this->settings->disableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.components.index')) + ->assertResponseContainsInRows($componentA) + ->assertResponseContainsInRows($componentB); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.components.index')) + ->assertResponseContainsInRows($componentA) + ->assertResponseContainsInRows($componentB); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.components.index')) + ->assertResponseContainsInRows($componentA) + ->assertResponseContainsInRows($componentB); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.components.index')) + ->assertResponseContainsInRows($componentA) + ->assertResponseContainsInRows($componentB); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.components.index')) + ->assertResponseContainsInRows($componentA) + ->assertResponseDoesNotContainInRows($componentB); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.components.index')) + ->assertResponseDoesNotContainInRows($componentA) + ->assertResponseContainsInRows($componentB); + } +} diff --git a/tests/Feature/Api/Consumables/ConsumablesIndexTest.php b/tests/Feature/Api/Consumables/ConsumablesIndexTest.php new file mode 100644 index 0000000000..33c10ed078 --- /dev/null +++ b/tests/Feature/Api/Consumables/ConsumablesIndexTest.php @@ -0,0 +1,60 @@ +count(2)->create(); + + $consumableA = Consumable::factory()->for($companyA)->create(); + $consumableB = Consumable::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->viewConsumables()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->viewConsumables()->make()); + + $this->settings->disableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.consumables.index')) + ->assertResponseContainsInRows($consumableA) + ->assertResponseContainsInRows($consumableB); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.consumables.index')) + ->assertResponseContainsInRows($consumableA) + ->assertResponseContainsInRows($consumableB); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.consumables.index')) + ->assertResponseContainsInRows($consumableA) + ->assertResponseContainsInRows($consumableB); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.consumables.index')) + ->assertResponseContainsInRows($consumableA) + ->assertResponseContainsInRows($consumableB); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.consumables.index')) + ->assertResponseContainsInRows($consumableA) + ->assertResponseDoesNotContainInRows($consumableB); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.consumables.index')) + ->assertResponseDoesNotContainInRows($consumableA) + ->assertResponseContainsInRows($consumableB); + } +} diff --git a/tests/Feature/Api/Licenses/LicensesIndexTest.php b/tests/Feature/Api/Licenses/LicensesIndexTest.php new file mode 100644 index 0000000000..a21a27da73 --- /dev/null +++ b/tests/Feature/Api/Licenses/LicensesIndexTest.php @@ -0,0 +1,60 @@ +count(2)->create(); + + $licenseA = License::factory()->for($companyA)->create(); + $licenseB = License::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->viewLicenses()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->viewLicenses()->make()); + + $this->settings->disableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.licenses.index')) + ->assertResponseContainsInRows($licenseA) + ->assertResponseContainsInRows($licenseB); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.licenses.index')) + ->assertResponseContainsInRows($licenseA) + ->assertResponseContainsInRows($licenseB); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.licenses.index')) + ->assertResponseContainsInRows($licenseA) + ->assertResponseContainsInRows($licenseB); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.licenses.index')) + ->assertResponseContainsInRows($licenseA) + ->assertResponseContainsInRows($licenseB); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.licenses.index')) + ->assertResponseContainsInRows($licenseA) + ->assertResponseDoesNotContainInRows($licenseB); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.licenses.index')) + ->assertResponseDoesNotContainInRows($licenseA) + ->assertResponseContainsInRows($licenseB); + } +} diff --git a/tests/Feature/DashboardTest.php b/tests/Feature/DashboardTest.php new file mode 100644 index 0000000000..4e9459fb06 --- /dev/null +++ b/tests/Feature/DashboardTest.php @@ -0,0 +1,19 @@ +actingAs(User::factory()->create()) + ->get(route('home')) + ->assertRedirect(route('view-assets')); + } +} diff --git a/tests/Support/CustomTestMacros.php b/tests/Support/CustomTestMacros.php new file mode 100644 index 0000000000..0a30d7c243 --- /dev/null +++ b/tests/Support/CustomTestMacros.php @@ -0,0 +1,66 @@ +{$property})) { + throw new RuntimeException( + "The property ({$property}) either does not exist or is null on the model which isn't helpful for comparison." + ); + } + }; + + TestResponse::macro( + 'assertResponseContainsInRows', + function (Model $model, string $property = 'name') use ($guardAgainstNullProperty) { + $guardAgainstNullProperty($model, $property); + + Assert::assertTrue(collect($this['rows'])->pluck($property)->contains($model->{$property})); + + return $this; + } + ); + + TestResponse::macro( + 'assertResponseDoesNotContainInRows', + function (Model $model, string $property = 'name') use ($guardAgainstNullProperty) { + $guardAgainstNullProperty($model, $property); + + Assert::assertFalse(collect($this['rows'])->pluck($property)->contains($model->{$property})); + + return $this; + } + ); + + TestResponse::macro( + 'assertResponseContainsInResults', + function (Model $model, string $property = 'id') use ($guardAgainstNullProperty) { + $guardAgainstNullProperty($model, $property); + + Assert::assertTrue(collect($this->json('results'))->pluck('id')->contains($model->{$property})); + + return $this; + } + ); + + TestResponse::macro( + 'assertResponseDoesNotContainInResults', + function (Model $model, string $property = 'id') use ($guardAgainstNullProperty) { + $guardAgainstNullProperty($model, $property); + + Assert::assertFalse(collect($this->json('results'))->pluck('id')->contains($model->{$property})); + + return $this; + } + ); + } +} diff --git a/tests/Support/InteractsWithAuthentication.php b/tests/Support/InteractsWithAuthentication.php new file mode 100644 index 0000000000..27b20e382b --- /dev/null +++ b/tests/Support/InteractsWithAuthentication.php @@ -0,0 +1,16 @@ +update(['full_multiple_companies_support' => 1]); } + public function disableMultipleFullCompanySupport(): Settings + { + return $this->update(['full_multiple_companies_support' => 0]); + } + public function enableWebhook(): Settings { return $this->update([ diff --git a/tests/TestCase.php b/tests/TestCase.php index 28051c7c7f..30237f3171 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -5,11 +5,15 @@ namespace Tests; use App\Http\Middleware\SecurityHeaders; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; +use Tests\Support\CustomTestMacros; +use Tests\Support\InteractsWithAuthentication; use Tests\Support\InteractsWithSettings; abstract class TestCase extends BaseTestCase { use CreatesApplication; + use CustomTestMacros; + use InteractsWithAuthentication; use LazilyRefreshDatabase; private array $globallyDisabledMiddleware = [ @@ -25,5 +29,7 @@ abstract class TestCase extends BaseTestCase if (collect(class_uses_recursive($this))->contains(InteractsWithSettings::class)) { $this->initializeSettings(); } + + $this->registerCustomMacros(); } } diff --git a/tests/Unit/CompanyScopingTest.php b/tests/Unit/CompanyScopingTest.php new file mode 100644 index 0000000000..669dd5ed41 --- /dev/null +++ b/tests/Unit/CompanyScopingTest.php @@ -0,0 +1,169 @@ + [Accessory::class], + 'Assets' => [Asset::class], + 'Components' => [Component::class], + 'Consumables' => [Consumable::class], + 'Licenses' => [License::class], + ]; + } + + /** @dataProvider models */ + public function testCompanyScoping($model) + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $modelA = $model::factory()->for($companyA)->create(); + $modelB = $model::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->make()); + + $this->settings->disableMultipleFullCompanySupport(); + + $this->actingAs($superUser); + $this->assertCanSee($modelA); + $this->assertCanSee($modelB); + + $this->actingAs($userInCompanyA); + $this->assertCanSee($modelA); + $this->assertCanSee($modelB); + + $this->actingAs($userInCompanyB); + $this->assertCanSee($modelA); + $this->assertCanSee($modelB); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAs($superUser); + $this->assertCanSee($modelA); + $this->assertCanSee($modelB); + + $this->actingAs($userInCompanyA); + $this->assertCanSee($modelA); + $this->assertCannotSee($modelB); + + $this->actingAs($userInCompanyB); + $this->assertCannotSee($modelA); + $this->assertCanSee($modelB); + } + + public function testAssetMaintenanceCompanyScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $assetMaintenanceForCompanyA = AssetMaintenance::factory()->for(Asset::factory()->for($companyA))->create(); + $assetMaintenanceForCompanyB = AssetMaintenance::factory()->for(Asset::factory()->for($companyB))->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->make()); + + $this->settings->disableMultipleFullCompanySupport(); + + $this->actingAs($superUser); + $this->assertCanSee($assetMaintenanceForCompanyA); + $this->assertCanSee($assetMaintenanceForCompanyB); + + $this->actingAs($userInCompanyA); + $this->assertCanSee($assetMaintenanceForCompanyA); + $this->assertCanSee($assetMaintenanceForCompanyB); + + $this->actingAs($userInCompanyB); + $this->assertCanSee($assetMaintenanceForCompanyA); + $this->assertCanSee($assetMaintenanceForCompanyB); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAs($superUser); + $this->assertCanSee($assetMaintenanceForCompanyA); + $this->assertCanSee($assetMaintenanceForCompanyB); + + $this->actingAs($userInCompanyA); + $this->assertCanSee($assetMaintenanceForCompanyA); + $this->assertCannotSee($assetMaintenanceForCompanyB); + + $this->actingAs($userInCompanyB); + $this->assertCannotSee($assetMaintenanceForCompanyA); + $this->assertCanSee($assetMaintenanceForCompanyB); + } + + public function testLicenseSeatCompanyScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $licenseSeatA = LicenseSeat::factory()->for(Asset::factory()->for($companyA))->create(); + $licenseSeatB = LicenseSeat::factory()->for(Asset::factory()->for($companyB))->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->make()); + + $this->settings->disableMultipleFullCompanySupport(); + + $this->actingAs($superUser); + $this->assertCanSee($licenseSeatA); + $this->assertCanSee($licenseSeatB); + + $this->actingAs($userInCompanyA); + $this->assertCanSee($licenseSeatA); + $this->assertCanSee($licenseSeatB); + + $this->actingAs($userInCompanyB); + $this->assertCanSee($licenseSeatA); + $this->assertCanSee($licenseSeatB); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAs($superUser); + $this->assertCanSee($licenseSeatA); + $this->assertCanSee($licenseSeatB); + + $this->actingAs($userInCompanyA); + $this->assertCanSee($licenseSeatA); + $this->assertCannotSee($licenseSeatB); + + $this->actingAs($userInCompanyB); + $this->assertCannotSee($licenseSeatA); + $this->assertCanSee($licenseSeatB); + } + + private function assertCanSee(Model $model) + { + $this->assertTrue( + get_class($model)::all()->contains($model), + 'User was not able to see expected model' + ); + } + + private function assertCannotSee(Model $model) + { + $this->assertFalse( + get_class($model)::all()->contains($model), + 'User was able to see model from a different company' + ); + } +}