From 0a0a5e9f04ef50b365edace2b1a84b95a79f0425 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Mon, 14 Aug 2023 12:53:20 -0700 Subject: [PATCH] Create 1.4 response filter --- app/controllers/general.php | 4 + src/Appwrite/Utopia/Response/Filters/V16.php | 366 +++++++++ .../unit/Utopia/Response/Filters/V16Test.php | 700 ++++++++++++++++++ 3 files changed, 1070 insertions(+) create mode 100644 src/Appwrite/Utopia/Response/Filters/V16.php create mode 100644 tests/unit/Utopia/Response/Filters/V16Test.php diff --git a/app/controllers/general.php b/app/controllers/general.php index 59821296d8..d3edee4176 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -25,6 +25,7 @@ use Appwrite\Utopia\Response\Filters\V12 as ResponseV12; use Appwrite\Utopia\Response\Filters\V13 as ResponseV13; use Appwrite\Utopia\Response\Filters\V14 as ResponseV14; use Appwrite\Utopia\Response\Filters\V15 as ResponseV15; +use Appwrite\Utopia\Response\Filters\V15 as ResponseV16; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -305,6 +306,9 @@ App::init() case version_compare($responseFormat, '0.15.3', '<='): Response::setFilter(new ResponseV15()); break; + case version_compare($responseFormat, '1.4.0', '<'): + Response::setFilter(new ResponseV16()); + break; default: Response::setFilter(null); } diff --git a/src/Appwrite/Utopia/Response/Filters/V16.php b/src/Appwrite/Utopia/Response/Filters/V16.php new file mode 100644 index 0000000000..763b20bb1f --- /dev/null +++ b/src/Appwrite/Utopia/Response/Filters/V16.php @@ -0,0 +1,366 @@ +parseDeployment($parsedResponse); + break; + case Response::MODEL_PROXY_RULE: + // We won't be supporting the domain endpoints for older SDKs + // since these APIs are internal. As such, no filtering required + break; + case Response::MODEL_EXECUTION: + $parsedResponse = $this->parseExecution($parsedResponse); + break; + case Response::MODEL_FUNCTION: + $parsedResponse = $this->parseFunction($parsedResponse); + break; + case Response::MODEL_PROJECT: + $parsedResponse = $this->parseProject($parsedResponse); + break; + // We've decided to push these usage changes to a future release so + // these changes may still be helpful down the line. + // case Response::MODEL_USAGE_BUCKETS: + // $parsedResponse = $this->parseUsageBuckets($parsedResponse); + // break; + // case Response::MODEL_USAGE_COLLECTION: + // $parsedResponse = $this->parseUsageCollection($parsedResponse); + // break; + // case Response::MODEL_USAGE_DATABASE: + // $parsedResponse = $this->parseUsageDatabase($parsedResponse); + // break; + // case Response::MODEL_USAGE_DATABASES: + // $parsedResponse = $this->parseUsageDatabases($parsedResponse); + // break; + // case Response::MODEL_USAGE_FUNCTION: + // $parsedResponse = $this->parseUsageFunction($parsedResponse); + // break; + // case Response::MODEL_USAGE_FUNCTIONS: + // $parsedResponse = $this->parseUsageFunctions($parsedResponse); + // break; + // case Response::MODEL_USAGE_PROJECT: + // $parsedResponse = $this->parseUsageProject($parsedResponse); + // break; + // case Response::MODEL_USAGE_STORAGE: + // $parsedResponse = $this->parseUsageStorage($parsedResponse); + // break; + // case Response::MODEL_USAGE_USERS: + // $parsedResponse = $this->parseUsageUsers($parsedResponse); + // break; + case Response::MODEL_VARIABLE: + $parsedResponse = $this->parseVariable($parsedResponse); + break; + } + + return $parsedResponse; + } + + protected function parseDeployment(array $content) + { + $content['buildStderr'] = ''; + $content['buildStdout'] = $content['buildLogs']; + unset($content['buildLogs']); + return $content; + } + + protected function parseExecution(array $content) + { + if (isset($content['responseStatusCode'])) { + $content['statusCode'] = $content['responseStatusCode']; + unset($content['responseStatusCode']); + } + + if (isset($content['responseBody'])) { + $content['response'] = $content['responseBody']; + unset($content['responseBody']); + } + + if (isset($content['logs'])) { + $content['stdout'] = $content['logs']; + unset($content['logs']); + } + + if (isset($content['errors'])) { + $content['stderr'] = $content['errors']; + unset($content['errors']); + } + + return $content; + } + + protected function parseFunction(array $content) + { + $content['schedulePrevious'] = ''; + $content['scheduleNext'] = ''; + + if (!empty($content['schedule'])) { + $cron = new CronExpression($content['schedule']); + $content['schedulePrevious'] = DateTime::formatTz(DateTime::format($cron->getPreviousRunDate())); + $content['scheduleNext'] = DateTime::formatTz(DateTime::format($cron->getNextRunDate())); + } + + return $content; + } + + protected function parseProject(array $content) + { + foreach ($content['providers'] ?? [] as $i => $provider) { + $content['providers'][$i]['name'] = \ucfirst($provider['key']); + unset($content['providers'][$i]['key']); + } + + $content['domains'] = []; + return $content; + } + + protected function parseUsageBuckets(array $content) + { + if (isset($content['filesTotal'])) { + $content['filesCount'] = $content['filesTotal']; + unset($content['filesTotal']); + } + + $attributesToInit = ['filesCreate', 'filesRead', 'filesUpdate', 'filesDelete']; + foreach ($attributesToInit as $attribute) { + $content[$attribute] = []; + } + + return $content; + } + + protected function parseUsageCollection(array $content) + { + if (isset($content['documentsTotal'])) { + $content['documentsCount'] = $content['documentsTotal']; + unset($content['documentsTotal']); + } + + $attributesToInit = ['documentsCreate', 'documentsRead', 'documentsUpdate', 'documentsDelete']; + foreach ($attributesToInit as $attribute) { + $content[$attribute] = []; + } + + return $content; + } + + protected function parseUsageDatabase(array $content) + { + if (isset($content['documentsTotal'])) { + $content['documentsCount'] = $content['documentsTotal']; + unset($content['documentsTotal']); + } + + if (isset($content['collectionsTotal'])) { + $content['collectionsCount'] = $content['collectionsTotal']; + unset($content['collectionsTotal']); + } + + $attributesToInit = [ + 'documentsCreate', + 'documentsRead', + 'documentsUpdate', + 'documentsDelete', + 'collectionsCreate', + 'collectionsRead', + 'collectionsUpdate', + 'collectionsDelete', + ]; + foreach ($attributesToInit as $attribute) { + $content[$attribute] = []; + } + + return $content; + } + + protected function parseUsageDatabases(array $content) + { + if (isset($content['documentsTotal'])) { + $content['documentsCount'] = $content['documentsTotal']; + unset($content['documentsTotal']); + } + + if (isset($content['collectionsTotal'])) { + $content['collectionsCount'] = $content['collectionsTotal']; + unset($content['collectionsTotal']); + } + + if (isset($content['databasesTotal'])) { + $content['databasesCount'] = $content['databasesTotal']; + unset($content['databasesTotal']); + } + + $attributesToInit = [ + 'documentsCreate', + 'documentsRead', + 'documentsUpdate', + 'documentsDelete', + 'collectionsCreate', + 'collectionsRead', + 'collectionsUpdate', + 'collectionsDelete', + 'databasesCreate', + 'databasesRead', + 'databasesUpdate', + 'databasesDelete', + ]; + foreach ($attributesToInit as $attribute) { + $content[$attribute] = []; + } + + return $content; + } + + protected function parseUsageFunction(array $content) + { + $attributesToInit = [ + 'buildsFailure', + 'buildsSuccess', + 'executionsFailure', + 'executionsSuccess', + ]; + foreach ($attributesToInit as $attribute) { + $content[$attribute] = []; + } + + return $content; + } + + protected function parseUsageFunctions(array $content) + { + $attributesToInit = [ + 'buildsFailure', + 'buildsSuccess', + 'executionsFailure', + 'executionsSuccess', + ]; + foreach ($attributesToInit as $attribute) { + $content[$attribute] = []; + } + + return $content; + } + + protected function parseUsageProject(array $content) + { + if (isset($content['requestsTotal'])) { + $content['requests'] = $content['requestsTotal']; + unset($content['requestsTotal']); + } + + if (isset($content['executionsTotal'])) { + $content['executions'] = $content['executionsTotal']; + unset($content['executionsTotal']); + } + + if (isset($content['documentsTotal'])) { + $content['documents'] = $content['documentsTotal']; + unset($content['documentsTotal']); + } + + if (isset($content['databasesTotal'])) { + $content['databases'] = $content['databasesTotal']; + unset($content['databasesTotal']); + } + + if (isset($content['usersTotal'])) { + $content['users'] = $content['usersTotal']; + unset($content['usersTotal']); + } + + if (isset($content['filesStorage'])) { + $content['storage'] = $content['filesStorage']; + unset($content['filesStorage']); + } + + if (isset($content['bucketsTotal'])) { + $content['buckets'] = $content['bucketsTotal']; + unset($content['bucketsTotal']); + } + + return $content; + } + + protected function parseUsageStorage(array $content) + { + if (isset($content['bucketsTotal'])) { + $content['bucketsCount'] = $content['bucketsTotal']; + unset($content['bucketsTotal']); + } + + if (isset($content['filesTotal'])) { + $content['filesCount'] = $content['filesTotal']; + unset($content['filesTotal']); + } + + if (isset($content['filesStorage'])) { + $content['storage'] = $content['filesStorage']; + unset($content['filesStorage']); + } + + $attributesToInit = [ + 'bucketsCreate', + 'bucketsRead', + 'bucketsUpdate', + 'bucketsDelete', + 'filesCreate', + 'filesRead', + 'filesUpdate', + 'filesDelete', + ]; + foreach ($attributesToInit as $attribute) { + $content[$attribute] = []; + } + + return $content; + } + + protected function parseUsageUsers(array $content) + { + if (isset($content['usersTotal'])) { + $content['usersCount'] = $content['usersTotal']; + unset($content['usersTotal']); + } + + if (isset($content['sessionsTotal'])) { + $content['sessionsCreate'] = $content['sessionsTotal']; + unset($content['sessionsTotal']); + } + + $attributesToInit = [ + 'usersCreate', + 'usersRead', + 'usersUpdate', + 'usersDelete', + 'sessionsProviderCreate', + 'sessionsDelete', + ]; + foreach ($attributesToInit as $attribute) { + $content[$attribute] = []; + } + + return $content; + } + + protected function parseVariable(array $content) + { + if (isset($content['resourceId'])) { + $content['functionId'] = $content['resourceId']; + unset($content['resourceId']); + } + + return $content; + } +} diff --git a/tests/unit/Utopia/Response/Filters/V16Test.php b/tests/unit/Utopia/Response/Filters/V16Test.php new file mode 100644 index 0000000000..e819c80e5e --- /dev/null +++ b/tests/unit/Utopia/Response/Filters/V16Test.php @@ -0,0 +1,700 @@ +filter = new V16(); + } + + public function tearDown(): void + { + } + + public function deploymentProvider(): array + { + return [ + 'buildStdout and buildStderr' => [ + [ + 'buildLogs' => 'Compiling source files...', + ], + [ + 'buildStdout' => 'Compiling source files...', + 'buildStderr' => '', + ], + ], + ]; + } + + /** + * @dataProvider deploymentProvider + */ + public function testDeployment(array $content, array $expected): void + { + $model = Response::MODEL_DEPLOYMENT; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function executionProvider(): array + { + return [ + 'statusCode' => [ + [ + 'responseStatusCode' => 200, + ], + [ + 'statusCode' => 200, + ], + ], + 'response' => [ + [ + 'responseBody' => 'Sample response.', + ], + [ + 'response' => 'Sample response.', + ], + ], + 'stdout' => [ + [ + 'logs' => 'Sample log.', + ], + [ + 'stdout' => 'Sample log.', + ], + ], + 'stderr' => [ + [ + 'errors' => 'Sample error.', + ], + [ + 'stderr' => 'Sample error.', + ], + ], + ]; + } + + /** + * @dataProvider executionProvider + */ + public function testExecution(array $content, array $expected): void + { + $model = Response::MODEL_EXECUTION; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function functionProvider(): array + { + return [ + 'empty schedule' => [ + [ + 'schedule' => '', + ], + [ + 'schedule' => '', + 'schedulePrevious' => '', + 'scheduleNext' => '', + ], + ], + ]; + } + + /** + * @dataProvider functionProvider + */ + public function testFunction(array $content, array $expected): void + { + $model = Response::MODEL_FUNCTION; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function testFunctionSchedulePreviousScheduleNext(): void + { + $model = Response::MODEL_FUNCTION; + + $content = [ + 'schedule' => '0 * * * *', + ]; + + $cron = new CronExpression($content['schedule']); + + $expected = [ + 'schedule' => '0 * * * *', + 'scheduleNext' => DateTime::formatTz(DateTime::format($cron->getNextRunDate())), + 'schedulePrevious' => DateTime::formatTz(DateTime::format($cron->getPreviousRunDate())), + ]; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function projectProvider(): array + { + return [ + 'providers' => [ + [ + 'providers' => [ + [ + 'key' => 'github', + 'name' => 'GitHub', + 'appId' => 'client_id', + 'secret' => 'client_secret', + 'enabled' => true, + ], + ], + ], + [ + 'providers' => [ + [ + 'name' => 'Github', + 'appId' => 'client_id', + 'secret' => 'client_secret', + 'enabled' => true, + ], + ], + 'domains' => [], + ], + ], + 'domains' => [ + [ + ], + [ + 'domains' => [], + ], + ], + ]; + } + + /** + * @dataProvider projectProvider + */ + public function testProject(array $content, array $expected): void + { + $model = Response::MODEL_PROJECT; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function usageBucketsProvider(): array + { + $metrics = [ + [ + 'value' => 123, + 'date' => Model::TYPE_DATETIME_EXAMPLE, + ] + ]; + return [ + 'filesCount and deleted usage' => [ + [ + 'filesTotal' => $metrics, + ], + [ + 'filesCount' => $metrics, + 'filesCreate' => [], + 'filesRead' => [], + 'filesUpdate' => [], + 'filesDelete' => [], + ], + ], + ]; + } + + /** + * @dataProvider usageBucketsProvider + */ + public function testUsageBuckets(array $content, array $expected): void + { + $model = Response::MODEL_USAGE_BUCKETS; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function usageCollectionProvider(): array + { + $metrics = [ + [ + 'value' => 123, + 'date' => Model::TYPE_DATETIME_EXAMPLE, + ] + ]; + return [ + 'documentsCount and deleted usage' => [ + [ + 'documentsTotal' => $metrics, + ], + [ + 'documentsCount' => $metrics, + 'documentsCreate' => [], + 'documentsRead' => [], + 'documentsUpdate' => [], + 'documentsDelete' => [], + ], + ], + ]; + } + + /** + * @dataProvider usageCollectionProvider + */ + public function testUsageCollection(array $content, array $expected): void + { + $model = Response::MODEL_USAGE_COLLECTION; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function usageDatabaseProvider(): array + { + $metrics = [ + [ + 'value' => 123, + 'date' => Model::TYPE_DATETIME_EXAMPLE, + ] + ]; + return [ + 'collectionsCount and deleted usage' => [ + [ + 'collectionsTotal' => $metrics, + ], + [ + 'collectionsCount' => $metrics, + 'documentsCreate' => [], + 'documentsRead' => [], + 'documentsUpdate' => [], + 'documentsDelete' => [], + 'collectionsCreate' => [], + 'collectionsRead' => [], + 'collectionsUpdate' => [], + 'collectionsDelete' => [], + ], + ], + 'documentsCount and deleted usage' => [ + [ + 'documentsTotal' => $metrics, + ], + [ + 'documentsCount' => $metrics, + 'documentsCreate' => [], + 'documentsRead' => [], + 'documentsUpdate' => [], + 'documentsDelete' => [], + 'collectionsCreate' => [], + 'collectionsRead' => [], + 'collectionsUpdate' => [], + 'collectionsDelete' => [], + ], + ], + ]; + } + + /** + * @dataProvider usageDatabaseProvider + */ + public function testUsageDatabase(array $content, array $expected): void + { + $model = Response::MODEL_USAGE_DATABASE; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function usageDatabasesProvider(): array + { + $metrics = [ + [ + 'value' => 123, + 'date' => Model::TYPE_DATETIME_EXAMPLE, + ] + ]; + return [ + 'databasesCount and deleted usage' => [ + [ + 'databasesTotal' => $metrics, + ], + [ + 'databasesCount' => $metrics, + 'documentsCreate' => [], + 'documentsRead' => [], + 'documentsUpdate' => [], + 'documentsDelete' => [], + 'collectionsCreate' => [], + 'collectionsRead' => [], + 'collectionsUpdate' => [], + 'collectionsDelete' => [], + 'databasesCreate' => [], + 'databasesRead' => [], + 'databasesUpdate' => [], + 'databasesDelete' => [], + ], + ], + 'collectionsCount and deleted usage' => [ + [ + 'collectionsTotal' => $metrics, + ], + [ + 'collectionsCount' => $metrics, + 'documentsCreate' => [], + 'documentsRead' => [], + 'documentsUpdate' => [], + 'documentsDelete' => [], + 'collectionsCreate' => [], + 'collectionsRead' => [], + 'collectionsUpdate' => [], + 'collectionsDelete' => [], + 'databasesCreate' => [], + 'databasesRead' => [], + 'databasesUpdate' => [], + 'databasesDelete' => [], + ], + ], + 'documentsCount and deleted usage' => [ + [ + 'documentsTotal' => $metrics, + ], + [ + 'documentsCount' => $metrics, + 'documentsCreate' => [], + 'documentsRead' => [], + 'documentsUpdate' => [], + 'documentsDelete' => [], + 'collectionsCreate' => [], + 'collectionsRead' => [], + 'collectionsUpdate' => [], + 'collectionsDelete' => [], + 'databasesCreate' => [], + 'databasesRead' => [], + 'databasesUpdate' => [], + 'databasesDelete' => [], + ], + ], + ]; + } + + /** + * @dataProvider usageDatabasesProvider + */ + public function testUsageDatabases(array $content, array $expected): void + { + $model = Response::MODEL_USAGE_DATABASES; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function usageFunctionProvider(): array + { + return [ + 'deleted usage' => [ + [ + ], + [ + 'buildsFailure' => [], + 'buildsSuccess' => [], + 'executionsFailure' => [], + 'executionsSuccess' => [], + ], + ], + ]; + } + + /** + * @dataProvider usageFunctionProvider + */ + public function testUsageFunction(array $content, array $expected): void + { + $model = Response::MODEL_USAGE_FUNCTION; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function usageFunctionsProvider(): array + { + return [ + 'deleted usage' => [ + [ + ], + [ + 'buildsFailure' => [], + 'buildsSuccess' => [], + 'executionsFailure' => [], + 'executionsSuccess' => [], + ], + ], + ]; + } + + /** + * @dataProvider usageFunctionsProvider + */ + public function testUsageFunctions(array $content, array $expected): void + { + $model = Response::MODEL_USAGE_FUNCTIONS; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function usageProjectProvider(): array + { + $metrics = [ + [ + 'value' => 123, + 'date' => Model::TYPE_DATETIME_EXAMPLE, + ] + ]; + return [ + 'requests usage' => [ + [ + 'requestsTotal' => $metrics, + ], + [ + 'requests' => $metrics, + ], + ], + 'executions usage' => [ + [ + 'executionsTotal' => $metrics, + ], + [ + 'executions' => $metrics, + ], + ], + 'documents usage' => [ + [ + 'documentsTotal' => $metrics, + ], + [ + 'documents' => $metrics, + ], + ], + 'databases usage' => [ + [ + 'databasesTotal' => $metrics, + ], + [ + 'databases' => $metrics, + ], + ], + 'users usage' => [ + [ + 'usersTotal' => $metrics, + ], + [ + 'users' => $metrics, + ], + ], + 'storage usage' => [ + [ + 'filesStorage' => $metrics, + ], + [ + 'storage' => $metrics, + ], + ], + 'buckets usage' => [ + [ + 'bucketsTotal' => $metrics, + ], + [ + 'buckets' => $metrics, + ], + ], + ]; + } + + /** + * @dataProvider usageProjectProvider + */ + public function testUsageProject(array $content, array $expected): void + { + $model = Response::MODEL_USAGE_PROJECT; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function usageStorageProvider(): array + { + $metrics = [ + [ + 'value' => 123, + 'date' => Model::TYPE_DATETIME_EXAMPLE, + ] + ]; + return [ + 'bucketsCount usage' => [ + [ + 'bucketsTotal' => $metrics, + ], + [ + 'bucketsCount' => $metrics, + 'bucketsCreate' => [], + 'bucketsRead' => [], + 'bucketsUpdate' => [], + 'bucketsDelete' => [], + 'filesCreate' => [], + 'filesRead' => [], + 'filesUpdate' => [], + 'filesDelete' => [], + ], + ], + 'filesCount usage' => [ + [ + 'filesTotal' => $metrics, + ], + [ + 'filesCount' => $metrics, + 'bucketsCreate' => [], + 'bucketsRead' => [], + 'bucketsUpdate' => [], + 'bucketsDelete' => [], + 'filesCreate' => [], + 'filesRead' => [], + 'filesUpdate' => [], + 'filesDelete' => [], + ], + ], + 'storage usage' => [ + [ + 'filesStorage' => $metrics, + ], + [ + 'storage' => $metrics, + 'bucketsCreate' => [], + 'bucketsRead' => [], + 'bucketsUpdate' => [], + 'bucketsDelete' => [], + 'filesCreate' => [], + 'filesRead' => [], + 'filesUpdate' => [], + 'filesDelete' => [], + ], + ], + ]; + } + + /** + * @dataProvider usageStorageProvider + */ + public function testUsageStorage(array $content, array $expected): void + { + $model = Response::MODEL_USAGE_STORAGE; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function usageUsersProvider(): array + { + $metrics = [ + [ + 'value' => 123, + 'date' => Model::TYPE_DATETIME_EXAMPLE, + ] + ]; + return [ + 'usersCount usage' => [ + [ + 'usersTotal' => $metrics, + ], + [ + 'usersCount' => $metrics, + 'usersCreate' => [], + 'usersRead' => [], + 'usersUpdate' => [], + 'usersDelete' => [], + 'sessionsProviderCreate' => [], + 'sessionsDelete' => [], + ], + ], + 'sessionsCreate usage' => [ + [ + 'sessionsTotal' => $metrics, + ], + [ + 'sessionsCreate' => $metrics, + 'usersCreate' => [], + 'usersRead' => [], + 'usersUpdate' => [], + 'usersDelete' => [], + 'sessionsProviderCreate' => [], + 'sessionsDelete' => [], + ], + ], + ]; + } + + /** + * @dataProvider usageUsersProvider + */ + public function testUsageUsers(array $content, array $expected): void + { + $model = Response::MODEL_USAGE_USERS; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function usageVariableProvider(): array + { + return [ + 'functionId' => [ + [ + 'resourceId' => '5e5ea5c16897e', + ], + [ + 'functionId' => '5e5ea5c16897e', + ], + ], + ]; + } + + /** + * @dataProvider usageVariableProvider + */ + public function testVariable(array $content, array $expected): void + { + $model = Response::MODEL_VARIABLE; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } +}