Skip to content

Commit d43741a

Browse files
author
Ace Nassri
authored
feat(functions): add Firebase RTDB sample (GoogleCloudPlatform#1224)
~**Do not merge** until b/173261804 is addressed.~
1 parent 43d11d6 commit d43741a

File tree

5 files changed

+322
-0
lines changed

5 files changed

+322
-0
lines changed

functions/firebase_rtdb/composer.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"require": {
3+
"google/cloud-functions-framework": "^0.7.1",
4+
"guzzlehttp/guzzle": "^7.2.0"
5+
},
6+
"scripts": {
7+
"start": [
8+
"Composer\\Config::disableProcessTimeout",
9+
"FUNCTION_SIGNATURE_TYPE=cloudevent FUNCTION_TARGET=firebaseRTDB php -S localhost:${PORT:-8080} vendor/bin/router.php"
10+
]
11+
},
12+
"require-dev": {
13+
"google/cloud-logging": "^1.21"
14+
}
15+
}

functions/firebase_rtdb/index.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
2+
/**
3+
* Copyright 2020 Google LLC.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
// [START functions_firebase_rtdb]
19+
20+
use Google\CloudFunctions\CloudEvent;
21+
22+
function firebaseRTDB(CloudEvent $cloudevent)
23+
{
24+
$log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');
25+
26+
fwrite($log, "Event: " . $cloudevent->getId() . PHP_EOL);
27+
28+
$data = $cloudevent->getData();
29+
$resource = $data['resource'] ?? '';
30+
31+
fwrite($log, 'Function triggered by change to: ' . $resource . PHP_EOL);
32+
33+
$isAdmin = isset($data['auth']['admin']) && $data['auth']['admin'] == true;
34+
35+
fwrite($log, 'Admin?: ' . var_export($isAdmin, true) . PHP_EOL);
36+
fwrite($log, 'Delta: ' . json_encode($data['delta'] ?? '') . PHP_EOL);
37+
}
38+
// [END functions_firebase_rtdb]
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
xml version="1.0" encoding="UTF-8"?>
2+
17+
<phpunit bootstrap="../../testing/bootstrap.php" convertWarningsToExceptions="false">
18+
<testsuites>
19+
<testsuite name="Cloud Functions Firebase RTDB Test Suite">
20+
<directory>.directory>
21+
<exclude>vendorexclude>
22+
testsuite>
23+
testsuites>
24+
<logging>
25+
<log type="coverage-clover" target="build/logs/clover.xml"/>
26+
logging>
27+
<filter>
28+
<whitelist>
29+
<directory suffix=".php">.directory>
30+
<exclude>
31+
<directory>./vendordirectory>
32+
exclude>
33+
whitelist>
34+
filter>
35+
phpunit>
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
2+
/**
3+
* Copyright 2020 Google LLC.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace Google\Cloud\Samples\Functions\HelloworldStorage\Test;
21+
22+
use Google\Cloud\Logging\LoggingClient;
23+
use Google\Cloud\TestUtils\CloudFunctionDeploymentTrait;
24+
use Google\Cloud\TestUtils\EventuallyConsistentTestTrait;
25+
use GuzzleHttp\Client;
26+
use PHPUnit\Framework\TestCase;
27+
28+
/**
29+
* Class DeployTest.
30+
*
31+
* This test is not run by the CI system.
32+
*
33+
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
34+
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
35+
*/
36+
class DeployTest extends TestCase
37+
{
38+
use CloudFunctionDeploymentTrait;
39+
use EventuallyConsistentTestTrait;
40+
41+
/** @var string */
42+
private static $entryPoint = 'firebaseRTDB';
43+
44+
/** @var string */
45+
private static $functionSignatureType = 'cloudevent';
46+
47+
/** @var string */
48+
private static $rtdbPath = 'foods';
49+
50+
/** @var LoggingClient */
51+
private static $loggingClient;
52+
53+
/** @var Database */
54+
private static $database;
55+
56+
/**
57+
* Deploy the Cloud Function, called from DeploymentTrait::deployApp().
58+
*
59+
* Overrides CloudFunctionDeploymentTrait::doDeploy().
60+
*/
61+
private static function doDeploy()
62+
{
63+
// self::projectId is undefined
64+
$projectId = self::requireEnv('GOOGLE_PROJECT_ID');
65+
66+
$resource = sprintf(
67+
'projects/_/instances/%s/refs/%s',
68+
$projectId,
69+
self::$rtdbPath
70+
);
71+
$event = 'providers/google.firebase.database/eventTypes/ref.write';
72+
73+
return self::$fn->deploy([
74+
'--trigger-resource' => $resource,
75+
'--trigger-event' => $event
76+
], '');
77+
}
78+
79+
public function dataProvider()
80+
{
81+
$data = ['taco' => (string) uniqid()];
82+
return [
83+
[
84+
'data' => $data,
85+
'expected' => json_encode($data)
86+
],
87+
];
88+
}
89+
90+
/**
91+
* @dataProvider dataProvider
92+
*/
93+
public function testFirebaseRTDB(array $data, string $expected): void
94+
{
95+
// Trigger storage upload.
96+
$objectUri = $this->updateRTDB(self::$rtdbPath, $data);
97+
98+
// Give event and log systems a head start.
99+
// If log retrieval fails to find logs for our function within retry limit, increase sleep time.
100+
sleep(5);
101+
102+
$fiveMinAgo = date(\DateTime::RFC3339, strtotime('-5 minutes'));
103+
$this->processFunctionLogs($fiveMinAgo, function (\Iterator $logs) use ($expected) {
104+
// Concatenate all relevant log messages.
105+
$actual = '';
106+
foreach ($logs as $log) {
107+
$info = $log->info();
108+
if (isset($info['textPayload'])) {
109+
$actual .= $info['textPayload'];
110+
}
111+
}
112+
113+
// Only testing one property to decrease odds the expected logs are
114+
// split between log requests.
115+
$this->assertStringContainsString($expected, $actual);
116+
});
117+
}
118+
119+
/**
120+
* Update a value in Firebase Realtime Database (RTDB).
121+
*
122+
* @param string $path Path of the RTDB attribute to set.
123+
* @param string $data Data to upload as an object..
124+
*
125+
* @throws \RuntimeException
126+
*/
127+
private function updateRTDB(string $path, array $data): void
128+
{
129+
$client = new Client([
130+
'base_uri' => sprintf('https://%s.firebaseio.com', self::$projectId)
131+
]);
132+
133+
$url = '/' . $path . '.json';
134+
$url_response = $client->put($url, [
135+
'json' => $data
136+
]);
137+
}
138+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
2+
/**
3+
* Copyright 2020 Google LLC.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace Google\Cloud\Samples\Functions\HelloworldHttp\Test;
21+
22+
use PHPUnit\Framework\TestCase;
23+
use Google\CloudFunctions\CloudEvent;
24+
use Google\Cloud\TestUtils\CloudFunctionLocalTestTrait;
25+
26+
/**
27+
* Class IntegrationTest.
28+
*
29+
* Integration Test for firebaseRTDB.
30+
*/
31+
class IntegrationTest extends TestCase
32+
{
33+
use CloudFunctionLocalTestTrait;
34+
35+
/** @var string */
36+
private static $entryPoint = 'firebaseRTDB';
37+
38+
/** @var string */
39+
private static $functionSignatureType = 'cloudevent';
40+
41+
public function dataProvider()
42+
{
43+
return [
44+
[
45+
'cloudevent' => [
46+
'id' => uniqid(),
47+
'source' => 'firebase.googleapis.com',
48+
'specversion' => '1.0',
49+
'type' => 'google.firebase.database.ref.v1.created',
50+
],
51+
'data' => [
52+
'resource' => 'projects/_/instances/my-instance/refs/messages',
53+
'data' => ['new' => 'value'],
54+
'delta' => null,
55+
],
56+
'statusCode' => '200',
57+
],
58+
];
59+
}
60+
61+
/**
62+
* @dataProvider dataProvider
63+
*/
64+
public function testFirebaseRTDB(array $cloudevent, array $data, string $statusCode): void
65+
{
66+
// Prepare the HTTP headers for a CloudEvent.
67+
$cloudEventHeaders = [];
68+
foreach ($cloudevent as $key => $value) {
69+
$cloudEventHeaders['ce-' . $key] = $value;
70+
}
71+
72+
// Send an HTTP request using CloudEvent metadata.
73+
$params = [
74+
'body' => json_encode($data),
75+
'headers' => $cloudEventHeaders + [
76+
// Instruct the function framework to parse the body as JSON.
77+
'content-type' => 'application/json'
78+
],
79+
];
80+
$resp = $this->request(CloudEvent::fromArray($cloudevent), $params);
81+
82+
// The Cloud Function logs all data to stderr.
83+
$actual = self::$localhost->getIncrementalErrorOutput();
84+
85+
// Confirm the status code.
86+
$this->assertEquals($statusCode, $resp->getStatusCode());
87+
88+
// Verify the data properties are logged by the function.
89+
foreach ($data as $property => $value) {
90+
if (is_string($value)) {
91+
$this->assertStringContainsString($value, $actual);
92+
}
93+
}
94+
$this->assertStringContainsString($cloudevent['id'], $actual);
95+
}
96+
}

0 commit comments

Comments
 (0)