feat: 实现TCP Server

This commit is contained in:
zimoyin
2026-03-02 21:59:43 +08:00
parent 043306819b
commit a79dfae57d
144 changed files with 15785 additions and 140 deletions
+240
View File
@@ -0,0 +1,240 @@
<?php
namespace tests\flow;
use app\flow\FlowProcessor;
use app\flow\ProcessContext;
use app\flow\ProcessEngine;
use app\flow\config\ProcessConfig;
use app\flow\DbOperationType;
/**
* FlowProcessor 单元测试
*
* 通过继承并覆盖 saveToDatabase / sendVoice / sendWebSocketNotification
* 隔离外部依赖(数据库、语音服务、WebSocket),专注业务逻辑验证。
*/
class FlowProcessorTest extends TestCase
{
/**
* 创建可测试的 FlowProcessor(覆盖外部依赖方法)
*/
protected function createTestableProcessor(ProcessConfig $config = null): FlowProcessor
{
$config = $config ?? ProcessConfig::createNoMorningWash();
return new class($config) extends FlowProcessor {
public array $savedContexts = [];
public array $voiceSent = [];
public array $wsSent = [];
protected function saveToDatabase(ProcessContext $context): void
{
$this->savedContexts[] = [
'operation' => $context->dbOperation,
'step' => $context->currentStep,
'batchNo' => $context->batchNo,
'processType' => $context->processType,
];
}
protected function sendVoice(ProcessContext $context): void
{
$this->voiceSent[] = $context->getFullVoice();
}
protected function sendWebSocketNotification(ProcessContext $context): void
{
$this->wsSent[] = $context->currentStep;
}
};
}
/**
* 测试成功流程触发 saveToDatabase
*/
public function testSuccessfulFlowSavesToDatabase(): void
{
$processor = $this->createTestableProcessor();
$context = $this->createContext([
'readerType' => '清洗',
'currentStep' => '',
'endoscopeId' => '1',
]);
// 直接调用 handleResult(绕过 fromPacketContext
$context->needDatabaseOperation = true;
$context->dbOperation = DbOperationType::INSERT;
$context->currentStep = '清洗';
$context->processType = '手工洗';
$context->batchNo = 'BATCH001';
$this->invokeHandleResult($processor, $context);
$this->assertNotEmpty($processor->savedContexts);
$this->assertEquals(DbOperationType::INSERT, $processor->savedContexts[0]['operation']);
$this->assertEquals('清洗', $processor->savedContexts[0]['step']);
}
/**
* 测试成功流程触发 sendVoice
*/
public function testSuccessfulFlowSendsVoice(): void
{
$processor = $this->createTestableProcessor();
$context = $this->createContext([
'endoscopeName' => '胃镜01',
]);
$context->setVoice('清洗完成');
$context->needDatabaseOperation = false;
$this->invokeHandleResult($processor, $context);
$this->assertNotEmpty($processor->voiceSent);
$this->assertStringContainsString('清洗完成', $processor->voiceSent[0]);
}
/**
* 测试失败流程不触发 saveToDatabase
*/
public function testFailedFlowDoesNotSaveToDatabase(): void
{
$processor = $this->createTestableProcessor();
$context = $this->createContext();
$context->setError('刷错,清洗剩余120秒');
$context->needDatabaseOperation = true;
$this->invokeHandleResult($processor, $context);
$this->assertEmpty($processor->savedContexts);
}
/**
* 测试失败流程仍然播报语音
*/
public function testFailedFlowStillSendsVoice(): void
{
$processor = $this->createTestableProcessor();
$context = $this->createContext();
$context->setError('刷错,清洗剩余120秒');
$context->setVoice('刷错,清洗剩余120秒');
$this->invokeHandleResult($processor, $context);
$this->assertNotEmpty($processor->voiceSent);
}
/**
* 测试成功流程需要 WebSocket 时触发通知
*/
public function testWebSocketNotificationSentWhenNeeded(): void
{
$processor = $this->createTestableProcessor();
$context = $this->createContext(['currentStep' => '清洗']);
$context->needDatabaseOperation = false;
$context->needWebSocketNotify = true;
$this->invokeHandleResult($processor, $context);
$this->assertContains('清洗', $processor->wsSent);
}
/**
* 测试 needWebSocketNotify=false 时不发送 WebSocket
*/
public function testWebSocketNotificationNotSentWhenNotNeeded(): void
{
$processor = $this->createTestableProcessor();
$context = $this->createContext(['currentStep' => '清洗']);
$context->needDatabaseOperation = false;
$context->needWebSocketNotify = false;
$this->invokeHandleResult($processor, $context);
$this->assertEmpty($processor->wsSent);
}
/**
* 测试结束操作为 update
*/
public function testEndOperationIsUpdate(): void
{
$processor = $this->createTestableProcessor();
$context = $this->createContext([
'currentStep' => '结束',
'batchNo' => 'BATCH001',
]);
$context->needDatabaseOperation = true;
$context->dbOperation = DbOperationType::UPDATE;
$context->actionEndTime = date('Y-m-d H:i:s');
$this->invokeHandleResult($processor, $context);
$this->assertNotEmpty($processor->savedContexts);
$this->assertEquals(DbOperationType::UPDATE, $processor->savedContexts[0]['operation']);
}
/**
* 测试 getActionType 各流程类型映射
* 注:getActionType 已移至 EctActionsRepository,此测试验证映射逻辑
*/
public function testGetActionTypeMapping(): void
{
$repo = \app\repository\EctActionsRepository::new();
$method = new \ReflectionMethod($repo, 'mapActionType');
$method->setAccessible(true);
$this->assertEquals(1, $method->invoke($repo, '手工洗'));
$this->assertEquals(1, $method->invoke($repo, '手工洗(晨洗)'));
$this->assertEquals(1, $method->invoke($repo, '手工洗(加强)'));
$this->assertEquals(2, $method->invoke($repo, '机洗'));
$this->assertEquals(2, $method->invoke($repo, '机洗(晨洗)'));
$this->assertEquals(2, $method->invoke($repo, '机洗(加强)'));
$this->assertEquals(7, $method->invoke($repo, '测漏'));
$this->assertEquals(8, $method->invoke($repo, '存储'));
$this->assertEquals(0, $method->invoke($repo, '诊疗'));
// 未知类型 fallback 到 1
$this->assertEquals(1, $method->invoke($repo, '未知类型'));
}
/**
* 测试 updateConfig 更新引擎配置
*/
public function testUpdateConfig(): void
{
$processor = $this->createTestableProcessor();
$newConfig = ProcessConfig::createMachineWash();
$processor->updateConfig($newConfig);
$this->assertFalse($processor->engine->getNode('漂洗')->isEnabled());
$this->assertFalse($processor->engine->getNode('消毒')->isEnabled());
}
/**
* 测试静态工厂 create
*/
public function testStaticFactoryCreate(): void
{
$processor = FlowProcessor::create(ProcessConfig::createStandard());
$this->assertInstanceOf(FlowProcessor::class, $processor);
$this->assertInstanceOf(ProcessEngine::class, $processor->engine);
}
/**
* 通过反射调用 handleResultprotected 方法)
*/
private function invokeHandleResult(FlowProcessor $processor, ProcessContext $context): void
{
$method = new \ReflectionMethod($processor, 'handleResult');
$method->setAccessible(true);
$method->invoke($processor, $context);
}
}