pendingRequest(); $this->loginAs() ->get(route('system-settings.dingtalk-bindings.index')) ->assertOk() ->assertSeeText('钉钉账号绑定') ->assertSeeText('企***@example.test') ->assertSeeText('*******8000') ->assertDontSee('enterprise-user@example.test') ->assertDontSee('13800138000'); $this->post(route('system-settings.dingtalk-bindings.approve', $bindingRequest->id), [ 'user_id' => $this->user->id, ])->assertRedirect(route('system-settings.dingtalk-bindings.index')); $this->assertDatabaseHas('dingtalk_identities', [ 'user_id' => $this->user->id, 'union_id' => 'union-pending-admin', 'bound_by' => $this->user->id, ]); $this->assertDatabaseHas('dingtalk_binding_requests', [ 'id' => $bindingRequest->id, 'status' => DingTalkBindingRequest::APPROVED, 'matched_user_id' => $this->user->id, 'reviewed_by' => $this->user->id, ]); $this->assertDatabaseHas('dingtalk_auth_audits', [ 'event' => 'binding_admin_approved', 'outcome' => 'success', 'user_id' => $this->user->id, 'actor_user_id' => $this->user->id, ]); } public function test_admin_can_reject_pending_request_without_creating_identity(): void { $bindingRequest = $this->pendingRequest(); $this->loginAs() ->post(route('system-settings.dingtalk-bindings.reject', $bindingRequest->id)) ->assertRedirect(route('system-settings.dingtalk-bindings.index')); $this->assertDatabaseHas('dingtalk_binding_requests', [ 'id' => $bindingRequest->id, 'status' => DingTalkBindingRequest::REJECTED, ]); $this->assertDatabaseMissing('dingtalk_identities', ['union_id' => 'union-pending-admin']); $this->assertDatabaseHas('dingtalk_auth_audits', [ 'event' => 'binding_rejected', 'outcome' => 'denied', ]); } public function test_admin_can_revoke_binding_and_audit_the_action(): void { $bindingRequest = $this->pendingRequest(['status' => DingTalkBindingRequest::APPROVED]); $identity = DingTalkIdentity::query()->create([ 'user_id' => $this->user->id, 'union_id' => $bindingRequest->union_id, 'open_id' => $bindingRequest->open_id, 'dingtalk_user_id' => $bindingRequest->dingtalk_user_id, 'nickname' => $bindingRequest->nickname, 'bound_at' => now(), ]); $this->loginAs() ->delete(route('system-settings.dingtalk-bindings.destroy', $identity->id)) ->assertRedirect(route('system-settings.dingtalk-bindings.index')); $this->assertDatabaseMissing('dingtalk_identities', ['id' => $identity->id]); $this->assertDatabaseHas('dingtalk_binding_requests', [ 'id' => $bindingRequest->id, 'status' => DingTalkBindingRequest::REVOKED, ]); $this->assertDatabaseHas('dingtalk_auth_audits', [ 'event' => 'binding_revoked', 'outcome' => 'success', 'user_id' => $this->user->id, ]); } public function test_regular_user_cannot_open_binding_administration(): void { $regularUser = User::query()->create([ 'name' => '普通用户', 'email' => 'regular-user@example.test', 'password' => 'KnownPassword123!', 'locale' => 'zh-CN', 'enabled' => true, 'landing_page' => 'dashboard', 'created_by' => $this->user->id, 'created_from' => 'test', ]); $regularUser->roles()->attach(Role::where('name', SystemAccess::USER)->firstOrFail()->id); $regularUser->companies()->attach($this->company->id); $this->withExceptionHandling(); $this->loginAs($regularUser) ->get(route('system-settings.dingtalk-bindings.index')) ->assertForbidden(); } private function pendingRequest(array $overrides = []): DingTalkBindingRequest { return DingTalkBindingRequest::query()->create(array_merge([ 'union_id' => 'union-pending-admin', 'open_id' => 'open-pending-admin', 'dingtalk_user_id' => 'ding-user-pending-admin', 'nickname' => '企业员工', 'email_hash' => hash('sha256', 'enterprise-user@example.test'), 'mobile_hash' => hash('sha256', '13800138000'), 'email_hint' => '企***@example.test', 'mobile_hint' => '*******8000', 'status' => DingTalkBindingRequest::PENDING, 'match_reason' => 'no_unique_account', 'last_attempted_at' => now(), ], $overrides)); } }