Files
portal/app/tests/Feature/Auth/RegistrationTest.php
T

187 lines
7.2 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
// Самозапись пишет tenants/users/email_verifications через BYPASSRLS pgsql_supplier
// (на публичном роуте нет tenant-GUC). SharesSupplierPdo шарит PDO под DatabaseTransactions.
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
beforeEach(function () {
config(['services.captcha.fake_passes' => true]);
});
function registerPayload(array $over = []): array
{
return array_merge([
'email' => 'newclient@example.ru',
'password' => 'fresh-pass-123',
'accept_offer' => true,
'accept_pdn' => true,
'captcha_token' => 'tok-123',
], $over);
}
test('register создаёт pending-тенанта + неактивного владельца + код, без входа', function () {
$r = $this->postJson('/api/auth/register', registerPayload());
$r->assertStatus(201);
$r->assertJsonPath('status', 'pending_email_confirm');
$r->assertJsonPath('email', 'newclient@example.ru');
expect($r->json('_dev_plain_code'))->toMatch('/^\d{6}$/');
$user = User::where('email', 'newclient@example.ru')->first();
expect($user)->not->toBeNull();
expect($user->is_active)->toBeFalse();
$tenant = Tenant::find($user->tenant_id);
expect($tenant->status)->toBe('pending_email_confirm');
expect((float) $tenant->balance_rub)->toBe(0.0);
// Вход НЕ выполнен.
$this->getJson('/api/auth/me')->assertStatus(401);
});
test('register пишет email_verifications через pgsql_supplier (BYPASSRLS)', function () {
$connections = [];
DB::listen(function ($q) use (&$connections) {
if (str_contains($q->sql, 'email_verifications')) {
$connections[] = $q->connectionName;
}
});
$this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
expect($connections)->not->toBeEmpty();
expect(array_values(array_unique($connections)))->toBe(['pgsql_supplier']);
});
test('register отклоняет неверную капчу (422), аккаунт не создаётся', function () {
config(['services.captcha.fake_passes' => false]);
$this->postJson('/api/auth/register', registerPayload())
->assertStatus(422)
->assertJsonValidationErrors(['captcha_token']);
expect(User::where('email', 'newclient@example.ru')->exists())->toBeFalse();
});
test('register требует accept_offer, accept_pdn, captcha_token', function () {
$this->postJson('/api/auth/register', registerPayload(['accept_offer' => false]))
->assertStatus(422)->assertJsonValidationErrors(['accept_offer']);
$this->postJson('/api/auth/register', registerPayload(['accept_pdn' => false]))
->assertStatus(422)->assertJsonValidationErrors(['accept_pdn']);
$payload = registerPayload();
unset($payload['captcha_token']);
$this->postJson('/api/auth/register', $payload)
->assertStatus(422)->assertJsonValidationErrors(['captcha_token']);
});
test('confirm верным кодом активирует тенанта/владельца, баланс 300, выполняет вход', function () {
$reg = $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
$code = $reg->json('_dev_plain_code');
$r = $this->postJson('/api/auth/confirm-email', [
'email' => 'newclient@example.ru',
'code' => $code,
]);
$r->assertOk();
$r->assertJsonPath('user.email', 'newclient@example.ru');
$r->assertJsonPath('requires_2fa', false);
$user = User::where('email', 'newclient@example.ru')->first();
expect($user->is_active)->toBeTrue();
$tenant = Tenant::find($user->tenant_id);
expect($tenant->status)->toBe('active');
expect((float) $tenant->balance_rub)->toBe(300.0);
});
test('confirm неверным кодом → 422 + failed_attempts++', function () {
$reg = $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
$real = $reg->json('_dev_plain_code');
$wrong = $real === '000000' ? '111111' : '000000';
$r = $this->postJson('/api/auth/confirm-email', [
'email' => 'newclient@example.ru',
'code' => $wrong,
]);
$r->assertStatus(422);
expect($r->json('attempts_remaining'))->toBe(4);
});
test('5 неверных кодов инвалидируют запись (даже верный код больше не проходит)', function () {
$reg = $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
$real = $reg->json('_dev_plain_code');
$wrong = $real === '000000' ? '111111' : '000000';
for ($i = 0; $i < 5; $i++) {
$this->postJson('/api/auth/confirm-email', [
'email' => 'newclient@example.ru',
'code' => $wrong,
])->assertStatus(422);
}
$this->postJson('/api/auth/confirm-email', [
'email' => 'newclient@example.ru',
'code' => $real,
])->assertStatus(422)->assertJsonPath('reason', 'too_many_attempts');
});
test('протухший код отклоняется, resend выдаёт рабочий', function () {
$reg = $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
$user = User::where('email', 'newclient@example.ru')->first();
DB::table('email_verifications')->where('user_id', $user->id)
->update(['expires_at' => now()->subMinutes(5)]);
$this->postJson('/api/auth/confirm-email', [
'email' => 'newclient@example.ru',
'code' => $reg->json('_dev_plain_code'),
])->assertStatus(422)->assertJsonPath('reason', 'expired');
$resend = $this->postJson('/api/auth/resend-code', ['email' => 'newclient@example.ru'])->assertOk();
$newCode = $resend->json('_dev_plain_code');
expect($newCode)->toMatch('/^\d{6}$/');
$this->postJson('/api/auth/confirm-email', [
'email' => 'newclient@example.ru',
'code' => $newCode,
])->assertOk();
});
test('повторный register на неподтверждённый email не создаёт второго тенанта', function () {
$this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
$this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
expect(User::where('email', 'newclient@example.ru')->count())->toBe(1);
expect(Tenant::where('contact_email', 'newclient@example.ru')->count())->toBe(1);
});
test('register на активный email → 422', function () {
$tenant = Tenant::factory()->create();
User::factory()->create([
'tenant_id' => $tenant->id,
'email' => 'active@example.ru',
'is_active' => true,
]);
$this->postJson('/api/auth/register', registerPayload(['email' => 'active@example.ru']))
->assertStatus(422)->assertJsonValidationErrors(['email']);
});
test('resend на несуществующий email → унифицированный ответ (anti-enumeration)', function () {
$this->postJson('/api/auth/resend-code', ['email' => 'nobody@example.ru'])
->assertOk()
->assertJsonMissingPath('_dev_plain_code');
});