Initialer Laravel Commit für BetiX
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

This commit is contained in:
2026-04-04 18:01:50 +02:00
commit 0280278978
374 changed files with 65210 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
<?php
namespace Tests;
use Illuminate\Contracts\Console\Kernel;
trait CreatesApplication
{
/**
* Creates the application.
*/
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
return $app;
}
}

View File

@@ -0,0 +1,90 @@
<?php
use App\Models\User;
use Illuminate\Support\Facades\RateLimiter;
use Laravel\Fortify\Features;
test('login screen can be rendered', function () {
$response = $this->get(route('login'));
$response->assertOk();
});
test('users can authenticate using the login screen', function () {
$user = User::factory()->create();
$response = $this->post(route('login'), [
'email' => $user->email,
'password' => 'password',
]);
$this->assertAuthenticated();
$response->assertRedirect(route('dashboard', absolute: false));
});
test('users with two factor enabled are redirected to two factor challenge', function () {
if (! Features::canManageTwoFactorAuthentication()) {
$this->markTestSkipped('Two-factor authentication is not enabled.');
}
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => true,
]);
$user = User::factory()->create();
$user->forceFill([
'two_factor_secret' => encrypt('test-secret'),
'two_factor_recovery_codes' => encrypt(json_encode(['code1', 'code2'])),
'two_factor_confirmed_at' => now(),
])->save();
$response = $this->post(route('login'), [
'email' => $user->email,
'password' => 'password',
]);
$response->assertRedirect(route('two-factor.login'));
$response->assertSessionHas('login.id', $user->id);
$this->assertGuest();
});
test('users can not authenticate with invalid password', function () {
$user = User::factory()->create();
$this->post(route('login'), [
'email' => $user->email,
'password' => 'wrong-password',
]);
$this->assertGuest();
});
test('users can logout', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)->post(route('logout'));
$this->assertGuest();
$response->assertRedirect(route('home'));
});
test('users are rate limited', function () {
$user = User::factory()->create();
for ($i = 0; $i < 5; $i++) {
$this->post(route('login'), [
'email' => $user->email,
'password' => 'wrong-password',
]);
}
$response = $this->post(route('login'), [
'email' => $user->email,
'password' => 'wrong-password',
]);
$response->assertStatus(302);
$response->assertSessionHasErrors('email');
});

View File

@@ -0,0 +1,95 @@
<?php
use App\Models\User;
use Illuminate\Auth\Events\Verified;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\URL;
test('email verification screen can be rendered', function () {
$user = User::factory()->unverified()->create();
$response = $this->actingAs($user)->get(route('verification.notice'));
$response->assertOk();
});
test('email can be verified', function () {
$user = User::factory()->unverified()->create();
Event::fake();
$verificationUrl = URL::temporarySignedRoute(
'verification.verify',
now()->addMinutes(60),
['id' => $user->id, 'hash' => sha1($user->email)]
);
$response = $this->actingAs($user)->get($verificationUrl);
Event::assertDispatched(Verified::class);
expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
$response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
});
test('email is not verified with invalid hash', function () {
$user = User::factory()->unverified()->create();
Event::fake();
$verificationUrl = URL::temporarySignedRoute(
'verification.verify',
now()->addMinutes(60),
['id' => $user->id, 'hash' => sha1('wrong-email')]
);
$this->actingAs($user)->get($verificationUrl);
Event::assertNotDispatched(Verified::class);
expect($user->fresh()->hasVerifiedEmail())->toBeFalse();
});
test('email is not verified with invalid user id', function () {
$user = User::factory()->unverified()->create();
Event::fake();
$verificationUrl = URL::temporarySignedRoute(
'verification.verify',
now()->addMinutes(60),
['id' => 123, 'hash' => sha1($user->email)]
);
$this->actingAs($user)->get($verificationUrl);
Event::assertNotDispatched(Verified::class);
expect($user->fresh()->hasVerifiedEmail())->toBeFalse();
});
test('verified user is redirected to dashboard from verification prompt', function () {
$user = User::factory()->create();
Event::fake();
$response = $this->actingAs($user)->get(route('verification.notice'));
Event::assertNotDispatched(Verified::class);
$response->assertRedirect(route('dashboard', absolute: false));
});
test('already verified user visiting verification link is redirected without firing event again', function () {
$user = User::factory()->create();
Event::fake();
$verificationUrl = URL::temporarySignedRoute(
'verification.verify',
now()->addMinutes(60),
['id' => $user->id, 'hash' => sha1($user->email)]
);
$this->actingAs($user)->get($verificationUrl)
->assertRedirect(route('dashboard', absolute: false).'?verified=1');
Event::assertNotDispatched(Verified::class);
expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
});

View File

