Files
coolify/tests/Unit/PrivateKeyStorageTest.php

317 lines
11 KiB
PHP

<?php
use App\Models\PrivateKey;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class PrivateKeyStorageTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
// Set up a test team for the tests
$this->actingAs(\App\Models\User::factory()->create());
}
protected function getValidPrivateKey(): string
{
return '-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
-----END OPENSSH PRIVATE KEY-----';
}
/** @test */
public function it_successfully_stores_private_key_in_filesystem()
{
Storage::fake('ssh-keys');
$privateKey = PrivateKey::createAndStore([
'name' => 'Test Key',
'description' => 'Test Description',
'private_key' => $this->getValidPrivateKey(),
'team_id' => currentTeam()->id,
]);
$this->assertDatabaseHas('private_keys', [
'id' => $privateKey->id,
'name' => 'Test Key',
]);
$filename = "ssh_key@{$privateKey->uuid}";
Storage::disk('ssh-keys')->assertExists($filename);
$storedContent = Storage::disk('ssh-keys')->get($filename);
$this->assertEquals($privateKey->private_key, $storedContent);
}
/** @test */
public function it_throws_exception_when_storage_fails()
{
Storage::fake('ssh-keys');
// Mock Storage::put to return false (simulating storage failure)
Storage::shouldReceive('disk')
->with('ssh-keys')
->andReturn(
\Mockery::mock()
->shouldReceive('exists')
->andReturn(true)
->shouldReceive('put')
->with(\Mockery::any(), 'test')
->andReturn(true)
->shouldReceive('delete')
->with(\Mockery::any())
->andReturn(true)
->shouldReceive('put')
->with(\Mockery::pattern('/ssh_key@/'), \Mockery::any())
->andReturn(false) // Simulate storage failure
->getMock()
);
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Failed to write SSH key to filesystem');
PrivateKey::createAndStore([
'name' => 'Test Key',
'description' => 'Test Description',
'private_key' => $this->getValidPrivateKey(),
'team_id' => currentTeam()->id,
]);
// Assert that no database record was created due to transaction rollback
$this->assertDatabaseMissing('private_keys', [
'name' => 'Test Key',
]);
}
/** @test */
public function it_throws_exception_when_storage_directory_is_not_writable()
{
Storage::fake('ssh-keys');
// Mock Storage disk to simulate directory not writable
Storage::shouldReceive('disk')
->with('ssh-keys')
->andReturn(
\Mockery::mock()
->shouldReceive('exists')
->with('')
->andReturn(true)
->shouldReceive('put')
->with(\Mockery::pattern('/\.test_write_/'), 'test')
->andReturn(false) // Simulate directory not writable
->getMock()
);
$this->expectException(\Exception::class);
$this->expectExceptionMessage('SSH keys storage directory is not writable');
PrivateKey::createAndStore([
'name' => 'Test Key',
'description' => 'Test Description',
'private_key' => $this->getValidPrivateKey(),
'team_id' => currentTeam()->id,
]);
}
/** @test */
public function it_creates_storage_directory_if_not_exists()
{
Storage::fake('ssh-keys');
// Mock Storage disk to simulate directory not existing, then being created
Storage::shouldReceive('disk')
->with('ssh-keys')
->andReturn(
\Mockery::mock()
->shouldReceive('exists')
->with('')
->andReturn(false) // Directory doesn't exist
->shouldReceive('makeDirectory')
->with('')
->andReturn(true) // Successfully create directory
->shouldReceive('put')
->with(\Mockery::pattern('/\.test_write_/'), 'test')
->andReturn(true) // Directory is writable after creation
->shouldReceive('delete')
->with(\Mockery::pattern('/\.test_write_/'))
->andReturn(true)
->shouldReceive('put')
->with(\Mockery::pattern('/ssh_key@/'), \Mockery::any())
->andReturn(true)
->shouldReceive('exists')
->with(\Mockery::pattern('/ssh_key@/'))
->andReturn(true)
->shouldReceive('get')
->with(\Mockery::pattern('/ssh_key@/'))
->andReturn($this->getValidPrivateKey())
->getMock()
);
$privateKey = PrivateKey::createAndStore([
'name' => 'Test Key',
'description' => 'Test Description',
'private_key' => $this->getValidPrivateKey(),
'team_id' => currentTeam()->id,
]);
$this->assertDatabaseHas('private_keys', [
'id' => $privateKey->id,
'name' => 'Test Key',
]);
}
/** @test */
public function it_throws_exception_when_file_content_verification_fails()
{
Storage::fake('ssh-keys');
// Mock Storage disk to simulate file being created but with wrong content
Storage::shouldReceive('disk')
->with('ssh-keys')
->andReturn(
\Mockery::mock()
->shouldReceive('exists')
->with('')
->andReturn(true)
->shouldReceive('put')
->with(\Mockery::pattern('/\.test_write_/'), 'test')
->andReturn(true)
->shouldReceive('delete')
->with(\Mockery::pattern('/\.test_write_/'))
->andReturn(true)
->shouldReceive('put')
->with(\Mockery::pattern('/ssh_key@/'), \Mockery::any())
->andReturn(true) // File created successfully
->shouldReceive('exists')
->with(\Mockery::pattern('/ssh_key@/'))
->andReturn(true) // File exists
->shouldReceive('get')
->with(\Mockery::pattern('/ssh_key@/'))
->andReturn('corrupted content') // But content is wrong
->shouldReceive('delete')
->with(\Mockery::pattern('/ssh_key@/'))
->andReturn(true) // Clean up bad file
->getMock()
);
$this->expectException(\Exception::class);
$this->expectExceptionMessage('SSH key file content verification failed');
PrivateKey::createAndStore([
'name' => 'Test Key',
'description' => 'Test Description',
'private_key' => $this->getValidPrivateKey(),
'team_id' => currentTeam()->id,
]);
// Assert that no database record was created due to transaction rollback
$this->assertDatabaseMissing('private_keys', [
'name' => 'Test Key',
]);
}
/** @test */
public function it_successfully_deletes_private_key_from_filesystem()
{
Storage::fake('ssh-keys');
$privateKey = PrivateKey::createAndStore([
'name' => 'Test Key',
'description' => 'Test Description',
'private_key' => $this->getValidPrivateKey(),
'team_id' => currentTeam()->id,
]);
$filename = "ssh_key@{$privateKey->uuid}";
Storage::disk('ssh-keys')->assertExists($filename);
$privateKey->delete();
Storage::disk('ssh-keys')->assertMissing($filename);
}
/** @test */
public function it_handles_database_transaction_rollback_on_storage_failure()
{
Storage::fake('ssh-keys');
// Count initial private keys
$initialCount = PrivateKey::count();
// Mock storage failure after database save
Storage::shouldReceive('disk')
->with('ssh-keys')
->andReturn(
\Mockery::mock()
->shouldReceive('exists')
->with('')
->andReturn(true)
->shouldReceive('put')
->with(\Mockery::pattern('/\.test_write_/'), 'test')
->andReturn(true)
->shouldReceive('delete')
->with(\Mockery::pattern('/\.test_write_/'))
->andReturn(true)
->shouldReceive('put')
->with(\Mockery::pattern('/ssh_key@/'), \Mockery::any())
->andReturn(false) // Storage fails
->getMock()
);
try {
PrivateKey::createAndStore([
'name' => 'Test Key',
'description' => 'Test Description',
'private_key' => $this->getValidPrivateKey(),
'team_id' => currentTeam()->id,
]);
} catch (\Exception $e) {
// Expected exception
}
// Assert that database was rolled back
$this->assertEquals($initialCount, PrivateKey::count());
$this->assertDatabaseMissing('private_keys', [
'name' => 'Test Key',
]);
}
/** @test */
public function it_successfully_updates_private_key_with_transaction()
{
Storage::fake('ssh-keys');
$privateKey = PrivateKey::createAndStore([
'name' => 'Test Key',
'description' => 'Test Description',
'private_key' => $this->getValidPrivateKey(),
'team_id' => currentTeam()->id,
]);
$newPrivateKey = str_replace('Test', 'Updated', $this->getValidPrivateKey());
$privateKey->updatePrivateKey([
'name' => 'Updated Key',
'private_key' => $newPrivateKey,
]);
$this->assertDatabaseHas('private_keys', [
'id' => $privateKey->id,
'name' => 'Updated Key',
]);
$filename = "ssh_key@{$privateKey->uuid}";
$storedContent = Storage::disk('ssh-keys')->get($filename);
$this->assertEquals($newPrivateKey, $storedContent);
}
}