Files
toolbox/tests/Unit/ErpRequestReportServiceTest.php
T
2026-08-12 18:00:09 +08:00

268 lines
14 KiB
PHP

<?php
namespace Tests\Unit;
use App\Services\ConfigService;
use App\Services\DingTalkService;
use App\Services\ErpRequestReportService;
use Carbon\CarbonImmutable;
use Illuminate\Database\Connection;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Query\Builder;
use InvalidArgumentException;
use Mockery;
use RuntimeException;
use Tests\TestCase;
class ErpRequestReportServiceTest extends TestCase
{
public function test_it_defaults_to_the_previous_calendar_day(): void
{
CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-08-02 16:30:00', 'UTC'));
try {
$database = Mockery::mock(DatabaseManager::class);
$connection = Mockery::mock(Connection::class);
$query = Mockery::mock(Builder::class);
$dingTalkService = Mockery::mock(DingTalkService::class);
$configService = Mockery::mock(ConfigService::class);
$configService->shouldReceive('get')
->once()
->with(ErpRequestReportService::DINGTALK_TOKEN_CONFIG_KEY)
->andReturn('report-token');
$database->shouldReceive('connection')->once()->with('agentslave')->andReturn($connection);
$connection->shouldReceive('table')->once()->with('request_records')->andReturn($query);
$query->shouldReceive('selectRaw')->once()->andReturnSelf();
$query->shouldReceive('leftJoin')->once()->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.created', '>=', '2026-08-02 00:00:00')->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.created', '<', '2026-08-03 00:00:00')->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.request_uri', 'like', '/openapi/erp/%')->andReturnSelf();
$query->shouldReceive('groupByRaw')->once()->andReturnSelf();
$query->shouldReceive('orderBy')->twice()->andReturnSelf();
$query->shouldReceive('orderByRaw')->once()->andReturnSelf();
$query->shouldReceive('get')->once()->andReturn(collect());
$dingTalkService->shouldReceive('sendTextToToken')
->once()
->with('report-token', "2026-08-02 ERP OpenAPI 请求统计\n无请求记录")
->andReturnTrue();
$result = (new ErpRequestReportService($database, $dingTalkService, $configService))->sendReport();
$this->assertSame('2026-08-02', $result['date']);
$this->assertSame('2026-08-02 00:00:00', $result['from']);
$this->assertSame('2026-08-02 23:59:59', $result['to']);
} finally {
CarbonImmutable::setTestNow();
}
}
public function test_it_sends_the_previous_days_erp_requests_grouped_by_company_and_uri(): void
{
$database = Mockery::mock(DatabaseManager::class);
$connection = Mockery::mock(Connection::class);
$query = Mockery::mock(Builder::class);
$dingTalkService = Mockery::mock(DingTalkService::class);
$configService = Mockery::mock(ConfigService::class);
$configService->shouldReceive('get')
->once()
->with(ErpRequestReportService::DINGTALK_TOKEN_CONFIG_KEY)
->andReturn('report-token');
$database->shouldReceive('connection')->once()->with('agentslave')->andReturn($connection);
$connection->shouldReceive('table')->once()->with('request_records')->andReturn($query);
$query->shouldReceive('selectRaw')->once()->with("agents.name as agent_name, agents.code as agent_code, SUBSTRING_INDEX(request_records.request_uri, '?', 1) as request_uri, COUNT(*) as request_count")->andReturnSelf();
$query->shouldReceive('leftJoin')->once()->with('agents', 'agents.id', '=', 'request_records.user_id')->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.created', '>=', '2026-08-02 00:00:00')->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.created', '<', '2026-08-03 00:00:00')->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.request_uri', 'like', '/openapi/erp/%')->andReturnSelf();
$query->shouldReceive('groupByRaw')->once()->with("agents.id, agents.name, agents.code, SUBSTRING_INDEX(request_records.request_uri, '?', 1)")->andReturnSelf();
$query->shouldReceive('orderBy')->once()->with('agents.name')->andReturnSelf();
$query->shouldReceive('orderBy')->once()->with('agents.code')->andReturnSelf();
$query->shouldReceive('orderByRaw')->once()->with("SUBSTRING_INDEX(request_records.request_uri, '?', 1)")->andReturnSelf();
$query->shouldReceive('get')->once()->andReturn(collect([
(object) [
'agent_name' => '广州医路精密医疗器械有限公司',
'agent_code' => 'G201704010003',
'request_uri' => '/openapi/erp/deliveries',
'request_count' => 2,
],
(object) [
'agent_name' => '广州医路精密医疗器械有限公司',
'agent_code' => 'G201704010003',
'request_uri' => '/openapi/erp/orders?access_token=secret',
'request_count' => 3,
],
]));
$dingTalkService->shouldReceive('sendTextToToken')
->once()
->with('report-token', "2026-08-02 ERP OpenAPI 请求统计\n\n广州医路精密医疗器械有限公司 G201704010003\n/openapi/erp/deliveries 2次\n/openapi/erp/orders 3次")
->andReturnTrue();
$result = (new ErpRequestReportService($database, $dingTalkService, $configService))
->sendReport('2026-08-02');
$this->assertSame('2026-08-02', $result['date']);
$this->assertSame(5, $result['request_count']);
$this->assertSame(1, $result['company_count']);
}
public function test_it_supports_inclusive_date_ranges(): void
{
$database = Mockery::mock(DatabaseManager::class);
$connection = Mockery::mock(Connection::class);
$query = Mockery::mock(Builder::class);
$dingTalkService = Mockery::mock(DingTalkService::class);
$configService = Mockery::mock(ConfigService::class);
$configService->shouldReceive('get')
->once()
->with(ErpRequestReportService::DINGTALK_TOKEN_CONFIG_KEY)
->andReturn('report-token');
$database->shouldReceive('connection')->once()->with('agentslave')->andReturn($connection);
$connection->shouldReceive('table')->once()->with('request_records')->andReturn($query);
$query->shouldReceive('selectRaw')->once()->andReturnSelf();
$query->shouldReceive('leftJoin')->once()->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.created', '>=', '2026-08-01 00:00:00')->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.created', '<', '2026-08-08 00:00:00')->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.request_uri', 'like', '/openapi/erp/%')->andReturnSelf();
$query->shouldReceive('groupByRaw')->once()->andReturnSelf();
$query->shouldReceive('orderBy')->twice()->andReturnSelf();
$query->shouldReceive('orderByRaw')->once()->andReturnSelf();
$query->shouldReceive('get')->once()->andReturn(collect());
$dingTalkService->shouldReceive('sendTextToToken')
->once()
->with('report-token', "2026-08-01 ~ 2026-08-07 ERP OpenAPI 请求统计\n无请求记录")
->andReturnTrue();
$result = (new ErpRequestReportService($database, $dingTalkService, $configService))
->sendReport(null, '2026-08-01', '2026-08-07');
$this->assertSame('2026-08-01 ~ 2026-08-07', $result['date']);
$this->assertSame('2026-08-01 00:00:00', $result['from']);
$this->assertSame('2026-08-07 23:59:59', $result['to']);
}
public function test_it_supports_inclusive_datetime_ranges(): void
{
$database = Mockery::mock(DatabaseManager::class);
$connection = Mockery::mock(Connection::class);
$query = Mockery::mock(Builder::class);
$dingTalkService = Mockery::mock(DingTalkService::class);
$configService = Mockery::mock(ConfigService::class);
$configService->shouldReceive('get')
->once()
->with(ErpRequestReportService::DINGTALK_TOKEN_CONFIG_KEY)
->andReturn('report-token');
$database->shouldReceive('connection')->once()->with('agentslave')->andReturn($connection);
$connection->shouldReceive('table')->once()->with('request_records')->andReturn($query);
$query->shouldReceive('selectRaw')->once()->andReturnSelf();
$query->shouldReceive('leftJoin')->once()->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.created', '>=', '2026-08-02 08:00:00')->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.created', '<', '2026-08-02 18:00:01')->andReturnSelf();
$query->shouldReceive('where')->once()->with('request_records.request_uri', 'like', '/openapi/erp/%')->andReturnSelf();
$query->shouldReceive('groupByRaw')->once()->andReturnSelf();
$query->shouldReceive('orderBy')->twice()->andReturnSelf();
$query->shouldReceive('orderByRaw')->once()->andReturnSelf();
$query->shouldReceive('get')->once()->andReturn(collect());
$dingTalkService->shouldReceive('sendTextToToken')
->once()
->with('report-token', "2026-08-02 08:00:00 ~ 2026-08-02 18:00:00 ERP OpenAPI 请求统计\n无请求记录")
->andReturnTrue();
$result = (new ErpRequestReportService($database, $dingTalkService, $configService))
->sendReport(null, '2026-08-02 08:00:00', '2026-08-02 18:00:00');
$this->assertSame('2026-08-02 08:00:00 ~ 2026-08-02 18:00:00', $result['date']);
}
public function test_it_rejects_date_mixed_with_from_to(): void
{
$database = Mockery::mock(DatabaseManager::class);
$dingTalkService = Mockery::mock(DingTalkService::class);
$configService = Mockery::mock(ConfigService::class);
$configService->shouldReceive('get')
->once()
->with(ErpRequestReportService::DINGTALK_TOKEN_CONFIG_KEY)
->andReturn('report-token');
$database->shouldNotReceive('connection');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('--date 不能与 --from/--to 同时使用');
(new ErpRequestReportService($database, $dingTalkService, $configService))
->sendReport('2026-08-02', '2026-08-01', '2026-08-07');
}
public function test_it_requires_a_configured_dingtalk_token_before_querying(): void
{
$database = Mockery::mock(DatabaseManager::class);
$dingTalkService = Mockery::mock(DingTalkService::class);
$configService = Mockery::mock(ConfigService::class);
$configService->shouldReceive('get')
->once()
->with(ErpRequestReportService::DINGTALK_TOKEN_CONFIG_KEY)
->andReturn(null);
$database->shouldNotReceive('connection');
$dingTalkService->shouldNotReceive('sendTextToToken');
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('未配置 ERP 请求日报的钉钉机器人 Token');
(new ErpRequestReportService($database, $dingTalkService, $configService))
->sendReport('2026-08-02');
}
public function test_it_splits_large_reports_into_safe_dingtalk_messages(): void
{
$database = Mockery::mock(DatabaseManager::class);
$connection = Mockery::mock(Connection::class);
$query = Mockery::mock(Builder::class);
$dingTalkService = Mockery::mock(DingTalkService::class);
$configService = Mockery::mock(ConfigService::class);
$sentMessages = [];
$configService->shouldReceive('get')
->once()
->with(ErpRequestReportService::DINGTALK_TOKEN_CONFIG_KEY)
->andReturn('report-token');
$database->shouldReceive('connection')->once()->with('agentslave')->andReturn($connection);
$connection->shouldReceive('table')->once()->with('request_records')->andReturn($query);
$query->shouldReceive('selectRaw')->once()->andReturnSelf();
$query->shouldReceive('leftJoin')->once()->andReturnSelf();
$query->shouldReceive('where')->times(3)->andReturnSelf();
$query->shouldReceive('groupByRaw')->once()->andReturnSelf();
$query->shouldReceive('orderBy')->twice()->andReturnSelf();
$query->shouldReceive('orderByRaw')->once()->andReturnSelf();
$query->shouldReceive('get')->once()->andReturn(collect(range(1, 300))->map(
fn (int $index) => (object) [
'agent_name' => '广州医路精密医疗器械有限公司',
'agent_code' => 'G201704010003',
'request_uri' => '/openapi/erp/'.str_pad((string) $index, 100, 'x'),
'request_count' => 1,
]
));
$dingTalkService->shouldReceive('sendTextToToken')
->atLeast()->once()
->withArgs(function (string $token, string $message) use (&$sentMessages): bool {
$sentMessages[] = $message;
return $token === 'report-token';
})
->andReturnTrue();
(new ErpRequestReportService($database, $dingTalkService, $configService))->sendReport('2026-08-02');
$this->assertGreaterThan(1, count($sentMessages));
$this->assertContainsOnly('string', $sentMessages);
$this->assertTrue(collect($sentMessages)->every(fn (string $message) => strlen($message) <= 18_000));
$this->assertStringContainsString('/openapi/erp/'.str_pad('300', 100, 'x').' 1次', implode("\n", $sentMessages));
}
}