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); } }