Skip to content

Commit 1ff7973

Browse files
authored
feat(functions): add functions_http_form_data sample (GoogleCloudPlatform#1196)
1 parent 03515be commit 1ff7973

File tree

9 files changed

+413
-2
lines changed

9 files changed

+413
-2
lines changed

functions/helloworld_http/test/SampleUnitTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public static function setUpBeforeClass(): void
3434
require_once __DIR__ . '/../index.php';
3535
}
3636

37-
public function testFunction() : void
37+
public function testFunction(): void
3838
{
3939
$name = uniqid();
4040
$request = new ServerRequest('POST', '/', [], json_encode(['name' => $name]));

functions/http_content_type/test/TestCasesTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
trait TestCasesTrait
2222
{
23-
public static function cases() : array
23+
public static function cases(): array
2424
{
2525
return [
2626
[
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"require": {
3+
"google/cloud-functions-framework": "^0.6",
4+
"guzzlehttp/psr7": "^1.7"
5+
},
6+
"scripts": {
7+
"start": [
8+
"Composer\\Config::disableProcessTimeout",
9+
"TMPDIR=./tmp FUNCTION_TARGET=uploadFile php -S localhost:${PORT:-8080} vendor/bin/router.php"
10+
]
11+
}
12+
}

functions/http_form_data/index.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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_http_form_data]
19+
20+
use Psr\Http\Message\ServerRequestInterface;
21+
use Psr\Http\Message\ResponseInterface;
22+
use GuzzleHttp\Psr7\Response;
23+
24+
function uploadFile(ServerRequestInterface $request): ResponseInterface
25+
{
26+
if ($request->getMethod() != 'POST') {
27+
return new Response(405, [], 'Method Not Allowed: expected POST, found ' . $request->getMethod());
28+
}
29+
30+
$contentType = $request->getHeader('Content-Type')[0];
31+
if (strpos($contentType, 'multipart/form-data') !== 0) {
32+
return new Response(400, [], 'Bad Request: content of type "multipart/form-data" not provided, found ' . $contentType);
33+
}
34+
35+
$fileList = [];
36+
/** @var $file Psr\Http\Message\UploadedFileInterface */
37+
foreach ($request->getUploadedFiles() as $name => $file) {
38+
// Use caution when trusting the client-provided filename:
39+
// https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload
40+
$fileList[] = $file->getClientFilename();
41+
42+
infoLog('Processing ' . $file->getClientFilename());
43+
$filename = tempnam(sys_get_temp_dir(), $name . '.') . '-' . $file->getClientFilename();
44+
45+
// Use $file->getStream() to process the file contents in ways other than a direct "file save".
46+
infoLog('Saving to ' . $filename);
47+
$file->moveTo($filename);
48+
}
49+
50+
if (empty($fileList)) {
51+
$msg = 'Bad Request: no files sent for upload';
52+
errorLog($msg);
53+
return new Response(400, [], $msg);
54+
}
55+
56+
return new Response(201, [], 'Saved ' . join(', ', $fileList));
57+
}
58+
59+
function errorLog($msg): void
60+
{
61+
$stream = fopen('php://stderr', 'wb');
62+
$entry = json_encode(['msg' => $msg, 'severity' => 'error'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
63+
fwrite($stream, $entry . PHP_EOL);
64+
}
65+
66+
function infoLog($msg): void
67+
{
68+
$stream = fopen('php://stderr', 'wb');
69+
$entry = json_encode(['message' => $msg, 'severity' => 'info'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
70+
fwrite($stream, $entry . PHP_EOL);
71+
}
72+
73+
// [END functions_http_form_data]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
xml version="1.0" encoding="UTF-8"?>
2+
17+
<phpunit bootstrap="../../testing/bootstrap.php" convertWarningsToExceptions="false">
18+
<testsuites>
19+
<testsuite name="Cloud Functions HTTP Form Data Suite">
20+
<directory>testdirectory>
21+
testsuite>
22+
testsuites>
23+
<logging>
24+
<log type="coverage-clover" target="build/logs/clover.xml"/>
25+
logging>
26+
<filter>
27+
<whitelist>
28+
<directory suffix=".php">.directory>
29+
<exclude>
30+
<directory>./vendordirectory>
31+
exclude>
32+
whitelist>
33+
filter>
34+
phpunit>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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 Google\Cloud\TestUtils\CloudFunctionDeploymentTrait;
23+
use GuzzleHttp\Exception\ClientException;
24+
use PHPUnit\Framework\TestCase;
25+
26+
require_once __DIR__ . '/TestCasesTrait.php';
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 TestCasesTrait;
40+
41+
private static $name = 'uploadFile';
42+
43+
public function testFunction(): void
44+
{
45+
foreach (self::cases() as $test) {
46+
$method = $test['method'];
47+
$resp = $this->client->$method('', [
48+
'multipart' => $test['multipart'],
49+
]);
50+
$this->assertEquals($test['code'], $resp->getStatusCode(), $test['label'] . ' code:');
51+
$actual = trim((string) $resp->getBody());
52+
$this->assertContains($test['expected'], $actual, $test['label'] . ':');
53+
}
54+
}
55+
56+
public function testErrorCases(): void
57+
{
58+
$actual = $actualCode = null;
59+
foreach (self::errorCases() as $test) {
60+
try {
61+
$method = $test['method'];
62+
$resp = $this->client->$method('', [
63+
'multipart' => $test['multipart'],
64+
]);
65+
66+
$actual = $resp->getBody()->getContents();
67+
$actualCode = $resp->getStatusCode();
68+
} catch (ClientException $e) {
69+
// Expected exception, nothing to do here.
70+
$actual = $actualCode = $e->getMessage();
71+
} finally {
72+
print $actual . $actualCode;
73+
$this->assertContains($test['code'], $actualCode, $test['label'] . ' code:');
74+
$this->assertContains($test['expected'], $actual, $test['label'] . ':');
75+
}
76+
}
77+
}
78+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 Google\Cloud\TestUtils\CloudFunctionLocalTestTrait;
23+
use GuzzleHttp\Exception\ClientException;
24+
use PHPUnit\Framework\TestCase;
25+
26+
require_once __DIR__ . '/TestCasesTrait.php';
27+
28+
/**
29+
* Class SystemTest.
30+
*/
31+
class SystemTest extends TestCase
32+
{
33+
use CloudFunctionLocalTestTrait;
34+
use TestCasesTrait;
35+
36+
private static $name = 'uploadFile';
37+
38+
public function testFunction(): void
39+
{
40+
foreach (self::cases() as $test) {
41+
$method = $test['method'];
42+
$resp = $this->client->$method('/', [
43+
'multipart' => $test['multipart'],
44+
]);
45+
$this->assertEquals($test['code'], $resp->getStatusCode(), $test['label'] . ' code:');
46+
$actual = trim((string) $resp->getBody());
47+
$this->assertContains($test['expected'], $actual, $test['label'] . ':');
48+
}
49+
}
50+
51+
public function testErrorCases(): void
52+
{
53+
$actual = null;
54+
foreach (self::errorCases() as $test) {
55+
try {
56+
$method = $test['method'];
57+
$resp = $this->client->$method('/', [
58+
'multipart' => $test['multipart'],
59+
]);
60+
$actual = $resp->getBody()->getContents();
61+
} catch (ClientException $e) {
62+
// Expected exception, nothing to do here.
63+
$actual = $e->getMessage();
64+
} finally {
65+
$this->assertContains($test['code'], $actual, $test['label'] . ' code:');
66+
$this->assertContains($test['expected'], $actual, $test['label'] . ':');
67+
}
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)