From e9923c8e077f1eedf7fa196f11c4a19a1657e24a Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 14 Dec 2021 16:37:38 -0800 Subject: [PATCH] Civi::pipe - Basic JSON session --- Civi/Pipe/BasicJsonSession.php | 72 +++++++++++ .../Civi/Pipe/BasicJsonSessionTest.php | 120 ++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 Civi/Pipe/BasicJsonSession.php create mode 100644 tests/phpunit/Civi/Pipe/BasicJsonSessionTest.php diff --git a/Civi/Pipe/BasicJsonSession.php b/Civi/Pipe/BasicJsonSession.php new file mode 100644 index 0000000000..6772abf641 --- /dev/null +++ b/Civi/Pipe/BasicJsonSession.php @@ -0,0 +1,72 @@ +methods = new PublicMethods(); + return json_encode(["Civi::pipe" => ['json']]); + } + + /** + * @inheritDoc + */ + protected function onRequest(string $requestLine): ?string { + $request = \json_decode($requestLine, TRUE); + if ($request === NULL || !is_array($request) || count($request) !== 1) { + throw new \InvalidArgumentException('Malformed request'); + } + + foreach ($request as $type => $params) { + $method = str_replace('.', '_', mb_strtolower($type)); + if (is_string($method) && preg_match(self::METHOD_REGEX, $method) && is_callable([$this->methods, $method])) { + $response = call_user_func([$this->methods, $method], $this, $params); + return \json_encode(['OK' => $response]); + } + else { + return $this->onException($requestLine, new \InvalidArgumentException('Invalid request type')); + } + } + + throw new \RuntimeException("Unreachable: Request count and request loop were mismatched"); + } + + /** + * @inheritDoc + */ + protected function onException(string $requestLine, \Throwable $t): ?string { + return \json_encode([ + 'ERR' => [ + 'type' => get_class($t), + 'message' => $t->getMessage(), + 'trace' => $t->getTraceAsString(), + ], + ]); + } + +} diff --git a/tests/phpunit/Civi/Pipe/BasicJsonSessionTest.php b/tests/phpunit/Civi/Pipe/BasicJsonSessionTest.php new file mode 100644 index 0000000000..580e02cf90 --- /dev/null +++ b/tests/phpunit/Civi/Pipe/BasicJsonSessionTest.php @@ -0,0 +1,120 @@ +input = fopen('php://memory', 'w'); + $this->output = fopen('php://memory', 'w'); + $this->server = new BasicJsonSession($this->input, $this->output); + } + + protected function tearDown(): void { + fclose($this->input); + fclose($this->output); + $this->input = $this->output = $this->server = NULL; + parent::tearDown(); + } + + public function testInvalid() { + $responses = $this->runLines(['{"WIGGUM":123}']); + $this->assertEquals('Invalid request type', json_decode($responses[1], 1)['ERR']['message']); + } + + public function testEcho() { + $this->assertRequestResponse([ + '{"ECHO":123}' => '{"OK":123}', + '{"ECHO":true}' => '{"OK":true}', + '{"ECHO":[1,4,9]}' => '{"OK":[1,4,9]}', + ]); + } + + public function testControl() { + $this->assertRequestResponse([ + '{"OPTIONS":[]}' => '{"OK":{"bufferSize":524288,"responsePrefix":null}}', + '{"OPTIONS":{"responsePrefix":"ZZ"}}' => 'ZZ{"OK":{"responsePrefix":"ZZ"}}', + '{"ECHO":456}' => 'ZZ{"OK":456}', + ]); + } + + public function testApi3() { + $responses = $this->runLines(['{"API3":["System", "get"]}']); + + $this->assertEquals($this->standardHeader, $responses[0]); + + $data = json_decode($responses[1], TRUE); + $this->assertEquals(\CRM_Utils_System::version(), $data['OK']['values'][0]['version']); + } + + public function testApi4() { + $responses = $this->runLines(['{"API4":["Contact", "getFields"]}']); + + $this->assertEquals($this->standardHeader, $responses[0]); + + $data = json_decode($responses[1], TRUE); + $this->assertTrue(is_array($data['OK'])); + $fields = \CRM_Utils_Array::index(['name'], $data['OK']); + $this->assertEquals('Number', $fields['id']['input_type']); + } + + /** + * @param array $requestResponse + * List of requests and the corresponding responses. + * Requests are sent in the same order given. + * Ex: ['{"ECHO":1}' => '{"OK":1}'] + */ + protected function assertRequestResponse(array $requestResponse) { + $responses = $this->runLines(array_keys($requestResponse)); + $next = function() use (&$responses) { + return array_shift($responses); + }; + + $this->assertEquals($this->standardHeader, $next()); + $this->assertNotEmpty($requestResponse); + foreach ($requestResponse as $request => $expectResponse) { + $this->assertEquals($expectResponse, $next(), "The request ($request) should return expected response."); + } + } + + /** + * @param string[] $lines + * List of statements to send. (Does not include the line-delimiter.) + * @return string[] + * List of responses. (Does not include the line-delimiter.) + */ + protected function runLines(array $lines): array { + foreach ($lines as $line) { + fwrite($this->input, $line . "\n"); + } + fseek($this->input, 0); + + $this->server->run(); + + fseek($this->output, 0); + return explode("\n", stream_get_contents($this->output)); + } + + protected function getOutputLine() { + return stream_get_line($this->output, 10000, "\n"); + } + +} -- 2.25.1