Skip to content

Commit 18ecb8a

Browse files
committed
Implement actual Google Cloud API connector and add tests
1 parent fc5e495 commit 18ecb8a

File tree

8 files changed

+249
-7
lines changed

8 files changed

+249
-7
lines changed

.github/workflows/run-tests.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,11 @@ jobs:
6161
CI_DB_DATABASE: test
6262
CI_DB_USERNAME: root
6363
CI_DB_PASSWORD: root
64+
CI_CLOUD_TASKS_PROJECT_ID: ${{ secrets.CI_CLOUD_TASKS_PROJECT_ID }}
65+
CI_CLOUD_TASKS_QUEUE: ${{ secrets.CI_CLOUD_TASKS_QUEUE }}
66+
CI_CLOUD_TASKS_LOCATION: ${{ secrets.CI_CLOUD_TASKS_LOCATION }}
67+
CI_CLOUD_TASKS_SERVICE_ACCOUNT_EMAIL: ${{ secrets.CI_CLOUD_TASKS_SERVICE_ACCOUNT_EMAIL }}
68+
CI_SERVICE_ACCOUNT_JSON_KEY: ${{ secrets.CI_SERVICE_ACCOUNT_JSON_KEY }}
6469
run: |
70+
echo $CI_SERVICE_ACCOUNT_JSON_KEY > tests/Support/gcloud-key-valid.json
6571
vendor/bin/phpunit

phpunit.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<testsuite name="Testsuite">
1313
<file>./tests/ConfigTest.php</file>
1414
<file>./tests/TaskHandlerTest.php</file>
15+
<file>./tests/CloudTasksApiTest.php</file>
1516
</testsuite>
1617
</testsuites>
1718
<php>

src/CloudTasksApi.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@
44

55
namespace Stackkit\LaravelGoogleCloudTasksQueue;
66

7+
use Google\Cloud\Tasks\V2\RetryConfig;
8+
use Google\Cloud\Tasks\V2\Task;
79
use Illuminate\Support\Facades\Facade;
810

11+
/**
12+
* @method static RetryConfig getRetryConfig(string $queueName)
13+
* @method static Task createTask(string $queueName, Task $task)
14+
* @method static void deleteTask(string $taskName)
15+
* @method static Task getTask(string $taskName)
16+
* @method static int|null getRetryUntilTimestamp(string $taskName)
17+
*/
918
class CloudTasksApi extends Facade
1019
{
1120
protected static function getFacadeAccessor()

src/CloudTasksApiConcrete.php

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Stackkit\LaravelGoogleCloudTasksQueue;
66

7+
use Google\Cloud\Tasks\V2\Attempt;
78
use Google\Cloud\Tasks\V2\CloudTasksClient;
89
use Google\Cloud\Tasks\V2\RetryConfig;
910
use Google\Cloud\Tasks\V2\Task;
@@ -27,16 +28,42 @@ public function getRetryConfig(string $queueName): RetryConfig
2728

2829
public function createTask(string $queueName, Task $task): Task
2930
{
30-
// TODO: Implement createTask() method.
31+
return $this->client->createTask($queueName, $task);
3132
}
3233

3334
public function deleteTask(string $taskName): void
3435
{
35-
// TODO: Implement deleteTask() method.
36+
$this->client->deleteTask($taskName);
3637
}
3738

38-
public function getRetryUntilTimestamp(CloudTasksJob $job): ?int
39+
public function getTask(string $taskName): Task
3940
{
40-
// TODO: Implement getRetryUntilTimestamp() method.
41+
return $this->client->getTask($taskName);
42+
}
43+
44+
45+
public function getRetryUntilTimestamp(string $taskName): ?int
46+
{
47+
$task = $this->getTask($taskName);
48+
49+
$attempt = $task->getFirstAttempt();
50+
51+
if (!$attempt instanceof Attempt) {
52+
return null;
53+
}
54+
55+
$queueName = implode('/', array_slice(explode('/', $task->getName()), 0, 6));
56+
57+
$retryConfig = $this->getRetryConfig($queueName);
58+
59+
if (! $retryConfig->hasMaxRetryDuration()) {
60+
return null;
61+
}
62+
63+
$maxDurationInSeconds = $retryConfig->getMaxRetryDuration()->getSeconds();
64+
65+
$firstAttemptTimestamp = $attempt->getDispatchTime()->toDateTime()->getTimestamp();
66+
67+
return $firstAttemptTimestamp + $maxDurationInSeconds;
4168
}
4269
}

