You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
7.1 KiB
190 lines
7.1 KiB
<?php
|
|
|
|
namespace Tests\Unit\Services;
|
|
|
|
use App\Models\Company;
|
|
use App\Models\CompanyQccAccount;
|
|
use App\Services\QccCallService;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use RuntimeException;
|
|
use Tests\TestCase;
|
|
|
|
class QccCallServiceTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
config([
|
|
'database.default' => 'sqlite',
|
|
'database.connections.sqlite.database' => ':memory:',
|
|
'audit.enabled' => false,
|
|
]);
|
|
DB::purge('sqlite');
|
|
|
|
Schema::create('companies', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('company_name')->nullable();
|
|
$table->string('credit_code')->nullable();
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
});
|
|
Schema::create('company_qcc_accounts', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->unsignedBigInteger('company_id')->unique();
|
|
$table->string('status', 20);
|
|
$table->unsignedBigInteger('selected_by')->nullable();
|
|
$table->dateTime('selected_at');
|
|
$table->dateTime('first_success_at')->nullable();
|
|
$table->string('first_success_source')->nullable();
|
|
$table->string('first_success_ref')->nullable();
|
|
$table->text('unknown_note')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
Schema::dropIfExists('company_qcc_accounts');
|
|
Schema::dropIfExists('companies');
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_first_success_marks_candidate_occupied_only_once(): void
|
|
{
|
|
$company = $this->company();
|
|
$account = $this->selectedAccount($company);
|
|
$service = new QccCallService();
|
|
|
|
$service->recordSuccess($company->id, 'enterprise-profile', 'request-first');
|
|
$firstSuccessAt = $account->fresh()->first_success_at;
|
|
|
|
$service->recordSuccess($company->id, 'risk-check', 'request-second');
|
|
$account = $account->fresh();
|
|
|
|
$this->assertSame(CompanyQccAccount::STATUS_OCCUPIED, $account->status);
|
|
$this->assertSame('enterprise-profile', $account->first_success_source);
|
|
$this->assertSame('request-first', $account->first_success_ref);
|
|
$this->assertTrue($firstSuccessAt->equalTo($account->first_success_at));
|
|
}
|
|
|
|
public function test_unknown_candidate_blocks_follow_up_calls(): void
|
|
{
|
|
$company = $this->company();
|
|
$account = $this->selectedAccount($company);
|
|
$service = new QccCallService();
|
|
|
|
try {
|
|
$service->call($company, 'enterprise-profile', 'request-timeout', function () {
|
|
throw new RuntimeException('timeout');
|
|
}, fn($result) => $result === true);
|
|
$this->fail('Expected timeout exception was not thrown.');
|
|
} catch (RuntimeException $exception) {
|
|
$this->assertSame('timeout', $exception->getMessage());
|
|
}
|
|
|
|
$this->assertSame(CompanyQccAccount::STATUS_UNKNOWN, $account->fresh()->status);
|
|
$this->expectException(RuntimeException::class);
|
|
$this->expectExceptionMessage('待人工确认');
|
|
$service->call($company, 'enterprise-profile', 'request-retry', fn() => true, fn($result) => $result === true);
|
|
}
|
|
|
|
public function test_later_timeout_does_not_downgrade_occupied_account(): void
|
|
{
|
|
$company = $this->company();
|
|
$account = $this->selectedAccount($company, CompanyQccAccount::STATUS_OCCUPIED);
|
|
$service = new QccCallService();
|
|
|
|
try {
|
|
$service->call($company, 'enterprise-profile', 'request-timeout', function () {
|
|
throw new RuntimeException('timeout');
|
|
}, fn($result) => $result === true);
|
|
$this->fail('Expected timeout exception was not thrown.');
|
|
} catch (RuntimeException $exception) {
|
|
$this->assertSame('timeout', $exception->getMessage());
|
|
}
|
|
|
|
$this->assertSame(CompanyQccAccount::STATUS_OCCUPIED, $account->fresh()->status);
|
|
}
|
|
|
|
public function test_indeterminate_response_marks_selected_account_unknown_but_explicit_failure_does_not(): void
|
|
{
|
|
$company = $this->company();
|
|
$account = $this->selectedAccount($company);
|
|
$service = new QccCallService();
|
|
|
|
$service->call(
|
|
$company,
|
|
'enterprise-profile',
|
|
'request-business-failed',
|
|
fn() => ['code' => 400],
|
|
fn($result) => $result['code'] === 200,
|
|
fn($result) => !array_key_exists('code', $result)
|
|
);
|
|
$this->assertSame(CompanyQccAccount::STATUS_SELECTED, $account->fresh()->status);
|
|
|
|
$service->call(
|
|
$company,
|
|
'enterprise-profile',
|
|
'request-unparseable',
|
|
fn() => [],
|
|
fn($result) => ($result['code'] ?? null) === 200,
|
|
fn($result) => !array_key_exists('code', $result) ? '元禾响应缺少业务状态码' : false
|
|
);
|
|
$account = $account->fresh();
|
|
$this->assertSame(CompanyQccAccount::STATUS_UNKNOWN, $account->status);
|
|
$this->assertSame('元禾响应缺少业务状态码', $account->unknown_note);
|
|
}
|
|
|
|
public function test_result_classifier_exception_marks_selected_account_unknown(): void
|
|
{
|
|
$company = $this->company();
|
|
$account = $this->selectedAccount($company);
|
|
$service = new QccCallService();
|
|
|
|
try {
|
|
$service->call(
|
|
$company,
|
|
'enterprise-profile',
|
|
'request-classifier-error',
|
|
fn() => [],
|
|
function () {
|
|
throw new RuntimeException('响应字段不完整');
|
|
}
|
|
);
|
|
$this->fail('Expected classifier exception was not thrown.');
|
|
} catch (RuntimeException $exception) {
|
|
$this->assertSame('响应字段不完整', $exception->getMessage());
|
|
}
|
|
|
|
$account = $account->fresh();
|
|
$this->assertSame(CompanyQccAccount::STATUS_UNKNOWN, $account->status);
|
|
$this->assertStringContainsString('调用结果判定异常', $account->unknown_note);
|
|
}
|
|
|
|
protected function company(): Company
|
|
{
|
|
$id = DB::table('companies')->insertGetId([
|
|
'company_name' => '测试企业',
|
|
'credit_code' => '91320100TEST00001',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
return Company::findOrFail($id);
|
|
}
|
|
|
|
protected function selectedAccount(Company $company, string $status = CompanyQccAccount::STATUS_SELECTED): CompanyQccAccount
|
|
{
|
|
return CompanyQccAccount::create([
|
|
'company_id' => $company->id,
|
|
'status' => $status,
|
|
'selected_at' => now(),
|
|
'first_success_at' => $status === CompanyQccAccount::STATUS_OCCUPIED ? now()->subMinute() : null,
|
|
'first_success_source' => $status === CompanyQccAccount::STATUS_OCCUPIED ? 'seed' : null,
|
|
'first_success_ref' => $status === CompanyQccAccount::STATUS_OCCUPIED ? 'seed-ref' : null,
|
|
]);
|
|
}
|
|
}
|