151 lines
5.7 KiB
PHP
151 lines
5.7 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
use App\Models\Tenant;
|
||
|
|
use App\Models\User;
|
||
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Тесты PATCH /api/auth/me/notification-preferences (Settings → Уведомления).
|
||
|
|
*
|
||
|
|
* Принимает {prefs: {event: {channel: bool}}, sound_enabled?: bool}.
|
||
|
|
* Валидация: события ∈ ALL_EVENTS (8), каналы ∈ {inapp, push, email}.
|
||
|
|
* Незадекларированные ключи отбрасываются (защита от schema-pollution).
|
||
|
|
*/
|
||
|
|
uses(DatabaseTransactions::class);
|
||
|
|
|
||
|
|
beforeEach(function () {
|
||
|
|
$this->tenant = Tenant::factory()->create();
|
||
|
|
$this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
|
||
|
|
$this->actingAs($this->user);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('PATCH без auth: 401', function () {
|
||
|
|
auth()->logout();
|
||
|
|
$this->patchJson('/api/auth/me/notification-preferences', [
|
||
|
|
'prefs' => ['new_lead' => ['email' => true]],
|
||
|
|
])->assertStatus(401);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('PATCH успех: сохраняет prefs + возвращает user', function () {
|
||
|
|
$response = $this->patchJson('/api/auth/me/notification-preferences', [
|
||
|
|
'prefs' => [
|
||
|
|
'new_lead' => ['inapp' => true, 'push' => false, 'email' => true],
|
||
|
|
'reminder' => ['inapp' => true, 'email' => true],
|
||
|
|
],
|
||
|
|
'sound_enabled' => false,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
$userResp = $response->json('user');
|
||
|
|
expect($userResp['notification_preferences']['new_lead']['email'])->toBeTrue();
|
||
|
|
expect($userResp['notification_preferences']['new_lead']['inapp'])->toBeTrue();
|
||
|
|
expect($userResp['notification_preferences']['new_lead']['push'])->toBeFalse();
|
||
|
|
expect($userResp['sound_enabled'])->toBeFalse();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('PATCH: неизвестные events отбрасываются', function () {
|
||
|
|
$response = $this->patchJson('/api/auth/me/notification-preferences', [
|
||
|
|
'prefs' => [
|
||
|
|
'new_lead' => ['email' => true],
|
||
|
|
'fake_event' => ['email' => true], // не в ALL_EVENTS — должно отброситься
|
||
|
|
],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
$prefs = $response->json('user.notification_preferences');
|
||
|
|
expect($prefs)->toHaveKey('new_lead');
|
||
|
|
expect($prefs)->not->toHaveKey('fake_event');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('PATCH: неизвестные каналы отбрасываются', function () {
|
||
|
|
$response = $this->patchJson('/api/auth/me/notification-preferences', [
|
||
|
|
'prefs' => [
|
||
|
|
'new_lead' => [
|
||
|
|
'email' => true,
|
||
|
|
'sms' => true, // SMS — не в нашей schema
|
||
|
|
'webhook' => true, // тоже не из schema
|
||
|
|
],
|
||
|
|
],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
$prefs = $response->json('user.notification_preferences.new_lead');
|
||
|
|
expect($prefs)->toHaveKey('email');
|
||
|
|
expect($prefs)->not->toHaveKey('sms');
|
||
|
|
expect($prefs)->not->toHaveKey('webhook');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('PATCH: 422 без prefs', function () {
|
||
|
|
$this->patchJson('/api/auth/me/notification-preferences', [
|
||
|
|
'sound_enabled' => true,
|
||
|
|
])->assertStatus(422);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('PATCH: sound_enabled опционален (без него не меняется)', function () {
|
||
|
|
$this->user->update(['sound_enabled' => true]);
|
||
|
|
|
||
|
|
$response = $this->patchJson('/api/auth/me/notification-preferences', [
|
||
|
|
'prefs' => ['new_lead' => ['email' => true]],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
expect($response->json('user.sound_enabled'))->toBeTrue();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('GET /api/auth/me возвращает notification_preferences + sound_enabled', function () {
|
||
|
|
$response = $this->getJson('/api/auth/me');
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
$user = $response->json('user');
|
||
|
|
expect($user)->toHaveKey('notification_preferences');
|
||
|
|
expect($user)->toHaveKey('sound_enabled');
|
||
|
|
// schema-default: 8 events.
|
||
|
|
expect($user['notification_preferences'])->toHaveKey('new_lead');
|
||
|
|
expect($user['notification_preferences'])->toHaveKey('reminder');
|
||
|
|
expect($user['notification_preferences'])->toHaveKey('low_balance');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('PATCH: prefs.* должен быть объект (не строка)', function () {
|
||
|
|
$this->patchJson('/api/auth/me/notification-preferences', [
|
||
|
|
'prefs' => [
|
||
|
|
'new_lead' => 'true', // строка вместо объекта
|
||
|
|
],
|
||
|
|
])->assertStatus(422);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('PATCH: bool-значения каналов кастятся', function () {
|
||
|
|
$response = $this->patchJson('/api/auth/me/notification-preferences', [
|
||
|
|
'prefs' => [
|
||
|
|
'new_lead' => ['email' => 1, 'inapp' => 0, 'push' => '1'],
|
||
|
|
],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
$prefs = $response->json('user.notification_preferences.new_lead');
|
||
|
|
expect($prefs['email'])->toBeTrue();
|
||
|
|
expect($prefs['inapp'])->toBeFalse();
|
||
|
|
expect($prefs['push'])->toBeTrue();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('PATCH полностью замещает prefs (не merge): ранее сохранённые отсутствующие events исчезают', function () {
|
||
|
|
// Дано: user имеет полный default-набор (schema-default 8 events).
|
||
|
|
$this->user->update(['notification_preferences' => [
|
||
|
|
'new_lead' => ['email' => true],
|
||
|
|
'reminder' => ['email' => true],
|
||
|
|
'low_balance' => ['email' => true],
|
||
|
|
]]);
|
||
|
|
|
||
|
|
// Update только new_lead — остальные пропадают (replace-семантика).
|
||
|
|
$response = $this->patchJson('/api/auth/me/notification-preferences', [
|
||
|
|
'prefs' => ['new_lead' => ['email' => false]],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
$prefs = $response->json('user.notification_preferences');
|
||
|
|
expect($prefs)->toHaveKey('new_lead');
|
||
|
|
expect($prefs)->not->toHaveKey('reminder');
|
||
|
|
expect($prefs)->not->toHaveKey('low_balance');
|
||
|
|
});
|