#feature: some update
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
|
||||
class HostAccessTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
config(['toolbox.admin_host' => 'toolbox.local']);
|
||||
}
|
||||
|
||||
public function test_admin_host_can_open_the_toolbox_menu(): void
|
||||
{
|
||||
$response = $this->get('http://toolbox.local/');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertSee('<admin-dashboard>', false);
|
||||
}
|
||||
|
||||
public function test_ip_host_gets_the_standalone_production_diagnosis_page(): void
|
||||
{
|
||||
$response = $this->get('http://192.168.1.20/production-diagnosis');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertSee('<production-diagnosis>', false);
|
||||
$response->assertDontSee('<admin-dashboard>', false);
|
||||
}
|
||||
|
||||
public function test_ip_host_cannot_open_other_toolbox_pages(): void
|
||||
{
|
||||
$this
|
||||
->get('http://192.168.1.20/')
|
||||
->assertNotFound();
|
||||
|
||||
$this
|
||||
->get('http://192.168.1.20/env')
|
||||
->assertNotFound();
|
||||
|
||||
$this
|
||||
->get('http://192.168.1.20/settings')
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_ip_host_can_only_call_the_production_diagnosis_api(): void
|
||||
{
|
||||
$this
|
||||
->postJson('http://192.168.1.20/api/production-diagnosis/diagnose', [])
|
||||
->assertUnprocessable();
|
||||
|
||||
$this
|
||||
->getJson('http://192.168.1.20/api/admin/meta')
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_unconfigured_hostname_is_rejected(): void
|
||||
{
|
||||
$this
|
||||
->get('http://attacker.example/production-diagnosis')
|
||||
->assertNotFound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Services\ProductionDiagnosisService;
|
||||
use Mockery;
|
||||
use RuntimeException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ProductionDiagnosisTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
config(['toolbox.admin_host' => 'toolbox.local']);
|
||||
}
|
||||
|
||||
public function test_validation_errors_are_returned_to_ip_clients(): void
|
||||
{
|
||||
$this
|
||||
->postJson('http://192.168.1.20/api/production-diagnosis/diagnose', [])
|
||||
->assertUnprocessable()
|
||||
->assertJson([
|
||||
'success' => false,
|
||||
'message' => '请求参数验证失败',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_successful_diagnosis_response_is_unchanged(): void
|
||||
{
|
||||
$result = [
|
||||
'type' => 'case',
|
||||
'type_label' => '病例',
|
||||
'code' => 'C123',
|
||||
'found' => true,
|
||||
'entity' => ['patient_name' => '测试患者'],
|
||||
'checks' => [],
|
||||
'can_production' => true,
|
||||
];
|
||||
|
||||
$service = Mockery::mock(ProductionDiagnosisService::class);
|
||||
$service->shouldReceive('diagnose')
|
||||
->once()
|
||||
->with('case', 'C123')
|
||||
->andReturn($result);
|
||||
$this->app->instance(ProductionDiagnosisService::class, $service);
|
||||
|
||||
$this
|
||||
->postJson('http://192.168.1.20/api/production-diagnosis/diagnose', [
|
||||
'type' => 'case',
|
||||
'code' => ' C123 ',
|
||||
])
|
||||
->assertOk()
|
||||
->assertExactJson([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_internal_exception_details_are_not_returned(): void
|
||||
{
|
||||
$service = Mockery::mock(ProductionDiagnosisService::class);
|
||||
$service->shouldReceive('diagnose')
|
||||
->once()
|
||||
->andThrow(new RuntimeException('SQLSTATE[HY000] secret database detail'));
|
||||
$this->app->instance(ProductionDiagnosisService::class, $service);
|
||||
|
||||
$response = $this->postJson('http://192.168.1.20/api/production-diagnosis/diagnose', [
|
||||
'type' => 'case',
|
||||
'code' => 'C123',
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertInternalServerError()
|
||||
->assertExactJson([
|
||||
'success' => false,
|
||||
'message' => '诊断服务暂不可用,请稍后重试',
|
||||
]);
|
||||
$response->assertDontSee('SQLSTATE');
|
||||
$response->assertDontSee('secret database detail');
|
||||
}
|
||||
|
||||
public function test_ip_client_is_rate_limited_after_thirty_requests_per_minute(): void
|
||||
{
|
||||
for ($attempt = 1; $attempt <= 30; $attempt++) {
|
||||
$this
|
||||
->withServerVariables(['REMOTE_ADDR' => '192.168.1.50'])
|
||||
->postJson('http://192.168.1.20/api/production-diagnosis/diagnose', [])
|
||||
->assertUnprocessable();
|
||||
}
|
||||
|
||||
$this
|
||||
->withServerVariables(['REMOTE_ADDR' => '192.168.1.50'])
|
||||
->postJson('http://192.168.1.20/api/production-diagnosis/diagnose', [])
|
||||
->assertTooManyRequests();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Enums\CaseLabelBit;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CaseLabelBitTest extends TestCase
|
||||
{
|
||||
public function test_split_returns_each_set_bit(): void
|
||||
{
|
||||
$this->assertSame([], CaseLabelBit::split(0));
|
||||
$this->assertSame([2], CaseLabelBit::split(2));
|
||||
$this->assertSame([2, 4], CaseLabelBit::split(6));
|
||||
$this->assertSame([1, 2, 4, 8], CaseLabelBit::split(15));
|
||||
}
|
||||
|
||||
public function test_to_text_describes_stuck_reasons(): void
|
||||
{
|
||||
$this->assertSame('无标记', CaseLabelBit::toText(0));
|
||||
$this->assertSame('新病例订单卡生产', CaseLabelBit::toText(CaseLabelBit::APPLIANCE_NEED_MONEY));
|
||||
$this->assertSame(
|
||||
'新病例订单卡生产 / 产品变更卡生产',
|
||||
CaseLabelBit::toText(CaseLabelBit::APPLIANCE_NEED_MONEY | CaseLabelBit::UPGRADE_NEED_MONEY)
|
||||
);
|
||||
}
|
||||
|
||||
public function test_unknown_bit_falls_back_to_generic_label(): void
|
||||
{
|
||||
$this->assertSame('未知标记位 16', CaseLabelBit::crmLabel(16));
|
||||
$this->assertStringContainsString('未在 CRM 枚举中定义', CaseLabelBit::description(16));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Services\DingTalkService;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DingTalkServiceTest extends TestCase
|
||||
{
|
||||
public function test_it_sends_text_to_a_robot_token(): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://oapi.dingtalk.com/robot/send?access_token=report-token' => Http::response([
|
||||
'errcode' => 0,
|
||||
]),
|
||||
]);
|
||||
|
||||
$sent = (new DingTalkService)->sendTextToToken('report-token', '日报内容');
|
||||
|
||||
$this->assertTrue($sent);
|
||||
Http::assertSent(fn ($request) => $request->url() === 'https://oapi.dingtalk.com/robot/send?access_token=report-token'
|
||||
&& $request->data() === [
|
||||
'msgtype' => 'text',
|
||||
'text' => ['content' => '日报内容'],
|
||||
'at' => ['atMobiles' => [], 'isAtAll' => false],
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_reports_a_dingtalk_business_error(): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://oapi.dingtalk.com/robot/send?access_token=invalid-token' => Http::response([
|
||||
'errcode' => 310000,
|
||||
'errmsg' => 'invalid token',
|
||||
]),
|
||||
]);
|
||||
|
||||
$sent = (new DingTalkService)->sendTextToToken('invalid-token', '日报内容');
|
||||
|
||||
$this->assertFalse($sent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Clients\JenkinsClient;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Tests\TestCase;
|
||||
|
||||
class JenkinsClientTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
config([
|
||||
'jenkins.host' => 'https://jenkins.example.com',
|
||||
'jenkins.username' => 'test-user',
|
||||
'jenkins.api_token' => 'test-token',
|
||||
'jenkins.timeout' => 30,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_parameterized_build_posts_form_json_parameters_and_returns_queue_url(): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://jenkins.example.com/job/deploy/api/json' => Http::response([
|
||||
'nextBuildNumber' => 42,
|
||||
]),
|
||||
'https://jenkins.example.com/crumbIssuer/api/json' => Http::response([
|
||||
'crumbRequestField' => 'Jenkins-Crumb',
|
||||
'crumb' => 'test-crumb',
|
||||
]),
|
||||
'https://jenkins.example.com/job/deploy/build?delay=0sec' => Http::response('', 201, [
|
||||
'Location' => 'https://jenkins.example.com/queue/item/123/',
|
||||
]),
|
||||
]);
|
||||
|
||||
$result = app(JenkinsClient::class)->triggerBuild('deploy', [
|
||||
'project' => 'portal',
|
||||
'branchName' => 'release/1.0',
|
||||
]);
|
||||
|
||||
$this->assertTrue($result['success']);
|
||||
$this->assertSame('https://jenkins.example.com/queue/item/123/', $result['queue_url']);
|
||||
$this->assertSame(42, $result['build_number']);
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
parse_str($request->body(), $form);
|
||||
$payload = json_decode((string) ($form['json'] ?? ''), true);
|
||||
$parameters = collect($payload['parameter'] ?? [])->pluck('value', 'name');
|
||||
|
||||
return $request->method() === 'POST'
|
||||
&& $request->url() === 'https://jenkins.example.com/job/deploy/build?delay=0sec'
|
||||
&& $parameters['project'] === 'portal'
|
||||
&& $parameters['branchName'] === 'release/1.0';
|
||||
});
|
||||
}
|
||||
|
||||
public function test_cancel_build_with_queue_url_cancels_pending_queue_item(): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://jenkins.example.com/queue/item/123/api/json' => Http::response([
|
||||
'why' => 'In the quiet period',
|
||||
]),
|
||||
'https://jenkins.example.com/crumbIssuer/api/json' => Http::response([
|
||||
'crumbRequestField' => 'Jenkins-Crumb',
|
||||
'crumb' => 'test-crumb',
|
||||
]),
|
||||
'https://jenkins.example.com/queue/cancelItem?id=123' => Http::response('', 200),
|
||||
]);
|
||||
|
||||
$result = app(JenkinsClient::class)->cancelBuild('deploy', 'https://jenkins.example.com/queue/item/123/');
|
||||
|
||||
$this->assertTrue($result['success']);
|
||||
$this->assertTrue($result['cancelled_queue']);
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
return $request->method() === 'POST'
|
||||
&& $request->url() === 'https://jenkins.example.com/queue/cancelItem?id=123';
|
||||
});
|
||||
}
|
||||
|
||||
public function test_cancel_build_without_queue_url_cancels_matching_queued_job(): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://jenkins.example.com/queue/api/json' => Http::response([
|
||||
'items' => [
|
||||
[
|
||||
'id' => 456,
|
||||
'task' => [
|
||||
'fullName' => 'deploy',
|
||||
'name' => 'deploy',
|
||||
],
|
||||
],
|
||||
],
|
||||
]),
|
||||
'https://jenkins.example.com/crumbIssuer/api/json' => Http::response([
|
||||
'crumbRequestField' => 'Jenkins-Crumb',
|
||||
'crumb' => 'test-crumb',
|
||||
]),
|
||||
'https://jenkins.example.com/queue/cancelItem?id=456' => Http::response('', 200),
|
||||
]);
|
||||
|
||||
$result = app(JenkinsClient::class)->cancelBuild('deploy', null, 42);
|
||||
|
||||
$this->assertTrue($result['success']);
|
||||
$this->assertTrue($result['cancelled_queue']);
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
return $request->method() === 'POST'
|
||||
&& $request->url() === 'https://jenkins.example.com/queue/cancelItem?id=456';
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace Tests\Unit;
|
||||
use App\Services\JiraService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use JiraRestApi\Project\ProjectService;
|
||||
use Tests\TestCase;
|
||||
|
||||
class JiraServiceTest extends TestCase
|
||||
@@ -263,6 +264,25 @@ class JiraServiceTest extends TestCase
|
||||
$this->assertEquals('2.70.0.0', $method->invoke($this->jiraService, '2.69.0.0'));
|
||||
}
|
||||
|
||||
public function test_upcoming_release_version_falls_back_to_next_minor_when_jira_versions_are_not_maintained()
|
||||
{
|
||||
$projectService = $this->createMock(ProjectService::class);
|
||||
$projectService->method('getVersions')->with('TP')->willReturn(new \ArrayObject([
|
||||
(object) ['name' => '1.34.0.0', 'released' => false],
|
||||
(object) ['name' => '1.37.0.0', 'released' => false],
|
||||
]));
|
||||
|
||||
$reflection = new \ReflectionClass($this->jiraService);
|
||||
$property = $reflection->getProperty('projectService');
|
||||
$property->setValue($this->jiraService, $projectService);
|
||||
|
||||
$this->assertSame([
|
||||
'version' => '1.46.0.0',
|
||||
'description' => null,
|
||||
'release_date' => null,
|
||||
], $this->jiraService->getUpcomingReleaseVersion('TP', '1.45.0.0'));
|
||||
}
|
||||
|
||||
public function test_test_mail_template_defaults_use_next_versions()
|
||||
{
|
||||
$defaults = $this->jiraService->getTestMailTemplateDefaults();
|
||||
@@ -270,6 +290,17 @@ class JiraServiceTest extends TestCase
|
||||
$this->assertEquals('2.70.0.0', $defaults['container_groups']['agent']['default_version']);
|
||||
$this->assertEquals('2.65.0.0', $defaults['container_groups']['portal']['default_version']);
|
||||
$this->assertEquals('1.43.0.0', $defaults['container_groups']['portal-ticket']['default_version']);
|
||||
$this->assertEquals('1.12.0.0', $defaults['container_groups']['mono']['default_version']);
|
||||
$this->assertSame('mono', array_key_last($defaults['container_groups']));
|
||||
$this->assertEquals(
|
||||
['portal-mono-be-aplct', 'portal-mono-be-web'],
|
||||
array_column($defaults['container_groups']['mono']['containers'], 'name')
|
||||
);
|
||||
$this->assertEquals(
|
||||
['中国', '中国'],
|
||||
array_column($defaults['container_groups']['mono']['containers'], 'location')
|
||||
);
|
||||
$this->assertFalse($defaults['container_groups']['mono']['database_enabled']);
|
||||
}
|
||||
|
||||
public function test_extract_bug_stage_from_labels()
|
||||
|
||||
Reference in New Issue
Block a user