mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-29 10:04:36 +08:00
279 lines
11 KiB
PHP
279 lines
11 KiB
PHP
<?php
|
|
|
|
use App\Models\Application;
|
|
use App\Models\ApplicationDeploymentQueue;
|
|
use App\Models\ApplicationPreview;
|
|
use App\Models\Environment;
|
|
use App\Models\GitlabApp;
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\Project;
|
|
use App\Models\Server;
|
|
use App\Models\Team;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Illuminate\Testing\TestResponse;
|
|
use Tests\TestCase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
Server::flushIdentityMap();
|
|
Queue::fake();
|
|
InstanceSettings::forceCreate(['id' => 0]);
|
|
});
|
|
|
|
function gitlabSystemHookSha(): string
|
|
{
|
|
return 'da1560886d4f094c3e6c9ef40349f7d38b5d27d7';
|
|
}
|
|
|
|
function gitlabSystemHookSource(string $token): GitlabApp
|
|
{
|
|
return GitlabApp::create([
|
|
'name' => "gitlab-system-hook-{$token}",
|
|
'api_url' => 'https://gitlab.example.com/api/v4',
|
|
'html_url' => 'https://gitlab.example.com',
|
|
'custom_user' => 'git',
|
|
'custom_port' => 22,
|
|
'webhook_token' => $token,
|
|
'team_id' => Team::factory()->create()->id,
|
|
'is_system_wide' => false,
|
|
'is_public' => false,
|
|
]);
|
|
}
|
|
|
|
function gitlabSystemHookApplication(GitlabApp $source, int $projectId = 15, string $branch = 'main'): Application
|
|
{
|
|
$project = Project::factory()->create(['team_id' => $source->team_id]);
|
|
$environment = Environment::factory()->create(['project_id' => $project->id]);
|
|
$server = Server::factory()->create(['team_id' => $source->team_id]);
|
|
$server->settings->update([
|
|
'is_reachable' => true,
|
|
'is_usable' => true,
|
|
'force_disabled' => false,
|
|
]);
|
|
$destination = $server->standaloneDockers()->firstOrFail();
|
|
|
|
$application = Application::create([
|
|
'name' => "gitlab-system-hook-app-{$source->id}",
|
|
'git_repository' => 'mike/diaspora',
|
|
'git_branch' => $branch,
|
|
'build_pack' => 'nixpacks',
|
|
'ports_exposes' => '3000',
|
|
'environment_id' => $environment->id,
|
|
'destination_id' => $destination->id,
|
|
'destination_type' => $destination->getMorphClass(),
|
|
'source_id' => $source->id,
|
|
'source_type' => GitlabApp::class,
|
|
'repository_project_id' => $projectId,
|
|
]);
|
|
$application->settings->update([
|
|
'is_auto_deploy_enabled' => true,
|
|
'is_preview_deployments_enabled' => true,
|
|
]);
|
|
|
|
return $application->refresh();
|
|
}
|
|
|
|
/**
|
|
* A push payload in the shape of the GitLab system hook docs example: it has
|
|
* `event_name` and a top-level `project_id`, but no `object_kind` and no `project.id`.
|
|
*
|
|
* @see https://docs.gitlab.com/administration/system_hooks/#push-events
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
function gitlabSystemHookDocumentedPush(int $projectId = 15, string $ref = 'refs/heads/main'): array
|
|
{
|
|
return [
|
|
'event_name' => 'push',
|
|
'before' => '95790bf891e76fee5e1747ab589903a6a1f80f22',
|
|
'after' => gitlabSystemHookSha(),
|
|
'ref' => $ref,
|
|
'checkout_sha' => gitlabSystemHookSha(),
|
|
'user_id' => 4,
|
|
'project_id' => $projectId,
|
|
'project' => ['name' => 'Diaspora', 'path_with_namespace' => 'mike/diaspora', 'default_branch' => 'main'],
|
|
'commits' => [[
|
|
'id' => gitlabSystemHookSha(),
|
|
'message' => 'Add simple search to projects in public area',
|
|
'added' => ['app.php'],
|
|
'modified' => [],
|
|
'removed' => [],
|
|
]],
|
|
'total_commits_count' => 1,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* A push payload as current GitLab sends it to system hooks
|
|
* (Gitlab::DataBuilder::Push, the same data as for project hooks).
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
function gitlabSystemHookRuntimePush(int $projectId = 15): array
|
|
{
|
|
$payload = gitlabSystemHookDocumentedPush($projectId);
|
|
$payload['object_kind'] = 'push';
|
|
$payload['project']['id'] = $projectId;
|
|
|
|
return $payload;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
function gitlabSystemHookMergeRequest(int $projectId = 15, string $action = 'open'): array
|
|
{
|
|
return [
|
|
'object_kind' => 'merge_request',
|
|
'event_type' => 'merge_request',
|
|
'project' => ['id' => $projectId, 'path_with_namespace' => 'mike/diaspora'],
|
|
'object_attributes' => [
|
|
'action' => $action,
|
|
'iid' => 1,
|
|
'url' => 'https://gitlab.example.com/mike/diaspora/-/merge_requests/1',
|
|
'title' => 'MS-Viewport',
|
|
'source_branch' => 'ms-viewport',
|
|
'target_branch' => 'main',
|
|
'source_project_id' => $projectId,
|
|
'target_project_id' => $projectId,
|
|
'last_commit' => ['id' => gitlabSystemHookSha(), 'message' => 'Fix viewport'],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
function gitlabSystemHookSend(TestCase $test, array $payload, ?string $token, string $event = 'System Hook'): TestResponse
|
|
{
|
|
$headers = ['HTTP_X-Gitlab-Event' => $event];
|
|
if ($token !== null) {
|
|
$headers['HTTP_X-Gitlab-Token'] = $token;
|
|
}
|
|
|
|
return $test->call('POST', '/webhooks/source/gitlab/events', [], [], [], $headers + [
|
|
'REMOTE_ADDR' => '203.0.113.20',
|
|
'CONTENT_TYPE' => 'application/json',
|
|
], json_encode($payload, JSON_THROW_ON_ERROR));
|
|
}
|
|
|
|
function gitlabSystemHookDeployments(Application $application): array
|
|
{
|
|
return ApplicationDeploymentQueue::query()->where('application_id', $application->id)->pluck('commit')->all();
|
|
}
|
|
|
|
describe('GitLab system hook push', function () {
|
|
test('a push to the tracked branch queues a deployment', function (array $payload) {
|
|
$source = gitlabSystemHookSource('system-hook-token');
|
|
$application = gitlabSystemHookApplication($source);
|
|
|
|
gitlabSystemHookSend($this, $payload, 'system-hook-token')
|
|
->assertOk()
|
|
->assertJsonPath('0.application', $application->name);
|
|
|
|
expect(gitlabSystemHookDeployments($application))->toBe([gitlabSystemHookSha()]);
|
|
})->with([
|
|
'documented payload' => [fn () => gitlabSystemHookDocumentedPush()],
|
|
'runtime payload' => [fn () => gitlabSystemHookRuntimePush()],
|
|
]);
|
|
|
|
test('a push to another branch queues no deployment', function () {
|
|
$source = gitlabSystemHookSource('system-hook-token');
|
|
$application = gitlabSystemHookApplication($source);
|
|
|
|
gitlabSystemHookSend($this, gitlabSystemHookDocumentedPush(ref: 'refs/heads/develop'), 'system-hook-token')->assertOk();
|
|
|
|
expect(gitlabSystemHookDeployments($application))->toBe([]);
|
|
});
|
|
|
|
test('a push for a project that no application tracks queues no deployment', function () {
|
|
$source = gitlabSystemHookSource('system-hook-token');
|
|
$application = gitlabSystemHookApplication($source, projectId: 15);
|
|
|
|
gitlabSystemHookSend($this, gitlabSystemHookDocumentedPush(projectId: 99), 'system-hook-token')
|
|
->assertOk()
|
|
->assertExactJson([]);
|
|
|
|
expect(gitlabSystemHookDeployments($application))->toBe([]);
|
|
});
|
|
|
|
test('a token only deploys applications of its own source', function () {
|
|
$source = gitlabSystemHookSource('system-hook-token');
|
|
$otherSource = gitlabSystemHookSource('other-source-token');
|
|
$application = gitlabSystemHookApplication($source);
|
|
$otherApplication = gitlabSystemHookApplication($otherSource);
|
|
|
|
gitlabSystemHookSend($this, gitlabSystemHookDocumentedPush(), 'system-hook-token')->assertOk();
|
|
|
|
expect(gitlabSystemHookDeployments($application))->toBe([gitlabSystemHookSha()])
|
|
->and(gitlabSystemHookDeployments($otherApplication))->toBe([]);
|
|
});
|
|
|
|
test('a missing or invalid token is rejected', function (?string $token) {
|
|
$application = gitlabSystemHookApplication(gitlabSystemHookSource('system-hook-token'));
|
|
|
|
gitlabSystemHookSend($this, gitlabSystemHookDocumentedPush(), $token)->assertUnauthorized();
|
|
|
|
expect(gitlabSystemHookDeployments($application))->toBe([]);
|
|
})->with([
|
|
'missing' => [null],
|
|
'invalid' => ['wrong-token'],
|
|
]);
|
|
});
|
|
|
|
describe('GitLab system hook merge request', function () {
|
|
test('an opened merge request queues a preview deployment', function () {
|
|
$source = gitlabSystemHookSource('system-hook-token');
|
|
$application = gitlabSystemHookApplication($source);
|
|
|
|
gitlabSystemHookSend($this, gitlabSystemHookMergeRequest(), 'system-hook-token')->assertOk();
|
|
|
|
expect(ApplicationPreview::query()->where('application_id', $application->id)->pluck('pull_request_id')->all())->toBe([1])
|
|
->and(ApplicationDeploymentQueue::query()->where('application_id', $application->id)->sole()->pull_request_id)->toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('GitLab system hook events without a deployment', function () {
|
|
test('the event is ignored without an error', function (array $payload) {
|
|
$source = gitlabSystemHookSource('system-hook-token');
|
|
$application = gitlabSystemHookApplication($source);
|
|
|
|
gitlabSystemHookSend($this, $payload, 'system-hook-token')
|
|
->assertOk()
|
|
->assertJsonPath('status', 'failed')
|
|
->assertJsonPath('message', 'Event not allowed. Only push and merge_request events are allowed.');
|
|
|
|
expect(gitlabSystemHookDeployments($application))->toBe([]);
|
|
})->with([
|
|
'project_create' => [['event_name' => 'project_create', 'project_id' => 15, 'name' => 'Diaspora', 'path_with_namespace' => 'mike/diaspora']],
|
|
'user_create' => [['event_name' => 'user_create', 'user_id' => 41, 'username' => 'js', 'email' => 'js@gitlabhq.com']],
|
|
'repository_update' => [['event_name' => 'repository_update', 'project_id' => 15, 'changes' => [['ref' => 'refs/heads/main', 'after' => gitlabSystemHookSha()]], 'refs' => ['refs/heads/main']]],
|
|
'tag_push' => [['event_name' => 'tag_push', 'ref' => 'refs/tags/v1.0.0', 'after' => gitlabSystemHookSha(), 'project_id' => 15, 'commits' => []]],
|
|
]);
|
|
});
|
|
|
|
describe('GitLab project webhook', function () {
|
|
test('a push to the tracked branch still queues a deployment', function () {
|
|
$source = gitlabSystemHookSource('system-hook-token');
|
|
$application = gitlabSystemHookApplication($source);
|
|
$payload = gitlabSystemHookRuntimePush();
|
|
unset($payload['event_name'], $payload['project_id']);
|
|
|
|
gitlabSystemHookSend($this, $payload, 'system-hook-token', event: 'Push Hook')->assertOk();
|
|
|
|
expect(gitlabSystemHookDeployments($application))->toBe([gitlabSystemHookSha()]);
|
|
});
|
|
|
|
test('a payload without object_kind is still rejected', function () {
|
|
$application = gitlabSystemHookApplication(gitlabSystemHookSource('system-hook-token'));
|
|
|
|
gitlabSystemHookSend($this, gitlabSystemHookDocumentedPush(), 'system-hook-token', event: 'Push Hook')
|
|
->assertOk()
|
|
->assertJsonPath('message', 'Event not allowed. Only push and merge_request events are allowed.');
|
|
|
|
expect(gitlabSystemHookDeployments($application))->toBe([]);
|
|
});
|
|
});
|