@@ -0,0 +1,22 @@
<?php
use App\Models\User;
use Inertia\Testing\AssertableInertia as Assert;
test('confirm password screen can be rendered', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)->get(route('password.confirm'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('auth/ConfirmPassword')
);
});
test('password confirmation requires authentication', function () {
$response = $this->get(route('password.confirm'));
$response->assertRedirect(route('login'));
});

View File

@@ -0,0 +1,76 @@
<?php
use App\Notifications\ResetPassword;
use App\Models\User;
use Illuminate\Support\Facades\Notification;
test('reset password link screen can be rendered', function () {
$response = $this->get(route('password.request'));
$response->assertOk();
});
test('reset password link can be requested', function () {
$user = User::factory()->create();
Notification::fake();
$this->post(route('password.email'), ['email' => $user->email]);
Notification::assertSentTo($user, ResetPassword::class);
});
test('reset password screen can be rendered', function () {
$user = User::factory()->create();
Notification::fake();
$this->post(route('password.email'), ['email' => $user->email]);
Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
$response = $this->get(route('password.reset', [
'token' => $notification->token,
'email' => 'test@example.com' // Any email works for rendering
]));
$response->assertOk();
return true;
});
});
test('password can be reset with valid token', function () {
$user = User::factory()->create();
Notification::fake();
$this->post(route('password.email'), ['email' => $user->email]);
Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
$response = $this->post(route('password.update'), [
'token' => $notification->token,
'email' => $user->email,
'password' => 'new-password',
'password_confirmation' => 'new-password',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('login'));
return true;
});
});
test('password cannot be reset with invalid token', function () {
$user = User::factory()->create();
$response = $this->post(route('password.update'), [
'token' => 'invalid-token',
'email' => $user->email,
'password' => 'newpassword123',
'password_confirmation' => 'newpassword123',
]);
$response->assertSessionHasErrors('email');
});

View File

@@ -0,0 +1,41 @@
<?php
test('registration screen can be rendered', function () {
$response = $this->get(route('register'));
$response->assertOk();
});
test('new users can register', function () {
$response = $this->post('/register', [
'username' => 'Dolo',
'first_name' => 'Kevin',
'last_name' => 'Geiger',
'email' => 'laynox9@gmail.com',
'birthdate' => '2004-01-23',
'gender' => 'male',
'phone' => '+4915112350255',
'country' => 'DE',
'address_line1' => 'Siedlerstr. 15',
'address_line2' => '',
'city' => 'Türkheim',
'postal_code' => '86842',
'currency' => 'EUR',
'password' => 'Geheim123!',
'password_confirmation' => 'Geheim123!',
'is_adult' => true,
'terms_accepted' => true
]);
if ($response->status() !== 302) {
// Log errors to see what's failing if it's not a redirect
$errors = session('errors');
if ($errors) {
fwrite(STDERR, print_r($errors->getMessages(), true));
}
}
$response->assertStatus(302);
$this->assertAuthenticated('web');
$response->assertRedirect(route('dashboard', absolute: false));
});

View File

@@ -0,0 +1,45 @@
<?php
use App\Models\User;
use Inertia\Testing\AssertableInertia as Assert;
use Laravel\Fortify\Features;
test('two factor challenge redirects to login when not authenticated', function () {
if (! Features::canManageTwoFactorAuthentication()) {
$this->markTestSkipped('Two-factor authentication is not enabled.');
}
$response = $this->get(route('two-factor.login'));
$response->assertRedirect(route('login'));
});
test('two factor challenge can be rendered', function () {
if (! Features::canManageTwoFactorAuthentication()) {
$this->markTestSkipped('Two-factor authentication is not enabled.');
}
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => true,
]);
$user = User::factory()->create();
$user->forceFill([
'two_factor_secret' => encrypt('test-secret'),
'two_factor_recovery_codes' => encrypt(json_encode(['code1', 'code2'])),
'two_factor_confirmed_at' => now(),
])->save();
$this->post(route('login'), [
'email' => $user->email,
'password' => 'password',
]);
$this->get(route('two-factor.login'))
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('auth/TwoFactorChallenge')
);
});

View File

@@ -0,0 +1,29 @@
<?php
use App\Notifications\VerifyEmail;
use App\Models\User;
use Illuminate\Support\Facades\Notification;
test('sends verification notification', function () {
$user = User::factory()->unverified()->create();
Notification::fake();
$this->actingAs($user)
->post(route('verification.send'))
->assertRedirect(route('dashboard'));
Notification::assertSentTo($user, VerifyEmail::class);
});
test('does not send verification notification if email is verified', function () {
Notification::fake();
$user = User::factory()->create();
$this->actingAs($user)
->post(route('verification.send'))
->assertRedirect(route('dashboard'));
Notification::assertNothingSent();
});

View File

