Skip to content

Commit e2aa01c

Browse files
committed
First go at integration tests
1 parent 89db939 commit e2aa01c

File tree

16 files changed

+610
-28
lines changed

16 files changed

+610
-28
lines changed

.env.travis

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ APP_THEME=pterodactyl
55
APP_TIMEZONE=UTC
66
APP_URL=http://localhost/
77

8-
DB_HOST=127.0.0.1
9-
DB_DATABASE=travis
10-
DB_USERNAME=root
11-
DB_PASSWORD=""
8+
TESTING_DB_HOST=127.0.0.1
9+
TESTING_DB_DATABASE=travis
10+
TESTING_DB_USERNAME=root
11+
TESTING_DB_PASSWORD=""
1212

1313
CACHE_DRIVER=array
1414
SESSION_DRIVER=array

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ before_script:
1414
- echo 'opcache.enable_cli=1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
1515
- cp .env.travis .env
1616
- travis_retry composer install --no-interaction --prefer-dist --no-suggest
17-
- php artisan migrate --seed
1817
script:
19-
- vendor/bin/phpunit --coverage-clover coverage.xml
18+
- vendor/bin/phpunit --bootstrap vendor/autoload.php --coverage-clover coverage.xml tests/Unit
19+
- vendor/bin/phpunit tests/Integration
2020
notifications:
2121
email: false
2222
webhooks:

app/Transformers/Api/Application/BaseTransformer.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
use Cake\Chronos\Chronos;
66
use Pterodactyl\Models\ApiKey;
77
use Illuminate\Container\Container;
8+
use Illuminate\Database\Eloquent\Model;
89
use League\Fractal\TransformerAbstract;
910
use Pterodactyl\Services\Acl\Api\AdminAcl;
11+
use Pterodactyl\Transformers\Api\Client\BaseClientTransformer;
1012
use Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException;
1113

14+
/**
15+
* @method array transform(Model $model)
16+
*/
1217
abstract class BaseTransformer extends TransformerAbstract
1318
{
1419
const RESPONSE_TIMEZONE = 'UTC';
@@ -88,7 +93,7 @@ protected function makeTransformer(string $abstract, array $parameters = [])
8893
$transformer = Container::getInstance()->makeWith($abstract, $parameters);
8994
$transformer->setKey($this->getKey());
9095

91-
if (! $transformer instanceof self) {
96+
if (! $transformer instanceof self || $transformer instanceof BaseClientTransformer) {
9297
throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__);
9398
}
9499

app/Transformers/Api/Application/LocationTransformer.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,22 @@ public function getResourceName(): string
3232
*/
3333
public function transform(Location $location): array
3434
{
35-
return $location->toArray();
35+
return [
36+
'id' => $location->id,
37+
'short' => $location->short,
38+
'long' => $location->long,
39+
$location->getUpdatedAtColumn() => $this->formatTimestamp($location->updated_at),
40+
$location->getCreatedAtColumn() => $this->formatTimestamp($location->created_at),
41+
];
3642
}
3743

3844
/**
3945
* Return the nodes associated with this location.
4046
*
4147
* @param \Pterodactyl\Models\Location $location
4248
* @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource
49+
*
50+
* @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException
4351
*/
4452
public function includeServers(Location $location)
4553
{
@@ -57,6 +65,8 @@ public function includeServers(Location $location)
5765
*
5866
* @param \Pterodactyl\Models\Location $location
5967
* @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource
68+
*
69+
* @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException
6070
*/
6171
public function includeNodes(Location $location)
6272
{

bootstrap/tests.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
use Illuminate\Contracts\Console\Kernel;
4+
use Symfony\Component\Console\Output\ConsoleOutput;
5+
6+
require __DIR__ . '/autoload.php';
7+
8+
$app = require __DIR__ . '/app.php';
9+
10+
/** @var \Pterodactyl\Console\Kernel $kernel */
11+
$kernel = $app->make(Kernel::class);
12+
13+
/**
14+
* Bootstrap the kernel and prepare application for testing.
15+
*/
16+
$kernel->bootstrap();
17+
18+
$output = new ConsoleOutput;
19+
20+
/**
21+
* Perform database migrations and reseeding before continuing with
22+
* running the tests.
23+
*/
24+
$output->writeln(PHP_EOL . '<comment>Refreshing database for Integration tests...</comment>');
25+
$kernel->call('migrate:fresh', ['--database' => 'testing']);
26+
27+
$output->writeln('<comment>Seeding database for Integration tests...</comment>' . PHP_EOL);
28+
$kernel->call('db:seed', ['--database' => 'testing']);

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
},
6666
"autoload-dev": {
6767
"psr-4": {
68+
"Pterodactyl\\Tests\\Integration\\": "tests/Integration",
6869
"Tests\\": "tests/"
6970
}
7071
},

config/database.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,28 @@
4343
'prefix' => env('DB_PREFIX', ''),
4444
'strict' => env('DB_STRICT_MODE', false),
4545
],
46+
47+
/*
48+
| -------------------------------------------------------------------------
49+
| Test Database Connection
50+
| -------------------------------------------------------------------------
51+
|
52+
| This connection is used by the integration and HTTP tests for Pterodactyl
53+
| development. Normal users of the Panel do not need to adjust any settings
54+
| in here.
55+
*/
56+
'testing' => [
57+
'driver' => 'mysql',
58+
'host' => env('TESTING_DB_HOST', '127.0.0.1'),
59+
'port' => env('TESTING_DB_PORT', '3306'),
60+
'database' => env('TESTING_DB_DATABASE', 'panel_test'),
61+
'username' => env('TESTING_DB_USERNAME', 'pterodactyl_test'),
62+
'password' => env('TESTING_DB_PASSWORD', ''),
63+
'charset' => 'utf8mb4',
64+
'collation' => 'utf8mb4_unicode_ci',
65+
'prefix' => '',
66+
'strict' => false,
67+
],
4668
],
4769

