CaiWuXiTong/tests/Feature/Business/CourierSigningSubjectTest.php
limeng 597104430e 协议签署:每条公司主体增加删除功能 (#304)
每条公司主体增加确认删除,保留历史快照,删除默认主体后选择剩余主体,全部删除回退当前公司。相关21项测试246条断言及隔离页面交互通过。
2026-10-11 14:26:31 +08:00

190 lines
7.9 KiB
PHP

<?php
namespace Tests\Feature\Business;
use App\Models\Auth\Permission;
use App\Models\Business\CourierAgreementSignature;
use App\Models\Business\CourierSigningSubject;
use Tests\Feature\FeatureTestCase;
class CourierSigningSubjectTest extends FeatureTestCase
{
protected function grantWorkspaceAccess(): void
{
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
}
public function testAdminCanCreateUpdateAndSwitchSigningSubjects(): void
{
$this->grantWorkspaceAccess();
$first = $this->loginAs()->postJson(
route('business.courier-agreement-signatures.subjects.store'),
['name' => '甲公司', 'unified_social_credit_code' => '91320100MA1TEST001'],
);
$first->assertOk()
->assertJsonPath('subject.name', '甲公司')
->assertJsonPath('subject.is_default', true);
$firstSubject = CourierSigningSubject::query()->where('name', '甲公司')->firstOrFail();
$second = $this->postJson(
route('business.courier-agreement-signatures.subjects.store'),
['name' => '乙公司'],
);
$second->assertOk()->assertJsonPath('subject.is_default', false);
$secondSubject = CourierSigningSubject::query()->where('name', '乙公司')->firstOrFail();
$this->postJson(
route('business.courier-agreement-signatures.subjects.default', $secondSubject),
)->assertOk()->assertJsonPath('subject.is_default', true);
$this->assertFalse($firstSubject->fresh()->is_default);
$this->assertTrue($secondSubject->fresh()->is_default);
$this->postJson(
route('business.courier-agreement-signatures.subjects.update', $secondSubject),
['name' => '乙公司(新)', 'unified_social_credit_code' => '91320100MA1TEST002'],
)->assertOk()->assertJsonPath('subject.name', '乙公司(新)');
$this->assertDatabaseHas('business_courier_signing_subjects', [
'id' => $secondSubject->id,
'name' => '乙公司(新)',
'is_default' => 1,
]);
}
public function testDuplicateSubjectNameIsRejected(): void
{
$this->grantWorkspaceAccess();
$this->loginAs()->postJson(
route('business.courier-agreement-signatures.subjects.store'),
['name' => '甲公司'],
)->assertOk();
$this->withExceptionHandling()->postJson(
route('business.courier-agreement-signatures.subjects.store'),
['name' => '甲公司'],
)->assertStatus(422)
->assertJsonValidationErrors(['name']);
}
public function testDeletingAnOrdinarySubjectPreservesTheDefaultAndHistoricalAgreement(): void
{
$this->grantWorkspaceAccess();
$default = $this->subject('默认公司', true);
$subject = $this->subject('历史签署公司');
$task = CourierAgreementSignature::create([
'company_id' => $this->company->id,
'business_no' => 'AGR-SUBJECT-DELETE',
'idempotency_key' => 'subject-delete-snapshot',
'document_type' => 'account-proof',
'template_code' => 'TPL-ACCOUNT-CERT-V2',
'template_version' => '2',
'source_file_name' => '收款账户证明文件.doc',
'status' => CourierAgreementSignature::STATUS_ARCHIVED,
'courier_name' => '测试快递',
'company_full_name' => $subject->name,
'organization_name' => '测试快递有限公司',
'payload' => ['signing_subject' => $subject->name],
'environment' => 'sandbox',
'source_pdf_path' => 'subject-delete/source.pdf',
'source_pdf_sha256' => str_repeat('a', 64),
'created_by' => $this->user->id,
'updated_by' => $this->user->id,
]);
$this->loginAs()->deleteJson($this->deleteUrl($subject), ['delete_confirmed' => true])
->assertOk()->assertJsonCount(1, 'subjects')
->assertJsonPath('subjects.0.id', $default->id)
->assertJsonPath('subjects.0.is_default', true);
$this->assertSoftDeleted($subject);
$this->assertSame($this->user->id, $subject->fresh()->updated_by);
$this->assertSame($subject->name, $task->fresh()->company_full_name);
$this->assertSame($subject->name, $task->fresh()->payload['signing_subject']);
$this->assertFalse($task->fresh()->trashed());
$this->withExceptionHandling()->deleteJson($this->deleteUrl($subject), ['delete_confirmed' => true])->assertNotFound();
}
public function testDeletingTheDefaultSelectsOneRemainingSubjectInTheSameCompany(): void
{
$this->grantWorkspaceAccess();
$default = $this->subject('默认公司', true);
$next = $this->subject('剩余公司一');
$other = $this->subject('剩余公司二');
$foreign = $this->subject('其他租户公司', true, $this->company->id + 100);
$this->loginAs()->deleteJson($this->deleteUrl($default), ['delete_confirmed' => true])
->assertOk()->assertJsonCount(2, 'subjects')
->assertJsonPath('subjects.0.id', $next->id)
->assertJsonPath('subjects.0.is_default', true);
$this->assertSoftDeleted($default);
$this->assertTrue($next->fresh()->is_default);
$this->assertFalse($other->fresh()->is_default);
$this->assertTrue($foreign->fresh()->is_default);
$this->assertSame(1, CourierSigningSubject::query()->where('is_default', true)->count());
}
public function testLastSubjectCanBeDeletedAndANewSubjectBecomesDefault(): void
{
$this->grantWorkspaceAccess();
$subject = $this->subject('唯一公司', true);
$this->loginAs()->postJson($this->deleteUrl($subject), ['_method' => 'DELETE', 'delete_confirmed' => '1'])
->assertOk()->assertJsonCount(0, 'subjects')->assertJsonPath('subject', null);
$this->assertSoftDeleted($subject);
$this->postJson(route('business.courier-agreement-signatures.subjects.store'), ['name' => '唯一公司'])
->assertOk()->assertJsonPath('subject.is_default', true);
}
public function testSubjectDeletionRequiresConfirmation(): void
{
$this->grantWorkspaceAccess();
$subject = $this->subject('保留公司', true);
$this->withExceptionHandling()->loginAs()->deleteJson($this->deleteUrl($subject))
->assertStatus(422)->assertJsonValidationErrors(['delete_confirmed']);
$this->assertFalse($subject->fresh()->trashed());
}
public function testSubjectDeletionEnforcesCompanyAndManagementPermission(): void
{
$this->grantWorkspaceAccess();
$subject = $this->subject('本租户公司', true);
$foreign = $this->subject('其他租户公司', true, $this->company->id + 100);
$this->withExceptionHandling()->loginAs()->deleteJson($this->deleteUrl($foreign), ['delete_confirmed' => true])
->assertNotFound();
$this->assertFalse($foreign->fresh()->trashed());
$this->user->roles()->detach();
$this->user->unsetRelation('roles');
$this->deleteJson($this->deleteUrl($subject), ['delete_confirmed' => true])->assertForbidden();
$this->assertFalse($subject->fresh()->trashed());
}
private function subject(string $name, bool $default = false, ?int $companyId = null): CourierSigningSubject
{
return CourierSigningSubject::create([
'company_id' => $companyId ?? $this->company->id,
'name' => $name,
'is_default' => $default,
'created_by' => $this->user->id,
'updated_by' => $this->user->id,
]);
}
private function deleteUrl(CourierSigningSubject $subject): string
{
return route('business.courier-agreement-signatures.subjects.destroy', $subject);
}
}