src/CloudTasksApiContract.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ interface CloudTasksApiContract
1212
public function getRetryConfig(string $queueName): RetryConfig;
1313
public function createTask(string $queueName, Task $task): Task;
1414
public function deleteTask(string $taskName): void;
15-
public function getRetryUntilTimestamp(CloudTasksJob $job): ?int;
15+
public function getTask(string $taskName): Task;
16+
public function getRetryUntilTimestamp(string $taskName): ?int;
1617
}

src/CloudTasksApiFake.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ public function deleteTask(string $taskName): void
4242
$this->deletedTasks[] = $taskName;
4343
}
4444

45-
public function getRetryUntilTimestamp(CloudTasksJob $job): ?int
45+
public function getTask(string $taskName): Task
46+
{
47+
return (new Task())
48+
->setName($taskName);
49+
}
50+
51+
52+
public function getRetryUntilTimestamp(string $taskName): ?int
4653
{
4754
return null;
4855
}

src/TaskHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ private function handleTask($task)
9494
// max retry duration has been set. If that duration
9595
// has passed, it should stop trying altogether.
9696
if ($job->attempts() > 0) {
97-
$job->setRetryUntil(CloudTasksApi::getRetryUntilTimestamp($job));
97+
$job->setRetryUntil(CloudTasksApi::getRetryUntilTimestamp(request()->header('X-Cloudtasks-Taskname')));
9898
}
9999

100100
app('queue.worker')->process($this->config['connection'], $job, new WorkerOptions());

