diff --git a/app/Models/Application.php b/app/Models/Application.php
index c284528f1..a68c1d54a 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -4,6 +4,7 @@ namespace App\Models;
use App\Enums\ApplicationDeploymentStatus;
use Illuminate\Database\Eloquent\Casts\Attribute;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Process\InvokedProcess;
@@ -104,7 +105,7 @@ use Visus\Cuid2\Cuid2;
class Application extends BaseModel
{
- use SoftDeletes;
+ use HasFactory, SoftDeletes;
private static $parserVersion = '4';
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 83b91b254..e0a66c58b 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -11,6 +11,7 @@ use App\Notifications\Server\Reachable;
use App\Notifications\Server\Unreachable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
@@ -48,7 +49,7 @@ use Symfony\Component\Yaml\Yaml;
class Server extends BaseModel
{
- use SchemalessAttributesTrait, SoftDeletes;
+ use HasFactory, SchemalessAttributesTrait, SoftDeletes;
public static $batch_counter = 0;
diff --git a/config/database.php b/config/database.php
index f48a68082..6f4acbfd2 100644
--- a/config/database.php
+++ b/config/database.php
@@ -49,6 +49,22 @@ return [
'search_path' => 'public',
'sslmode' => 'prefer',
],
+
+ 'testing' => [
+ 'driver' => 'pgsql',
+ 'url' => env('DATABASE_TEST_URL'),
+ 'host' => env('DB_TEST_HOST', 'postgres'),
+ 'port' => env('DB_TEST_PORT', '5432'),
+ 'database' => env('DB_TEST_DATABASE', 'coolify_test'),
+ 'username' => env('DB_TEST_USERNAME', 'coolify'),
+ 'password' => env('DB_TEST_PASSWORD', 'password'),
+ 'charset' => 'utf8',
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ 'search_path' => 'public',
+ 'sslmode' => 'prefer',
+ ],
+
],
/*
diff --git a/database/factories/ApplicationFactory.php b/database/factories/ApplicationFactory.php
new file mode 100644
index 000000000..ded507c56
--- /dev/null
+++ b/database/factories/ApplicationFactory.php
@@ -0,0 +1,22 @@
+ fake()->unique()->name(),
+ 'destination_id' => 1,
+ 'git_repository' => fake()->url(),
+ 'git_branch' => fake()->word(),
+ 'build_pack' => 'nixpacks',
+ 'ports_exposes' => '3000',
+ 'environment_id' => 1,
+ 'destination_id' => 1,
+ ];
+ }
+}
diff --git a/database/factories/ServerFactory.php b/database/factories/ServerFactory.php
new file mode 100644
index 000000000..29546bf56
--- /dev/null
+++ b/database/factories/ServerFactory.php
@@ -0,0 +1,17 @@
+ fake()->unique()->name(),
+ 'ip' => fake()->unique()->ipv4(),
+ 'private_key_id' => 1,
+ ];
+ }
+}
diff --git a/phpunit.xml b/phpunit.xml
index 45cb69439..f1c2be92d 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -13,8 +13,8 @@
-
-
+
+
diff --git a/tests/Feature/ExecuteContainerCommandTest.php b/tests/Feature/ExecuteContainerCommandTest.php
new file mode 100644
index 000000000..6d485fe65
--- /dev/null
+++ b/tests/Feature/ExecuteContainerCommandTest.php
@@ -0,0 +1,57 @@
+shouldSetUpDatabase()) {
+ $this->setUpTestDatabase();
+ }
+ // Create test data
+ $this->user = User::factory()->create();
+ $this->team = $this->user->teams()->first();
+ $this->server = Server::factory()->create(['team_id' => $this->team->id]);
+ $this->application = Application::factory()->create();
+
+ // Login the user
+ $this->actingAs($this->user);
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->shouldSetUpDatabase()) {
+ $this->tearDownTestDatabase();
+ }
+ parent::tearDown();
+ }
+
+ private function shouldSetUpDatabase(): bool
+ {
+ return in_array($this->name(), [
+ 'it_allows_valid_container_access',
+ 'it_prevents_cross_server_container_access',
+ ]);
+ }
+}
diff --git a/tests/Traits/HandlesTestDatabase.php b/tests/Traits/HandlesTestDatabase.php
new file mode 100644
index 000000000..adb577e7c
--- /dev/null
+++ b/tests/Traits/HandlesTestDatabase.php
@@ -0,0 +1,78 @@
+createTestDatabase($database);
+
+ // Run migrations
+ Artisan::call('migrate:fresh', [
+ '--database' => 'testing',
+ '--seed' => false,
+ ]);
+ } catch (\Exception $e) {
+ $this->tearDownTestDatabase();
+ throw $e;
+ }
+ }
+
+ protected function tearDownTestDatabase(): void
+ {
+ try {
+ // Drop test database
+ $database = config('database.connections.testing.database');
+ $this->dropTestDatabase($database);
+ } catch (\Exception $e) {
+ // Log error but don't throw
+ error_log('Failed to tear down test database: '.$e->getMessage());
+ }
+ }
+
+ protected function createTestDatabase($database)
+ {
+ try {
+ // Connect to postgres database to create/drop test database
+ config(['database.connections.pgsql.database' => 'postgres']);
+ DB::purge('pgsql');
+ DB::reconnect('pgsql');
+
+ // Drop if exists and create new database
+ DB::connection('pgsql')->statement("DROP DATABASE IF EXISTS $database WITH (FORCE);");
+ DB::connection('pgsql')->statement("CREATE DATABASE $database;");
+
+ // Switch back to testing connection
+ DB::disconnect('pgsql');
+ DB::reconnect('testing');
+ } catch (\Exception $e) {
+ $this->tearDownTestDatabase();
+ throw new \Exception('Could not create test database: '.$e->getMessage());
+ }
+ }
+
+ protected function dropTestDatabase($database)
+ {
+ try {
+ // Connect to postgres database to drop test database
+ config(['database.connections.pgsql.database' => 'postgres']);
+ DB::purge('pgsql');
+ DB::reconnect('pgsql');
+
+ // Drop the test database
+ DB::connection('pgsql')->statement("DROP DATABASE IF EXISTS $database WITH (FORCE);");
+
+ DB::disconnect('pgsql');
+ } catch (\Exception $e) {
+ // Log error but don't throw
+ error_log('Failed to drop test database: '.$e->getMessage());
+ }
+ }
+}