From b32c6b38f3123b96a6e74847220f08a6590b6c7e Mon Sep 17 00:00:00 2001 From: Randall Wilk Date: Thu, 28 Sep 2023 11:04:17 -0500 Subject: [PATCH] Add ability to fetch all settings --- src/Contracts/Driver.php | 4 + src/Contracts/KeyGenerator.php | 4 + src/Contracts/Setting.php | 4 + src/Drivers/DatabaseDriver.php | 42 ++++++++ src/Drivers/EloquentDriver.php | 6 ++ src/Exceptions/InvalidBulkValueResult.php | 21 ++++ src/Exceptions/InvalidKeyGenerator.php | 18 ++++ src/Facades/Settings.php | 2 + src/Models/Setting.php | 45 ++++++++ src/Settings.php | 101 +++++++++++++++++- src/SettingsServiceProvider.php | 2 + src/Support/KeyGenerators/Md5KeyGenerator.php | 11 ++ .../KeyGenerators/ReadableKeyGenerator.php | 14 ++- src/helpers.php | 2 +- tests/Feature/Drivers/DatabaseDriverTest.php | 47 ++++++++ tests/Feature/Drivers/EloquentDriverTest.php | 47 ++++++++ tests/Feature/SettingsTest.php | 90 ++++++++++++++++ tests/Feature/TeamsTest.php | 39 +++++++ 18 files changed, 494 insertions(+), 5 deletions(-) create mode 100644 src/Exceptions/InvalidBulkValueResult.php create mode 100644 src/Exceptions/InvalidKeyGenerator.php diff --git a/src/Contracts/Driver.php b/src/Contracts/Driver.php index b99c707..1d1c922 100644 --- a/src/Contracts/Driver.php +++ b/src/Contracts/Driver.php @@ -4,12 +4,16 @@ namespace Rawilk\Settings\Contracts; +use Illuminate\Contracts\Support\Arrayable; + interface Driver { public function forget($key, $teamId = null); public function get(string $key, $default = null, $teamId = null); + public function all($teamId = null, $keys = null): array|Arrayable; + public function has($key, $teamId = null): bool; public function set(string $key, $value = null, $teamId = null); diff --git a/src/Contracts/KeyGenerator.php b/src/Contracts/KeyGenerator.php index bda0f88..8a10994 100644 --- a/src/Contracts/KeyGenerator.php +++ b/src/Contracts/KeyGenerator.php @@ -10,5 +10,9 @@ interface KeyGenerator { public function generate(string $key, Context $context = null): string; + public function removeContextFromKey(string $key): string; + public function setContextSerializer(ContextSerializer $serializer): self; + + public function contextPrefix(): string; } diff --git a/src/Contracts/Setting.php b/src/Contracts/Setting.php index 42189bb..2a80734 100644 --- a/src/Contracts/Setting.php +++ b/src/Contracts/Setting.php @@ -4,10 +4,14 @@ namespace Rawilk\Settings\Contracts; +use Illuminate\Contracts\Support\Arrayable; + interface Setting { public static function getValue(string $key, $default = null, $teamId = null); + public static function getAll($teamId = null, $keys = null): array|Arrayable; + public static function has($key, $teamId = null): bool; public static function removeSetting($key, $teamId = null); diff --git a/src/Drivers/DatabaseDriver.php b/src/Drivers/DatabaseDriver.php index aa8b5ef..874650c 100644 --- a/src/Drivers/DatabaseDriver.php +++ b/src/Drivers/DatabaseDriver.php @@ -4,9 +4,12 @@ namespace Rawilk\Settings\Drivers; +use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Connection; use Illuminate\Database\Query\Builder; +use Illuminate\Support\Collection; use Rawilk\Settings\Contracts\Driver; +use Rawilk\Settings\Facades\Settings; class DatabaseDriver implements Driver { @@ -41,6 +44,32 @@ public function get(string $key, $default = null, $teamId = null) return $value ?? $default; } + public function all($teamId = null, $keys = null): array|Arrayable + { + $keys = $this->normalizeKeys($keys); + + return $this->db() + ->when( + // False means we want settings without a context set. + $keys === false, + fn (Builder $query) => $query->where('key', 'NOT LIKE', '%' . Settings::getKeyGenerator()->contextPrefix() . '%'), + ) + ->when( + // When keys is a string, we're trying to do a partial lookup for context + is_string($keys), + fn (Builder $query) => $query->where('key', 'LIKE', "%{$keys}"), + ) + ->when( + $keys instanceof Collection && $keys->isNotEmpty(), + fn (Builder $query) => $query->whereIn('key', $keys), + ) + ->when( + $teamId !== false, + fn (Builder $query) => $query->where("{$this->table}.{$this->teamForeignKey}", $teamId) + ) + ->get(); + } + public function has($key, $teamId = null): bool { return $this->db() @@ -69,4 +98,17 @@ protected function db(): Builder { return $this->connection->table($this->table); } + + protected function normalizeKeys($keys): string|Collection|bool + { + if (is_bool($keys)) { + return $keys; + } + + if (is_string($keys)) { + return $keys; + } + + return collect($keys)->flatten()->filter(); + } } diff --git a/src/Drivers/EloquentDriver.php b/src/Drivers/EloquentDriver.php index 807feb8..4e19cdb 100644 --- a/src/Drivers/EloquentDriver.php +++ b/src/Drivers/EloquentDriver.php @@ -4,6 +4,7 @@ namespace Rawilk\Settings\Drivers; +use Illuminate\Contracts\Support\Arrayable; use Rawilk\Settings\Contracts\Driver; use Rawilk\Settings\Contracts\Setting; @@ -23,6 +24,11 @@ public function get(string $key, $default = null, $teamId = null) return $this->model::getValue($key, $default, $teamId); } + public function all($teamId = null, $keys = null): array|Arrayable + { + return $this->model::getAll($teamId, $keys); + } + public function has($key, $teamId = null): bool { return $this->model::has($key, $teamId); diff --git a/src/Exceptions/InvalidBulkValueResult.php b/src/Exceptions/InvalidBulkValueResult.php new file mode 100644 index 0000000..95adc25 --- /dev/null +++ b/src/Exceptions/InvalidBulkValueResult.php @@ -0,0 +1,21 @@ +when( + // False means we want settings without a context set. + $keys === false, + fn (Builder $query) => $query->where('key', 'NOT LIKE', '%' . Settings::getKeyGenerator()->contextPrefix() . '%'), + ) + ->when( + // When keys is a string, we're trying to do a partial lookup for context + is_string($keys), + fn (Builder $query) => $query->where('key', 'LIKE', "%{$keys}"), + ) + ->when( + $keys instanceof Collection && $keys->isNotEmpty(), + fn (Builder $query) => $query->whereIn('key', $keys), + ) + ->when( + $teamId !== false, + fn (Builder $query) => $query->where( + static::make()->getTable() . '.' . config('settings.team_foreign_key'), + $teamId, + ), + ) + ->get(); + } + public static function has($key, $teamId = null): bool { return static::query() @@ -78,4 +110,17 @@ public static function set(string $key, $value = null, $teamId = null) return static::updateOrCreate($data, compact('value')); } + + protected static function normalizeKeys($keys): string|Collection|bool + { + if (is_bool($keys)) { + return $keys; + } + + if (is_string($keys)) { + return $keys; + } + + return collect($keys)->flatten()->filter(); + } } diff --git a/src/Settings.php b/src/Settings.php index a2fbaea..0212a94 100755 --- a/src/Settings.php +++ b/src/Settings.php @@ -7,12 +7,16 @@ use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use Rawilk\Settings\Contracts\Driver; use Rawilk\Settings\Contracts\KeyGenerator; use Rawilk\Settings\Contracts\ValueSerializer; +use Rawilk\Settings\Exceptions\InvalidBulkValueResult; +use Rawilk\Settings\Exceptions\InvalidKeyGenerator; use Rawilk\Settings\Support\Context; +use Rawilk\Settings\Support\KeyGenerators\Md5KeyGenerator; class Settings { @@ -20,7 +24,7 @@ class Settings protected ?Cache $cache = null; - protected ?Context $context = null; + protected null|Context|bool $context = null; protected ?Encrypter $encrypter = null; @@ -40,6 +44,8 @@ class Settings /** @var null|string|int */ protected mixed $teamId = null; + protected ?string $teamForeignKey = null; + protected string $cacheKeyPrefix = ''; public function __construct( @@ -55,7 +61,11 @@ public function getDriver(): Driver return $this->driver; } - public function context(Context $context = null): self + /** + * Pass in `false` for context when calling `all()` to only return results + * that do not have context. + */ + public function context(Context|bool $context = null): self { $this->context = $context; @@ -83,6 +93,18 @@ public function setTeamId(mixed $id): self return $this; } + public function getTeamForeignKey(): ?string + { + return $this->teamForeignKey; + } + + public function setTeamForeignKey(?string $foreignKey): self + { + $this->teamForeignKey = $foreignKey; + + return $this; + } + public function forget($key) { $key = $this->normalizeKey($key); @@ -145,6 +167,38 @@ public function get(string $key, $default = null) return $value ?? $default; } + public function all($keys = null): Collection + { + $keys = $this->normalizeBulkLookupKey($keys); + + $values = collect($this->driver->all( + teamId: $this->teams ? $this->teamId : false, + keys: $keys, + ))->map(function (mixed $record): mixed { + $record = $this->normalizeBulkRetrievedValue($record); + $value = $record->value; + + if ($value !== null) { + $value = $this->unserializeValue($this->decryptValue($value)); + } + + $record->value = $value; + $record->original_key = $record->key; + $record->key = $this->keyGenerator->removeContextFromKey($record->key); + + return $record; + }); + + if ($this->resetContext) { + $this->context(); + } + + $this->temporarilyDisableCache = false; + $this->resetContext = true; + + return $values; + } + public function has($key): bool { $key = $this->normalizeKey($key); @@ -284,6 +338,11 @@ public function useCacheKeyPrefix(string $prefix): self return $this; } + public function getKeyGenerator(): KeyGenerator + { + return $this->keyGenerator; + } + protected function normalizeKey(string $key): string { if (Str::startsWith(haystack: $key, needles: 'file_')) { @@ -373,4 +432,42 @@ protected function doNotResetContext(): self return $this; } + + protected function normalizeBulkRetrievedValue(mixed $record): object + { + if (is_array($record)) { + $record = (object) $record; + } + + throw_unless( + is_object($record) || $record instanceof Model, + InvalidBulkValueResult::notObject(), + ); + + throw_unless( + isset($record->key, $record->value), + InvalidBulkValueResult::missingValueOrKey(), + ); + + return $record; + } + + protected function normalizeBulkLookupKey($key): string|Collection|bool + { + if (is_null($key) && $this->context !== null) { + throw_if( + $this->keyGenerator instanceof Md5KeyGenerator, + InvalidKeyGenerator::forPartialLookup($this->keyGenerator::class), + ); + + return is_bool($this->context) + ? $this->context + : $this->keyGenerator->generate('', $this->context); + } + + return collect($key) + ->flatten() + ->filter() + ->map(fn (string $key): string => $this->getKeyForStorage($this->normalizeKey($key))); + } } diff --git a/src/SettingsServiceProvider.php b/src/SettingsServiceProvider.php index c201959..751fefb 100644 --- a/src/SettingsServiceProvider.php +++ b/src/SettingsServiceProvider.php @@ -85,6 +85,8 @@ protected function registerSettings(): void $app['config']['settings.encryption'] ? $settings->enableEncryption() : $settings->disableEncryption(); $app['config']['settings.teams'] ? $settings->enableTeams() : $settings->disableTeams(); + $settings->setTeamForeignKey($app['config']['settings.team_foreign_key'] ?? 'team_id'); + return $settings; }); } diff --git a/src/Support/KeyGenerators/Md5KeyGenerator.php b/src/Support/KeyGenerators/Md5KeyGenerator.php index e1196d4..d827090 100644 --- a/src/Support/KeyGenerators/Md5KeyGenerator.php +++ b/src/Support/KeyGenerators/Md5KeyGenerator.php @@ -7,6 +7,7 @@ use Rawilk\Settings\Contracts\ContextSerializer; use Rawilk\Settings\Contracts\KeyGenerator as KeyGeneratorContract; use Rawilk\Settings\Support\Context; +use RuntimeException; class Md5KeyGenerator implements KeyGeneratorContract { @@ -17,10 +18,20 @@ public function generate(string $key, Context $context = null): string return md5($key . $this->serializer->serialize($context)); } + public function removeContextFromKey(string $key): string + { + throw new RuntimeException('Md5KeyGenerator does not support removing the context from the key.'); + } + public function setContextSerializer(ContextSerializer $serializer): self { $this->serializer = $serializer; return $this; } + + public function contextPrefix(): string + { + throw new RuntimeException('Md5KeyGenerator does not support a context prefix.'); + } } diff --git a/src/Support/KeyGenerators/ReadableKeyGenerator.php b/src/Support/KeyGenerators/ReadableKeyGenerator.php index c8ac3fd..9c73f73 100644 --- a/src/Support/KeyGenerators/ReadableKeyGenerator.php +++ b/src/Support/KeyGenerators/ReadableKeyGenerator.php @@ -18,19 +18,29 @@ public function generate(string $key, Context $context = null): string $key = $this->normalizeKey($key); if ($context) { - $key .= ':c:::' . $this->serializer->serialize($context); + $key .= $this->contextPrefix() . $this->serializer->serialize($context); } return $key; } - public function setContextSerializer(ContextSerializer $serializer): KeyGeneratorContract + public function removeContextFromKey(string $key): string + { + return Str::before($key, $this->contextPrefix()); + } + + public function setContextSerializer(ContextSerializer $serializer): self { $this->serializer = $serializer; return $this; } + public function contextPrefix(): string + { + return ':c:::'; + } + protected function normalizeKey(string $key): string { // We want to preserve period characters in the key, however everything else is fair game diff --git a/src/helpers.php b/src/helpers.php index 20aa94f..0fbf514 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -29,7 +29,7 @@ function settings($key = null, $default = null, $context = null) return null; } - if ($context instanceof Context) { + if ($context instanceof Context || is_bool($context)) { $settings->context($context); } diff --git a/tests/Feature/Drivers/DatabaseDriverTest.php b/tests/Feature/Drivers/DatabaseDriverTest.php index 42357f6..ea66ef4 100644 --- a/tests/Feature/Drivers/DatabaseDriverTest.php +++ b/tests/Feature/Drivers/DatabaseDriverTest.php @@ -155,3 +155,50 @@ 'team_id' => 1, ]); }); + +it('can get all persisted settings', function () { + $this->driver->set('one', 'value 1', false); + $this->driver->set('two', 'value 2', false); + + $settings = $this->driver->all(); + + expect($settings)->toHaveCount(2) + ->first()->value->toBe('value 1') + ->and($settings[1]->value)->toBe('value 2'); +}); + +it('can get a subset of persisted settings', function () { + $this->driver->set('one', 'value 1', false); + $this->driver->set('two', 'value 2', false); + $this->driver->set('three', 'value 3', false); + + $settings = $this->driver->all(keys: ['one', 'three']); + + expect($settings)->toHaveCount(2) + ->first()->value->toBe('value 1') + ->and($settings[1]->value)->toBe('value 3'); +}); + +it('can get all of a teams persisted settings', function () { + $this->driver->set('one', 'value 1', 1); + $this->driver->set('two', 'value 2', 1); + $this->driver->set('one', 'team 2 value 1', 2); + + $settings = $this->driver->all(teamId: 1); + + expect($settings)->toHaveCount(2) + ->first()->value->toBe('value 1') + ->and($settings[1]->value)->toBe('value 2'); +}); + +it('can do partial lookups on all', function () { + $this->driver->set('one:1', 'value 1'); + $this->driver->set('one:2', 'value 2_1'); + $this->driver->set('two:1', 'value 2'); + + $settings = $this->driver->all(keys: ':1'); + + expect($settings)->toHaveCount(2) + ->first()->value->toBe('value 1') + ->and($settings[1]->value)->toBe('value 2'); +}); diff --git a/tests/Feature/Drivers/EloquentDriverTest.php b/tests/Feature/Drivers/EloquentDriverTest.php index fe32ead..dfd5cdf 100644 --- a/tests/Feature/Drivers/EloquentDriverTest.php +++ b/tests/Feature/Drivers/EloquentDriverTest.php @@ -149,3 +149,50 @@ 'team_id' => 1, ]); }); + +it('can get all persisted settings', function () { + $this->driver->set('one', 'value 1', false); + $this->driver->set('two', 'value 2', false); + + $settings = $this->driver->all(); + + expect($settings)->toHaveCount(2) + ->first()->value->toBe('value 1') + ->and($settings[1]->value)->toBe('value 2'); +}); + +it('can get a subset of persisted settings', function () { + $this->driver->set('one', 'value 1', false); + $this->driver->set('two', 'value 2', false); + $this->driver->set('three', 'value 3', false); + + $settings = $this->driver->all(keys: ['one', 'three']); + + expect($settings)->toHaveCount(2) + ->first()->value->toBe('value 1') + ->and($settings[1]->value)->toBe('value 3'); +}); + +it('can get all of a teams persisted settings', function () { + $this->driver->set('one', 'value 1', 1); + $this->driver->set('two', 'value 2', 1); + $this->driver->set('one', 'team 2 value 1', 2); + + $settings = $this->driver->all(teamId: 1); + + expect($settings)->toHaveCount(2) + ->first()->value->toBe('value 1') + ->and($settings[1]->value)->toBe('value 2'); +}); + +it('can do partial lookups on all', function () { + $this->driver->set('one:1', 'value 1'); + $this->driver->set('one:2', 'value 2_1'); + $this->driver->set('two:1', 'value 2'); + + $settings = $this->driver->all(keys: ':1'); + + expect($settings)->toHaveCount(2) + ->first()->value->toBe('value 1') + ->and($settings[1]->value)->toBe('value 2'); +}); diff --git a/tests/Feature/SettingsTest.php b/tests/Feature/SettingsTest.php index 8bd5863..f3ff91d 100644 --- a/tests/Feature/SettingsTest.php +++ b/tests/Feature/SettingsTest.php @@ -3,9 +3,17 @@ declare(strict_types=1); use Illuminate\Support\Facades\DB; +use Rawilk\Settings\Contracts\Setting; +use Rawilk\Settings\Drivers\EloquentDriver; +use Rawilk\Settings\Exceptions\InvalidKeyGenerator; use Rawilk\Settings\Facades\Settings as SettingsFacade; use Rawilk\Settings\Support\Context; +use Rawilk\Settings\Support\ContextSerializers\ContextSerializer; +use Rawilk\Settings\Support\ContextSerializers\DotNotationContextSerializer; +use Rawilk\Settings\Support\KeyGenerators\Md5KeyGenerator; +use Rawilk\Settings\Support\KeyGenerators\ReadableKeyGenerator; use Rawilk\Settings\Support\ValueSerializers\JsonValueSerializer; +use Rawilk\Settings\Tests\Support\Models\Team; beforeEach(function () { config([ @@ -303,6 +311,88 @@ ->and($settings->get('array-value'))->toMatchArray(['foo' => 'bar', 'bool' => true]); }); +it('can get all persisted values', function () { + // config([ + // 'settings.teams' => true, + // ]); + // + // migrateTeams(); + // migrateTestTables(); + + $settings = settings(); + (fn () => $this->keyGenerator = (new ReadableKeyGenerator)->setContextSerializer(new DotNotationContextSerializer))->call($settings); + + // $team = Team::factory()->create(); + // + // $settings->enableTeams(); + // $settings->setTeamId($team->id); + // + // $context = new Context(['id' => 'foo']); + + $settings->set('one', 'value 1'); + $settings->set('two', 'value 2'); + + $storedSettings = $settings->all(); + + expect($storedSettings)->toHaveCount(2) + ->and($storedSettings[0]->key)->toBe('one') + ->and($storedSettings[0]->original_key)->toBe('one') + ->and($storedSettings[0]->value)->toBe('value 1') + ->and($storedSettings[1]->key)->toBe('two') + ->and($storedSettings[1]->original_key)->toBe('two') + ->and($storedSettings[1]->value)->toBe('value 2'); +}); + +test('retrieving all settings works with the Eloquent driver', function () { + $settings = settings(); + (fn () => $this->driver = new EloquentDriver(app(Setting::class)))->call($settings); + (fn () => $this->keyGenerator = (new ReadableKeyGenerator)->setContextSerializer(new DotNotationContextSerializer))->call($settings); + + $settings->set('one', 'value 1'); + $settings->set('two', 'value 2'); + + $storedSettings = $settings->all(); + + expect($storedSettings)->toHaveCount(2) + ->and($storedSettings[0]->key)->toBe('one') + ->and($storedSettings[0]->original_key)->toBe('one') + ->and($storedSettings[0]->value)->toBe('value 1') + ->and($storedSettings[1]->key)->toBe('two') + ->and($storedSettings[1]->original_key)->toBe('two') + ->and($storedSettings[1]->value)->toBe('value 2'); +}); + +it('can retrieve all settings for a given context', function () { + $settings = settings(); + (fn () => $this->keyGenerator = (new ReadableKeyGenerator)->setContextSerializer(new DotNotationContextSerializer))->call($settings); + + $context = new Context(['id' => 'foo']); + $contextTwo = new Context(['id' => 'foobar']); + + $settings->set('one', 'no context value'); + $settings->set('two', 'no context value 2'); + $settings->context($context)->set('one', 'context one value 1'); + $settings->context($context)->set('two', 'context one value 2'); + $settings->context($contextTwo)->set('one', 'context two value 1'); + + $storedSettings = $settings->context($context)->all(); + + expect($storedSettings)->toHaveCount(2) + ->and($storedSettings[0]->key)->toBe('one') + ->and($storedSettings[0]->original_key)->toBe('one:c:::id:foo') + ->and($storedSettings[0]->value)->toBe('context one value 1') + ->and($storedSettings[1]->key)->toBe('two') + ->and($storedSettings[1]->original_key)->toBe('two:c:::id:foo') + ->and($storedSettings[1]->value)->toBe('context one value 2'); +}); + +it('throws an exception when doing a partial context lookup using the md5 key generator', function () { + $settings = settings(); + (fn () => $this->keyGenerator = (new Md5KeyGenerator)->setContextSerializer(new ContextSerializer))->call($settings); + + SettingsFacade::context(new Context(['id' => 1]))->all(); +})->throws(InvalidKeyGenerator::class); + // Helpers... function assertQueryCount(int $expected): void diff --git a/tests/Feature/TeamsTest.php b/tests/Feature/TeamsTest.php index d239e63..a01d097 100644 --- a/tests/Feature/TeamsTest.php +++ b/tests/Feature/TeamsTest.php @@ -4,6 +4,9 @@ use Illuminate\Support\Facades\DB; use Rawilk\Settings\Facades\Settings as SettingsFacade; +use Rawilk\Settings\Support\Context; +use Rawilk\Settings\Support\ContextSerializers\DotNotationContextSerializer; +use Rawilk\Settings\Support\KeyGenerators\ReadableKeyGenerator; use Rawilk\Settings\Tests\Support\Models\Team; beforeEach(function () { @@ -163,3 +166,39 @@ expect(SettingsFacade::get('foo'))->toBe('team 1 value'); }); + +test("all of a team's settings can be retrieved at once", function () { + $team = Team::first(); + + $settings = settings(); + (fn () => $this->keyGenerator = (new ReadableKeyGenerator)->setContextSerializer(new DotNotationContextSerializer))->call($settings); + + $settings->set('one', 'non-team value'); + $settings->context(new Context(['id' => 'foo']))->set('one', 'non-team value context 1'); + + $settings->setTeamId($team); + + $settings->set('one', 'team value'); + $settings->set('two', 'team value 2'); + $settings->context(new Context(['id' => 'foo']))->set('one', 'team value context 1'); + + $storedSettings = $settings->all(); + + expect($storedSettings)->toHaveCount(3) + ->and($storedSettings[0]->key)->toBe('one') + ->and($storedSettings[0]->team_id)->toBe($team->id) + ->and($storedSettings[0]->value)->toBe('team value') + ->and($storedSettings[1]->key)->toBe('two') + ->and($storedSettings[1]->team_id)->toBe($team->id) + ->and($storedSettings[1]->value)->toBe('team value 2') + ->and($storedSettings[2]->key)->toBe('one') + ->and($storedSettings[2]->team_id)->toBe($team->id) + ->and($storedSettings[2]->value)->toBe('team value context 1') + ->and($storedSettings[2]->original_key)->toBe('one:c:::id:foo'); + + // Can get all team settings without context attached to them. + $nonContextSettings = $settings->context(false)->all(); + + expect($nonContextSettings)->toHaveCount(2) + ->and($nonContextSettings->pluck('value'))->not->toContain('team value context 1'); +});