e040fccba6
- 修改 AbstractProcessNode 中 ProcessContext 的命名空间引用为 app\flow\context\ProcessContext - 引入 app\flow\vo\CanHandleResult 用于节点处理结果表示 - 更新前置策略执行后对成功状态的判断,改为调用 isSuccess() 方法 - 增加日志记录细节,便于调试策略执行中断时的错误信息 - 优化代码注释,提升代码可读性和维护性
241 lines
7.9 KiB
PHP
241 lines
7.9 KiB
PHP
<?php
|
||
|
||
namespace tests\flow;
|
||
|
||
use app\flow\FlowProcessor;
|
||
use app\flow\context\ProcessContext;
|
||
use app\flow\ProcessEngine;
|
||
use app\flow\config\ProcessConfig;
|
||
use app\flow\enum\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);
|
||
}
|
||
}
|