Skip to content

Commit ecf2217

Browse files
authored
[Google] Added more tests (#37)
* Added more testts * cs
1 parent 36a4bd5 commit ecf2217

File tree

3 files changed

+179
-31
lines changed

3 files changed

+179
-31
lines changed

src/Runtime.php

+42-31
Original file line numberDiff line numberDiff line change
@@ -51,29 +51,16 @@ protected static function register(GenericRuntime $runtime): GenericRuntime
5151
return $self;
5252
}
5353

54-
private function createCloudEvent(): ?CloudEvent
54+
protected function createCloudEvent(): ?CloudEvent
5555
{
56-
$body = \fopen('php://input', 'r') ?: null;
57-
if (null === $body) {
58-
$message = 'Could not create CloudEvent from request with no body';
59-
$this->sendHttpResponseAndExit(400, $message, [self::FUNCTION_STATUS_HEADER => 'crash']);
60-
61-
return null;
62-
}
63-
64-
$body = stream_get_contents($body);
65-
$rawHeaders = \function_exists('getallheaders') ? getallheaders() : $this->getHeadersFromServer($_SERVER);
66-
$headers = [];
67-
foreach ($rawHeaders as $name => $value) {
68-
$headers[strtolower($name)] = $value;
69-
}
70-
56+
$body = $this->getBody();
57+
$headers = $this->getHeaders();
7158
$eventType = $this->getEventType($headers);
7259

7360
// We expect JSON if the content-type ends in "json" or if the event
7461
// type is legacy or structured Cloud Event.
7562
$shouldValidateJson = in_array($eventType, [self::TYPE_LEGACY, self::TYPE_STRUCTURED])
76-
|| 'json' === substr($headers['content-type'], -4);
63+
|| (isset($headers['content-type']) && 'json' === substr($headers['content-type'], -4));
7764

7865
if (!$shouldValidateJson) {
7966
$data = $body;
@@ -106,6 +93,42 @@ private function createCloudEvent(): ?CloudEvent
10693
}
10794
}
10895

96+
protected function sendHttpResponseAndExit(int $status, string $body, array $headers)
97+
{
98+
error_log($body);
99+
header('HTTP/1.1 '.$status);
100+
foreach ($headers as $name => $value) {
101+
header($name.': '.$value);
102+
}
103+
echo $body;
104+
105+
exit(0);
106+
}
107+
108+
protected function getHeaders(): array
109+
{
110+
$rawHeaders = \function_exists('getallheaders') ? getallheaders() : $this->getHeadersFromServer($_SERVER);
111+
$headers = [];
112+
foreach ($rawHeaders as $name => $value) {
113+
$headers[strtolower($name)] = $value;
114+
}
115+
116+
return $headers;
117+
}
118+
119+
protected function getBody(): string
120+
{
121+
$body = \fopen('php://input', 'r') ?: null;
122+
if (null === $body) {
123+
$message = 'Could not create CloudEvent from request with no body';
124+
$this->sendHttpResponseAndExit(400, $message, [self::FUNCTION_STATUS_HEADER => 'crash']);
125+
126+
return '';
127+
}
128+
129+
return stream_get_contents($body);
130+
}
131+
109132
/**
110133
* @psalm-return self::TYPE_*
111134
*/
@@ -115,7 +138,7 @@ private function getEventType(array $headers): int
115138
return self::TYPE_BINARY;
116139
}
117140

