mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(api): validate resource names and descriptions like the UI
The API and MCP saved names and descriptions that the UI rejects, so the resource could not be saved in the UI after that. Applications, services, databases, servers, and private keys now use the UI name and description rules on create and update, with the UI error messages. Closes #12466 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3bc82eb71d
commit
32541a32ce
@@ -1595,8 +1595,8 @@ class ApplicationsController extends Controller
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_enabled', 'domains', 'noindex_domains', 'git_repository', 'git_branch', 'git_commit_sha', 'git_https_username', 'git_https_password', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'dockerfile_location', 'dockerfile_target_build', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'use_build_secrets', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'http_basic_auth_hash_algorithm', 'http_basic_auth_bcrypt_cost', 'http_basic_auth_argon2id_memory_cost', 'http_basic_auth_argon2id_time_cost', 'connect_to_docker_network', 'force_domain_override', 'autogenerate_domain', 'is_container_label_escape_enabled', 'tags', 'is_preserve_repository_enabled', 'preview_url_template', 'max_restart_count', ...self::APPLICATION_SETTING_FIELDS];
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string|nullable',
|
||||
'name' => ValidationPatterns::nameRules(required: false, nullable: false),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'project_uuid' => 'string|required',
|
||||
'environment_name' => 'string|nullable',
|
||||
'environment_uuid' => 'string|nullable',
|
||||
@@ -3638,10 +3638,10 @@ class ApplicationsController extends Controller
|
||||
$allowedFields = ['name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_enabled', 'domains', 'noindex_domains', 'git_repository', 'git_branch', 'git_commit_sha', 'git_https_username', 'git_https_password', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'dockerfile_location', 'dockerfile_target_build', 'docker_compose_location', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'use_build_secrets', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'http_basic_auth_hash_algorithm', 'http_basic_auth_bcrypt_cost', 'http_basic_auth_argon2id_memory_cost', 'http_basic_auth_argon2id_time_cost', 'connect_to_docker_network', 'force_domain_override', 'is_container_label_escape_enabled', 'is_preserve_repository_enabled', 'preview_url_template', 'max_restart_count', 'github_app_uuid', 'dockerfile', ...self::APPLICATION_SETTING_FIELDS];
|
||||
|
||||
$validationRules = [
|
||||
'name' => 'string|max:255',
|
||||
'name' => ValidationPatterns::nameRules(required: false, nullable: false),
|
||||
'github_app_uuid' => 'string',
|
||||
'dockerfile' => 'string',
|
||||
'description' => 'string|nullable',
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'watch_paths' => 'string|nullable',
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
'docker_compose_domains.*' => 'array:name,domain,redirect',
|
||||
|
||||
@@ -659,8 +659,8 @@ class DatabasesController extends Controller
|
||||
return $return;
|
||||
}
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string|nullable',
|
||||
'name' => ValidationPatterns::nameRules(required: false, nullable: false),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'image' => 'string',
|
||||
'is_public' => 'boolean',
|
||||
'public_port' => 'numeric|nullable|between:1024,65535',
|
||||
@@ -676,7 +676,7 @@ class DatabasesController extends Controller
|
||||
'redact_all_env_values_in_logs' => 'boolean',
|
||||
'include_coolify_default_variables' => 'boolean',
|
||||
'is_log_drain_enabled' => 'boolean',
|
||||
]);
|
||||
], ValidationPatterns::combinedMessages());
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
@@ -2237,8 +2237,8 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$this->normalizeNumericLimits($request);
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string|nullable',
|
||||
'name' => ValidationPatterns::nameRules(required: false, nullable: false),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'image' => 'string',
|
||||
'project_uuid' => 'string|required',
|
||||
'environment_name' => 'string|nullable',
|
||||
@@ -2258,7 +2258,7 @@ class DatabasesController extends Controller
|
||||
'instant_deploy' => 'boolean',
|
||||
'tags' => 'array|nullable',
|
||||
'tags.*' => 'string|min:2',
|
||||
]);
|
||||
], ValidationPatterns::combinedMessages());
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
@@ -5233,9 +5233,9 @@ class DatabasesController extends Controller
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'destination_uuid' => 'required|string',
|
||||
'name' => 'string|max:255|nullable',
|
||||
'name' => ValidationPatterns::nameRules(required: false),
|
||||
'clone_volumes' => 'boolean',
|
||||
]);
|
||||
], ValidationPatterns::combinedMessages());
|
||||
$allowedFields = ['destination_uuid', 'name', 'clone_volumes'];
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
@@ -187,10 +188,10 @@ class SecurityController extends Controller
|
||||
return $return;
|
||||
}
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string|max:255',
|
||||
'name' => ValidationPatterns::nameRules(required: false, nullable: false),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'private_key' => 'required|string',
|
||||
]);
|
||||
], ValidationPatterns::combinedMessages());
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors = $validator->errors();
|
||||
@@ -325,10 +326,10 @@ class SecurityController extends Controller
|
||||
}
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string|max:255',
|
||||
'name' => ValidationPatterns::nameRules(required: false, nullable: false),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'private_key' => 'string',
|
||||
]);
|
||||
], ValidationPatterns::combinedMessages());
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
|
||||
@@ -551,8 +551,8 @@ class ServersController extends Controller
|
||||
return $return;
|
||||
}
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => ['string', 'max:255', 'not_regex:'.self::SERVER_NAME_CONTROL_CHARACTERS_PATTERN],
|
||||
'description' => 'string|nullable',
|
||||
'name' => ['not_regex:'.self::SERVER_NAME_CONTROL_CHARACTERS_PATTERN, ...ValidationPatterns::nameRules(required: false, nullable: false)],
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'ip' => ['string', 'required', new ValidServerIp],
|
||||
'port' => 'integer|nullable|between:1,65535',
|
||||
'private_key_uuid' => 'string|required',
|
||||
@@ -563,6 +563,7 @@ class ServersController extends Controller
|
||||
'proxy_type' => 'string|nullable',
|
||||
], [
|
||||
...ValidationPatterns::serverUsernameMessages(),
|
||||
...ValidationPatterns::combinedMessages(),
|
||||
'name.not_regex' => 'The name must not contain control characters such as new lines.',
|
||||
]);
|
||||
|
||||
@@ -756,8 +757,8 @@ class ServersController extends Controller
|
||||
return $return;
|
||||
}
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => ['string', 'max:255', 'nullable', 'not_regex:'.self::SERVER_NAME_CONTROL_CHARACTERS_PATTERN],
|
||||
'description' => 'string|nullable',
|
||||
'name' => ['not_regex:'.self::SERVER_NAME_CONTROL_CHARACTERS_PATTERN, ...ValidationPatterns::nameRules(required: false, nullable: false)],
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'ip' => ['string', 'nullable', new ValidServerIp],
|
||||
'port' => 'integer|nullable|between:1,65535',
|
||||
'private_key_uuid' => 'string|nullable',
|
||||
@@ -783,6 +784,7 @@ class ServersController extends Controller
|
||||
'backup_compression_cpu_percentage' => 'integer|in:25,50,75,100',
|
||||
], [
|
||||
...ValidationPatterns::serverUsernameMessages(),
|
||||
...ValidationPatterns::combinedMessages(),
|
||||
'name.not_regex' => 'The name must not contain control characters such as new lines.',
|
||||
]);
|
||||
|
||||
|
||||
@@ -416,8 +416,8 @@ class ServicesController extends Controller
|
||||
'environment_uuid' => 'string|nullable',
|
||||
'server_uuid' => 'string|required',
|
||||
'destination_uuid' => 'string|nullable',
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string|nullable',
|
||||
'name' => ValidationPatterns::nameRules(required: false, nullable: false),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'instant_deploy' => 'boolean',
|
||||
'connect_to_docker_network' => 'boolean',
|
||||
'urls' => 'array|nullable',
|
||||
@@ -431,6 +431,7 @@ class ServicesController extends Controller
|
||||
'tags.*' => 'string|min:2',
|
||||
];
|
||||
$validationMessages = [
|
||||
...ValidationPatterns::combinedMessages(),
|
||||
'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.',
|
||||
];
|
||||
$validator = Validator::make($request->all(), $validationRules, $validationMessages);
|
||||
@@ -640,8 +641,8 @@ class ServicesController extends Controller
|
||||
'environment_uuid' => 'string|nullable',
|
||||
'server_uuid' => 'string|required',
|
||||
'destination_uuid' => 'string',
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string|nullable',
|
||||
'name' => ValidationPatterns::nameRules(required: false, nullable: false),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'instant_deploy' => 'boolean',
|
||||
'connect_to_docker_network' => 'boolean',
|
||||
'docker_compose_raw' => 'string|required',
|
||||
@@ -656,6 +657,7 @@ class ServicesController extends Controller
|
||||
'tags.*' => 'string|min:2',
|
||||
];
|
||||
$validationMessages = [
|
||||
...ValidationPatterns::combinedMessages(),
|
||||
'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.',
|
||||
];
|
||||
$validator = Validator::make($request->all(), $validationRules, $validationMessages);
|
||||
@@ -1244,8 +1246,8 @@ class ServicesController extends Controller
|
||||
$allowedFields = ['name', 'description', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls', 'force_domain_override', 'is_container_label_escape_enabled', 'redact_all_env_values_in_logs', 'include_coolify_default_variables'];
|
||||
|
||||
$validationRules = [
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string|nullable',
|
||||
'name' => ValidationPatterns::nameRules(required: false, nullable: false),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'instant_deploy' => 'boolean',
|
||||
'connect_to_docker_network' => 'boolean',
|
||||
'docker_compose_raw' => 'string|nullable',
|
||||
@@ -1259,6 +1261,7 @@ class ServicesController extends Controller
|
||||
'include_coolify_default_variables' => 'boolean',
|
||||
];
|
||||
$validationMessages = [
|
||||
...ValidationPatterns::combinedMessages(),
|
||||
'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.',
|
||||
];
|
||||
$validator = Validator::make($request->all(), $validationRules, $validationMessages);
|
||||
@@ -3298,9 +3301,9 @@ class ServicesController extends Controller
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'destination_uuid' => 'required|string',
|
||||
'name' => 'string|max:255|nullable',
|
||||
'name' => ValidationPatterns::nameRules(required: false),
|
||||
'clone_volumes' => 'boolean',
|
||||
]);
|
||||
], ValidationPatterns::combinedMessages());
|
||||
$allowedFields = ['destination_uuid', 'name', 'clone_volumes'];
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
|
||||
@@ -488,14 +488,16 @@ class ValidationPatterns
|
||||
|
||||
/**
|
||||
* Get validation rules for name fields
|
||||
*
|
||||
* Set $nullable to false for optional fields that may be left out but not set to null.
|
||||
*/
|
||||
public static function nameRules(bool $required = true, int $minLength = 3, int $maxLength = 255): array
|
||||
public static function nameRules(bool $required = true, int $minLength = 3, int $maxLength = 255, bool $nullable = true): array
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if ($required) {
|
||||
$rules[] = 'required';
|
||||
} else {
|
||||
} elseif ($nullable) {
|
||||
$rules[] = 'nullable';
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
const UI_REJECTED_DESCRIPTION = '{[ > : < ]}';
|
||||
const UI_ACCEPTED_DESCRIPTION = 'Main API (prod) - v2.0, owner "ops" & admin@example.com! Ünïcode 日本';
|
||||
|
||||
beforeEach(function () {
|
||||
config([
|
||||
'cache.default' => 'array',
|
||||
'session.driver' => 'array',
|
||||
'app.maintenance.driver' => 'file',
|
||||
'constants.ssh.mux_enabled' => false,
|
||||
]);
|
||||
Queue::fake();
|
||||
Bus::fake();
|
||||
|
||||
InstanceSettings::forceCreate(['id' => 0, 'is_api_enabled' => true]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
$this->user = User::factory()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$token = $this->user->createToken('write-token', ['write']);
|
||||
$token->accessToken->forceFill(['team_id' => $this->team->id])->save();
|
||||
$this->headers = ['Authorization' => 'Bearer '.$token->plainTextToken];
|
||||
|
||||
$this->privateKey = PrivateKey::factory()->create(['team_id' => $this->team->id, 'description' => 'original']);
|
||||
$this->server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'private_key_id' => $this->privateKey->id,
|
||||
'description' => 'original',
|
||||
]);
|
||||
$this->destination = StandaloneDocker::where('server_id', $this->server->id)->first();
|
||||
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
||||
});
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: Application|Service|StandalonePostgresql|Server|PrivateKey}
|
||||
*/
|
||||
function describedResource(object $test, string $type): array
|
||||
{
|
||||
$destination = [
|
||||
'environment_id' => $test->environment->id,
|
||||
'destination_id' => $test->destination->id,
|
||||
'destination_type' => $test->destination->getMorphClass(),
|
||||
'description' => 'original',
|
||||
];
|
||||
|
||||
return match ($type) {
|
||||
'application' => ['applications', Application::factory()->create($destination)],
|
||||
'service' => ['services', Service::factory()->create([
|
||||
...$destination,
|
||||
'server_id' => $test->server->id,
|
||||
'docker_compose_raw' => "services:\n web:\n image: nginx:alpine\n",
|
||||
])],
|
||||
'database' => ['databases', StandalonePostgresql::create([
|
||||
...$destination,
|
||||
'name' => 'test-postgres',
|
||||
'image' => 'postgres:15-alpine',
|
||||
'postgres_user' => 'postgres',
|
||||
'postgres_password' => 'password',
|
||||
'postgres_db' => 'postgres',
|
||||
])],
|
||||
'server' => ['servers', $test->server],
|
||||
'private key' => ['security/keys', $test->privateKey],
|
||||
};
|
||||
}
|
||||
|
||||
$resourceTypes = ['application', 'service', 'database', 'server', 'private key'];
|
||||
|
||||
test('updating a resource rejects a description that the UI rejects', function (string $type) {
|
||||
[$path, $resource] = describedResource($this, $type);
|
||||
|
||||
$this->withHeaders($this->headers)
|
||||
->patchJson("/api/v1/{$path}/{$resource->uuid}", ['description' => UI_REJECTED_DESCRIPTION])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('description');
|
||||
|
||||
expect($resource->fresh()->description)->toBe('original');
|
||||
})->with($resourceTypes);
|
||||
|
||||
test('updating a resource accepts a description that the UI accepts', function (string $type) {
|
||||
[$path, $resource] = describedResource($this, $type);
|
||||
|
||||
$this->withHeaders($this->headers)
|
||||
->patchJson("/api/v1/{$path}/{$resource->uuid}", ['description' => UI_ACCEPTED_DESCRIPTION])
|
||||
->assertSuccessful();
|
||||
|
||||
expect($resource->fresh()->description)->toBe(UI_ACCEPTED_DESCRIPTION);
|
||||
})->with($resourceTypes);
|
||||
|
||||
test('updating a resource rejects a name that the UI rejects', function (string $type, mixed $name) {
|
||||
[$path, $resource] = describedResource($this, $type);
|
||||
$originalName = $resource->name;
|
||||
|
||||
$this->withHeaders($this->headers)
|
||||
->patchJson("/api/v1/{$path}/{$resource->uuid}", ['name' => $name])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('name');
|
||||
|
||||
expect($resource->fresh()->name)->toBe($originalName);
|
||||
})->with($resourceTypes)->with([
|
||||
'disallowed characters' => '{[ > < ]}',
|
||||
'too short' => 'ab',
|
||||
'null' => null,
|
||||
]);
|
||||
|
||||
test('updating a resource accepts a name that the UI accepts', function (string $type) {
|
||||
[$path, $resource] = describedResource($this, $type);
|
||||
$name = 'owner/repo:main (prod) #2 & co, ünï @ v1.0+';
|
||||
|
||||
$this->withHeaders($this->headers)
|
||||
->patchJson("/api/v1/{$path}/{$resource->uuid}", ['name' => $name])
|
||||
->assertSuccessful();
|
||||
|
||||
expect($resource->fresh()->name)->toBe($name);
|
||||
})->with($resourceTypes);
|
||||
|
||||
test('creating a private key rejects a description that the UI rejects', function () {
|
||||
$this->withHeaders($this->headers)
|
||||
->postJson('/api/v1/security/keys', [
|
||||
'private_key' => generateSSHKey('ed25519')['private'],
|
||||
'description' => UI_REJECTED_DESCRIPTION,
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('description');
|
||||
|
||||
expect(PrivateKey::where('description', UI_REJECTED_DESCRIPTION)->exists())->toBeFalse();
|
||||
});
|
||||
|
||||
test('creating a postgresql database rejects a description that the UI rejects', function () {
|
||||
$this->withHeaders($this->headers)
|
||||
->postJson('/api/v1/databases/postgresql', [
|
||||
'server_uuid' => $this->server->uuid,
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'environment_uuid' => $this->environment->uuid,
|
||||
'description' => UI_REJECTED_DESCRIPTION,
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('description');
|
||||
|
||||
expect(StandalonePostgresql::where('description', UI_REJECTED_DESCRIPTION)->exists())->toBeFalse();
|
||||
});
|
||||
Reference in New Issue
Block a user