commercetools-8.x-1.2-alpha1/tests/modules/commercetools_test/src/CommerceToolsTesting.php
tests/modules/commercetools_test/src/CommerceToolsTesting.php
<?php
declare(strict_types=1);
namespace Drupal\commercetools_test;
use Drupal\commercetools\CommercetoolsApiService;
use Drupal\commercetools\CommercetoolsApiServiceInterface;
use Drupal\commercetools\CommercetoolsService;
use Drupal\Component\Serialization\Yaml;
use Drupal\test_helpers\Stub\HttpClientFactoryStub;
use Drupal\test_helpers\TestHelpers;
use Drupal\test_helpers_http_client_mock\HttpClientFactoryMock;
use GraphQL\Language\Parser;
use GraphQL\Language\Printer;
/**
* A class with helper functions for tests.
*/
class CommerceToolsTesting {
/**
* Env variable to control the mocking commercetools API mode.
*
* Allowed values:
* - mock (default): reads responses only from the stored asset files, if
* missing - throws an exception.
* - append: uses the asset files first, if missing - makes a real call and
* create/overwrite asset files.
* - store: always makes direct calls to API and create/overwrite asset files.
*/
const ENV_CT_API_MOCK_MODE = 'CT_API_MOCK_MODE';
const CT_API_MOCK_MODE_MOCK = 'mock';
const CT_API_MOCK_MODE_STORE = 'store';
const CT_API_MOCK_MODE_APPEND = 'append';
/**
* Env variable to log usage of stored responses to a file.
*/
const ENV_LOG_STORED_RESPONSES_FILE = 'CT_LOG_STORED_RESPONSES_FILE';
/**
* Env variables to override the test configuration variables.
*/
/**
* A directory to store responses of the real Commercetools API calls.
*/
const RESPONSES_STORAGE_DIRECTORY = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'assets';
/**
* Regexp to match the Commercetools API URL.
*
* @var string
*/
const COMMERCETOOLS_API_URL_REGEXP = '/commercetools\.com/';
/**
* Get the module test configuration.
*
* @param string $moduleName
* The original module, the test module is assumed with the '_test' suffix.
*
* @return array
* Array of the module configuration.
*/
public static function getTestConfiguration(string $moduleName): array {
$moduleTestName = $moduleName . '_test';
$modulePaths = [
'commercetools' => __DIR__ . '/../../../..',
'commercetools_content' => __DIR__ . '/../../../../modules/commercetools_content',
'commercetools_decoupled' => __DIR__ . '/../../../../modules/commercetools_decoupled',
'commercetools_demo' => __DIR__ . '/../../../../modules/commercetools_demo',
];
if (!isset($modulePaths[$moduleName])) {
throw new \Exception("The path for the module $moduleName is not configured.");
}
$configInstallDirectory = $modulePaths[$moduleName] . "/config/install";
if (file_exists($configInstallDirectory)) {
$configInstallFiles = scandir($configInstallDirectory);
foreach ($configInstallFiles ?? [] as $file) {
if (!preg_match('#^(.+)\.yml#', $file, $matches)) {
continue;
}
$configName = $matches[1];
$config[$configName] = Yaml::decode(file_get_contents($configInstallDirectory . DIRECTORY_SEPARATOR . $file));
}
}
$configOverrideFile = $modulePaths[$moduleName] . "/tests/modules/$moduleTestName/config/override/config.yml";
if (file_exists($configOverrideFile)) {
$configOverride = Yaml::decode(file_get_contents($configOverrideFile));
foreach ($configOverride as $key => $value) {
$config[$key] = array_merge($config[$key] ?? [], $configOverride[$key]);
}
}
if (empty($config)) {
return [];
}
// Get just the first key of the array, should work for our cases.
$configName = key($config);
// Set the demo API credentials only for the non-mock modes.
if (self::getCtApiMockMode() !== self::CT_API_MOCK_MODE_MOCK) {
$demoAccountName = 'b2c_lifestyle';
$demoAccount = Yaml::decode(
file_get_contents(
__DIR__ . '/../../../../modules/commercetools_demo/fixtures/demo_accounts/' . $demoAccountName . '.yml'
)
);
if (!is_array($demoAccount)) {
throw new \Exception("Can't read the demo account configuration for $demoAccountName.");
}
$config['commercetools.api'] = $demoAccount['config']['commercetools.api'];
}
return $config;
}
/**
* Gets the commercetools API mocking mode.
*
* Controllable by the CT_API_MOCK_MODE environment variable.
* To make real requests, it is required to pass real credentials via the
* environment variables.
*/
public static function getCtApiMockMode(): string {
return getenv(self::ENV_CT_API_MOCK_MODE) ?: self::CT_API_MOCK_MODE_MOCK;
}
/**
* Initiates the working `commercetools.api` Drupal service for unit tests.
*
* @param string|null $testName
* The name of the test.
*
* @return \Drupal\commercetools\CommercetoolsApiServiceInterface
* A working instance of the `commercetools.api` Drupal service.
*/
public static function getCommercetoolsApiService(?string $testName = NULL): CommercetoolsApiServiceInterface {
$config = self::getTestConfiguration('commercetools');
$moduleDirectory = TestHelpers::getModuleRoot(CommercetoolsService::class);
TestHelpers::service('string_translation');
TestHelpers::service('commercetools.psr_cache_adapter', servicesYamlFile: $moduleDirectory . '/commercetools.services.yml', initService: TRUE);
TestHelpers::service('path.current');
foreach ($config as $configName => $configData) {
TestHelpers::service('config.factory')->stubSetConfig($configName, $configData);
}
TestHelpers::service('commercetools.config', servicesYamlFile: $moduleDirectory . '/commercetools.services.yml', initService: TRUE);
$httpClientMockConfig = self::getHttpClientFactoryTestConfiguration();
// Initiate the CtHttpClientFactoryStub service with custom configuration.
$ctHttpClientFactoryStub = new CommercetoolsHttpClientFactoryStub(
requestMockMode: $httpClientMockConfig[HttpClientFactoryMock::SETTING_KEY_REQUEST_MOCK_MODE],
responsesStorageDirectory: $httpClientMockConfig[HttpClientFactoryMock::SETTING_KEY_RESPONSES_STORAGE_DIRECTORY],
testName: $testName,
options: [
HttpClientFactoryStub::OPTION_URI_REGEXP => $httpClientMockConfig[HttpClientFactoryMock::SETTING_KEY_URI_REGEXP],
HttpClientFactoryStub::OPTION_LOG_STORED_RESPONSES_USAGE_FILE => $httpClientMockConfig[HttpClientFactoryMock::SETTING_KEY_LOG_STORED_RESPONSES_USAGE_FILE] ?? NULL,
],
);
TestHelpers::service('http_client_factory', $ctHttpClientFactoryStub, forceOverride: TRUE);
TestHelpers::service('logger.channel.commercetools', TestHelpers::service('logger.factory')->get('commercetools'));
/**
* @var \Drupal\commercetools\CommercetoolsApiServiceInterface $service
*/
$service = TestHelpers::service(
'commercetools.api',
CommercetoolsApiService::class,
forceOverride: TRUE,
initService: TRUE
);
return $service;
}
/**
* Configures the HTTP client factory mock with test environment.
*/
public static function configureHttpClientFactoryMock(): void {
// Deploys a configuration from environment variables or mocked values.
$httpClientFactoryStub = \Drupal::service('http_client_factory');
$config = self::getHttpClientFactoryTestConfiguration();
foreach ($config as $key => $value) {
$httpClientFactoryStub->stubSetConfig($key, $value);
}
}
/**
* Generates the configuration for the HttpClientFactoryMock service.
*
* @return array
* The configuration array.
*/
public static function getHttpClientFactoryTestConfiguration(): array {
$config = [];
$config[HttpClientFactoryMock::SETTING_KEY_RESPONSES_STORAGE_DIRECTORY] = self::RESPONSES_STORAGE_DIRECTORY;
$config[HttpClientFactoryMock::SETTING_KEY_URI_REGEXP] = self::COMMERCETOOLS_API_URL_REGEXP;
$config[HttpClientFactoryMock::SETTING_KEY_REQUEST_MOCK_MODE] =
match (CommerceToolsTesting::getCtApiMockMode()) {
self::CT_API_MOCK_MODE_STORE => HttpClientFactoryStub::HTTP_CLIENT_MODE_STORE,
self::CT_API_MOCK_MODE_MOCK => HttpClientFactoryStub::HTTP_CLIENT_MODE_MOCK,
self::CT_API_MOCK_MODE_APPEND => HttpClientFactoryStub::HTTP_CLIENT_MODE_APPEND,
default => HttpClientFactoryStub::HTTP_CLIENT_MODE_MOCK,
};
if ($logFile = getenv(self::ENV_LOG_STORED_RESPONSES_FILE)) {
$config[HttpClientFactoryMock::SETTING_KEY_LOG_STORED_RESPONSES_USAGE_FILE] = $logFile;
}
return $config;
}
/**
* Initiates the working `commercetools.api` Drupal service for tests.
*
* @param string|null $testName
* The name of the test.
*
* @return \Drupal\commercetools\CommercetoolsService
* A working instance of the `commercetools` Drupal service.
*/
public static function getCommercetoolsService(?string $testName = NULL): CommercetoolsService {
$container = TestHelpers::getContainer();
if (!$container->has('commercetools.api')) {
self::getCommercetoolsApiService($testName);
}
TestHelpers::service('language_manager', initService: TRUE);
$moduleDirectory = TestHelpers::getModuleRoot(CommercetoolsService::class);
TestHelpers::service('commercetools.localization', servicesYamlFile: $moduleDirectory . '/commercetools.services.yml', initService: TRUE);
$service = TestHelpers::service('commercetools', servicesYamlFile: $moduleDirectory . '/commercetools.services.yml', initService: TRUE);
return $service;
}
/**
* Set the module test configuration.
*
* @param string $moduleName
* The module name.
*/
public static function setCommercetoolsTestConfig(string $moduleName) {
$configFactory = \Drupal::configFactory();
foreach (self::getTestConfiguration($moduleName) as $configName => $configData) {
$config = $configFactory->getEditable($configName);
foreach ($configData as $key => $value) {
$config->set($key, $value);
}
$config->save();
}
}
/**
* Formats the Commercetools request asset metadata body as multiline YAML.
*
* To make reviewing of the modified stored asset files metadata easier with
* better diffs.
*
* @param array $thMetadata
* The metadata array.
*
* @return array
* The formatted metadata array.
*/
public static function formatCtRequestAssetMetadata(array $thMetadata) {
if (!empty($thMetadata['body'])) {
try {
$bodyJsonDecoded = json_decode($thMetadata['body'], TRUE);
// If the body is not in JSON format, it will return NULL.
// In this case, we do not modify the body.
if ($bodyJsonDecoded === NULL) {
return $thMetadata;
}
// Detect GraphQL requests and force prettify them.
if (isset($bodyJsonDecoded['query'])) {
$graphqlQuery = $bodyJsonDecoded['query'];
$queryObject = Parser::parse($graphqlQuery);
$bodyJsonDecoded['query'] = Printer::doPrint($queryObject);
}
$encoded = Yaml::encode($bodyJsonDecoded);
unset($thMetadata['body']);
$thMetadata['body_formatted'] = '# Encoded using YAML format by the function .CommerceToolsTesting::formatCtRequestAssetMetadata()' . PHP_EOL . $encoded;
}
catch (\JsonException) {
// Do not modify if the body is not in JSON format.
}
}
return $thMetadata;
}
}
