feat(webhook): skip deployment on [skip ci]/[skip cd] commit markers
Add DetectsSkipDeployCommits trait with two strategies: shouldSkipDeploy (all commits must contain the marker) for push events, and shouldSkipDeployAny (any single marker triggers skip) for PR/MR titles and latest-commit signals. Apply trait to Bitbucket, Gitea, GitHub, GitLab webhook controllers and ProcessGithubPullRequestWebhook job. PRs pass pullRequestTitle through to the job constructor for evaluation.
This commit is contained in:
parent
9af0351144
commit
46180dbbf9
8 changed files with 296 additions and 1 deletions
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use App\Actions\Application\CleanupPreviewDeployment;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Webhook\Concerns\DetectsSkipDeployCommits;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Exception;
|
||||
|
|
@ -12,6 +13,8 @@
|
|||
|
||||
class Bitbucket extends Controller
|
||||
{
|
||||
use DetectsSkipDeployCommits;
|
||||
|
||||
public function manual(Request $request)
|
||||
{
|
||||
try {
|
||||
|
|
@ -31,6 +34,16 @@ public function manual(Request $request)
|
|||
$branch = data_get($payload, 'push.changes.0.new.name');
|
||||
$full_name = data_get($payload, 'repository.full_name');
|
||||
$commit = data_get($payload, 'push.changes.0.new.target.hash');
|
||||
// Bitbucket webhooks ship up to 5 commits per change. Larger pushes
|
||||
// are evaluated only on the visible 5.
|
||||
$skip_deploy_commits = self::shouldSkipDeploy(
|
||||
collect(data_get($payload, 'push.changes', []))
|
||||
->flatMap(fn ($change) => data_get($change, 'commits', []))
|
||||
->pluck('message')
|
||||
->filter()
|
||||
->values()
|
||||
->all()
|
||||
);
|
||||
|
||||
if (! $branch) {
|
||||
return response([
|
||||
|
|
@ -45,6 +58,8 @@ public function manual(Request $request)
|
|||
$full_name = data_get($payload, 'repository.full_name');
|
||||
$pull_request_id = data_get($payload, 'pullrequest.id');
|
||||
$pull_request_html_url = data_get($payload, 'pullrequest.links.html.href');
|
||||
$pull_request_title = data_get($payload, 'pullrequest.title');
|
||||
$skip_deploy_pr = self::shouldSkipDeployAny([$pull_request_title]);
|
||||
$commit = data_get($payload, 'pullrequest.source.commit.hash');
|
||||
}
|
||||
$applications = Application::where('git_repository', 'like', "%$full_name%");
|
||||
|
|
@ -119,6 +134,17 @@ public function manual(Request $request)
|
|||
}
|
||||
if ($x_bitbucket_event === 'repo:push') {
|
||||
if ($application->isDeployable()) {
|
||||
if ($skip_deploy_commits ?? false) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'skipped',
|
||||
'message' => 'All commits contain [skip cd] or [skip ci]. Skipping deployment.',
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
$deployment_uuid = new Cuid2;
|
||||
$result = queue_application_deployment(
|
||||
application: $application,
|
||||
|
|
@ -161,6 +187,15 @@ public function manual(Request $request)
|
|||
}
|
||||
if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:updated') {
|
||||
if ($application->isPRDeployable()) {
|
||||
if ($skip_deploy_pr ?? false) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'skipped',
|
||||
'message' => 'PR title contains [skip cd] or [skip ci]. Skipping preview deployment.',
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
$deployment_uuid = new Cuid2;
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Webhook\Concerns;
|
||||
|
||||
trait DetectsSkipDeployCommits
|
||||
{
|
||||
/**
|
||||
* Returns true if there is at least one non-empty message and every message
|
||||
* contains [skip cd] or [skip ci] (case-insensitive).
|
||||
*
|
||||
* Accepts commit messages from a push payload. Null/empty entries are
|
||||
* filtered before evaluation.
|
||||
*
|
||||
* @param array<int, string|null> $messages
|
||||
*/
|
||||
public static function shouldSkipDeploy(array $messages): bool
|
||||
{
|
||||
$messages = array_values(array_filter($messages, fn ($m) => filled($m)));
|
||||
|
||||
if (empty($messages)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($messages as $message) {
|
||||
$lower = strtolower((string) $message);
|
||||
if (! str_contains($lower, '[skip cd]') && ! str_contains($lower, '[skip ci]')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if at least one non-empty message contains [skip cd] or
|
||||
* [skip ci]. Used for PR/MR title + latest-commit signals where any one
|
||||
* marker should trigger the skip.
|
||||
*
|
||||
* @param array<int, string|null> $messages
|
||||
*/
|
||||
public static function shouldSkipDeployAny(array $messages): bool
|
||||
{
|
||||
foreach ($messages as $message) {
|
||||
if (! filled($message)) {
|
||||
continue;
|
||||
}
|
||||
$lower = strtolower((string) $message);
|
||||
if (str_contains($lower, '[skip cd]') || str_contains($lower, '[skip ci]')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use App\Actions\Application\CleanupPreviewDeployment;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Webhook\Concerns\DetectsSkipDeployCommits;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Exception;
|
||||
|
|
@ -13,6 +14,8 @@
|
|||
|
||||
class Gitea extends Controller
|
||||
{
|
||||
use DetectsSkipDeployCommits;
|
||||
|
||||
public function manual(Request $request)
|
||||
{
|
||||
try {
|
||||
|
|
@ -40,12 +43,15 @@ public function manual(Request $request)
|
|||
$removed_files = data_get($payload, 'commits.*.removed');
|
||||
$modified_files = data_get($payload, 'commits.*.modified');
|
||||
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
|
||||
$skip_deploy_commits = self::shouldSkipDeploy(data_get($payload, 'commits.*.message', []));
|
||||
}
|
||||
if ($x_gitea_event === 'pull_request') {
|
||||
$action = data_get($payload, 'action');
|
||||
$full_name = data_get($payload, 'repository.full_name');
|
||||
$pull_request_id = data_get($payload, 'number');
|
||||
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
|
||||
$pull_request_title = data_get($payload, 'pull_request.title');
|
||||
$skip_deploy_pr = self::shouldSkipDeployAny([$pull_request_title]);
|
||||
$branch = data_get($payload, 'pull_request.head.ref');
|
||||
$base_branch = data_get($payload, 'pull_request.base.ref');
|
||||
}
|
||||
|
|
@ -112,6 +118,17 @@ public function manual(Request $request)
|
|||
if ($application->isDeployable()) {
|
||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||
if ($is_watch_path_triggered || blank($application->watch_paths)) {
|
||||
if ($skip_deploy_commits ?? false) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'skipped',
|
||||
'message' => 'All commits contain [skip cd] or [skip ci]. Skipping deployment.',
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
$deployment_uuid = new Cuid2;
|
||||
$result = queue_application_deployment(
|
||||
application: $application,
|
||||
|
|
@ -170,6 +187,15 @@ public function manual(Request $request)
|
|||
if ($x_gitea_event === 'pull_request') {
|
||||
if ($action === 'opened' || $action === 'synchronized' || $action === 'reopened') {
|
||||
if ($application->isPRDeployable()) {
|
||||
if ($skip_deploy_pr ?? false) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'skipped',
|
||||
'message' => 'PR title contains [skip cd] or [skip ci]. Skipping preview deployment.',
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
$deployment_uuid = new Cuid2;
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Http\Controllers\Webhook;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Webhook\Concerns\DetectsSkipDeployCommits;
|
||||
use App\Jobs\GithubAppPermissionJob;
|
||||
use App\Jobs\ProcessGithubPullRequestWebhook;
|
||||
use App\Models\Application;
|
||||
|
|
@ -16,6 +17,8 @@
|
|||
|
||||
class Github extends Controller
|
||||
{
|
||||
use DetectsSkipDeployCommits;
|
||||
|
||||
public function manual(Request $request)
|
||||
{
|
||||
try {
|
||||
|
|
@ -43,12 +46,14 @@ public function manual(Request $request)
|
|||
$removed_files = data_get($payload, 'commits.*.removed');
|
||||
$modified_files = data_get($payload, 'commits.*.modified');
|
||||
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
|
||||
$skip_deploy_commits = self::shouldSkipDeploy(data_get($payload, 'commits.*.message', []));
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
$action = data_get($payload, 'action');
|
||||
$full_name = data_get($payload, 'repository.full_name');
|
||||
$pull_request_id = data_get($payload, 'number');
|
||||
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
|
||||
$pull_request_title = data_get($payload, 'pull_request.title');
|
||||
$branch = data_get($payload, 'pull_request.head.ref');
|
||||
$base_branch = data_get($payload, 'pull_request.base.ref');
|
||||
$before_sha = data_get($payload, 'before');
|
||||
|
|
@ -126,6 +131,17 @@ public function manual(Request $request)
|
|||
if ($application->isDeployable()) {
|
||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||
if ($is_watch_path_triggered || blank($application->watch_paths)) {
|
||||
if ($skip_deploy_commits ?? false) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'skipped',
|
||||
'message' => 'All commits contain [skip cd] or [skip ci]. Skipping deployment.',
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
$deployment_uuid = new Cuid2;
|
||||
$result = queue_application_deployment(
|
||||
application: $application,
|
||||
|
|
@ -201,6 +217,7 @@ public function manual(Request $request)
|
|||
action: $action,
|
||||
pullRequestId: $pull_request_id,
|
||||
pullRequestHtmlUrl: $pull_request_html_url,
|
||||
pullRequestTitle: $pull_request_title ?? null,
|
||||
beforeSha: $before_sha,
|
||||
afterSha: $after_sha,
|
||||
commitSha: data_get($payload, 'pull_request.head.sha', 'HEAD'),
|
||||
|
|
@ -274,12 +291,14 @@ public function normal(Request $request)
|
|||
$removed_files = data_get($payload, 'commits.*.removed');
|
||||
$modified_files = data_get($payload, 'commits.*.modified');
|
||||
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
|
||||
$skip_deploy_commits = self::shouldSkipDeploy(data_get($payload, 'commits.*.message', []));
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
$action = data_get($payload, 'action');
|
||||
$id = data_get($payload, 'repository.id');
|
||||
$pull_request_id = data_get($payload, 'number');
|
||||
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
|
||||
$pull_request_title = data_get($payload, 'pull_request.title');
|
||||
$branch = data_get($payload, 'pull_request.head.ref');
|
||||
$base_branch = data_get($payload, 'pull_request.base.ref');
|
||||
$before_sha = data_get($payload, 'before');
|
||||
|
|
@ -328,6 +347,17 @@ public function normal(Request $request)
|
|||
if ($application->isDeployable()) {
|
||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||
if ($is_watch_path_triggered || blank($application->watch_paths)) {
|
||||
if ($skip_deploy_commits ?? false) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'skipped',
|
||||
'message' => 'All commits contain [skip cd] or [skip ci]. Skipping deployment.',
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
$deployment_uuid = new Cuid2;
|
||||
$result = queue_application_deployment(
|
||||
application: $application,
|
||||
|
|
@ -399,6 +429,7 @@ public function normal(Request $request)
|
|||
action: $action,
|
||||
pullRequestId: $pull_request_id,
|
||||
pullRequestHtmlUrl: $pull_request_html_url,
|
||||
pullRequestTitle: $pull_request_title ?? null,
|
||||
beforeSha: $before_sha,
|
||||
afterSha: $after_sha,
|
||||
commitSha: data_get($payload, 'pull_request.head.sha', 'HEAD'),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use App\Actions\Application\CleanupPreviewDeployment;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Webhook\Concerns\DetectsSkipDeployCommits;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Exception;
|
||||
|
|
@ -13,6 +14,8 @@
|
|||
|
||||
class Gitlab extends Controller
|
||||
{
|
||||
use DetectsSkipDeployCommits;
|
||||
|
||||
public function manual(Request $request)
|
||||
{
|
||||
try {
|
||||
|
|
@ -61,6 +64,7 @@ public function manual(Request $request)
|
|||
$removed_files = data_get($payload, 'commits.*.removed');
|
||||
$modified_files = data_get($payload, 'commits.*.modified');
|
||||
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
|
||||
$skip_deploy_commits = self::shouldSkipDeploy(data_get($payload, 'commits.*.message', []));
|
||||
}
|
||||
if ($x_gitlab_event === 'merge_request') {
|
||||
$action = data_get($payload, 'object_attributes.action');
|
||||
|
|
@ -69,6 +73,9 @@ public function manual(Request $request)
|
|||
$full_name = data_get($payload, 'project.path_with_namespace');
|
||||
$pull_request_id = data_get($payload, 'object_attributes.iid');
|
||||
$pull_request_html_url = data_get($payload, 'object_attributes.url');
|
||||
$pull_request_title = data_get($payload, 'object_attributes.title');
|
||||
$latest_commit_message = data_get($payload, 'object_attributes.last_commit.message');
|
||||
$skip_deploy_pr = self::shouldSkipDeployAny([$pull_request_title, $latest_commit_message]);
|
||||
if (! $branch) {
|
||||
$return_payloads->push([
|
||||
'status' => 'failed',
|
||||
|
|
@ -147,6 +154,17 @@ public function manual(Request $request)
|
|||
if ($application->isDeployable()) {
|
||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||
if ($is_watch_path_triggered || blank($application->watch_paths)) {
|
||||
if ($skip_deploy_commits ?? false) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'skipped',
|
||||
'message' => 'All commits contain [skip cd] or [skip ci]. Skipping deployment.',
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
$deployment_uuid = new Cuid2;
|
||||
$result = queue_application_deployment(
|
||||
application: $application,
|
||||
|
|
@ -206,6 +224,15 @@ public function manual(Request $request)
|
|||
if ($x_gitlab_event === 'merge_request') {
|
||||
if ($action === 'open' || $action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update') {
|
||||
if ($application->isPRDeployable()) {
|
||||
if ($skip_deploy_pr ?? false) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'skipped',
|
||||
'message' => 'PR title or latest commit contains [skip cd] or [skip ci]. Skipping preview deployment.',
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
$deployment_uuid = new Cuid2;
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use App\Actions\Application\CleanupPreviewDeployment;
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Http\Controllers\Webhook\Concerns\DetectsSkipDeployCommits;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\GithubApp;
|
||||
|
|
@ -17,6 +18,7 @@
|
|||
|
||||
class ProcessGithubPullRequestWebhook implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use DetectsSkipDeployCommits;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public int $tries = 3;
|
||||
|
|
@ -31,6 +33,7 @@ public function __construct(
|
|||
public string $action,
|
||||
public int $pullRequestId,
|
||||
public string $pullRequestHtmlUrl,
|
||||
public ?string $pullRequestTitle,
|
||||
public ?string $beforeSha,
|
||||
public ?string $afterSha,
|
||||
public string $commitSha,
|
||||
|
|
@ -83,6 +86,10 @@ private function handleOpenAction(Application $application, ?GithubApp $githubAp
|
|||
return;
|
||||
}
|
||||
|
||||
if (self::shouldSkipDeployAny([$this->pullRequestTitle])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if PR deployments from public contributors are restricted
|
||||
if (! $application->settings->is_pr_deployments_public_enabled) {
|
||||
$trustedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR', 'CONTRIBUTOR'];
|
||||
|
|
|
|||
|
|
@ -132,7 +132,6 @@ services:
|
|||
image: ghcr.io/coollabsio/maxio:latest
|
||||
pull_policy: always
|
||||
container_name: coolify-minio
|
||||
command: server /data --console-address ":9001"
|
||||
ports:
|
||||
- "${FORWARD_MINIO_PORT:-9000}:9000"
|
||||
- "${FORWARD_MINIO_PORT_CONSOLE:-9001}:9001"
|
||||
|
|
|
|||
115
tests/Unit/DetectsSkipDeployCommitsTest.php
Normal file
115
tests/Unit/DetectsSkipDeployCommitsTest.php
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\Webhook\Concerns\DetectsSkipDeployCommits;
|
||||
|
||||
$harness = new class
|
||||
{
|
||||
use DetectsSkipDeployCommits;
|
||||
};
|
||||
|
||||
$harnessClass = get_class($harness);
|
||||
|
||||
describe('shouldSkipDeploy (all-must-match)', function () use ($harnessClass) {
|
||||
test('returns false when messages array is empty', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeploy([]))->toBeFalse();
|
||||
});
|
||||
|
||||
test('returns false when only nulls or empty strings are provided', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeploy([null, '', null]))->toBeFalse();
|
||||
});
|
||||
|
||||
test('returns true when all messages contain [skip ci]', function () use ($harnessClass) {
|
||||
$messages = [
|
||||
'Update docs [skip ci]',
|
||||
'Fix typo [skip ci]',
|
||||
];
|
||||
expect($harnessClass::shouldSkipDeploy($messages))->toBeTrue();
|
||||
});
|
||||
|
||||
test('returns true when single message contains [skip cd]', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeploy(['Update README [skip cd]']))->toBeTrue();
|
||||
});
|
||||
|
||||
test('returns true with mixed [skip ci] and [skip cd] (case-insensitive)', function () use ($harnessClass) {
|
||||
$messages = [
|
||||
'Docs [SKIP CI]',
|
||||
'Changelog [Skip Cd]',
|
||||
];
|
||||
expect($harnessClass::shouldSkipDeploy($messages))->toBeTrue();
|
||||
});
|
||||
|
||||
test('returns false when at least one message has no skip marker', function () use ($harnessClass) {
|
||||
$messages = [
|
||||
'Update docs [skip ci]',
|
||||
'Actual feature change',
|
||||
];
|
||||
expect($harnessClass::shouldSkipDeploy($messages))->toBeFalse();
|
||||
});
|
||||
|
||||
test('returns false when single message has no skip marker', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeploy(['Deploy this please']))->toBeFalse();
|
||||
});
|
||||
|
||||
test('null entries are filtered before evaluation', function () use ($harnessClass) {
|
||||
$messages = [
|
||||
null,
|
||||
'Docs [skip ci]',
|
||||
null,
|
||||
];
|
||||
expect($harnessClass::shouldSkipDeploy($messages))->toBeTrue();
|
||||
});
|
||||
|
||||
test('matches PR title scenario (single string)', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeploy(['chore: update readme [skip ci]']))->toBeTrue();
|
||||
expect($harnessClass::shouldSkipDeploy(['feat: real change']))->toBeFalse();
|
||||
expect($harnessClass::shouldSkipDeploy([null]))->toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldSkipDeployAny (any-marker)', function () use ($harnessClass) {
|
||||
test('returns false when messages array is empty', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeployAny([]))->toBeFalse();
|
||||
});
|
||||
|
||||
test('returns false when only nulls or empty strings are provided', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeployAny([null, '', null]))->toBeFalse();
|
||||
});
|
||||
|
||||
test('returns true when any one message contains [skip ci]', function () use ($harnessClass) {
|
||||
$messages = [
|
||||
'Real feature change',
|
||||
'docs: update readme [skip ci]',
|
||||
];
|
||||
expect($harnessClass::shouldSkipDeployAny($messages))->toBeTrue();
|
||||
});
|
||||
|
||||
test('returns true when any one message contains [skip cd]', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeployAny(['feature change', 'chore [skip cd]']))->toBeTrue();
|
||||
});
|
||||
|
||||
test('returns true case-insensitively', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeployAny(['feat: docs [SKIP CI]']))->toBeTrue();
|
||||
expect($harnessClass::shouldSkipDeployAny(['feat: docs [Skip Cd]']))->toBeTrue();
|
||||
});
|
||||
|
||||
test('returns false when no message contains a skip marker', function () use ($harnessClass) {
|
||||
$messages = [
|
||||
'feat: add new endpoint',
|
||||
'fix: handle edge case',
|
||||
];
|
||||
expect($harnessClass::shouldSkipDeployAny($messages))->toBeFalse();
|
||||
});
|
||||
|
||||
test('null and empty entries are skipped, real markers still match', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeployAny([null, '', 'docs [skip ci]', null]))->toBeTrue();
|
||||
expect($harnessClass::shouldSkipDeployAny([null, '', null]))->toBeFalse();
|
||||
});
|
||||
|
||||
test('PR title alone with skip marker triggers skip', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeployAny(['chore: update readme [skip ci]']))->toBeTrue();
|
||||
});
|
||||
|
||||
test('PR title without skip marker but commit message with skip marker triggers skip', function () use ($harnessClass) {
|
||||
expect($harnessClass::shouldSkipDeployAny(['feat: real change', 'wip [skip cd]']))->toBeTrue();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue