158 lines
6.4 KiB
PHP
158 lines
6.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Business;
|
|
|
|
use App\Models\Auth\Permission;
|
|
use Illuminate\Routing\Exceptions\InvalidSignatureException;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Modules\EcommerceFinance\Services\SupplierDingTalkNotifier;
|
|
use Modules\EcommerceFinance\Services\SupplierSummaryConfirmation;
|
|
use Modules\EcommerceFinance\Services\SupplierSummaryShareLink;
|
|
use Tests\Feature\FeatureTestCase;
|
|
|
|
class SupplierSummaryConfirmationLinkTest extends FeatureTestCase
|
|
{
|
|
private const WEBHOOK = 'https://oapi.dingtalk.com/robot/send?access_token=summary-link-test-token';
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->company->makeCurrent(true);
|
|
$this->user->attachPermission(
|
|
Permission::where('name', 'read-admin-panel')->firstOrFail()
|
|
);
|
|
config([
|
|
'dingtalk.summary_confirmation_mode' => 'link',
|
|
'app.platform_timezone' => 'Asia/Shanghai',
|
|
]);
|
|
}
|
|
|
|
public function test_summary_push_creates_a_signed_manufacturer_confirmation_page_and_records_confirmation(): void
|
|
{
|
|
$companyId = (int) $this->company->id;
|
|
app(SupplierDingTalkNotifier::class)->save(
|
|
'常州富来居',
|
|
self::WEBHOOK,
|
|
'SEC-summary-link-test',
|
|
false,
|
|
);
|
|
Http::fake(['*' => Http::response(['errcode' => 0, 'errmsg' => 'ok'])]);
|
|
|
|
$result = app(SupplierDingTalkNotifier::class)->pushReconciliationSummary(
|
|
'常州富来居',
|
|
'2026-10',
|
|
855,
|
|
103420.50,
|
|
$companyId,
|
|
'settlement',
|
|
(int) $this->user->id,
|
|
);
|
|
|
|
$pageUrl = (string) $result['confirmation_url'];
|
|
$sentRequest = collect(Http::recorded())->first()[0] ?? null;
|
|
$this->assertNotNull($sentRequest);
|
|
$sentText = (string) ($sentRequest['markdown']['text'] ?? '');
|
|
$this->assertStringContainsString('oapi.dingtalk.com/robot/send', $sentRequest->url());
|
|
$this->assertStringContainsString('件数:**855** 件', $sentText);
|
|
$this->assertStringContainsString('货款:**¥103,420.50**', $sentText);
|
|
$this->assertStringNotContainsString('确认口径', $sentText);
|
|
$this->assertStringContainsString('[打开货款确认页](' . $pageUrl . ')', $sentText);
|
|
$this->assertStringContainsString('无需登录', $sentText);
|
|
|
|
$page = $this->get($pageUrl)
|
|
->assertOk()
|
|
->assertHeader('X-Robots-Tag', 'noindex, nofollow, noarchive')
|
|
->assertHeader('Referrer-Policy', 'no-referrer')
|
|
->assertSeeText('常州富来居 · 2026年10月')
|
|
->assertSeeText('855 件')
|
|
->assertSeeText('¥103,420.50')
|
|
->assertDontSeeText('确认口径')
|
|
->assertDontSeeText('按结算口径')
|
|
->assertSeeText('待厂家确认')
|
|
->assertSeeText('确认本期货款')
|
|
->assertSeeText('无需登录');
|
|
|
|
$html = (string) $page->getContent();
|
|
preg_match('/action="([^"]*supplier-summary-confirmations[^"]*\/confirm[^"]*)"/', $html, $matches);
|
|
$this->assertNotEmpty($matches);
|
|
$confirmUrl = html_entity_decode($matches[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
|
|
$redirect = $this->post($confirmUrl)
|
|
->assertStatus(302)
|
|
->assertSessionHas('success', '已确认本期货款,财务系统已实时更新。');
|
|
|
|
$record = app(SupplierSummaryConfirmation::class)->find($result['request_id'], $companyId);
|
|
$this->assertSame(SupplierSummaryConfirmation::STATUS_CONFIRMED, $record['status'] ?? null);
|
|
$this->assertSame('manufacturer_link', $record['confirmed_source'] ?? null);
|
|
$this->assertNotEmpty($record['confirmed_at'] ?? null);
|
|
$this->assertArrayNotHasKey('confirmed_by_dingtalk_user_id', $record);
|
|
|
|
$receiptRequest = collect(Http::recorded())->last()[0] ?? null;
|
|
$this->assertNotNull($receiptRequest);
|
|
$receiptText = (string) ($receiptRequest['markdown']['text'] ?? '');
|
|
$this->assertStringContainsString('供应商货款确认回执 · 常州富来居', $receiptText);
|
|
$this->assertStringContainsString('855** 件', $receiptText);
|
|
$this->assertStringContainsString('¥103,420.50', $receiptText);
|
|
$this->assertStringContainsString('厂家通过货款确认页点击确认', $receiptText);
|
|
|
|
$this->get($redirect->headers->get('Location'))
|
|
->assertOk()
|
|
->assertSeeText('本期货款已确认')
|
|
->assertSeeText('厂家已确认')
|
|
->assertSeeText('财务系统已实时收到确认结果')
|
|
->assertDontSee('<button type="submit" class="summary-confirm-button">', false);
|
|
|
|
$this->loginAs()->getJson(route('business.supplier-reconciliation.summary-confirmation.status', [
|
|
'supplier' => '常州富来居',
|
|
'month' => '2026-10',
|
|
'push_basis' => 'settlement',
|
|
]))->assertOk()->assertJson([
|
|
'status' => 'confirmed',
|
|
'label' => '厂家已确认',
|
|
]);
|
|
}
|
|
|
|
public function test_summary_confirmation_page_rejects_a_tampered_token(): void
|
|
{
|
|
$companyId = (int) $this->company->id;
|
|
$requestId = $this->recordPendingSummary($companyId, '晨丰');
|
|
$url = app(SupplierSummaryShareLink::class)->pageUrl(
|
|
$companyId,
|
|
'晨丰',
|
|
'2026-10',
|
|
'mine',
|
|
$requestId,
|
|
);
|
|
$tampered = preg_replace_callback(
|
|
'/supplier-summary-confirmations\/([^?\/]+)/',
|
|
static fn (array $matches): string => 'supplier-summary-confirmations/'
|
|
. ($matches[1][0] === 'A' ? 'B' : 'A') . substr($matches[1], 1),
|
|
$url,
|
|
1,
|
|
);
|
|
|
|
$this->assertNotSame($url, $tampered);
|
|
$this->expectException(InvalidSignatureException::class);
|
|
$this->get($tampered);
|
|
}
|
|
|
|
private function recordPendingSummary(int $companyId, string $supplier): string
|
|
{
|
|
$requestId = 'supplier-summary-l' . $companyId . '-' . str_repeat('a', 32);
|
|
app(SupplierSummaryConfirmation::class)->create(
|
|
$requestId,
|
|
$supplier,
|
|
'2026-10',
|
|
'mine',
|
|
12,
|
|
360.50,
|
|
$companyId,
|
|
null,
|
|
);
|
|
app(SupplierSummaryConfirmation::class)->markSent($requestId, $companyId);
|
|
|
|
return $requestId;
|
|
}
|
|
}
|