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)); } }