feat: 实现TCP Server
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射调用 handleResult(protected 方法)
|
||||
*/
|
||||
private function invokeHandleResult(FlowProcessor $processor, ProcessContext $context): void
|
||||
{
|
||||
$method = new \ReflectionMethod($processor, 'handleResult');
|
||||
$method->setAccessible(true);
|
||||
$method->invoke($processor, $context);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user