Skip to content

Commit 1089b02

Browse files
authored
Log Update results to database (#22)
1 parent ecdd86c commit 1089b02

File tree

6 files changed

+184
-3
lines changed

6 files changed

+184
-3
lines changed

app/Exceptions/UpdateException.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace App\Exceptions;
4+
5+
class UpdateException extends \Exception
6+
{
7+
public readonly string $updateStep;
8+
9+
public function __construct(
10+
string $updateStep,
11+
string $message,
12+
int $code = 0,
13+
?\Throwable $previous = null
14+
) {
15+
$this->updateStep = $updateStep;
16+
17+
parent::__construct($message, $code, $previous);
18+
}
19+
20+
public function getStep(): string
21+
{
22+
return $this->updateStep;
23+
}
24+
}

app/Jobs/UpdateSite.php

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
namespace App\Jobs;
66

7+
use App\Exceptions\UpdateException;
78
use App\Models\Site;
9+
use App\Models\Update;
810
use App\RemoteSite\Connection;
911
use App\RemoteSite\Responses\PrepareUpdate;
1012
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -65,16 +67,31 @@ public function handle(): void
6567
$prepareResult = $connection->prepareUpdate(["targetVersion" => $this->targetVersion]);
6668

6769
// Perform the actual extraction
68-
$this->performExtraction($prepareResult);
70+
try {
71+
$this->performExtraction($prepareResult);
72+
} catch (\Throwable $e) {
73+
throw new UpdateException(
74+
'extract',
75+
$e->getMessage(),
76+
(int) $e->getCode(),
77+
$e instanceof \Exception ? $e : null
78+
);
79+
}
6980

7081
// Run the postupdate steps
7182
if (!$connection->finalizeUpdate()->success) {
72-
throw new \Exception("Update for site failed in postprocessing: " . $this->site->id);
83+
throw new UpdateException(
84+
"finalize",
85+
"Update for site failed in postprocessing: " . $this->site->id
86+
);
7387
}
7488

7589
// Compare codes
7690
if ($this->site->getFrontendStatus() !== $this->preUpdateCode) {
77-
throw new \Exception("Status code has changed after update for site: " . $this->site->id);
91+
throw new UpdateException(
92+
"afterUpdate",
93+
"Status code has changed after update for site: " . $this->site->id
94+
);
7895
}
7996
}
8097

@@ -121,5 +138,25 @@ protected function performExtraction(PrepareUpdate $prepareResult): void
121138
"task" => "finalizeUpdate"
122139
]
123140
);
141+
142+
// Done, log successful update!
143+
$this->site->updates()->create([
144+
'old_version' => $this->site->cms_version,
145+
'new_version' => $this->targetVersion,
146+
'result' => true
147+
]);
148+
}
149+
150+
public function failed(\Exception $exception): void
151+
{
152+
// We log any issues during the update to the DB
153+
$this->site->updates()->create([
154+
'old_version' => $this->site->cms_version,
155+
'new_version' => $this->targetVersion,
156+
'result' => false,
157+
'failed_step' => $exception instanceof UpdateException ? $exception->getStep() : null,
158+
'failed_message' => $exception->getMessage(),
159+
'failed_trace' => $exception->getTraceAsString()
160+
]);
124161
}
125162
}

app/Models/Site.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\RemoteSite\Connection;
88
use GuzzleHttp\Client;
99
use Illuminate\Database\Eloquent\Model;
10+
use Illuminate\Database\Eloquent\Relations\HasMany;
1011
use Illuminate\Support\Facades\App;
1112

1213
class Site extends Model
@@ -55,4 +56,12 @@ public function getFrontendStatus(): int
5556

5657
return $httpClient->get($this->url)->getStatusCode();
5758
}
59+
60+
/**
61+
* @return HasMany<Update, $this>
62+
*/
63+
public function updates(): HasMany
64+
{
65+
return $this->hasMany(Update::class, 'site_id', 'id');
66+
}
5867
}

