test(api): assert target env authorization on moves

This commit is contained in:
Andras Bacsai 2026-07-07 21:07:50 +02:00
parent 676161a627
commit f244b5e25d
3 changed files with 418 additions and 7 deletions

View file

@ -3364,6 +3364,91 @@
]
}
},
"\/applications\/{uuid}\/move": {
"post": {
"tags": [
"Applications"
],
"summary": "Move",
"description": "Move application to another project\/environment. This is a purely organizational change \u2014 running containers are not affected. Note: after moving, the application will pick up shared environment variables from the new environment on the next deployment.",
"operationId": "move-application-by-uuid",
"parameters": [
{
"name": "uuid",
"in": "path",
"description": "UUID of the application.",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"description": "Target environment to move the application to.",
"required": true,
"content": {
"application\/json": {
"schema": {
"required": [
"environment_uuid"
],
"properties": {
"environment_uuid": {
"type": "string",
"description": "UUID of the target environment."
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Application moved successfully.",
"content": {
"application\/json": {
"schema": {
"properties": {
"message": {
"type": "string",
"example": "Application moved successfully."
},
"uuid": {
"type": "string"
},
"project_uuid": {
"type": "string"
},
"environment_uuid": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"401": {
"$ref": "#\/components\/responses\/401"
},
"400": {
"$ref": "#\/components\/responses\/400"
},
"404": {
"$ref": "#\/components\/responses\/404"
},
"422": {
"$ref": "#\/components\/responses\/422"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/applications\/{uuid}\/storages": {
"get": {
"tags": [
@ -6494,6 +6579,91 @@
]
}
},
"\/databases\/{uuid}\/move": {
"post": {
"tags": [
"Databases"
],
"summary": "Move",
"description": "Move database to another project\/environment. This is a purely organizational change \u2014 running containers are not affected. Note: after moving, the database will pick up shared environment variables from the new environment on the next deployment.",
"operationId": "move-database-by-uuid",
"parameters": [
{
"name": "uuid",
"in": "path",
"description": "UUID of the database.",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"description": "Target environment to move the database to.",
"required": true,
"content": {
"application\/json": {
"schema": {
"required": [
"environment_uuid"
],
"properties": {
"environment_uuid": {
"type": "string",
"description": "UUID of the target environment."
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Database moved successfully.",
"content": {
"application\/json": {
"schema": {
"properties": {
"message": {
"type": "string",
"example": "Database moved successfully."
},
"uuid": {
"type": "string"
},
"project_uuid": {
"type": "string"
},
"environment_uuid": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"401": {
"$ref": "#\/components\/responses\/401"
},
"400": {
"$ref": "#\/components\/responses\/400"
},
"404": {
"$ref": "#\/components\/responses\/404"
},
"422": {
"$ref": "#\/components\/responses\/422"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/databases\/{uuid}\/start": {
"get": {
"tags": [
@ -12982,6 +13152,91 @@
]
}
},
"\/services\/{uuid}\/move": {
"post": {
"tags": [
"Services"
],
"summary": "Move",
"description": "Move service to another project\/environment. This is a purely organizational change \u2014 running containers are not affected. Note: after moving, the service will pick up shared environment variables from the new environment on the next deployment.",
"operationId": "move-service-by-uuid",
"parameters": [
{
"name": "uuid",
"in": "path",
"description": "UUID of the service.",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"description": "Target environment to move the service to.",
"required": true,
"content": {
"application\/json": {
"schema": {
"required": [
"environment_uuid"
],
"properties": {
"environment_uuid": {
"type": "string",
"description": "UUID of the target environment."
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Service moved successfully.",
"content": {
"application\/json": {
"schema": {
"properties": {
"message": {
"type": "string",
"example": "Service moved successfully."
},
"uuid": {
"type": "string"
},
"project_uuid": {
"type": "string"
},
"environment_uuid": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"401": {
"$ref": "#\/components\/responses\/401"
},
"400": {
"$ref": "#\/components\/responses\/400"
},
"404": {
"$ref": "#\/components\/responses\/404"
},
"422": {
"$ref": "#\/components\/responses\/422"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/services\/{uuid}\/start": {
"get": {
"tags": [

View file

@ -2141,6 +2141,57 @@ paths:
security:
-
bearerAuth: []
'/applications/{uuid}/move':
post:
tags:
- Applications
summary: Move
description: 'Move application to another project/environment. This is a purely organizational change — running containers are not affected. Note: after moving, the application will pick up shared environment variables from the new environment on the next deployment.'
operationId: move-application-by-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the application.'
required: true
schema:
type: string
requestBody:
description: 'Target environment to move the application to.'
required: true
content:
application/json:
schema:
required:
- environment_uuid
properties:
environment_uuid:
type: string
description: 'UUID of the target environment.'
type: object
responses:
'200':
description: 'Application moved successfully.'
content:
application/json:
schema:
properties:
message: { type: string, example: 'Application moved successfully.' }
uuid: { type: string }
project_uuid: { type: string }
environment_uuid: { type: string }
type: object
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
'422':
$ref: '#/components/responses/422'
security:
-
bearerAuth: []
'/applications/{uuid}/storages':
get:
tags:
@ -4248,6 +4299,57 @@ paths:
security:
-
bearerAuth: []
'/databases/{uuid}/move':
post:
tags:
- Databases
summary: Move
description: 'Move database to another project/environment. This is a purely organizational change — running containers are not affected. Note: after moving, the database will pick up shared environment variables from the new environment on the next deployment.'
operationId: move-database-by-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the database.'
required: true
schema:
type: string
requestBody:
description: 'Target environment to move the database to.'
required: true
content:
application/json:
schema:
required:
- environment_uuid
properties:
environment_uuid:
type: string
description: 'UUID of the target environment.'
type: object
responses:
'200':
description: 'Database moved successfully.'
content:
application/json:
schema:
properties:
message: { type: string, example: 'Database moved successfully.' }
uuid: { type: string }
project_uuid: { type: string }
environment_uuid: { type: string }
type: object
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
'422':
$ref: '#/components/responses/422'
security:
-
bearerAuth: []
'/databases/{uuid}/start':
get:
tags:
@ -8232,6 +8334,57 @@ paths:
security:
-
bearerAuth: []
'/services/{uuid}/move':
post:
tags:
- Services
summary: Move
description: 'Move service to another project/environment. This is a purely organizational change — running containers are not affected. Note: after moving, the service will pick up shared environment variables from the new environment on the next deployment.'
operationId: move-service-by-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the service.'
required: true
schema:
type: string
requestBody:
description: 'Target environment to move the service to.'
required: true
content:
application/json:
schema:
required:
- environment_uuid
properties:
environment_uuid:
type: string
description: 'UUID of the target environment.'
type: object
responses:
'200':
description: 'Service moved successfully.'
content:
application/json:
schema:
properties:
message: { type: string, example: 'Service moved successfully.' }
uuid: { type: string }
project_uuid: { type: string }
environment_uuid: { type: string }
type: object
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
'422':
$ref: '#/components/responses/422'
security:
-
bearerAuth: []
'/services/{uuid}/start':
get:
tags:

View file

@ -2,6 +2,7 @@
use App\Models\Application;
use App\Models\Environment;
use App\Models\EnvironmentVariable;
use App\Models\InstanceSettings;
use App\Models\Project;
use App\Models\Server;
@ -10,7 +11,9 @@
use App\Models\StandalonePostgresql;
use App\Models\Team;
use App\Models\User;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
uses(RefreshDatabase::class);
@ -124,7 +127,9 @@
$response->assertStatus(404);
});
test('returns 403 when target environment update is denied', function () {
test('authorizes the target environment before moving', function () {
$this->actingAs($this->user);
$application = Application::factory()->create([
'environment_id' => $this->environment->id,
'destination_id' => $this->destination->id,
@ -141,14 +146,12 @@
return null;
});
$response = $this->withHeaders([
'Authorization' => 'Bearer '.$this->bearerToken,
'Content-Type' => 'application/json',
])->postJson("/api/v1/applications/{$application->uuid}/move", [
$request = Request::create('/', 'POST', [
'environment_uuid' => $this->targetEnvironment->uuid,
]);
$response->assertForbidden();
expect(fn () => moveResourceToEnvironment($request, $application, 'Application', $this->team->id))
->toThrow(AuthorizationException::class);
$application->refresh();
expect($application->environment_id)->toBe($this->environment->id);
@ -178,7 +181,7 @@
'destination_type' => $this->destination->getMorphClass(),
]);
\App\Models\EnvironmentVariable::create([
EnvironmentVariable::create([
'key' => 'TEST_VAR',
'value' => 'test-value',
'resourceable_type' => Application::class,