tests/CloudTasksApiTest.php

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests;
6+
7+
use Google\ApiCore\ApiException;
8+
use Google\Cloud\Tasks\V2\CloudTasksClient;
9+
use Google\Cloud\Tasks\V2\HttpMethod;
10+
use Google\Cloud\Tasks\V2\HttpRequest;
11+
use Google\Cloud\Tasks\V2\RetryConfig;
12+
use Google\Cloud\Tasks\V2\Task;
13+
use Google\Protobuf\Timestamp;
14+
use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksApi;
15+
16+
class CloudTasksApiTest extends TestCase
17+
{
18+
protected function setUp(): void
19+
{
20+
parent::setUp();
21+
22+
$requiredEnvs = [
23+
'CI_CLOUD_TASKS_PROJECT_ID',
24+
'CI_CLOUD_TASKS_QUEUE',
25+
'CI_CLOUD_TASKS_LOCATION',
26+
'CI_CLOUD_TASKS_SERVICE_ACCOUNT_EMAIL',
27+
'CI_SERVICE_ACCOUNT_JSON_KEY',
28+
];
29+
30+
foreach ($requiredEnvs as $env) {
31+
if (!env($env)) {
32+
$this->fail('Missing [' . $env . '] environment variable.');
33+
}
34+
}
35+
36+
$this->setConfigValue('project', env('CI_CLOUD_TASKS_PROJECT_ID'));
37+
$this->setConfigValue('queue', env('CI_CLOUD_TASKS_QUEUE'));
38+
$this->setConfigValue('location', env('CI_CLOUD_TASKS_LOCATION'));
39+
$this->setConfigValue('service_account_email', env('CI_CLOUD_TASKS_SERVICE_ACCOUNT_EMAIL'));
40+
41+
$this->client = new CloudTasksClient();
42+
43+
}
44+
45+
/**
46+
* @test
47+
*/
48+
public function test_get_retry_config()
49+
{
50+
// Act
51+
$retryConfig = CloudTasksApi::getRetryConfig(
52+
$this->client->queueName(
53+
env('CI_CLOUD_TASKS_PROJECT_ID'),
54+
env('CI_CLOUD_TASKS_LOCATION'),
55+
env('CI_CLOUD_TASKS_QUEUE')
56+
)
57+
);
58+
59+
// Assert
60+
$this->assertInstanceOf(RetryConfig::class, $retryConfig);
61+
$this->assertEquals(2, $retryConfig->getMaxAttempts());
62+
$this->assertEquals(5, $retryConfig->getMaxRetryDuration()->getSeconds());
63+
}
64+
65+
/**
66+
* @test
67+
*/
68+
public function test_create_task()
69+
{
70+
// Arrange
71+
$httpRequest = new HttpRequest();
72+
$httpRequest->setHttpMethod(HttpMethod::GET);
73+
$httpRequest->setUrl('https://example.com');
74+
75+
$cloudTask = new Task();
76+
$cloudTask->setHttpRequest($httpRequest);
77+
78+
// Act
79+
$task = CloudTasksApi::createTask(
80+
$this->client->queueName(
81+
env('CI_CLOUD_TASKS_PROJECT_ID'),
82+
env('CI_CLOUD_TASKS_LOCATION'),
83+
env('CI_CLOUD_TASKS_QUEUE')
84+
),
85+
$cloudTask
86+
);
87+
$taskName = $task->getName();
88+
89+
// Assert
90+
$this->assertMatchesRegularExpression(
91+
'/projects\/' . env('CI_CLOUD_TASKS_PROJECT_ID') . '\/locations\/' . env('CI_CLOUD_TASKS_LOCATION') . '\/queues\/' . env('CI_CLOUD_TASKS_QUEUE') . '\/tasks\/\d{19,}$/',
92+
$taskName
93+
);
94+
}
95+
96+
/**
97+
* @test
98+
*/
99+
public function test_delete_task_on_non_existing_task()
100+
{
101+
// Assert
102+
$this->expectException(ApiException::class);
103+
$this->expectExceptionMessage('Requested entity was not found.');
104+
105+
// Act
106+
CloudTasksApi::deleteTask(
107+
$this->client->taskName(
108+
env('CI_CLOUD_TASKS_PROJECT_ID'),
109+
env('CI_CLOUD_TASKS_LOCATION'),
110+
env('CI_CLOUD_TASKS_QUEUE'),
111+
'non-existing-id'
112+
),
113+
);
114+
115+
}
116+
117+
/**
118+
* @test
119+
*/
120+
public function test_delete_task()
121+
{
122+
// Arrange
123+
$httpRequest = new HttpRequest();
124+
$httpRequest->setHttpMethod(HttpMethod::GET);
125+
$httpRequest->setUrl('https://example.com');
126+
127+
$cloudTask = new Task();
128+
$cloudTask->setHttpRequest($httpRequest);
129+
$cloudTask->setScheduleTime(new Timestamp(['seconds' => time() + 10]));
130+
131+
$task = CloudTasksApi::createTask(
132+
$this->client->queueName(
133+
env('CI_CLOUD_TASKS_PROJECT_ID'),
134+
env('CI_CLOUD_TASKS_LOCATION'),
135+
env('CI_CLOUD_TASKS_QUEUE')
136+
),
137+
$cloudTask
138+
);
139+
140+
// Act
141+
$fresh = CloudTasksApi::getTask($task->getName());
142+
$this->assertInstanceOf(Task::class, $fresh);
143+
144+
CloudTasksApi::deleteTask($task->getName());
145+
146+
$this->expectException(ApiException::class);
147+
$this->expectExceptionMessage('NOT_FOUND');
148+
CloudTasksApi::getTask($task->getName());
149+
}
150+
151+
/**
152+
* @test
153+
*/
154+
public function test_get_retry_until_timestamp()
155+
{
156+
// Arrange
157+
$httpRequest = new HttpRequest();
158+
$httpRequest->setHttpMethod(HttpMethod::GET);
159+
$httpRequest->setUrl('https://httpstat.us/500');
160+
161+
$cloudTask = new Task();
162+
$cloudTask->setHttpRequest($httpRequest);
163+
164+
165+
$createdTask = CloudTasksApi::createTask(
166+
$this->client->queueName(
167+
env('CI_CLOUD_TASKS_PROJECT_ID'),
168+
env('CI_CLOUD_TASKS_LOCATION'),
169+
env('CI_CLOUD_TASKS_QUEUE')
170+
),
171+
$cloudTask,
172+
);
173+
174+
$secondsSlept = 0;
175+
while ($createdTask->getFirstAttempt() === null) {
176+
$createdTask = CloudTasksApi::getTask($createdTask->getName());
177+
sleep(1);
178+
$secondsSlept += 1;
179+
180+
if ($secondsSlept >= 30) {
181+
$this->fail('Task took too long to get executed.');
182+
}
183+
}
184+
185+
// The queue max retry duration is 5 seconds. The max retry until timestamp is calculated from the
186+
// first attempt, so we expect it to be [timestamp first attempt] + 5 seconds.
187+
$expected = $createdTask->getFirstAttempt()->getDispatchTime()->getSeconds() + 5;
188+
$actual = CloudTasksApi::getRetryUntilTimestamp($createdTask->getName());
189+
$this->assertSame($expected, $actual);
190+
}
191+
}

0 commit comments

Comments
 (0)