app/Models/Update.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Models;
6+
7+
use Illuminate\Database\Eloquent\Model;
8+
9+
class Update extends Model
10+
{
11+
protected $fillable = [
12+
'old_version',
13+
'new_version',
14+
'result',
15+
'failed_step',
16+
'failed_message',
17+
'failed_trace'
18+
];
19+
20+
protected function casts(): array
21+
{
22+
return [
23+
'result' => 'bool'
24+
];
25+
}
26+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class () extends Migration {
8+
/**
9+
* Run the migrations.
10+
*/
11+
public function up(): void
12+
{
13+
Schema::create('updates', function (Blueprint $table) {
14+
$table->id();
15+
$table->foreignId('site_id');
16+
$table->string('old_version');
17+
$table->string('new_version');
18+
$table->boolean('result');
19+
$table->string('failed_step')->nullable();
20+
$table->text('failed_message')->nullable();
21+
$table->text('failed_trace')->nullable();
22+
$table->timestamps();
23+
});
24+
}
25+
26+
/**
27+
* Reverse the migrations.
28+
*/
29+
public function down(): void
30+
{
31+
Schema::dropIfExists('updates');
32+
}
33+
};

tests/Unit/Jobs/UpdateSiteTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@
22

33
namespace Tests\Unit\Jobs;
44

5+
use App\Exceptions\UpdateException;
56
use App\Jobs\UpdateSite;
67
use App\Models\Site;
8+
use App\Models\Update;
79
use App\RemoteSite\Connection;
810
use App\RemoteSite\Responses\FinalizeUpdate;
911
use App\RemoteSite\Responses\GetUpdate;
1012
use App\RemoteSite\Responses\HealthCheck;
1113
use App\RemoteSite\Responses\PrepareUpdate;
14+
use Illuminate\Foundation\Testing\RefreshDatabase;
1215
use Illuminate\Support\Facades\App;
1316
use Illuminate\Support\Facades\Log;
1417
use Tests\TestCase;
1518

1619
class UpdateSiteTest extends TestCase
1720
{
21+
use RefreshDatabase;
22+
1823
public function testJobQuitsIfTargetVersionIsEqualOrNewer()
1924
{
2025
$site = $this->getSiteMock(['checkHealth' => $this->getHealthCheckMock(["cms_version" => "1.0.0"])]);
@@ -98,6 +103,52 @@ public function testJobFailsIfFinalizeUpdateReturnsFalse()
98103
$object->handle();
99104
}
100105

106+
public function testJobWritesFailLogOnFailing()
107+
{
108+
$siteMock = $this->getMockBuilder(Site::class)
109+
->onlyMethods(['getConnectionAttribute', 'getFrontendStatus'])
110+
->getMock();
111+
112+
$siteMock->id = 1;
113+
$siteMock->url = "http://example.org";
114+
$siteMock->cms_version = "1.0.0";
115+
116+
$object = new UpdateSite($siteMock, "1.0.1");
117+
$object->failed(new UpdateException("finalize", "This is a test"));
118+
119+
$failedUpdate = Update::first();
120+
121+
$this->assertEquals(false, $failedUpdate->result);
122+
$this->assertEquals("1.0.0", $failedUpdate->old_version);
123+
$this->assertEquals("1.0.1", $failedUpdate->new_version);
124+
$this->assertEquals("finalize", $failedUpdate->failed_step);
125+
$this->assertEquals("This is a test", $failedUpdate->failed_message);
126+
$this->assertNotEmpty($failedUpdate->failed_trace);
127+
}
128+
129+
public function testJobWritesSuccessLogForSuccessfulJobs()
130+
{
131+
$site = $this->getSiteMock(
132+
[
133+
'checkHealth' => $this->getHealthCheckMock(),
134+
'getUpdate' => $this->getGetUpdateMock("1.0.1"),
135+
'prepareUpdate' => $this->getPrepareUpdateMock(),
136+
'finalizeUpdate' => $this->getFinalizeUpdateMock(true)
137+
]
138+
);
139+
140+
App::bind(Connection::class, fn () => $this->getSuccessfulExtractionMock());
141+
142+
$object = new UpdateSite($site, "1.0.1");
143+
$object->handle();
144+
145+
$updateRow = Update::first();
146+
147+
$this->assertEquals(true, $updateRow->result);
148+
$this->assertEquals("1.0.0", $updateRow->old_version);
149+
$this->assertEquals("1.0.1", $updateRow->new_version);
150+
}
151+
101152
protected function getSiteMock(array $responses)
102153
{
103154
$connectionMock = $this->getMockBuilder(Connection::class)
@@ -120,6 +171,7 @@ function ($method) use ($responses) {
120171
$siteMock->method('getFrontendStatus')->willReturn(200);
121172
$siteMock->id = 1;
122173
$siteMock->url = "http://example.org";
174+
$siteMock->cms_version = "1.0.0";
123175

124176
return $siteMock;
125177
}

0 commit comments

Comments
 (0)