Path: blob/1.0-develop/tests/Integration/Services/Servers/BuildModificationServiceTest.php
7460 views
<?php12namespace Pterodactyl\Tests\Integration\Services\Servers;34use Mockery\MockInterface;5use GuzzleHttp\Psr7\Request;6use GuzzleHttp\Psr7\Response;7use Pterodactyl\Models\Server;8use Pterodactyl\Models\Allocation;9use GuzzleHttp\Exception\RequestException;10use Pterodactyl\Exceptions\DisplayException;11use Pterodactyl\Tests\Integration\IntegrationTestCase;12use Pterodactyl\Repositories\Wings\DaemonServerRepository;13use Pterodactyl\Services\Servers\BuildModificationService;14use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;1516class BuildModificationServiceTest extends IntegrationTestCase17{18private MockInterface $daemonServerRepository;1920/**21* Setup tests.22*/23public function setUp(): void24{25parent::setUp();2627$this->daemonServerRepository = $this->mock(DaemonServerRepository::class);28}2930/**31* Test that allocations can be added and removed from a server. Only the allocations on the32* current node and belonging to this server should be modified.33*/34public function testAllocationsCanBeModifiedForTheServer()35{36$server = $this->createServerModel();37$server2 = $this->createServerModel();3839/** @var \Pterodactyl\Models\Allocation[] $allocations */40$allocations = Allocation::factory()->times(4)->create(['node_id' => $server->node_id, 'notes' => 'Random notes']);4142$initialAllocationId = $server->allocation_id;43$allocations[0]->update(['server_id' => $server->id, 'notes' => 'Test notes']);4445// Some additional test allocations for the other server, not the server we are attempting46// to modify.47$allocations[2]->update(['server_id' => $server2->id]);48$allocations[3]->update(['server_id' => $server2->id]);4950$this->daemonServerRepository->expects('setServer->sync')->andReturnUndefined();5152$response = $this->getService()->handle($server, [53// Attempt to add one new allocation, and an allocation assigned to another server. The54// other server allocation should be ignored, and only the allocation for this server should55// be used.56'add_allocations' => [$allocations[2]->id, $allocations[1]->id],57// Remove the default server allocation, ensuring that the new allocation passed through58// in the data becomes the default allocation.59'remove_allocations' => [$server->allocation_id, $allocations[0]->id, $allocations[3]->id],60]);6162$this->assertInstanceOf(Server::class, $response);6364// Only one allocation should exist for this server now.65$this->assertCount(1, $response->allocations);66$this->assertSame($allocations[1]->id, $response->allocation_id);67$this->assertNull($response->allocation->notes);6869// These two allocations should not have been touched.70$this->assertDatabaseHas('allocations', ['id' => $allocations[2]->id, 'server_id' => $server2->id]);71$this->assertDatabaseHas('allocations', ['id' => $allocations[3]->id, 'server_id' => $server2->id]);7273// Both of these allocations should have been removed from the server, and have had their74// notes properly reset.75$this->assertDatabaseHas('allocations', ['id' => $initialAllocationId, 'server_id' => null, 'notes' => null]);76$this->assertDatabaseHas('allocations', ['id' => $allocations[0]->id, 'server_id' => null, 'notes' => null]);77}7879/**80* Test that an exception is thrown if removing the default allocation without also assigning81* new allocations to the server.82*/83public function testExceptionIsThrownIfRemovingTheDefaultAllocation()84{85$server = $this->createServerModel();86/** @var \Pterodactyl\Models\Allocation[] $allocations */87$allocations = Allocation::factory()->times(4)->create(['node_id' => $server->node_id]);8889$allocations[0]->update(['server_id' => $server->id]);9091$this->expectException(DisplayException::class);92$this->expectExceptionMessage('You are attempting to delete the default allocation for this server but there is no fallback allocation to use.');9394$this->getService()->handle($server, [95'add_allocations' => [],96'remove_allocations' => [$server->allocation_id, $allocations[0]->id],97]);98}99100/**101* Test that the build data for the server is properly passed along to the Wings instance so that102* the server data is updated in realtime. This test also ensures that only certain fields get updated103* for the server, and not just any arbitrary field.104*/105public function testServerBuildDataIsProperlyUpdatedOnWings()106{107$server = $this->createServerModel();108109$this->daemonServerRepository->expects('setServer')->with(\Mockery::on(function (Server $s) use ($server) {110return $s->id === $server->id;111}))->andReturnSelf();112113$this->daemonServerRepository->expects('sync')->withNoArgs()->andReturnUndefined();114115$response = $this->getService()->handle($server, [116'oom_disabled' => false,117'memory' => 256,118'swap' => 128,119'io' => 600,120'cpu' => 150,121'threads' => '1,2',122'disk' => 1024,123'backup_limit' => null,124'database_limit' => 10,125'allocation_limit' => 20,126]);127128$this->assertFalse($response->oom_disabled);129$this->assertSame(256, $response->memory);130$this->assertSame(128, $response->swap);131$this->assertSame(600, $response->io);132$this->assertSame(150, $response->cpu);133$this->assertSame('1,2', $response->threads);134$this->assertSame(1024, $response->disk);135$this->assertSame(0, $response->backup_limit);136$this->assertSame(10, $response->database_limit);137$this->assertSame(20, $response->allocation_limit);138}139140/**141* Test that an exception when connecting to the Wings instance is properly ignored142* when making updates. This allows for a server to be modified even when the Wings143* node is offline.144*/145public function testConnectionExceptionIsIgnoredWhenUpdatingServerSettings()146{147$server = $this->createServerModel();148149$this->daemonServerRepository->expects('setServer->sync')->andThrows(150new DaemonConnectionException(151new RequestException('Bad request', new Request('GET', '/test'), new Response())152)153);154155$response = $this->getService()->handle($server, ['memory' => 256, 'disk' => 10240]);156157$this->assertInstanceOf(Server::class, $response);158$this->assertSame(256, $response->memory);159$this->assertSame(10240, $response->disk);160161$this->assertDatabaseHas('servers', ['id' => $response->id, 'memory' => 256, 'disk' => 10240]);162}163164/**165* Test that no exception is thrown if we are only removing an allocation.166*/167public function testNoExceptionIsThrownIfOnlyRemovingAllocation()168{169$server = $this->createServerModel();170/** @var Allocation $allocation */171$allocation = Allocation::factory()->create(['node_id' => $server->node_id, 'server_id' => $server->id]);172173$this->daemonServerRepository->expects('setServer->sync')->andReturnUndefined();174175$this->getService()->handle($server, [176'remove_allocations' => [$allocation->id],177]);178179$this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => null]);180}181182/**183* Test that allocations in both the add and remove arrays are only added, and not removed.184* This scenario wouldn't really happen in the UI, but it is possible to perform via the API,185* so we want to make sure that the logic being used doesn't break if the allocation exists186* in both arrays.187*188* We'll default to adding the allocation in this case.189*/190public function testAllocationInBothAddAndRemoveIsAdded()191{192$server = $this->createServerModel();193/** @var Allocation $allocation */194$allocation = Allocation::factory()->create(['node_id' => $server->node_id]);195196$this->daemonServerRepository->expects('setServer->sync')->andReturnUndefined();197198$this->getService()->handle($server, [199'add_allocations' => [$allocation->id],200'remove_allocations' => [$allocation->id],201]);202203$this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => $server->id]);204}205206/**207* Test that using the same allocation ID multiple times in the array does not cause an error.208*/209public function testUsingSameAllocationIdMultipleTimesDoesNotError()210{211$server = $this->createServerModel();212/** @var Allocation $allocation */213$allocation = Allocation::factory()->create(['node_id' => $server->node_id, 'server_id' => $server->id]);214/** @var Allocation $allocation2 */215$allocation2 = Allocation::factory()->create(['node_id' => $server->node_id]);216217$this->daemonServerRepository->expects('setServer->sync')->andReturnUndefined();218219$this->getService()->handle($server, [220'add_allocations' => [$allocation2->id, $allocation2->id],221'remove_allocations' => [$allocation->id, $allocation->id],222]);223224$this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => null]);225$this->assertDatabaseHas('allocations', ['id' => $allocation2->id, 'server_id' => $server->id]);226}227228/**229* Test that any changes we made to the server or allocations are rolled back if there is an230* exception while performing any action. This is different from the connection exception231* test which should properly ignore connection issues. We want any other type of exception232* to properly be thrown back to the caller.233*/234public function testThatUpdatesAreRolledBackIfExceptionIsEncountered()235{236$server = $this->createServerModel();237/** @var Allocation $allocation */238$allocation = Allocation::factory()->create(['node_id' => $server->node_id]);239240$this->daemonServerRepository->expects('setServer->sync')->andThrows(new DisplayException('Test'));241242$this->expectException(DisplayException::class);243244$this->getService()->handle($server, ['add_allocations' => [$allocation->id]]);245246$this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => null]);247}248249private function getService(): BuildModificationService250{251return $this->app->make(BuildModificationService::class);252}253}254255256