user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $repository = Mockery::mock(DingPayableWorkflowMirrorRepository::class); $repository->shouldReceive('paginate')->once()->andReturn([ 'data' => [[ 'process_instance_id' => 'PROC-TEST-001', 'workflow' => [ 'businessId' => '202607031324000376786', 'title' => '应付申请流程', 'status' => 'COMPLETED', 'result' => 'agree', 'originatorUserId' => 'user-1', 'originatorUserName' => '扬灵', 'originatorDeptName' => '财务部', 'createAt' => '2026-08-28 10:00:00', 'finishTime' => '2026-08-28 11:00:00', ], 'detail' => ['类型' => '快递运费', '用途' => '拉到江西仓的车费', '付款金额' => '1200'], 'fields' => [ '应付明细' => [], '付款明细' => [['付款金额' => '1200']], '附件' => '', '未签字版合同附件或其他明细附件' => [[ 'spaceId' => '369334649', 'fileName' => '测试对账附件.xlsx', 'fileId' => '123456', 'fileType' => 'xlsx', ]], ], 'tasks' => [['status' => 'COMPLETED']], ]], 'total' => 1, 'status' => 'succeeded', 'last_synced_at' => '2026-08-28 11:05:00', ]); $this->app->instance(DingPayableWorkflowMirrorRepository::class, $repository); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'payable-cloud-warehouse-vehicle-fee', ]))->assertOk() ->assertSee('应付云仓车费') ->assertSee('钉钉审批编号') ->assertSee('202607031324000376786') ->assertSee('扬灵') ->assertSee('已完成') ->assertSee('拉到江西仓的车费') ->assertSee('用途同时包含“仓”“车费”') ->assertDontSeeText('全部表单字段') ->assertDontSeeText('付款明细') ->assertDontSeeText('审批任务') ->assertSee('测试对账附件.xlsx') ->assertSee('process_instance_id=PROC-TEST-001', false) ->assertSee('file_id=123456', false) ->assertSee('ding-payable-attachment"', false) ->assertSee('data-ding-payable-row', false) ->assertSee('is-clickable', false) ->assertSee('is-active', false) ->assertSee('镜像状态:succeeded'); } public function test_cloud_warehouse_vehicle_fee_page_uses_ding_talk_rejection_result(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $repository = Mockery::mock(DingPayableWorkflowMirrorRepository::class); $repository->shouldReceive('paginate')->once()->andReturn([ 'data' => [[ 'process_instance_id' => 'PROC-TEST-REJECTED', 'workflow' => [ 'businessId' => '202608291446000258827', 'title' => '应付申请流程', 'status' => 'COMPLETED', 'result' => 'refuse', 'originatorUserId' => 'user-1', 'originatorUserName' => '扬灵', ], 'detail' => ['类型' => '快递运费', '用途' => '拉到江西仓的车费'], 'fields' => [], 'tasks' => [], ]], 'total' => 1, 'status' => 'succeeded', 'last_synced_at' => '2026-08-31 13:42:04', ]); $this->app->instance(DingPayableWorkflowMirrorRepository::class, $repository); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'payable-cloud-warehouse-vehicle-fee', ]))->assertOk() ->assertSee('已拒绝', false) ->assertDontSee('已完成', false) ->assertSee('已拒绝'); } public function test_cloud_warehouse_vehicle_fee_page_keeps_an_unavailable_mirror_readable(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $repository = Mockery::mock(DingPayableWorkflowMirrorRepository::class); $repository->shouldReceive('paginate')->andThrow(new \RuntimeException('not ready')); $this->app->instance(DingPayableWorkflowMirrorRepository::class, $repository); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'payable-cloud-warehouse-vehicle-fee', ]))->assertOk() ->assertSee('钉钉应付流程镜像尚未完成同步,请稍后重试。') ->assertSee('镜像读取失败,请稍后重试。'); } public function test_cloud_warehouse_vehicle_fee_page_passes_workflow_time_and_status_filters(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $repository = Mockery::mock(DingPayableWorkflowMirrorRepository::class); $repository->shouldReceive('paginate')->once()->with( [ 'process_instance_id' => '', 'created_from' => '2026-07-01', 'finished_to' => '2026-07-31', 'status' => 'COMPLETED', ], 1, Mockery::type('int'), )->andReturn([ 'data' => [], 'total' => 0, 'status' => 'succeeded', 'last_synced_at' => '2026-08-29 10:11:24', ]); $this->app->instance(DingPayableWorkflowMirrorRepository::class, $repository); $this->loginAs()->get(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'payable-cloud-warehouse-vehicle-fee', 'created_from' => '2026-07-01', 'finished_to' => '2026-07-31', 'status' => 'completed', ]))->assertOk() ->assertSee('流程时间(创建时间至完成时间)') ->assertSee('2026-07-01') ->assertSee('2026-07-31') ->assertSee('状态') ->assertSee('全部状态'); } public function test_attachment_download_streams_dingtalk_file(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); config([ 'dingtalk.client_id' => 'test-client-id', 'dingtalk.client_secret' => 'test-client-secret', ]); $repository = Mockery::mock(DingPayableWorkflowMirrorRepository::class); $repository->shouldReceive('attachment') ->once() ->with('PROC-TEST-001', '123456') ->andReturn(['fileName' => '测试对账附件.xlsx', 'fileType' => 'xlsx', 'fileSize' => 9]); $this->app->instance(DingPayableWorkflowMirrorRepository::class, $repository); Http::fake([ 'oapi.dingtalk.com/*' => Http::response(['access_token' => 'test-token']), 'api.dingtalk.com/*' => Http::response([ 'success' => true, 'result' => ['downloadUri' => 'https://fake-oss.example.com/file'], ]), 'fake-oss.example.com/*' => Http::response('file-bytes', 200, ['Content-Type' => 'application/octet-stream']), ]); $response = $this->loginAs()->get(route('business.courier-payables.ding-payable-attachment.download', [ 'process_instance_id' => 'PROC-TEST-001', 'file_id' => '123456', ])); $response->assertOk() ->assertHeader('content-type', 'application/octet-stream'); $this->assertSame('file-bytes', $response->streamedContent()); $this->assertStringContainsString( rawurlencode('测试对账附件.xlsx'), (string) $response->headers->get('content-disposition'), ); } public function test_attachment_download_rejects_unknown_file(): void { $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); $repository = Mockery::mock(DingPayableWorkflowMirrorRepository::class); $repository->shouldReceive('attachment')->once()->andReturn(null); $this->app->instance(DingPayableWorkflowMirrorRepository::class, $repository); Http::fake(); $this->loginAs() ->withExceptionHandling() ->withHeaders(['X-Requested-With' => 'XMLHttpRequest']) ->get(route('business.courier-payables.ding-payable-attachment.download', [ 'process_instance_id' => 'PROC-UNKNOWN', 'file_id' => '999', ])) ->assertNotFound(); Http::assertNothingSent(); } }