|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace Unleash\Client\Bundle\Command; |
| 4 | + |
| 5 | +use LogicException; |
| 6 | +use Psr\SimpleCache\CacheInterface; |
| 7 | +use Symfony\Component\Console\Command\Command; |
| 8 | +use Symfony\Component\Console\Input\InputArgument; |
| 9 | +use Symfony\Component\Console\Input\InputInterface; |
| 10 | +use Symfony\Component\Console\Input\InputOption; |
| 11 | +use Symfony\Component\Console\Output\OutputInterface; |
| 12 | +use Symfony\Component\Console\Style\SymfonyStyle; |
| 13 | +use Unleash\Client\Configuration\Context; |
| 14 | +use Unleash\Client\Configuration\UnleashContext; |
| 15 | +use Unleash\Client\Unleash; |
| 16 | + |
| 17 | +final class TestFlagCommand extends Command |
| 18 | +{ |
| 19 | + public function __construct( |
| 20 | + string $name, |
| 21 | + private readonly Unleash $unleash, |
| 22 | + private readonly CacheInterface $cache, |
| 23 | + ) { |
| 24 | + parent::__construct($name); |
| 25 | + } |
| 26 | + |
| 27 | + protected function configure(): void |
| 28 | + { |
| 29 | + $this |
| 30 | + ->setDescription('Check the status of an Unleash feature') |
| 31 | + ->addArgument( |
| 32 | + name: 'flag', |
| 33 | + mode: InputArgument::REQUIRED, |
| 34 | + description: 'The name of the feature flag to check the result for', |
| 35 | + ) |
| 36 | + ->addOption( |
| 37 | + name: 'force', |
| 38 | + shortcut: 'f', |
| 39 | + mode: InputOption::VALUE_NONE, |
| 40 | + description: 'When this flag is present, fresh results without cache will be forced', |
| 41 | + ) |
| 42 | + ->addOption( |
| 43 | + name: 'user-id', |
| 44 | + mode: InputOption::VALUE_REQUIRED, |
| 45 | + description: "[Context] Provide the current user's ID", |
| 46 | + default: null, |
| 47 | + ) |
| 48 | + ->addOption( |
| 49 | + name: 'ip-address', |
| 50 | + mode: InputOption::VALUE_REQUIRED, |
| 51 | + description: '[Context] Provide the current IP address', |
| 52 | + default: null, |
| 53 | + ) |
| 54 | + ->addOption( |
| 55 | + name: 'session-id', |
| 56 | + mode: InputOption::VALUE_REQUIRED, |
| 57 | + description: '[Context] Provide the current session ID', |
| 58 | + default: null, |
| 59 | + ) |
| 60 | + ->addOption( |
| 61 | + name: 'hostname', |
| 62 | + mode: InputOption::VALUE_REQUIRED, |
| 63 | + description: '[Context] Provide the current hostname', |
| 64 | + default: null, |
| 65 | + ) |
| 66 | + ->addOption( |
| 67 | + name: 'environment', |
| 68 | + mode: InputOption::VALUE_REQUIRED, |
| 69 | + description: '[Context] Provide the current environment', |
| 70 | + default: null, |
| 71 | + ) |
| 72 | + ->addOption( |
| 73 | + name: 'current-time', |
| 74 | + mode: InputOption::VALUE_REQUIRED, |
| 75 | + description: '[Context] Provide the current date and time', |
| 76 | + default: null, |
| 77 | + ) |
| 78 | + ->addOption( |
| 79 | + 'custom-context', |
| 80 | + mode: InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, |
| 81 | + description: '[Context] Custom context values in the format [contextName]=[contextValue], for example: myCustomContextField=someValue', |
| 82 | + default: null, |
| 83 | + ) |
| 84 | + ->addOption( // must use positional arguments, because $suggestedValues is not a real argument |
| 85 | + 'expected', |
| 86 | + null, |
| 87 | + InputOption::VALUE_REQUIRED, |
| 88 | + 'For use in testing, if this option is present, the exit code will be either 0 or 1 depending on whether the expectation matches the result', |
| 89 | + null, |
| 90 | + ['true', 'false'], // suggested values |
| 91 | + ) |
| 92 | + ; |
| 93 | + } |
| 94 | + |
| 95 | + protected function execute(InputInterface $input, OutputInterface $output): int |
| 96 | + { |
| 97 | + $io = new SymfonyStyle($input, $output); |
| 98 | + |
| 99 | + $flagName = $input->getArgument('flag'); |
| 100 | + assert(is_string($flagName)); |
| 101 | + |
| 102 | + if ($input->getOption('force')) { |
| 103 | + $this->cache->clear(); |
| 104 | + } |
| 105 | + |
| 106 | + $result = $this->unleash->isEnabled( |
| 107 | + $flagName, |
| 108 | + $this->createContext($input), |
| 109 | + ); |
| 110 | + |
| 111 | + $expected = $input->getOption('expected'); |
| 112 | + if ($expected !== null) { |
| 113 | + $expected = $expected === 'true'; |
| 114 | + } |
| 115 | + $success = ($expected === null && $result) || ($expected !== null && $result === $expected); |
| 116 | + $message = "The feature flag '{$flagName}' evaluated to: " . ($result ? 'true' : 'false'); |
| 117 | + |
| 118 | + $success |
| 119 | + ? $io->success($message) |
| 120 | + : $io->error($message) |
| 121 | + ; |
| 122 | + |
| 123 | + return $expected === null |
| 124 | + ? Command::SUCCESS |
| 125 | + : ( |
| 126 | + $result === $expected |
| 127 | + ? Command::SUCCESS |
| 128 | + : Command::FAILURE |
| 129 | + ) |
| 130 | + ; |
| 131 | + } |
| 132 | + |
| 133 | + private function createContext(InputInterface $input): Context |
| 134 | + { |
| 135 | + $customContextInput = $input->getOption('custom-context'); |
| 136 | + assert(is_array($customContextInput)); |
| 137 | + |
| 138 | + $customContext = []; |
| 139 | + foreach ($customContextInput as $item) { |
| 140 | + if (!fnmatch('*=*', $item)) { |
| 141 | + throw new LogicException('The value must be a key=value pair.'); |
| 142 | + } |
| 143 | + [$key, $value] = explode('=', $item); |
| 144 | + $customContext[trim($key)] = trim($value); |
| 145 | + } |
| 146 | + |
| 147 | + $userId = $input->getOption('user-id'); |
| 148 | + $ipAddress = $input->getOption('ip-address'); |
| 149 | + $sessionId = $input->getOption('session-id'); |
| 150 | + $hostname = $input->getOption('hostname'); |
| 151 | + $environment = $input->getOption('environment'); |
| 152 | + $currentTime = $input->getOption('current-time'); |
| 153 | + |
| 154 | + assert($userId === null || is_string($userId)); |
| 155 | + assert($ipAddress === null || is_string($ipAddress)); |
| 156 | + assert($sessionId === null || is_string($sessionId)); |
| 157 | + assert($hostname === null || is_string($hostname)); |
| 158 | + assert($environment === null || is_string($environment)); |
| 159 | + assert($currentTime === null || is_string($currentTime)); |
| 160 | + |
| 161 | + return new UnleashContext( |
| 162 | + currentUserId: $userId, |
| 163 | + ipAddress: $ipAddress, |
| 164 | + sessionId: $sessionId, |
| 165 | + customContext: $customContext, |
| 166 | + hostname: $hostname, |
| 167 | + environment: $environment, |
| 168 | + currentTime: $currentTime, |
| 169 | + ); |
| 170 | + } |
| 171 | +} |
0 commit comments