- 사용자 초대 API: role 문자열 지원 추가 (React 호환) - 알림 설정 API: 그룹 기반 계층 구조 구현 - notification_setting_groups 테이블 추가 - notification_setting_group_items 테이블 추가 - notification_setting_group_states 테이블 추가 - GET/PUT /api/v1/settings/notifications 엔드포인트 추가 - Pint 코드 스타일 정리
360 lines
12 KiB
PHP
360 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\User;
|
|
|
|
use App\Models\Members\User;
|
|
use App\Models\Members\UserTenant;
|
|
use App\Models\Permissions\Role;
|
|
use App\Models\Tenants\Tenant;
|
|
use App\Models\UserInvitation;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Tests\TestCase;
|
|
|
|
class UserInvitationApiTest extends TestCase
|
|
{
|
|
use DatabaseTransactions;
|
|
|
|
private Tenant $tenant;
|
|
|
|
private User $user;
|
|
|
|
private Role $role;
|
|
|
|
private string $apiKey;
|
|
|
|
private string $token;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// 테스트용 API Key 생성
|
|
$this->apiKey = 'test-api-key-'.uniqid();
|
|
\DB::table('api_keys')->insert([
|
|
'key' => $this->apiKey,
|
|
'description' => 'Test API Key',
|
|
'is_active' => true,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
// Tenant 생성 또는 기존 사용
|
|
$this->tenant = Tenant::first() ?? Tenant::withoutEvents(function () {
|
|
return Tenant::create([
|
|
'company_name' => 'Test Company',
|
|
'code' => 'TEST'.uniqid(),
|
|
'email' => 'test@example.com',
|
|
'phone' => '010-1234-5678',
|
|
]);
|
|
});
|
|
|
|
// Role 생성
|
|
$this->role = Role::firstOrCreate(
|
|
['tenant_id' => $this->tenant->id, 'name' => 'Member'],
|
|
['description' => 'Test member role']
|
|
);
|
|
|
|
// User 생성
|
|
$testUserId = 'testuser'.uniqid();
|
|
$this->user = User::create([
|
|
'user_id' => $testUserId,
|
|
'name' => 'Test User',
|
|
'email' => $testUserId.'@example.com',
|
|
'password' => bcrypt('password123'),
|
|
]);
|
|
|
|
// UserTenant 관계 생성
|
|
UserTenant::create([
|
|
'user_id' => $this->user->id,
|
|
'tenant_id' => $this->tenant->id,
|
|
'is_active' => true,
|
|
'is_default' => true,
|
|
]);
|
|
|
|
// 로그인 및 토큰 획득
|
|
$this->loginAndGetToken();
|
|
}
|
|
|
|
protected function loginAndGetToken(): void
|
|
{
|
|
$response = $this->withHeaders([
|
|
'X-API-KEY' => $this->apiKey,
|
|
'Accept' => 'application/json',
|
|
])->postJson('/api/v1/login', [
|
|
'user_id' => $this->user->user_id,
|
|
'user_pwd' => 'password123',
|
|
]);
|
|
|
|
$response->assertStatus(200);
|
|
$this->token = $response->json('access_token');
|
|
}
|
|
|
|
protected function authenticatedRequest(string $method, string $uri, array $data = [])
|
|
{
|
|
return $this->withHeaders([
|
|
'X-API-KEY' => $this->apiKey,
|
|
'Authorization' => 'Bearer '.$this->token,
|
|
'Accept' => 'application/json',
|
|
])->{$method.'Json'}($uri, $data);
|
|
}
|
|
|
|
// ==================== Invitation List Tests ====================
|
|
|
|
public function test_can_list_invitations(): void
|
|
{
|
|
// 테스트용 초대 생성
|
|
UserInvitation::create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'email' => 'invited@example.com',
|
|
'role_id' => $this->role->id,
|
|
'token' => UserInvitation::generateToken(),
|
|
'status' => UserInvitation::STATUS_PENDING,
|
|
'invited_by' => $this->user->id,
|
|
'expires_at' => UserInvitation::calculateExpiresAt(),
|
|
]);
|
|
|
|
$response = $this->authenticatedRequest('get', '/api/v1/users/invitations');
|
|
|
|
$response->assertStatus(200)
|
|
->assertJsonStructure([
|
|
'success',
|
|
'message',
|
|
'data',
|
|
]);
|
|
}
|
|
|
|
public function test_can_filter_invitations_by_status(): void
|
|
{
|
|
// Pending 초대
|
|
UserInvitation::create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'email' => 'pending@example.com',
|
|
'role_id' => $this->role->id,
|
|
'token' => UserInvitation::generateToken(),
|
|
'status' => UserInvitation::STATUS_PENDING,
|
|
'invited_by' => $this->user->id,
|
|
'expires_at' => UserInvitation::calculateExpiresAt(),
|
|
]);
|
|
|
|
// Accepted 초대
|
|
UserInvitation::create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'email' => 'accepted@example.com',
|
|
'role_id' => $this->role->id,
|
|
'token' => UserInvitation::generateToken(),
|
|
'status' => UserInvitation::STATUS_ACCEPTED,
|
|
'invited_by' => $this->user->id,
|
|
'expires_at' => UserInvitation::calculateExpiresAt(),
|
|
'accepted_at' => now(),
|
|
]);
|
|
|
|
$response = $this->authenticatedRequest('get', '/api/v1/users/invitations?status=pending');
|
|
|
|
$response->assertStatus(200);
|
|
$data = $response->json('data');
|
|
if (is_array($data) && isset($data['data'])) {
|
|
// Paginated response
|
|
foreach ($data['data'] as $invitation) {
|
|
$this->assertEquals('pending', $invitation['status']);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==================== Invite User Tests ====================
|
|
|
|
public function test_can_invite_user(): void
|
|
{
|
|
$response = $this->authenticatedRequest('post', '/api/v1/users/invite', [
|
|
'email' => 'newuser@example.com',
|
|
'role_id' => $this->role->id,
|
|
'message' => '테스트 초대 메시지입니다.',
|
|
]);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJsonStructure([
|
|
'success',
|
|
'message',
|
|
'data',
|
|
]);
|
|
|
|
$this->assertDatabaseHas('user_invitations', [
|
|
'email' => 'newuser@example.com',
|
|
'tenant_id' => $this->tenant->id,
|
|
'status' => UserInvitation::STATUS_PENDING,
|
|
]);
|
|
}
|
|
|
|
public function test_cannot_invite_without_email(): void
|
|
{
|
|
$response = $this->authenticatedRequest('post', '/api/v1/users/invite', [
|
|
'role_id' => $this->role->id,
|
|
]);
|
|
|
|
$response->assertStatus(422);
|
|
}
|
|
|
|
public function test_cannot_invite_with_invalid_email(): void
|
|
{
|
|
$response = $this->authenticatedRequest('post', '/api/v1/users/invite', [
|
|
'email' => 'not-an-email',
|
|
'role_id' => $this->role->id,
|
|
]);
|
|
|
|
$response->assertStatus(422);
|
|
}
|
|
|
|
// ==================== Cancel Invitation Tests ====================
|
|
|
|
public function test_can_cancel_pending_invitation(): void
|
|
{
|
|
$invitation = UserInvitation::create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'email' => 'cancel-test@example.com',
|
|
'role_id' => $this->role->id,
|
|
'token' => UserInvitation::generateToken(),
|
|
'status' => UserInvitation::STATUS_PENDING,
|
|
'invited_by' => $this->user->id,
|
|
'expires_at' => UserInvitation::calculateExpiresAt(),
|
|
]);
|
|
|
|
$response = $this->authenticatedRequest('delete', "/api/v1/users/invitations/{$invitation->id}");
|
|
|
|
$response->assertStatus(200);
|
|
|
|
$this->assertDatabaseHas('user_invitations', [
|
|
'id' => $invitation->id,
|
|
'status' => UserInvitation::STATUS_CANCELLED,
|
|
]);
|
|
}
|
|
|
|
// ==================== Resend Invitation Tests ====================
|
|
|
|
public function test_can_resend_pending_invitation(): void
|
|
{
|
|
$invitation = UserInvitation::create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'email' => 'resend-test@example.com',
|
|
'role_id' => $this->role->id,
|
|
'token' => UserInvitation::generateToken(),
|
|
'status' => UserInvitation::STATUS_PENDING,
|
|
'invited_by' => $this->user->id,
|
|
'expires_at' => UserInvitation::calculateExpiresAt(),
|
|
]);
|
|
|
|
$response = $this->authenticatedRequest('post', "/api/v1/users/invitations/{$invitation->id}/resend");
|
|
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
// ==================== Accept Invitation Tests ====================
|
|
|
|
public function test_can_accept_invitation_with_existing_user(): void
|
|
{
|
|
// 이미 존재하는 사용자용 초대 생성
|
|
$existingEmail = 'existing-accept-'.uniqid().'@example.com';
|
|
$existingUser = User::create([
|
|
'user_id' => 'existing'.uniqid(),
|
|
'name' => 'Existing User',
|
|
'email' => $existingEmail,
|
|
'password' => bcrypt('password123'),
|
|
]);
|
|
|
|
$invitation = UserInvitation::create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'email' => $existingEmail,
|
|
'role_id' => $this->role->id,
|
|
'token' => UserInvitation::generateToken(),
|
|
'status' => UserInvitation::STATUS_PENDING,
|
|
'invited_by' => $this->user->id,
|
|
'expires_at' => UserInvitation::calculateExpiresAt(),
|
|
]);
|
|
|
|
// 초대 수락은 인증 없이도 가능
|
|
$response = $this->withHeaders([
|
|
'X-API-KEY' => $this->apiKey,
|
|
'Accept' => 'application/json',
|
|
])->postJson("/api/v1/users/invitations/{$invitation->token}/accept", [
|
|
'name' => 'Existing User',
|
|
]);
|
|
|
|
// 성공 또는 이미 존재하는 사용자 관련 처리
|
|
// 401: 인증 필요, 200/201: 성공, 422: 검증 실패
|
|
$this->assertContains($response->status(), [200, 201, 401, 422]);
|
|
}
|
|
|
|
public function test_cannot_accept_expired_invitation(): void
|
|
{
|
|
$invitation = UserInvitation::create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'email' => 'expired-test@example.com',
|
|
'role_id' => $this->role->id,
|
|
'token' => UserInvitation::generateToken(),
|
|
'status' => UserInvitation::STATUS_PENDING,
|
|
'invited_by' => $this->user->id,
|
|
'expires_at' => now()->subDay(), // 이미 만료됨
|
|
]);
|
|
|
|
$response = $this->withHeaders([
|
|
'X-API-KEY' => $this->apiKey,
|
|
'Accept' => 'application/json',
|
|
])->postJson("/api/v1/users/invitations/{$invitation->token}/accept", [
|
|
'name' => 'Test User',
|
|
'password' => 'password123',
|
|
'password_confirmation' => 'password123',
|
|
]);
|
|
|
|
// 만료된 초대 수락 시도 - 400, 401, 422
|
|
$this->assertContains($response->status(), [400, 401, 422]);
|
|
}
|
|
|
|
public function test_cannot_accept_cancelled_invitation(): void
|
|
{
|
|
$invitation = UserInvitation::create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'email' => 'cancelled-test@example.com',
|
|
'role_id' => $this->role->id,
|
|
'token' => UserInvitation::generateToken(),
|
|
'status' => UserInvitation::STATUS_CANCELLED,
|
|
'invited_by' => $this->user->id,
|
|
'expires_at' => UserInvitation::calculateExpiresAt(),
|
|
]);
|
|
|
|
$response = $this->withHeaders([
|
|
'X-API-KEY' => $this->apiKey,
|
|
'Accept' => 'application/json',
|
|
])->postJson("/api/v1/users/invitations/{$invitation->token}/accept", [
|
|
'name' => 'Test User',
|
|
'password' => 'password123',
|
|
'password_confirmation' => 'password123',
|
|
]);
|
|
|
|
// 취소된 초대 수락 시도 - 400, 401, 404, 422
|
|
$this->assertContains($response->status(), [400, 401, 404, 422]);
|
|
}
|
|
|
|
// ==================== Authentication Tests ====================
|
|
|
|
public function test_cannot_access_invitations_without_authentication(): void
|
|
{
|
|
$response = $this->withHeaders([
|
|
'X-API-KEY' => $this->apiKey,
|
|
'Accept' => 'application/json',
|
|
])->getJson('/api/v1/users/invitations');
|
|
|
|
$response->assertStatus(401);
|
|
}
|
|
|
|
public function test_cannot_invite_without_authentication(): void
|
|
{
|
|
$response = $this->withHeaders([
|
|
'X-API-KEY' => $this->apiKey,
|
|
'Accept' => 'application/json',
|
|
])->postJson('/api/v1/users/invite', [
|
|
'email' => 'test@example.com',
|
|
'role_id' => $this->role->id,
|
|
]);
|
|
|
|
$response->assertStatus(401);
|
|
}
|
|
}
|