@@ -0,0 +1,16 @@
<?php
use App\Models\User;
test('guests can visit the dashboard', function () {
$response = $this->get(route('dashboard'));
$response->assertOk();
});
test('authenticated users can visit the dashboard', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('dashboard'));
$response->assertOk();
});

View File

@@ -0,0 +1,7 @@
<?php
test('returns a successful response', function () {
$response = $this->get(route('home'));
$response->assertRedirect(route('dashboard'));
});

View File

@@ -0,0 +1,69 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class AdminControllerTest extends TestCase
{
private function base(): string { return rtrim((string) config('services.backend.base'), '/'); }
private function adminUser()
{
return User::factory()->create([
'username' => 'admin',
'role' => 'Admin',
'email_verified_at' => now()
]);
}
public function test_index_renders_for_admin()
{
Http::fake([
$this->base() . '/admin/overview' => Http::response([
'users' => [ ['id'=>1,'username'=>'u1','balance'=>0] ],
], 200),
]);
$res = $this->actingAs($this->adminUser(), 'web')->get('/admin/casino');
$res->assertStatus(200);
}
public function test_update_user_success_sets_flash()
{
$userToUpdate = User::factory()->create();
Http::fake([
$this->base() . '/admin/users/' . $userToUpdate->id => Http::response(['message' => 'User aktualisiert.'], 200),
]);
$res = $this->actingAs($this->adminUser(), 'web')
->post('/admin/users/' . $userToUpdate->id, [
'username' => 'newname',
'email' => 'new@example.com',
'vip_level' => 2
]);
$res->assertSessionHas('success');
}
public function test_update_user_client_error_sets_error()
{
$userToUpdate = User::factory()->create();
Http::fake([
$this->base() . '/admin/users/' . $userToUpdate->id => Http::response(['message' => 'Invalid'], 400),
]);
$res = $this->actingAs($this->adminUser(), 'web')
->post('/admin/users/' . $userToUpdate->id, [
'username' => 'newname',
'email' => 'invalid-email',
'vip_level' => 999
]);
$res->assertSessionHasErrors();
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class AdminSupportAdminControllerTest extends TestCase
{
private function base(): string { return rtrim((string) config('services.backend.base'), '/'); }
private function adminUser()
{
if (class_exists(User::class) && method_exists(User::class, 'factory')) {
return User::factory()->make(['id' => 1201, 'username' => 'admin', 'role' => 'Admin', 'email_verified_at' => now()]);
}
return new class { public $id=1201; public $username='admin'; public $role='Admin'; };
}
public function test_index_renders_with_threads()
{
Http::fake([
$this->base() . '/admin/support' => Http::response([
'enabled' => true,
'threads' => [ ['id' => 't-1', 'status' => 'ai'] ],
'ollama' => ['healthy' => true],
], 200),
]);
$res = $this->actingAs($this->adminUser())->get('/admin/support');
$res->assertStatus(200);
}
public function test_settings_success_sets_flash()
{
Http::fake([
$this->base() . '/admin/support/settings' => Http::response(['message' => 'OK'], 200),
]);
$res = $this->actingAs($this->adminUser())->post('/admin/support/settings', ['enabled' => true]);
$res->assertSessionHas('success');
}
public function test_reply_and_close_success_set_flash()
{
Http::fake([
$this->base() . '/admin/support/threads/t-1/message' => Http::response(['message' => 'Sent'], 200),
$this->base() . '/admin/support/threads/t-1/close' => Http::response(['message' => 'Closed'], 200),
]);
$this->actingAs($this->adminUser())
->post('/admin/support/threads/t-1/message', ['text' => 'hi'])
->assertSessionHas('success');
$this->actingAs($this->adminUser())
->post('/admin/support/threads/t-1/close')
->assertSessionHas('success');
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class BonusesControllerTest extends TestCase
{
private function base(): string
{
return rtrim((string) config('services.backend.base'), '/');
}
private function actingUser()
{
return User::factory()->create(['username' => 'bonususer', 'email_verified_at' => now()]);
}
public function test_app_index_success_normalizes_fields()
{
Http::fake([
$this->base() . '/bonuses/app*' => Http::response([
'available' => [
[
'id' => 1,
'title' => 'Welcome Bonus',
'type' => 'deposit_match',
'amount_value' => 100,
'amount_unit' => '%',
'min_deposit' => 10,
'max_amount' => 200,
'currency' => 'USD',
'code' => 'WELCOME100',
'starts_at' => now()->subDay()->toIso8601String(),
'expires_at' => now()->addDay()->toIso8601String(),
'description' => 'Match bonus',
],
],
'active' => [],
'history' => [],
'now' => now()->toIso8601String(),
], 200),
]);
$res = $this->actingAs($this->actingUser(), 'web')->get('/api/bonuses/app');
$res->assertStatus(200)
->assertJsonStructure([
'available' => [ [ 'id','title','type','amount_value','amount_unit','currency','code','starts_at','expires_at','description' ] ],
'active', 'history', 'now',
]);
}
public function test_app_index_server_error_maps_to_503()
{
Http::fake([
$this->base() . '/bonuses/app*' => Http::response(['message' => 'Down'], 500),
]);
$res = $this->actingAs($this->actingUser(), 'web')->get('/api/bonuses/app');
$res->assertStatus(503)->assertJson(['error' => 'service_unavailable']);
}
}

View File

@@ -0,0 +1,166 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class ChatControllerTest extends TestCase
{
// use RefreshDatabase; // Not required for proxy tests; uncomment if your auth relies on DB
private function base(): string
{
return rtrim((string) config('services.backend.base'), '/');
}
private function actingUser()
{
return User::factory()->create(['username' => 'tester', 'email_verified_at' => now()]);
}
public function test_get_chat_success_maps_messages_and_last_id()
{
Http::fake([
$this->base() . '/chat*' => Http::response([
'messages' => [
[
'id' => 1,
'user_id' => 12,
'username' => 'dolo',
'avatar' => 'http://a/1.png',
'message' => 'hi',
'reply_to_id' => null,
'reactions' => ['🔥' => 2, '😂' => 1],
'created_at' => now()->toIso8601String(),
],
[
'id' => 2,
'user_id' => 13,
'username' => 'sara',
'avatar' => null,
'message' => 'yo',
'reply_to_id' => 1,
'reactions' => [],
'created_at' => now()->toIso8601String(),
],
],
], 200),
]);
$res = $this->actingAs($this->actingUser(), 'web')
->get('/api/chat?limit=2');
$res->assertStatus(200)
->assertJsonStructure([
'data' => [
['id', 'user_id', 'message', 'reply_to_id', 'created_at', 'user' => ['id', 'username', 'avatar_url'], 'reactions_agg'],
],
'last_id',
]);
$json = $res->json();
$this->assertEquals(2, $json['last_id']);
$this->assertCount(2, $json['data']);
$this->assertEquals('dolo', $json['data'][0]['user']['username']);
}
public function test_get_chat_client_error_is_mapped()
{
Http::fake([
$this->base() . '/chat*' => Http::response(['message' => 'Invalid request'], 422),
]);
$res = $this->actingAs($this->actingUser(), 'web')
->get('/api/chat?limit=0');
$res->assertStatus(422)
->assertJson([
'error' => 'client_error',
'message' => 'Invalid request',
]);
}
public function test_get_chat_server_error_maps_to_503()
{
Http::fake([
$this->base() . '/chat*' => Http::response(['message' => 'Upstream failed'], 500),
]);
$res = $this->actingAs($this->actingUser(), 'web')
->get('/api/chat?limit=50');
$res->assertStatus(503)
->assertJson([
'error' => 'service_unavailable',
]);
}
public function test_get_chat_timeout_maps_to_502()
{
Http::fake(function () {
throw new \Illuminate\Http\Client\ConnectionException('timeout');
});
$res = $this->actingAs($this->actingUser(), 'web')
->get('/api/chat?limit=50');
$res->assertStatus(502)
->assertJson([
'error' => 'bad_gateway',
]);
}
public function test_post_chat_success_returns_minimal_message()
{
Http::fake([
$this->base() . '/chat' => Http::response(['success' => true, 'message_id' => 123], 200),
]);
$res = $this->actingAs($this->actingUser(), 'web')
->postJson('/api/chat', [
'message' => 'Hello world',
]);
$res->assertStatus(201)
->assertJsonStructure(['data' => ['id', 'user_id', 'message', 'created_at', 'user' => ['id', 'username', 'avatar_url'], 'reactions_agg']]);
$this->assertEquals(123, data_get($res->json(), 'data.id'));
$this->assertEquals('Hello world', data_get($res->json(), 'data.message'));
}
public function test_post_chat_client_error_is_mapped()
{
Http::fake([
$this->base() . '/chat' => Http::response(['message' => 'Too long'], 422),
]);
$res = $this->actingAs($this->actingUser(), 'web')
->postJson('/api/chat', [
'message' => str_repeat('x', 400),
]);
$res->assertStatus(422)
->assertJson([
'error' => 'client_error',
'message' => 'Too long',
]);
}
public function test_react_success_returns_200()
{
Http::fake([
$this->base() . '/chat/55/react' => Http::response(['ok' => true], 200),
]);
$res = $this->actingAs($this->actingUser(), 'web')
->postJson('/api/chat/55/react', [ 'emoji' => '🔥' ]);
$res->assertStatus(200)
->assertJson([
'data' => null,
]);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class GuildActionControllerTest extends TestCase
{
private function base(): string
{
return rtrim((string) config('services.backend.base'), '/');
}
private function actingUser()
{
if (class_exists(User::class) && method_exists(User::class, 'factory')) {
return User::factory()->make(['id' => 802, 'username' => 'guilduser', 'email_verified_at' => now()]);
}
return new class {
public $id = 802; public $username = 'guilduser'; public $role = 'User';
};
}
public function test_store_success()
{
Http::fake([
$this->base() . '/guilds' => Http::response([
'data' => ['id' => 1, 'name' => 'NewGuild', 'invite_code' => 'ABCDEF']
], 201),
]);
$res = $this->actingAs($this->actingUser())
->postJson('/guilds', [
'name' => 'NewGuild',
'tag' => 'ABCD',
]);
$res->assertStatus(201)
->assertJsonStructure(['data']);
}
public function test_join_success()
{
Http::fake([
$this->base() . '/guilds/join' => Http::response(['success' => true], 200),
]);
$res = $this->actingAs($this->actingUser())
->postJson('/guilds/join', [ 'invite_code' => 'AAAAAA' ]);
$res->assertStatus(200)
->assertJson(['success' => true]);
}
public function test_update_client_error_maps_to_client_error()
{
Http::fake([
$this->base() . '/guilds' => Http::response(['message' => 'Forbidden'], 403),
]);
$res = $this->actingAs($this->actingUser())
->patchJson('/guilds', [ 'name' => 'x' ]);
$res->assertStatus(403)
->assertJson(['message' => 'Forbidden']);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class GuildControllerTest extends TestCase
{
private function base(): string
{
return rtrim((string) config('services.backend.base'), '/');
}
private function actingUser()
{
if (class_exists(User::class) && method_exists(User::class, 'factory')) {
return User::factory()->make(['id' => 801, 'username' => 'guilduser']);
}
return new class {
public $id = 801; public $username = 'guilduser'; public $role = 'User';
};
}
public function test_index_renders_with_upstream_data()
{
Http::fake([
$this->base() . '/guilds' => Http::response([
'guild' => ['id' => 1, 'name' => 'Testers'],
'myRole' => 'member',
'canManage' => false,
'invite' => 'AAAAAA',
'wager' => ['value' => 0],
'topPlayers' => [],
], 200),
]);
$res = $this->actingAs($this->actingUser())->get('/guilds');
$res->assertStatus(200);
}
public function test_top_renders_list()
{
Http::fake([
$this->base() . '/guilds/top' => Http::response([
'guilds' => [ ['id' => 1, 'name' => 'A'], ['id' => 2, 'name' => 'B'] ],
], 200),
]);
$res = $this->actingAs($this->actingUser())->get('/guilds/top');
$res->assertStatus(200);
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class PromoControllerTest extends TestCase
{
private function base(): string
{
return rtrim((string) config('services.backend.base'), '/');
}
private function actingUser()
{
return User::factory()->create(['username' => 'promouser', 'email_verified_at' => now()]);
}
public function test_apply_success_passes_message()
{
Http::fake([
$this->base() . '/promos/apply' => Http::response([
'success' => true,
'message' => 'Applied',
], 200),
]);
$res = $this->actingAs($this->actingUser(), 'web')
->postJson('/api/promos/apply', ['code' => 'WELCOME']);
$res->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Applied',
]);
}
public function test_apply_client_error_maps_to_client_error()
{
Http::fake([
$this->base() . '/promos/apply' => Http::response(['message' => 'Invalid code'], 400),
]);
$res = $this->actingAs($this->actingUser(), 'web')
->postJson('/api/promos/apply', ['code' => 'bad!']);
$res->assertStatus(400)
->assertJson(['error' => 'client_error']);
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class SocialControllerTest extends TestCase
{
private function base(): string
{
return rtrim((string) config('services.backend.base'), '/');
}
private function actingUser($overrides = [])
{
return User::factory()->create(array_merge(['username' => 'socialuser', 'role' => 'User', 'email_verified_at' => now()], $overrides));
}
public function test_search_success_normalizes_minimal_fields()
{
Http::fake([
$this->base() . '/users/search*' => Http::response([
'users' => [
['id' => 1, 'username' => 'do', 'avatar_url' => null, 'vip_level' => 3],
['id' => 2, 'username' => 'dolo', 'avatar' => 'http://a/av.png'],
],
], 200),
]);
$res = $this->actingAs($this->actingUser(), 'web')->get('/api/users/search?q=do');
$res->assertStatus(200)
->assertJsonStructure([
[ 'id','username','avatar','vip_level' ]
]);
}
public function test_profile_show_404_is_forwarded()
{
Http::fake([
$this->base() . '/users/unknown' => Http::response(['message' => 'User not found.'], 404),
]);
$res = $this->actingAs($this->actingUser(), 'web')->get('/profile/unknown');
$res->assertStatus(404);
}
public function test_tip_client_error_is_mapped_to_errors_bag()
{
$user = $this->actingUser(['balance' => 0.0]); // Set balance to 0 to trigger local error
$target = User::factory()->create();
$res = $this->actingAs($user, 'web')->post("/profile/{$target->id}/tip", [
'currency' => 'USD', 'amount' => 1.0
]);
$res->assertSessionHasErrors(['amount']);
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class SupportChatControllerTest extends TestCase
{
private function base(): string
{
return rtrim((string) config('services.backend.base'), '/');
}
private function actingUser()
{
return User::factory()->create(['email_verified_at' => now()]);
}
public function test_status_success_passthrough()
{
$user = $this->actingUser();
Http::fake([
$this->base() . '/support/status' => Http::response([
'thread_id' => 't-1',
'status' => 'ai',
'messages' => [],
], 200),
]);
$res = $this->actingAs($user, 'web')->get('/api/support/status');
$res->assertStatus(200)
->assertJson([
'thread_id' => 't-1',
'status' => 'ai',
]);
}
public function test_status_client_error_maps_to_client_error()
{
Http::fake([
$this->base() . '/support/status' => Http::response(['message' => 'Invalid'], 400),
]);
$res = $this->actingAs($this->actingUser(), 'web')->get('/api/support/status');
$res->assertStatus(400)
->assertJson([
'error' => 'client_error',
]);
}
public function test_status_server_error_maps_to_503()
{
Http::fake([
$this->base() . '/support/status' => Http::response(['message' => 'Oops'], 500),
]);
$res = $this->actingAs($this->actingUser(), 'web')->get('/api/support/status');
$res->assertStatus(503)
->assertJson([
'error' => 'service_unavailable',
]);
}
public function test_start_success()
{
Http::fake([
$this->base() . '/support/start' => Http::response([
'thread_id' => 't-2',
'status' => 'ai',
], 200),
]);
$res = $this->actingAs($this->actingUser(), 'web')->postJson('/api/support/start', ['topic' => 'Konto']);
$res->assertStatus(200)->assertJson([
'thread_id' => 't-2',
'status' => 'ai',
]);
}
public function test_message_success()
{
Http::fake([
$this->base() . '/support/message' => Http::response(['ok' => true], 200),
]);
$res = $this->actingAs($this->actingUser(), 'web')->postJson('/api/support/message', ['text' => 'Hallo']);
$res->assertStatus(200);
}
public function test_stop_handoff_close_success()
{
Http::fake([
$this->base() . '/support/stop' => Http::response(['success' => true], 200),
$this->base() . '/support/handoff' => Http::response(['success' => true], 200),
$this->base() . '/support/close' => Http::response(['success' => true], 200),
]);
$this->actingAs($this->actingUser(), 'web')->postJson('/api/support/stop')->assertStatus(200);
$this->actingAs($this->actingUser(), 'web')->postJson('/api/support/handoff')->assertStatus(200);
$this->actingAs($this->actingUser(), 'web')->postJson('/api/support/close')->assertStatus(200);
}
public function test_stream_failure_maps_to_503()
{
Http::fake([
$this->base() . '/support/stream' => Http::response(['message' => 'Down'], 500),
]);
$res = $this->actingAs($this->actingUser(), 'web')->get('/api/support/stream');
$res->assertStatus(503)
->assertJson([
'error' => 'service_unavailable',
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class UserBonusControllerTest extends TestCase
{
private function base(): string
{
return rtrim((string) config('services.backend.base'), '/');
}
private function actingUser()
{
return User::factory()->create(['username' => 'bonususer', 'email_verified_at' => now()]);
}
public function test_index_success_normalizes_data()
{
Http::fake([
$this->base() . '/users/me/bonuses' => Http::response([
'bonuses' => [
[
'id' => 9,
'amount' => 12.5,
'wager_required' => 100,
'wager_progress' => 40,
'expires_at' => now()->addDay()->toIso8601String(),
'is_active' => true,
'promo' => [ 'code' => 'WELCOME', 'wager_multiplier' => 40 ],
],
],
], 200),
]);
$res = $this->actingAs($this->actingUser(), 'web')->get('/api/user/bonuses');
$res->assertStatus(200)
->assertJsonStructure(['data' => [[
'id','amount','wager_required','wager_progress','expires_at','is_active','completed_at','promo'
]]]);
}
public function test_index_server_error_maps_to_503()
{
Http::fake([
$this->base() . '/users/me/bonuses' => Http::response(['message' => 'Down'], 500),
]);
$res = $this->actingAs($this->actingUser(), 'web')->get('/api/user/bonuses');
$res->assertStatus(503)->assertJson(['error' => 'service_unavailable']);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class VaultControllerTest extends TestCase
{
private function base(): string
{
return rtrim((string) config('services.backend.base'), '/');
}
private function actingUser()
{
return User::factory()->create([
'username' => 'vaultuser',
'email_verified_at' => now(),
'balance' => 11.0000,
'vault_balance' => 5.0000,
'vault_pin_hash' => \Illuminate\Support\Facades\Hash::make('1234'),
]);
}
public function test_show_success_maps_shape()
{
$user = $this->actingUser();
\App\Models\WalletTransfer::create([
'user_id' => $user->id,
'type' => 'deposit',
'amount' => 1.00,
'currency' => 'BTX',
'balance_before' => 12.00,
'balance_after' => 11.00,
'vault_before' => 4.00,
'vault_after' => 5.00,
]);
$res = $this->actingAs($user, 'web')->get('/api/wallet/vault');
$res->assertStatus(200)
->assertJson([
'balance' => '11.0000',
'vault_balance' => '5.0000',
'currency' => 'BTX',
])
->assertJsonStructure(['transfers', 'now']);
}
public function test_show_client_error_not_applicable_for_local_show()
{
// Der lokale VaultController->show() wirft keinen 400er Client-Fehler wie der Proxy
$this->assertTrue(true);
}
public function test_show_server_error_not_applicable_for_local_show()
{
// Der lokale VaultController->show() wirft keinen 503er Service-Fehler wie der Proxy
$this->assertTrue(true);
}
public function test_deposit_success_maps_balances()
{
$user = $this->actingUser();
$res = $this->actingAs($user, 'web')
->postJson('/api/wallet/vault/deposit', [ 'amount' => '1.0000', 'pin' => '1234' ]);
$res->assertStatus(201)
->assertJson([
'balances' => [ 'balance' => '10.0000', 'vault_balance' => '6.0000' ],
])
->assertJsonStructure(['data', 'balances']);
}
public function test_withdraw_success_maps_balances()
{
$user = $this->actingUser();
$res = $this->actingAs($user, 'web')
->postJson('/api/wallet/vault/withdraw', [ 'amount' => '1.0000', 'pin' => '1234' ]);
$res->assertStatus(201)
->assertJson([
'balances' => [ 'balance' => '12.0000', 'vault_balance' => '4.0000' ],
]);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class VaultPinControllerTest extends TestCase
{
private function base(): string
{
return rtrim((string) config('services.backend.base'), '/');
}
private function actingUser()
{
return User::factory()->create([
'username' => 'pinuser',
'email_verified_at' => now(),
'vault_pin_hash' => \Illuminate\Support\Facades\Hash::make('4321'),
]);
}
public function test_set_success()
{
$user = $this->actingUser();
// Da ein PIN gesetzt ist, müssen wir current_pin mitsenden
$res = $this->actingAs($user, 'web')
->postJson('/api/wallet/vault/pin/set', [ 'pin' => '1234', 'current_pin' => '4321' ]);
$res->assertStatus(200)
->assertJson([
'success' => true,
]);
}
public function test_verify_success()
{
$user = $this->actingUser();
// Der actingUser() hat PIN 4321
$res = $this->actingAs($user, 'web')
->postJson('/api/wallet/vault/pin/verify', [ 'pin' => '4321' ]);
$res->assertStatus(200)
->assertJson([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class VipControllerTest extends TestCase
{
private function base(): string { return rtrim((string) config('services.backend.base'), '/'); }
private function actingUser()
{
if (class_exists(User::class) && method_exists(User::class, 'factory')) {
return User::factory()->make(['id' => 1001, 'username' => 'vipuser']);
}
return new class { public $id=1001; public $username='vipuser'; public $role='User'; };
}
public function test_index_renders()
{
Http::fake([
$this->base() . '/vip-levels' => Http::response([
'claimedLevels' => [1,2],
'rewards' => [],
'stats' => ['wagered' => 0],
'vip_level' => 3,
], 200),
]);
$res = $this->actingAs($this->actingUser())->get('/vip-levels');
$res->assertStatus(200);
}
public function test_claim_success_sets_flash()
{
Http::fake([
$this->base() . '/vip-levels/claim' => Http::response(['message' => 'Reward claimed successfully!'], 200),
]);
$res = $this->actingAs($this->actingUser())->post('/vip-levels/claim', ['level' => 2]);
$res->assertSessionHas('success');
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Tests\Feature\Gateway;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class WalletControllerTest extends TestCase
{
private function base(): string { return rtrim((string) config('services.backend.base'), '/'); }
private function actingUser()
{
if (class_exists(User::class) && method_exists(User::class, 'factory')) {
return User::factory()->make(['id' => 901, 'username' => 'walletuser']);
}
return new class { public $id=901; public $username='walletuser'; public $role='User'; };
}
public function test_index_renders_with_upstream_data()
{
Http::fake([
$this->base() . '/wallet' => Http::response([
'wallets' => [ ['code' => 'USD', 'balance' => 10] ],
'btxBalance' => 10,
], 200),
]);
$res = $this->actingAs($this->actingUser())->get('/wallet');
$res->assertStatus(200);
}
}

View File

@@ -0,0 +1,50 @@
<?php
use App\Models\User;
use Illuminate\Support\Facades\Hash;
test('password update page is displayed', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->get(route('user-password.edit'));
$response->assertOk();
});
test('password can be updated', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->from(route('user-password.edit'))
->put(route('user-password.update'), [
'current_password' => 'password',
'password' => 'new-password',
'password_confirmation' => 'new-password',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('user-password.edit'));
expect(Hash::check('new-password', $user->refresh()->password))->toBeTrue();
});
test('correct password must be provided to update password', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->from(route('user-password.edit'))
->put(route('user-password.update'), [
'current_password' => 'wrong-password',
'password' => 'new-password',
'password_confirmation' => 'new-password',
]);
$response
->assertSessionHasErrors('current_password')
->assertRedirect(route('user-password.edit'));
});

View File

@@ -0,0 +1,85 @@
<?php
use App\Models\User;
test('profile page is displayed', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->get(route('profile.edit'));
$response->assertOk();
});
test('profile information can be updated', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->patch(route('profile.update'), [
'name' => 'Test User',
'email' => 'test@example.com',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('profile.edit'));
$user->refresh();
expect($user->name)->toBe('Test User');
expect($user->email)->toBe('test@example.com');
expect($user->email_verified_at)->toBeNull();
});
test('email verification status is unchanged when the email address is unchanged', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->patch(route('profile.update'), [
'name' => 'Test User',
'email' => $user->email,
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('profile.edit'));
expect($user->refresh()->email_verified_at)->not->toBeNull();
});
test('user can delete their account', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->delete(route('profile.destroy'), [
'password' => 'password',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('home'));
$this->assertGuest();
expect($user->fresh())->toBeNull();
});
test('correct password must be provided to delete account', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->from(route('profile.edit'))
->delete(route('profile.destroy'), [
'password' => 'wrong-password',
]);
$response
->assertSessionHasErrors('password')
->assertRedirect(route('profile.edit'));
expect($user->fresh())->not->toBeNull();
});

View File

@@ -0,0 +1,79 @@
<?php
use App\Models\User;
use Inertia\Testing\AssertableInertia as Assert;
use Laravel\Fortify\Features;
test('two factor settings page can be rendered', function () {
if (! Features::canManageTwoFactorAuthentication()) {
$this->markTestSkipped('Two-factor authentication is not enabled.');
}
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => true,
]);
$user = User::factory()->create();
$this->actingAs($user)
->withSession(['auth.password_confirmed_at' => time()])
->get(route('two-factor.show'))
->assertInertia(fn (Assert $page) => $page
->component('settings/TwoFactor')
->where('twoFactorEnabled', false)
);
});
test('two factor settings page requires password confirmation when enabled', function () {
if (! Features::canManageTwoFactorAuthentication()) {
$this->markTestSkipped('Two-factor authentication is not enabled.');
}
$user = User::factory()->create();
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => true,
]);
$response = $this->actingAs($user)
->get(route('two-factor.show'));
$response->assertRedirect(route('password.confirm'));
});
test('two factor settings page does not requires password confirmation when disabled', function () {
if (! Features::canManageTwoFactorAuthentication()) {
$this->markTestSkipped('Two-factor authentication is not enabled.');
}
$user = User::factory()->create();
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => false,
]);
$this->actingAs($user)
->get(route('two-factor.show'))
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('settings/TwoFactor')
);
});
test('two factor settings page returns forbidden response when two factor is disabled', function () {
if (! Features::canManageTwoFactorAuthentication()) {
$this->markTestSkipped('Two-factor authentication is not enabled.');
}
config(['fortify.features' => []]);
$user = User::factory()->create();
$this->actingAs($user)
->withSession(['auth.password_confirmed_at' => time()])
->get(route('two-factor.show'))
->assertForbidden();
});

47
tests/Pest.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "pest()" function to bind a different classes or traits.
|
*/
pest()->extend(Tests\TestCase::class)
->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
->in('Feature');
/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/
expect()->extend('toBeOne', function () {
return $this->toBe(1);
});
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/
function something()
{
// ..
}

26
tests/TestCase.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\CreatesApplication;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
// Global disable for tests
$this->withoutMiddleware([
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\App\Http\Middleware\CheckBanned::class,
\App\Http\Middleware\EnforceRestriction::class,
]);
}
}

View File

@@ -0,0 +1,5 @@
<?php
test('that true is true', function () {
expect(true)->toBeTrue();
});