feat: connect via rcon
This commit is contained in:
@@ -63,3 +63,9 @@ AWS_BUCKET=
|
|||||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
VITE_APP_NAME="${APP_NAME}"
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
MC_RCON_HOST=minecraft
|
||||||
|
MC_RCON_PORT=25575
|
||||||
|
MC_RCON_PASSWORD=
|
||||||
|
MC_RCON_ACCESS_COMMAND="whitelist add {username}"
|
||||||
|
MC_RCON_TIMEOUT=5
|
||||||
|
|||||||
@@ -2,12 +2,20 @@
|
|||||||
|
|
||||||
namespace App\Filament\Resources\AccessRequests\Tables;
|
namespace App\Filament\Resources\AccessRequests\Tables;
|
||||||
|
|
||||||
|
use App\Enums\AccessRequestStatus;
|
||||||
|
use App\Models\AccessRequests;
|
||||||
|
use App\Services\AccessRequestApprover;
|
||||||
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Actions\BulkActionGroup;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Actions\ViewAction;
|
use Filament\Actions\ViewAction;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
|
use Filament\Support\Icons\Heroicon;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class AccessRequestsTable
|
class AccessRequestsTable
|
||||||
{
|
{
|
||||||
@@ -45,6 +53,33 @@ class AccessRequestsTable
|
|||||||
//
|
//
|
||||||
])
|
])
|
||||||
->recordActions([
|
->recordActions([
|
||||||
|
Action::make('accept')
|
||||||
|
->label('Accepter')
|
||||||
|
->icon(Heroicon::CheckCircle)
|
||||||
|
->color('success')
|
||||||
|
->requiresConfirmation()
|
||||||
|
->modalHeading('Accepter la demande')
|
||||||
|
->modalDescription(fn (AccessRequests $record): string => "Le joueur {$record->game_username} sera ajoute via RCON.")
|
||||||
|
->visible(fn (AccessRequests $record): bool => $record->status === AccessRequestStatus::PENDING)
|
||||||
|
->action(function (AccessRequests $record, AccessRequestApprover $approver): void {
|
||||||
|
try {
|
||||||
|
$approver->approve($record, Auth::user());
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->success()
|
||||||
|
->title('Demande acceptee')
|
||||||
|
->send();
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
report($exception);
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->danger()
|
||||||
|
->title('Acceptation impossible')
|
||||||
|
->body($exception->getMessage())
|
||||||
|
->persistent()
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
}),
|
||||||
ViewAction::make(),
|
ViewAction::make(),
|
||||||
EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -2,37 +2,27 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Enums\AccessRequestStatus;
|
||||||
use App\Http\Requests\AccessRequestsRequest;
|
use App\Http\Requests\AccessRequestsRequest;
|
||||||
use App\Models\AccessRequests;
|
use App\Models\AccessRequests;
|
||||||
|
use App\Models\GameServers;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
class AccessRequestsController extends Controller
|
class AccessRequestsController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
public function store(AccessRequestsRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
return AccessRequests::all();
|
$validated = $request->validated();
|
||||||
}
|
$serverId = $validated['server_id']
|
||||||
|
?? GameServers::where('slug', $validated['server_slug'])->valueOrFail('id');
|
||||||
|
|
||||||
public function store(AccessRequestsRequest $request)
|
$accessRequest = AccessRequests::create([
|
||||||
{
|
'game_username' => $validated['game_username'],
|
||||||
return AccessRequests::create($request->validated());
|
'message' => $validated['message'] ?? '',
|
||||||
}
|
'server_id' => $serverId,
|
||||||
|
'status' => AccessRequestStatus::PENDING,
|
||||||
|
]);
|
||||||
|
|
||||||
public function show(AccessRequests $accessRequests)
|
return response()->json($accessRequest, 201);
|
||||||
{
|
|
||||||
return $accessRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(AccessRequestsRequest $request, AccessRequests $accessRequests)
|
|
||||||
{
|
|
||||||
$accessRequests->update($request->validated());
|
|
||||||
|
|
||||||
return $accessRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function destroy(AccessRequests $accessRequests)
|
|
||||||
{
|
|
||||||
$accessRequests->delete();
|
|
||||||
|
|
||||||
return response()->json();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ class AccessRequestsRequest extends FormRequest
|
|||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'status' => ['required'],
|
'game_username' => ['required', 'string', 'regex:/^[A-Za-z0-9_]{3,16}$/'],
|
||||||
'message' => ['required'],
|
'message' => ['nullable', 'string', 'max:255'],
|
||||||
'reviewed_by' => ['nullable', 'exists:users'],
|
'server_id' => ['nullable', 'required_without:server_slug', 'ulid', 'exists:game_servers,id'],
|
||||||
'server_id' => ['required', 'exists:game_servers'],
|
'server_slug' => ['nullable', 'required_without:server_id', 'string', 'exists:game_servers,slug'],
|
||||||
'reviewed_at' => ['nullable', 'date'],
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class AccessRequests extends Model
|
|||||||
'game_username',
|
'game_username',
|
||||||
'message',
|
'message',
|
||||||
'server_id',
|
'server_id',
|
||||||
|
'reviewed_by',
|
||||||
|
'reviewed_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function reviewedBy(): BelongsTo
|
public function reviewedBy(): BelongsTo
|
||||||
@@ -36,7 +38,7 @@ class AccessRequests extends Model
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'id' => 'string',
|
'id' => 'string',
|
||||||
'reviewed_at' => 'timestamp',
|
'reviewed_at' => 'datetime',
|
||||||
'status' => AccessRequestStatus::class,
|
'status' => AccessRequestStatus::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Enums\AccessRequestStatus;
|
||||||
|
use App\Models\AccessRequests;
|
||||||
|
use App\Models\User;
|
||||||
|
use DomainException;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class AccessRequestApprover
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly RconAccessGrantor $rconAccessGrantor,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function approve(AccessRequests $accessRequest, ?User $reviewer = null): string
|
||||||
|
{
|
||||||
|
$accessRequest->refresh();
|
||||||
|
|
||||||
|
if ($accessRequest->status !== AccessRequestStatus::PENDING) {
|
||||||
|
throw new DomainException('Only pending access requests can be accepted.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = $this->rconAccessGrantor->grant($accessRequest);
|
||||||
|
|
||||||
|
DB::transaction(function () use ($accessRequest, $reviewer): void {
|
||||||
|
$accessRequest->forceFill([
|
||||||
|
'status' => AccessRequestStatus::ACCEPTED,
|
||||||
|
'reviewed_by' => $reviewer?->getKey(),
|
||||||
|
'reviewed_at' => now(),
|
||||||
|
])->save();
|
||||||
|
});
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class MinecraftRconClient
|
||||||
|
{
|
||||||
|
private const AUTH_PACKET_TYPE = 3;
|
||||||
|
|
||||||
|
private const COMMAND_PACKET_TYPE = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var resource|null
|
||||||
|
*/
|
||||||
|
private $socket = null;
|
||||||
|
|
||||||
|
private int $requestId = 1;
|
||||||
|
|
||||||
|
public function command(string $command): string
|
||||||
|
{
|
||||||
|
$this->connect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->authenticate();
|
||||||
|
|
||||||
|
$requestId = $this->nextRequestId();
|
||||||
|
$this->writePacket($requestId, self::COMMAND_PACKET_TYPE, $command);
|
||||||
|
|
||||||
|
$response = $this->readPacket();
|
||||||
|
|
||||||
|
if ($response['id'] !== $requestId) {
|
||||||
|
throw new RuntimeException('Unexpected RCON response id.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response['body'];
|
||||||
|
} finally {
|
||||||
|
$this->disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function connect(): void
|
||||||
|
{
|
||||||
|
$host = config('rcon.host');
|
||||||
|
$port = (int) config('rcon.port');
|
||||||
|
$timeout = (float) config('rcon.timeout');
|
||||||
|
|
||||||
|
if (blank($host)) {
|
||||||
|
throw new RuntimeException('MC_RCON_HOST is not configured.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$socket = @stream_socket_client(
|
||||||
|
"tcp://{$host}:{$port}",
|
||||||
|
$errorCode,
|
||||||
|
$errorMessage,
|
||||||
|
$timeout,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($socket === false) {
|
||||||
|
throw new RuntimeException("Unable to connect to Minecraft RCON: {$errorMessage} ({$errorCode}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->socket = $socket;
|
||||||
|
$seconds = (int) floor($timeout);
|
||||||
|
$microseconds = (int) (($timeout - $seconds) * 1_000_000);
|
||||||
|
|
||||||
|
stream_set_timeout($this->socket, $seconds, $microseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function authenticate(): void
|
||||||
|
{
|
||||||
|
$password = config('rcon.password');
|
||||||
|
|
||||||
|
if (blank($password)) {
|
||||||
|
throw new RuntimeException('MC_RCON_PASSWORD is not configured.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$requestId = $this->nextRequestId();
|
||||||
|
$this->writePacket($requestId, self::AUTH_PACKET_TYPE, (string) $password);
|
||||||
|
|
||||||
|
do {
|
||||||
|
$response = $this->readPacket();
|
||||||
|
} while ($response['type'] !== self::COMMAND_PACKET_TYPE);
|
||||||
|
|
||||||
|
if ($response['id'] === -1 || $response['id'] !== $requestId) {
|
||||||
|
throw new RuntimeException('Minecraft RCON authentication failed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function disconnect(): void
|
||||||
|
{
|
||||||
|
if (is_resource($this->socket)) {
|
||||||
|
fclose($this->socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->socket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function nextRequestId(): int
|
||||||
|
{
|
||||||
|
return $this->requestId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function writePacket(int $requestId, int $type, string $body): void
|
||||||
|
{
|
||||||
|
$payload = pack('VV', $requestId, $type).$body."\x00\x00";
|
||||||
|
$packet = pack('V', strlen($payload)).$payload;
|
||||||
|
$bytesWritten = 0;
|
||||||
|
|
||||||
|
while ($bytesWritten < strlen($packet)) {
|
||||||
|
$written = fwrite($this->socket, substr($packet, $bytesWritten));
|
||||||
|
|
||||||
|
if ($written === false || $written === 0) {
|
||||||
|
throw new RuntimeException('Unable to write RCON packet.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$bytesWritten += $written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{id: int, type: int, body: string}
|
||||||
|
*/
|
||||||
|
private function readPacket(): array
|
||||||
|
{
|
||||||
|
$sizeData = $this->readBytes(4);
|
||||||
|
$size = unpack('V', $sizeData)[1];
|
||||||
|
|
||||||
|
if ($size < 10) {
|
||||||
|
throw new RuntimeException('Invalid RCON packet size.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$packet = $this->readBytes($size);
|
||||||
|
$header = unpack('Vid/Vtype', substr($packet, 0, 8));
|
||||||
|
$id = $header['id'] >= 0x80000000
|
||||||
|
? $header['id'] - 0x100000000
|
||||||
|
: $header['id'];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $id,
|
||||||
|
'type' => $header['type'],
|
||||||
|
'body' => substr($packet, 8, -2),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readBytes(int $length): string
|
||||||
|
{
|
||||||
|
$data = '';
|
||||||
|
|
||||||
|
while (strlen($data) < $length) {
|
||||||
|
$chunk = fread($this->socket, $length - strlen($data));
|
||||||
|
|
||||||
|
if ($chunk === false || $chunk === '') {
|
||||||
|
throw new RuntimeException('Unable to read RCON packet.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data .= $chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\AccessRequests;
|
||||||
|
|
||||||
|
class RconAccessGrantor
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly MinecraftRconClient $rconClient,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function grant(AccessRequests $accessRequest): string
|
||||||
|
{
|
||||||
|
return $this->rconClient->command($this->commandFor($accessRequest->game_username));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function commandFor(string $gameUsername): string
|
||||||
|
{
|
||||||
|
$accessCommand = (string) config('rcon.access_command', 'whitelist add {username}');
|
||||||
|
|
||||||
|
if (! str_contains($accessCommand, '{username}')) {
|
||||||
|
$accessCommand = trim($accessCommand).' {username}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_replace('{username}', $gameUsername, $accessCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'host' => env('MC_RCON_HOST', 'minecraft'),
|
||||||
|
'port' => (int) env('MC_RCON_PORT', 25575),
|
||||||
|
'password' => env('MC_RCON_PASSWORD'),
|
||||||
|
'timeout' => (float) env('MC_RCON_TIMEOUT', 5),
|
||||||
|
'access_command' => env('MC_RCON_ACCESS_COMMAND', 'whitelist add {username}'),
|
||||||
|
];
|
||||||
+2
-1
@@ -23,7 +23,8 @@
|
|||||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||||
<env name="BROADCAST_CONNECTION" value="null"/>
|
<env name="BROADCAST_CONNECTION" value="null"/>
|
||||||
<env name="CACHE_STORE" value="array"/>
|
<env name="CACHE_STORE" value="array"/>
|
||||||
<env name="DB_DATABASE" value="testing"/>
|
<env name="DB_CONNECTION" value="sqlite"/>
|
||||||
|
<env name="DB_DATABASE" value=":memory:"/>
|
||||||
<env name="DB_URL" value=""/>
|
<env name="DB_URL" value=""/>
|
||||||
<env name="MAIL_MAILER" value="array"/>
|
<env name="MAIL_MAILER" value="array"/>
|
||||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||||
|
|||||||
+2
-6
@@ -1,10 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use App\Http\Controllers\AccessRequestsController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/health', function () {
|
Route::post('/access-requests', [AccessRequestsController::class, 'store']);
|
||||||
return response()->json([
|
|
||||||
'status' => 'ok'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Enums\AccessRequestStatus;
|
||||||
|
use App\Models\AccessRequests;
|
||||||
|
use App\Models\GameServers;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\AccessRequestApprover;
|
||||||
|
use App\Services\MinecraftRconClient;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use RuntimeException;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AccessRequestApproverTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_it_accepts_an_access_request_after_running_rcon_add(): void
|
||||||
|
{
|
||||||
|
config()->set('rcon.access_command', 'whitelist add {username}');
|
||||||
|
|
||||||
|
$rconClient = new FakeMinecraftRconClient;
|
||||||
|
$this->app->instance(MinecraftRconClient::class, $rconClient);
|
||||||
|
$reviewer = User::factory()->create();
|
||||||
|
$accessRequest = $this->pendingAccessRequest('LeonPlayer');
|
||||||
|
|
||||||
|
$output = app(AccessRequestApprover::class)->approve($accessRequest, $reviewer);
|
||||||
|
|
||||||
|
$this->assertSame('Added LeonPlayer to the whitelist', $output);
|
||||||
|
$this->assertSame(['whitelist add LeonPlayer'], $rconClient->commands);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('access_requests', [
|
||||||
|
'id' => $accessRequest->id,
|
||||||
|
'status' => AccessRequestStatus::ACCEPTED->value,
|
||||||
|
'reviewed_by' => $reviewer->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertNotNull($accessRequest->fresh()->reviewed_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_does_not_accept_when_rcon_fails(): void
|
||||||
|
{
|
||||||
|
$this->app->instance(MinecraftRconClient::class, new FakeMinecraftRconClient(shouldFail: true));
|
||||||
|
$accessRequest = $this->pendingAccessRequest('LeonPlayer');
|
||||||
|
|
||||||
|
$this->expectException(RuntimeException::class);
|
||||||
|
|
||||||
|
try {
|
||||||
|
app(AccessRequestApprover::class)->approve($accessRequest);
|
||||||
|
} finally {
|
||||||
|
$this->assertDatabaseHas('access_requests', [
|
||||||
|
'id' => $accessRequest->id,
|
||||||
|
'status' => AccessRequestStatus::PENDING->value,
|
||||||
|
'reviewed_by' => null,
|
||||||
|
'reviewed_at' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function pendingAccessRequest(string $gameUsername): AccessRequests
|
||||||
|
{
|
||||||
|
$server = GameServers::create([
|
||||||
|
'name' => 'Survival',
|
||||||
|
'slug' => 'survival',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return AccessRequests::create([
|
||||||
|
'status' => AccessRequestStatus::PENDING,
|
||||||
|
'game_username' => $gameUsername,
|
||||||
|
'message' => '',
|
||||||
|
'server_id' => $server->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeMinecraftRconClient extends MinecraftRconClient
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
public array $commands = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly bool $shouldFail = false,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function command(string $command): string
|
||||||
|
{
|
||||||
|
$this->commands[] = $command;
|
||||||
|
|
||||||
|
if ($this->shouldFail) {
|
||||||
|
throw new RuntimeException('rcon failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Added LeonPlayer to the whitelist';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Enums\AccessRequestStatus;
|
||||||
|
use App\Models\GameServers;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AccessRequestsApiTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_access_request_can_be_created_from_api(): void
|
||||||
|
{
|
||||||
|
$server = GameServers::create([
|
||||||
|
'name' => 'Survival',
|
||||||
|
'slug' => 'survival',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->postJson('/api/access-requests', [
|
||||||
|
'game_username' => 'LeonPlayer',
|
||||||
|
'message' => 'Je veux rejoindre le serveur.',
|
||||||
|
'server_id' => $server->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertCreated()
|
||||||
|
->assertJsonPath('game_username', 'LeonPlayer')
|
||||||
|
->assertJsonPath('message', 'Je veux rejoindre le serveur.')
|
||||||
|
->assertJsonPath('server_id', $server->id)
|
||||||
|
->assertJsonPath('status', AccessRequestStatus::PENDING->value);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('access_requests', [
|
||||||
|
'game_username' => 'LeonPlayer',
|
||||||
|
'message' => 'Je veux rejoindre le serveur.',
|
||||||
|
'server_id' => $server->id,
|
||||||
|
'status' => AccessRequestStatus::PENDING->value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_api_always_creates_pending_access_requests(): void
|
||||||
|
{
|
||||||
|
$server = GameServers::create([
|
||||||
|
'name' => 'Survival',
|
||||||
|
'slug' => 'survival',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->postJson('/api/access-requests', [
|
||||||
|
'game_username' => 'LeonPlayer',
|
||||||
|
'server_id' => $server->id,
|
||||||
|
'status' => AccessRequestStatus::ACCEPTED->value,
|
||||||
|
])->assertCreated();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('access_requests', [
|
||||||
|
'game_username' => 'LeonPlayer',
|
||||||
|
'server_id' => $server->id,
|
||||||
|
'status' => AccessRequestStatus::PENDING->value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_access_request_can_be_created_with_a_server_slug(): void
|
||||||
|
{
|
||||||
|
$server = GameServers::create([
|
||||||
|
'name' => 'Survival',
|
||||||
|
'slug' => 'survival',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->postJson('/api/access-requests', [
|
||||||
|
'game_username' => 'LeonPlayer',
|
||||||
|
'server_slug' => 'survival',
|
||||||
|
])->assertCreated();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('access_requests', [
|
||||||
|
'game_username' => 'LeonPlayer',
|
||||||
|
'server_id' => $server->id,
|
||||||
|
'status' => AccessRequestStatus::PENDING->value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user