user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); // Contract management must work even when upstream courier mirrors are unavailable. $this->app->instance(CourierInformationMirrorRepository::class, Mockery::mock(CourierInformationMirrorRepository::class)); } protected function tearDown(): void { Carbon::setTestNow(); parent::tearDown(); } public function test_only_archived_courier_cooperation_contracts_are_visible_without_duplicate_writes(): void { $contract = $this->signature('快递测试公司'); $this->signature('供应商不应出现', ['partner_type' => 'supplier']); $this->signature('未知合作方不应出现', ['partner_type' => 'other']); $this->signature('待归档不应出现', [], ['status' => 'completed']); $this->signature('证明文件不应出现', [], ['document_type' => 'account-proof']); $this->signature('其他租户不应出现', [], ['company_id' => $this->company->id + 10]); $deleted = $this->signature('已删除不应出现'); $deleted->delete(); $count = DB::table('business_courier_agreement_signatures')->count(); $this->page()->assertOk()->assertSeeText('快递测试公司')->assertSeeText('KD-2026-001') ->assertSeeText('乙方签署企业')->assertSeeText('法人测试姓名')->assertSeeText($this->user->name) ->assertSeeText('查看已签署合同')->assertSeeText('合同管理')->assertSeeText('甲方签署主体') ->assertDontSeeText('供应商不应出现')->assertDontSeeText('未知合作方不应出现') ->assertDontSeeText('待归档不应出现')->assertDontSeeText('证明文件不应出现') ->assertDontSeeText('其他租户不应出现')->assertDontSeeText('已删除不应出现') ->assertSee(route('business.courier-contracts.download', ['agreementSignature' => $contract->id, 'kind' => 'signed']), false); $this->page()->assertOk(); $this->assertSame($count, DB::table('business_courier_agreement_signatures')->count()); } public function test_reminders_use_contract_dates_and_include_today_and_day_thirty(): void { $this->signature('今天到期快递', ['cooperation_end_date' => '2026-10-15']); $this->signature('三十天边界快递', ['cooperation_end_date' => '2026-11-14']); $this->signature('有效期快递', ['cooperation_end_date' => '2026-11-15']); $this->signature('届满快递', ['cooperation_end_date' => '2026-10-14']); $this->signature('未来开始快递', ['cooperation_start_date' => '2026-10-20', 'cooperation_end_date' => '2026-11-01']); $response = $this->page()->assertOk()->assertSeeText('今天到期')->assertSeeText('已届满 1 天'); $counts = $response->viewData('content')['courierContractCounts']; $this->assertSame(['due' => 2, 'expired' => 1, 'active' => 1, 'upcoming' => 1, 'unknown' => 0], $counts); $this->page(['contract_status' => 'due'])->assertSeeText('今天到期快递')->assertSeeText('三十天边界快递') ->assertDontSeeText('有效期快递')->assertDontSeeText('届满快递')->assertDontSeeText('未来开始快递'); } public function test_missing_invalid_and_personal_metadata_is_not_guessed_from_signer_or_deadline(): void { $this->signature('缺日期快递', ['cooperation_start_date' => null, 'cooperation_end_date' => null, 'cooperation_contract_number' => null, 'cooperation_party_b_legal_representative' => null], ['operator_name' => '经办人不是法人', 'expires_at' => '2026-10-16']); $this->signature('无效日期快递', ['cooperation_end_date' => '2026-02-30']); $this->signature('倒置日期快递', ['cooperation_start_date' => '2027-01-01']); $this->signature('个人签署快递', ['counterparty_signer_type' => 'person']); $legacy = $this->signature('旧快递合同', ['partner_type' => null]); $payload = $legacy->payload; unset($payload['partner_type']); $legacy->update(['payload' => $payload]); $this->page()->assertOk()->assertSeeText('期限待核对')->assertSeeText('无法计算')->assertSeeText('未填写') ->assertSeeText('不适用(个人签署)')->assertSeeText('旧快递合同')->assertDontSeeText('经办人不是法人'); $this->page(['contract_status' => 'unknown'])->assertSeeText('缺日期快递')->assertSeeText('无效日期快递') ->assertSeeText('倒置日期快递')->assertDontSeeText('个人签署快递'); } public function test_search_date_filter_and_pagination_apply_to_all_archives_not_just_the_current_page(): void { setting()->set('default.list_limit', 2); setting()->save(); for ($i = 1; $i <= 5; $i++) { $this->signature('分页快递-' . $i, ['cooperation_contract_number' => 'PAGE-' . $i, 'cooperation_end_date' => '2026-11-0' . $i]); } $this->page()->assertSeeText('第 1 / 3 页,本页 2 条,共 5 条'); $this->page(['page' => 3])->assertSeeText('分页快递-5')->assertDontSeeText('分页快递-1'); $this->page(['contract_search' => 'PAGE-5'])->assertSeeText('分页快递-5')->assertDontSeeText('分页快递-1'); $this->page(['contract_search' => '乙方签署企业', 'contract_end_from' => '2026-11-04', 'contract_end_to' => '2026-11-03']) ->assertSeeText('分页快递-3')->assertSeeText('分页快递-4')->assertDontSeeText('分页快递-5'); } public function test_unreadable_archive_and_missing_attachment_are_explicit_and_not_exposed(): void { $unreadable = $this->signature('无法解密不应出现'); DB::table('business_courier_agreement_signatures')->where('id', $unreadable->id)->update(['payload' => 'invalid-encrypted-payload']); $missing = $this->signature('附件缺失快递'); Storage::disk('uploads')->delete($missing->signed_pdf_path); $this->page()->assertOk()->assertSeeText('加密快照无法读取')->assertDontSeeText('无法解密不应出现') ->assertSeeText('归档附件缺失,请核对')->assertDontSee($missing->signed_pdf_path, false); $this->withExceptionHandling(); $this->download($unreadable)->assertNotFound(); $this->download($missing)->assertNotFound(); } public function test_download_rechecks_tenant_archive_type_and_partner_and_never_serves_unsigned_originals(): void { $contract = $this->signature('可下载合同'); $this->download($contract)->assertOk()->assertHeader('content-type', 'application/pdf'); $this->download($contract, 'certificate')->assertOk(); $this->withExceptionHandling(); foreach ([ $this->signature('其他公司', [], ['company_id' => $this->company->id + 10]), $this->signature('待归档', [], ['status' => 'completed']), $this->signature('供应商', ['partner_type' => 'supplier']), $this->signature('非合同', [], ['document_type' => 'account-proof']), ] as $ineligible) { $this->download($ineligible)->assertNotFound(); } $this->download($contract, 'source')->assertNotFound(); } public function test_read_failure_is_not_presented_as_an_empty_healthy_archive(): void { Schema::drop('business_courier_agreement_signatures'); $this->page()->assertOk()->assertSeeText('协议签署数据表尚未建立')->assertSeeText('合同数据暂时不可用,请重试。'); } public function test_courier_information_access_is_required_for_archive_downloads(): void { $contract = $this->signature('页面权限测试'); $this->user->syncRoles([\App\Models\Auth\Role::where('name', 'user')->firstOrFail()->id]); setting()->set('system.sidebar_permissions', json_encode([ 'configured' => ['user_' . $this->user->id], 'subjects' => ['user_' . $this->user->id => ['courier-payables/courier-information']], ], JSON_UNESCAPED_UNICODE)); setting()->save(); $this->page()->assertOk(); $this->download($contract)->assertOk(); setting()->set('system.sidebar_permissions', json_encode([ 'configured' => ['user_' . $this->user->id], 'subjects' => ['user_' . $this->user->id => []], ], JSON_UNESCAPED_UNICODE)); setting()->save(); $this->withExceptionHandling(); $this->download($contract)->assertForbidden(); } public function test_object_storage_is_not_queried_per_row_when_opening_the_contract_list(): void { $this->signature('远程附件列表'); $this->loginAs(); config(['filesystems.disks.uploads.driver' => 's3']); Storage::shouldReceive('disk')->never(); $data = app(\App\Business\CourierContractPageData::class)->page(); $this->assertNull($data['courierContractError']); $this->assertTrue($data['courierContractRows']->items()[0]['has_signed_file']); } private function page(array $query = []) { return $this->loginAs()->get(route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-information']) . '?' . http_build_query(['courier_tab' => 'contracts'] + $query)); } private function download(CourierAgreementSignature $signature, string $kind = 'signed') { return $this->loginAs()->getJson(route('business.courier-contracts.download', ['agreementSignature' => $signature->id, 'kind' => $kind]), ['X-Requested-With' => 'XMLHttpRequest']); } private function signature(string $name, array $payload = [], array $attributes = []): CourierAgreementSignature { $key = 'test-' . bin2hex(random_bytes(8)); Storage::disk('uploads')->put($key . '/signed.pdf', '%PDF-1.4 synthetic signed contract'); Storage::disk('uploads')->put($key . '/certificate.pdf', '%PDF-1.4 synthetic certificate'); return CourierAgreementSignature::create($attributes + [ 'company_id' => $this->company->id, 'business_no' => $key, 'idempotency_key' => $key, 'document_type' => 'cooperation-contract', 'template_code' => 'TPL-COOPERATION-CONTRACT-V1', 'template_version' => '1', 'source_file_name' => 'contract.pdf', 'status' => 'archived', 'courier_name' => $name, 'company_full_name' => '甲方企业', 'organization_name' => '乙方签署企业', 'operator_name' => '经办人', 'payload' => $payload + [ 'partner_type' => 'courier', 'cooperation_contract_number' => 'KD-2026-001', 'cooperation_party_a_name' => '甲方企业', 'cooperation_party_b_name' => '乙方签署企业', 'cooperation_party_b_legal_representative' => '法人测试姓名', 'cooperation_start_date' => '2026-01-01', 'cooperation_end_date' => '2026-11-01', ], 'source_pdf_path' => $key . '/source.pdf', 'source_pdf_sha256' => '', 'signed_pdf_path' => $key . '/signed.pdf', 'certificate_path' => $key . '/certificate.pdf', 'archived_at' => now(), 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); } }