4870
/*

database/factories/ModelFactory.php

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Faker\Generator as Faker;
4+
use Pterodactyl\Models\ApiKey;
45

56
/*
67
|--------------------------------------------------------------------------
@@ -34,6 +35,8 @@
3435
'egg_id' => $faker->randomNumber(),
3536
'pack_id' => null,
3637
'installed' => 1,
38+
'database_limit' => null,
39+
'allocation_limit' => null,
3740
'created_at' => \Carbon\Carbon::now(),
3841
'updated_at' => \Carbon\Carbon::now(),
3942
];
@@ -74,7 +77,6 @@
7477
$factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) {
7578
return [
7679
'id' => $faker->unique()->randomNumber(),
77-
'uuid' => $faker->unique()->uuid,
7880
'public' => true,
7981
'name' => $faker->firstName,
8082
'fqdn' => $faker->ipv4,
@@ -224,21 +226,17 @@
224226
});
225227

226228
$factory->define(Pterodactyl\Models\ApiKey::class, function (Faker $faker) {
229+
static $token;
230+
227231
return [
228232
'id' => $faker->unique()->randomNumber(),
229233
'user_id' => $faker->randomNumber(),
234+
'key_type' => ApiKey::TYPE_APPLICATION,
230235
'identifier' => str_random(Pterodactyl\Models\ApiKey::IDENTIFIER_LENGTH),
231-
'token' => 'encrypted_string',
236+
'token' => $token ?: $token = encrypt(str_random(Pterodactyl\Models\ApiKey::KEY_LENGTH)),
237+
'allowed_ips' => null,
232238
'memo' => 'Test Function Key',
233239
'created_at' => \Carbon\Carbon::now()->toDateTimeString(),
234240
'updated_at' => \Carbon\Carbon::now()->toDateTimeString(),
235241
];
236242
});
237-
238-
$factory->define(Pterodactyl\Models\APIPermission::class, function (Faker $faker) {
239-
return [
240-
'id' => $faker->unique()->randomNumber(),
241-
'key_id' => $faker->randomNumber(),
242-
'permission' => mb_strtolower($faker->word),
243-
];
244-
});

phpunit.xml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phpunit backupGlobals="false"
33
backupStaticAttributes="false"
4-
bootstrap="vendor/autoload.php"
4+
bootstrap="bootstrap/tests.php"
55
colors="true"
66
convertErrorsToExceptions="true"
77
convertNoticesToExceptions="true"
@@ -24,10 +24,7 @@
2424
</filter>
2525
<php>
2626
<env name="APP_ENV" value="testing"/>
27-
<env name="DB_CONNECTION" value="mysql"/>
28-
<env name="DB_USERNAME" value="root"/>
29-
<env name="DB_PASSWORD" value=""/>
30-
<env name="DB_DATABASE" value="travis"/>
27+
<env name="DB_CONNECTION" value="testing"/>
3128
<env name="CACHE_DRIVER" value="array"/>
3229
<env name="SESSION_DRIVER" value="array"/>
3330
<env name="QUEUE_DRIVER" value="sync"/>
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
namespace Pterodactyl\Tests\Integration\Api\Application;
4+
5+
use Pterodactyl\Models\User;
6+
use PHPUnit\Framework\Assert;
7+
use Pterodactyl\Models\ApiKey;
8+
use Pterodactyl\Services\Acl\Api\AdminAcl;
9+
use Tests\Traits\Integration\CreatesTestModels;
10+
use Tests\Traits\IntegrationJsonRequestAssertions;
11+
use Pterodactyl\Tests\Integration\IntegrationTestCase;
12+
use Illuminate\Foundation\Testing\DatabaseTransactions;
13+
use Pterodactyl\Transformers\Api\Application\BaseTransformer;
14+
use Pterodactyl\Transformers\Api\Client\BaseClientTransformer;
15+
16+
abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase
17+
{
18+
use CreatesTestModels, DatabaseTransactions, IntegrationJsonRequestAssertions;
19+
20+
/**
21+
* @var \Pterodactyl\Models\ApiKey
22+
*/
23+
private $key;
24+
25+
/**
26+
* @var \Pterodactyl\Models\User
27+
*/
28+
private $user;
29+
30+
/**
31+
* Bootstrap application API tests. Creates a default admin user and associated API key
32+
* and also sets some default headers required for accessing the API.
33+
*/
34+
public function setUp()
35+
{
36+
parent::setUp();
37+
38+
$this->user = $this->createApiUser();
39+
$this->key = $this->createApiKey($this->user);
40+
41+
$this->withHeader('Accept', 'application/vnd.pterodactyl.v1+json');
42+
$this->withHeader('Authorization', 'Bearer ' . $this->getApiKey()->identifier . decrypt($this->getApiKey()->token));
43+
44+
$this->withMiddleware('api..key:' . ApiKey::TYPE_APPLICATION);
45+
}
46+
47+
/**
48+
* @return \Pterodactyl\Models\User
49+
*/
50+
public function getApiUser(): User
51+
{
52+
return $this->user;
53+
}
54+
55+
/**
56+
* @return \Pterodactyl\Models\ApiKey
57+
*/
58+
public function getApiKey(): ApiKey
59+
{
60+
return $this->key;
61+
}
62+
63+
/**
64+
* Creates a new default API key and refreshes the headers using it.
65+
*
66+
* @param \Pterodactyl\Models\User $user
67+
* @param array $permissions
68+
* @return \Pterodactyl\Models\ApiKey
69+
*/
70+
protected function createNewDefaultApiKey(User $user, array $permissions = []): ApiKey
71+
{
72+
$this->key = $this->createApiKey($user, $permissions);
73+
$this->refreshHeaders($this->key);
74+
75+
return $this->key;
76+
}
77+
78+
/**
79+
* Refresh the authorization header for a request to use a different API key.
80+
*
81+
* @param \Pterodactyl\Models\ApiKey $key
82+
*/
83+
protected function refreshHeaders(ApiKey $key)
84+
{
85+
$this->withHeader('Authorization', 'Bearer ' . $key->identifier . decrypt($key->token));
86+
}
87+
88+
/**
89+
* Create an administrative user.
90+
*
91+
* @return \Pterodactyl\Models\User
92+
*/
93+
protected function createApiUser(): User
94+
{
95+
return factory(User::class)->create([
96+
'root_admin' => true,
97+
]);
98+
}
99+
100+
/**
101+
* Create a new application API key for a given user model.
102+
*
103+
* @param \Pterodactyl\Models\User $user
104+
* @param array $permissions
105+
* @return \Pterodactyl\Models\ApiKey
106+
*/
107+
protected function createApiKey(User $user, array $permissions = []): ApiKey
108+
{
109+
return factory(ApiKey::class)->create(array_merge([
110+
'user_id' => $user->id,
111+
'key_type' => ApiKey::TYPE_APPLICATION,
112+
'r_servers' => AdminAcl::READ | AdminAcl::WRITE,
113+
'r_nodes' => AdminAcl::READ | AdminAcl::WRITE,
114+
'r_allocations' => AdminAcl::READ | AdminAcl::WRITE,
115+
'r_users' => AdminAcl::READ | AdminAcl::WRITE,
116+
'r_locations' => AdminAcl::READ | AdminAcl::WRITE,
117+
'r_nests' => AdminAcl::READ | AdminAcl::WRITE,
118+
'r_eggs' => AdminAcl::READ | AdminAcl::WRITE,
119+
'r_database_hosts' => AdminAcl::READ | AdminAcl::WRITE,
120+
'r_server_databases' => AdminAcl::READ | AdminAcl::WRITE,
121+
'r_packs' => AdminAcl::READ | AdminAcl::WRITE,
122+
], $permissions));
123+
}
124+
125+
/**
126+
* Return a transformer that can be used for testing purposes.
127+
*
128+
* @param string $abstract
129+
* @return \Pterodactyl\Transformers\Api\Application\BaseTransformer
130+
*/
131+
protected function getTransformer(string $abstract): BaseTransformer
132+
{
133+
/** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */
134+
$transformer = $this->app->make($abstract);
135+
$transformer->setKey($this->getApiKey());
136+
137+
Assert::assertInstanceOf(BaseTransformer::class, $transformer);
138+
Assert::assertNotInstanceOf(BaseClientTransformer::class, $transformer);
139+
140+
return $transformer;
141+
}
142+
}

0 commit comments

Comments
 (0)