118-
if ('application/cloudevents+json' === $headers['content-type']) {
141+
if (isset($headers['content-type']) && 'application/cloudevents+json' === $headers['content-type']) {
119142
return self::TYPE_STRUCTURED;
120143
}
121144

@@ -148,7 +171,7 @@ private function fromBinaryRequest(array $headers, $data): CloudEvent
148171
/**
149172
* Implementation from Zend\Diactoros\marshalHeadersFromSapi().
150173
*/
151-
public static function getHeadersFromServer(array $server): array
174+
private function getHeadersFromServer(array $server): array
152175
{
153176
$headers = [];
154177
foreach ($server as $key => $value) {
@@ -181,16 +204,4 @@ public static function getHeadersFromServer(array $server): array
181204

182205
return $headers;
183206
}
184-
185-
private function sendHttpResponseAndExit(int $status, string $body, array $headers)
186-
{
187-
error_log($body);
188-
header('HTTP/1.1 '.$status);
189-
foreach ($headers as $name => $value) {
190-
header($name.': '.$value);
191-
}
192-
echo $body;
193-
194-
exit(0);
195-
}
196207
}

tests/runtime/ExecutionStopped.php

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Runtime\GoogleCloud\Tests;
4+
5+
/**
6+
* An exception to be used in tests to make sure we called `exit()`;.
7+
*
8+
* @author Tobias Nyholm <[email protected]>
9+
*/
10+
class ExecutionStopped extends \RuntimeException
11+
{
12+
}

tests/runtime/RuntimeTest.php

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
namespace Runtime\GoogleCloud\Tests;
4+
5+
use Google\CloudFunctions\CloudEvent;
6+
use PHPUnit\Framework\TestCase;
7+
use Runtime\GoogleCloud\Runtime;
8+
use Symfony\Component\Runtime\RuntimeInterface;
9+
10+
class RuntimeTest extends TestCase
11+
{
12+
public function testStructuredType()
13+
{
14+
$input = [
15+
'id' => '1234567890',
16+
'source' => '//pubsub.googleapis.com/projects/MY-PROJECT/topics/MY-TOPIC',
17+
'specversion' => '1.0',
18+
'type' => 'com.google.cloud.pubsub.topic.publish',
19+
];
20+
21+
$runtime = $this->getRuntimeMock();
22+
$runtime->method('getBody')->willReturn(json_encode($input));
23+
$runtime->method('getHeaders')->willReturn(['content-type' => 'application/cloudevents+json']);
24+
25+
$output = $this->invokeCreateCloudEvent($runtime);
26+
$this->assertInstanceOf(CloudEvent::class, $output);
27+
$this->assertSame('1234567890', $output->getId());
28+
$this->assertSame('com.google.cloud.pubsub.topic.publish', $output->getType());
29+
}
30+
31+
public function testLegacyType()
32+
{
33+
$input = [
34+
'data' => 'foo',
35+
'context' => [
36+
'eventId' => '1413058901901494',
37+
'timestamp' => '2020-12-08T20:03:19.162Z',
38+
'eventType' => 'providers/cloud.pubsub/eventTypes/topic.publish',
39+
'resource' => [
40+
'name' => 'projects/MY-PROJECT/topics/MY-TOPIC',
41+
'service' => 'pubsub.googleapis.com',
42+
],
43+
],
44+
];
45+
46+
$runtime = $this->getRuntimeMock();
47+
$runtime->method('getBody')->willReturn(json_encode($input));
48+
$runtime->method('getHeaders')->willReturn([]);
49+
50+
$output = $this->invokeCreateCloudEvent($runtime);
51+
$this->assertInstanceOf(CloudEvent::class, $output);
52+
$this->assertSame(['message' => 'foo'], $output->getData());
53+
$this->assertSame('1413058901901494', $output->getId());
54+
$this->assertSame('google.cloud.pubsub.topic.v1.messagePublished', $output->getType());
55+
}
56+
57+
public function testValidateJsonWithJsonContentType()
58+
{
59+
$runtime = $this->getRuntimeMock();
60+
$runtime->method('getBody')->willReturn('not json');
61+
$runtime->method('getHeaders')->willReturn(['content-type' => 'application/json']);
62+
63+
$this->expectException(ExecutionStopped::class);
64+
$this->invokeCreateCloudEvent($runtime);
65+
}
66+
67+
public function testValidateJsonWithStructuredType()
68+
{
69+
$runtime = $this->getRuntimeMock();
70+
$runtime->method('getBody')->willReturn('not json');
71+
$runtime->method('getHeaders')->willReturn(['content-type' => 'application/cloudevents+json']);
72+
73+
$this->expectException(ExecutionStopped::class);
74+
$this->invokeCreateCloudEvent($runtime);
75+
}
76+
77+
public function testValidateJsonWithLegacyType()
78+
{
79+
$runtime = $this->getRuntimeMock();
80+
$runtime->method('getBody')->willReturn('not json');
81+
$runtime->method('getHeaders')->willReturn([]);
82+
83+
$this->expectException(ExecutionStopped::class);
84+
$this->invokeCreateCloudEvent($runtime);
85+
}
86+
87+
public function testNoValidateJsonWithBinaryType()
88+
{
89+
$runtime = $this->getRuntimeMock();
90+
$runtime->method('getBody')->willReturn('not json');
91+
$runtime->method('getHeaders')->willReturn([
92+
'ce-id' => '1234567890',
93+
'ce-source' => '//pubsub.googleapis.com/projects/MY-PROJECT/topics/MY-TOPIC',
94+
'ce-specversion' => '1.0',
95+
'ce-type' => 'com.google.cloud.pubsub.topic.publish',
96+
]);
97+
98+
$output = $this->invokeCreateCloudEvent($runtime);
99+
$this->assertInstanceOf(CloudEvent::class, $output);
100+
$this->assertSame('not json', $output->getData());
101+
}
102+
103+
private function invokeCreateCloudEvent(RuntimeInterface $runtime): ?CloudEvent
104+
{
105+
$reflection = new \ReflectionObject($runtime);
106+
$method = $reflection->getMethod('createCloudEvent');
107+
$method->setAccessible(true);
108+
109+
return $method->invoke($runtime);
110+
}
111+
112+
/**
113+
* @return \PHPUnit\Framework\MockObject\MockObject|Runtime
114+
*/
115+
private function getRuntimeMock()
116+
{
117+
$runtime = $this->getMockBuilder(Runtime::class)
118+
->disableOriginalConstructor()
119+
->onlyMethods(['getBody', 'getHeaders', 'sendHttpResponseAndExit'])
120+
->getMock();
121+
$runtime->method('sendHttpResponseAndExit')->willThrowException(new ExecutionStopped());
122+
123+
return $runtime;
124+
}
125+
}

0 commit comments

Comments
 (0)