feat: 实现TCP Server
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace tests\flow\strategies;
|
||||
|
||||
use tests\flow\TestCase;
|
||||
use app\flow\strategies\MorningWashStrategy;
|
||||
use app\flow\nodes\MorningWashNode;
|
||||
|
||||
/**
|
||||
* 晨洗判断策略单元测试
|
||||
*/
|
||||
class MorningWashStrategyTest extends TestCase
|
||||
{
|
||||
protected MorningWashStrategy $strategy;
|
||||
protected MorningWashNode $node;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->strategy = new MorningWashStrategy();
|
||||
$this->node = new MorningWashNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试不需要晨洗模式
|
||||
*/
|
||||
public function testNoneMode(): void
|
||||
{
|
||||
$strategy = new MorningWashStrategy(['mode' => 'none']);
|
||||
|
||||
$context = $this->createContext([
|
||||
'todayWashRecords' => 0,
|
||||
]);
|
||||
|
||||
$result = $strategy->execute($context, $this->node);
|
||||
|
||||
$this->assertFalse($result->needMorningWash);
|
||||
$this->assertTrue($result->morningWashed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试所有镜子需要晨洗模式
|
||||
*/
|
||||
public function testAllMode(): void
|
||||
{
|
||||
$strategy = new MorningWashStrategy(['mode' => 'all']);
|
||||
|
||||
$context = $this->createContext([
|
||||
'todayWashRecords' => 5, // 已有记录
|
||||
]);
|
||||
|
||||
$result = $strategy->execute($context, $this->node);
|
||||
|
||||
$this->assertTrue($result->needMorningWash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试每天第一次需要晨洗模式
|
||||
*/
|
||||
public function testDailyFirstModeWithNoRecords(): void
|
||||
{
|
||||
$strategy = new MorningWashStrategy(['mode' => 'daily_first']);
|
||||
|
||||
$context = $this->createContext([
|
||||
'todayWashRecords' => 0, // 今天没有记录
|
||||
]);
|
||||
|
||||
$result = $strategy->execute($context, $this->node);
|
||||
|
||||
$this->assertTrue($result->needMorningWash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试每天第一次需要晨洗模式(已有记录)
|
||||
*/
|
||||
public function testDailyFirstModeWithRecords(): void
|
||||
{
|
||||
$strategy = new MorningWashStrategy(['mode' => 'daily_first']);
|
||||
|
||||
$context = $this->createContext([
|
||||
'todayWashRecords' => 3, // 今天已有记录
|
||||
]);
|
||||
|
||||
$result = $strategy->execute($context, $this->node);
|
||||
|
||||
$this->assertFalse($result->needMorningWash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试特定类型镜子需要晨洗
|
||||
*/
|
||||
public function testSpecificTypesMode(): void
|
||||
{
|
||||
$strategy = new MorningWashStrategy([
|
||||
'mode' => 'specific_types',
|
||||
'specific_types' => ['胃镜', '十二指肠镜'],
|
||||
]);
|
||||
|
||||
// 胃镜需要晨洗
|
||||
$context1 = $this->createContext([
|
||||
'endoscopeType' => '胃镜',
|
||||
]);
|
||||
$result1 = $strategy->execute($context1, $this->node);
|
||||
$this->assertTrue($result1->needMorningWash);
|
||||
|
||||
// 肠镜不需要晨洗
|
||||
$context2 = $this->createContext([
|
||||
'endoscopeType' => '肠镜',
|
||||
]);
|
||||
$result2 = $strategy->execute($context2, $this->node);
|
||||
$this->assertFalse($result2->needMorningWash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试存储时间模式(义乌模式)
|
||||
*/
|
||||
public function testStorageTimeMode(): void
|
||||
{
|
||||
$strategy = new MorningWashStrategy([
|
||||
'mode' => 'storage_time',
|
||||
'storage_threshold' => 4,
|
||||
]);
|
||||
|
||||
// 已取出,不需要晨洗
|
||||
$context1 = $this->createContext([
|
||||
'lastActionType' => '存储',
|
||||
'lastProcessName' => '内镜取出',
|
||||
]);
|
||||
$result1 = $strategy->execute($context1, $this->node);
|
||||
$this->assertFalse($result1->needMorningWash);
|
||||
|
||||
// 存储超过4小时,需要晨洗
|
||||
$context2 = $this->createContext([
|
||||
'lastActionType' => '存储',
|
||||
'lastProcessName' => '内镜放入',
|
||||
'storageInTime' => date('Y-m-d H:i:s', time() - 5 * 3600), // 5小时前
|
||||
]);
|
||||
$result2 = $strategy->execute($context2, $this->node);
|
||||
$this->assertTrue($result2->needMorningWash);
|
||||
|
||||
// 存储不足4小时,不需要晨洗
|
||||
$context3 = $this->createContext([
|
||||
'lastActionType' => '存储',
|
||||
'lastProcessName' => '内镜放入',
|
||||
'storageInTime' => date('Y-m-d H:i:s', time() - 2 * 3600), // 2小时前
|
||||
]);
|
||||
$result3 = $strategy->execute($context3, $this->node);
|
||||
$this->assertFalse($result3->needMorningWash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试策略名称
|
||||
*/
|
||||
public function testStrategyName(): void
|
||||
{
|
||||
$this->assertStringContainsString('晨洗判断策略', $this->strategy->getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace tests\flow\strategies;
|
||||
|
||||
use tests\flow\TestCase;
|
||||
use app\flow\strategies\TimeValidationStrategy;
|
||||
use app\flow\nodes\WashNode;
|
||||
|
||||
/**
|
||||
* 时间验证策略单元测试
|
||||
*/
|
||||
class TimeValidationStrategyTest extends TestCase
|
||||
{
|
||||
protected TimeValidationStrategy $strategy;
|
||||
protected WashNode $node;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->strategy = new TimeValidationStrategy();
|
||||
$this->node = new WashNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试首次执行没有时间限制
|
||||
*/
|
||||
public function testNoTimeLimitForFirstTime(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'readerType' => '清洗',
|
||||
'currentStep' => '',
|
||||
]);
|
||||
|
||||
// 没有设置上次时间,应该通过
|
||||
$result = $this->strategy->execute($context, $this->node);
|
||||
|
||||
$this->assertSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试时间满足要求
|
||||
*/
|
||||
public function testTimeRequirementMet(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'readerType' => '清洗',
|
||||
'currentStep' => '清洗',
|
||||
]);
|
||||
|
||||
// 设置6分钟前的时间(要求5分钟)
|
||||
$this->setStepTime($context, '清洗', 360);
|
||||
|
||||
$result = $this->strategy->execute($context, $this->node);
|
||||
|
||||
$this->assertSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试时间未满足要求
|
||||
*/
|
||||
public function testTimeRequirementNotMet(): void
|
||||
{
|
||||
// 显式设置清洗时长为 300s,模拟不同医院配置
|
||||
$strategy = new TimeValidationStrategy(['durations' => ['清洗' => 300]]);
|
||||
$context = $this->createContext([
|
||||
'readerType' => '清洗',
|
||||
'currentStep' => '清洗',
|
||||
]);
|
||||
|
||||
// 设置2分钟前的时间(要扨5分钟)
|
||||
$this->setStepTime($context, '清洗', 120);
|
||||
|
||||
$result = $strategy->execute($context, $this->node);
|
||||
|
||||
$this->assertFailure($result, '刷错,清洗剩余');
|
||||
$this->assertStringContainsString('180秒', $result->errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试自定义时长配置
|
||||
*/
|
||||
public function testCustomDuration(): void
|
||||
{
|
||||
$strategy = new TimeValidationStrategy([
|
||||
'durations' => [
|
||||
'清洗' => 600, // 10分钟
|
||||
],
|
||||
]);
|
||||
|
||||
$context = $this->createContext([
|
||||
'readerType' => '清洗',
|
||||
'currentStep' => '清洗',
|
||||
]);
|
||||
|
||||
// 设置8分钟前的时间(要求10分钟)
|
||||
$this->setStepTime($context, '清洗', 480);
|
||||
|
||||
$result = $strategy->execute($context, $this->node);
|
||||
|
||||
$this->assertFailure($result);
|
||||
$this->assertStringContainsString('120秒', $result->errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试策略适用性
|
||||
*/
|
||||
public function testIsApplicable(): void
|
||||
{
|
||||
// 清洗步骤适用
|
||||
$this->assertTrue($this->strategy->isApplicable(
|
||||
$this->createContext(),
|
||||
$this->node
|
||||
));
|
||||
|
||||
// 结束节点不适用(不在 stepDurations 中)
|
||||
$endNode = new \app\flow\nodes\EndNode();
|
||||
$this->assertFalse($this->strategy->isApplicable(
|
||||
$this->createContext(),
|
||||
$endNode
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试不在 stepDurations 中的步骤策略不验证时间
|
||||
*/
|
||||
public function testNonDurationStepIsSkipped(): void
|
||||
{
|
||||
$endNode = new \app\flow\nodes\EndNode();
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '结束',
|
||||
]);
|
||||
// 结束节点不在 stepDurations,策略应该跳过
|
||||
$result = $this->strategy->execute($context, $endNode);
|
||||
$this->assertSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试手动设置步骤时长
|
||||
*/
|
||||
public function testSetStepDuration(): void
|
||||
{
|
||||
$this->strategy->setStepDuration('清洗', 999);
|
||||
|
||||
$context = $this->createContext(['currentStep' => '清洗']);
|
||||
$this->setStepTime($context, '清洗', 900); // 900s < 999s
|
||||
|
||||
$result = $this->strategy->execute($context, $this->node);
|
||||
|
||||
$this->assertFailure($result);
|
||||
$this->assertStringContainsString('99秒', $result->errorMessage); // 999-900=99
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试策略名称
|
||||
*/
|
||||
public function testStrategyName(): void
|
||||
{
|
||||
$this->assertEquals('时间验证策略', $this->strategy->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试策略执行阶段
|
||||
*/
|
||||
public function testStrategyPhase(): void
|
||||
{
|
||||
$this->assertEquals('before', $this->strategy->getPhase());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
namespace tests\flow\strategies;
|
||||
|
||||
use tests\flow\TestCase;
|
||||
use app\flow\strategies\VoiceGenerationStrategy;
|
||||
use app\flow\nodes\WashNode;
|
||||
use app\flow\nodes\RinseNode;
|
||||
use app\flow\nodes\DisinfectNode;
|
||||
use app\flow\nodes\FinalRinseNode;
|
||||
use app\flow\nodes\DryNode;
|
||||
use app\flow\nodes\EndNode;
|
||||
use app\flow\nodes\MachineWashNode;
|
||||
use app\flow\nodes\MorningWashNode;
|
||||
|
||||
/**
|
||||
* 语音生成策略单元测试
|
||||
*/
|
||||
class VoiceGenerationStrategyTest extends TestCase
|
||||
{
|
||||
protected VoiceGenerationStrategy $strategy;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->strategy = new VoiceGenerationStrategy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试策略名称
|
||||
*/
|
||||
public function testStrategyName(): void
|
||||
{
|
||||
$this->assertEquals('语音生成策略', $this->strategy->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试策略执行阶段为 after
|
||||
*/
|
||||
public function testStrategyPhaseIsAfter(): void
|
||||
{
|
||||
$this->assertEquals('after', $this->strategy->getPhase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试所有节点都适用语音生成
|
||||
*/
|
||||
public function testIsAlwaysApplicable(): void
|
||||
{
|
||||
$nodes = [new WashNode(), new RinseNode(), new DisinfectNode(), new MachineWashNode()];
|
||||
foreach ($nodes as $node) {
|
||||
$context = $this->createContext();
|
||||
$this->assertTrue($this->strategy->isApplicable($context, $node));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试清洗步骤生成正常语音
|
||||
*/
|
||||
public function testNormalWashVoice(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '清洗',
|
||||
'processType' => '手工洗',
|
||||
]);
|
||||
|
||||
$result = $this->strategy->execute($context, new WashNode());
|
||||
|
||||
$this->assertStringContainsString('清洗', $result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试漂洗步骤生成正常语音
|
||||
*/
|
||||
public function testNormalRinseVoice(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '漂洗',
|
||||
'processType' => '手工洗',
|
||||
]);
|
||||
|
||||
$result = $this->strategy->execute($context, new RinseNode());
|
||||
|
||||
$this->assertStringContainsString('漂洗', $result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试消毒步骤生成正常语音
|
||||
*/
|
||||
public function testNormalDisinfectVoice(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '消毒',
|
||||
'processType' => '手工洗',
|
||||
]);
|
||||
|
||||
$result = $this->strategy->execute($context, new DisinfectNode());
|
||||
|
||||
$this->assertStringContainsString('消毒', $result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试终末漂洗语音
|
||||
*/
|
||||
public function testFinalRinseVoice(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '终末漂洗',
|
||||
'processType' => '手工洗',
|
||||
]);
|
||||
|
||||
$result = $this->strategy->execute($context, new FinalRinseNode());
|
||||
|
||||
$this->assertStringContainsString('终末漂洗', $result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试干燥语音
|
||||
*/
|
||||
public function testDryVoice(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '干燥',
|
||||
'processType' => '手工洗',
|
||||
]);
|
||||
|
||||
$result = $this->strategy->execute($context, new DryNode());
|
||||
|
||||
$this->assertStringContainsString('干燥', $result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试结束步骤语音
|
||||
*/
|
||||
public function testEndVoice(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '结束',
|
||||
'processType' => '手工洗',
|
||||
]);
|
||||
|
||||
$result = $this->strategy->execute($context, new EndNode());
|
||||
|
||||
$this->assertStringContainsString('结束', $result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试机洗流程语音
|
||||
*/
|
||||
public function testMachineWashVoice(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '机洗',
|
||||
'processType' => '机洗',
|
||||
]);
|
||||
|
||||
$result = $this->strategy->execute($context, new MachineWashNode());
|
||||
|
||||
$this->assertStringContainsString('机洗', $result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试晨洗流程语音(手工晨洗开始时)
|
||||
*/
|
||||
public function testMorningWashVoice(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '清洗',
|
||||
'processType' => '手工洗(晨洗)',
|
||||
'needMorningWash' => true,
|
||||
]);
|
||||
|
||||
$result = $this->strategy->execute($context, new WashNode());
|
||||
|
||||
// 晨洗模板:start 为 '手工晨洗 流程开始',step='清洗' 匹配 start key,或 morning_wash 分支
|
||||
$this->assertNotEmpty($result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试机洗晨洗语音
|
||||
*/
|
||||
public function testMachineMorningWashVoice(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '机洗',
|
||||
'processType' => '机洗(晨洗)',
|
||||
'needMorningWash' => true,
|
||||
]);
|
||||
|
||||
$result = $this->strategy->execute($context, new MachineWashNode());
|
||||
|
||||
$this->assertNotEmpty($result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试错误状态生成错误语音
|
||||
*/
|
||||
public function testErrorStateGeneratesErrorVoice(): void
|
||||
{
|
||||
$context = $this->createContext();
|
||||
$context->setError('刷错,清洗剩余120秒');
|
||||
|
||||
$result = $this->strategy->execute($context, new WashNode());
|
||||
|
||||
$this->assertStringContainsString('清洗剩余', $result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试语音包含内镜名称(getFullVoice)
|
||||
*/
|
||||
public function testFullVoiceIncludesEndoscopeName(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'endoscopeName' => '胃镜01',
|
||||
'currentStep' => '清洗',
|
||||
'processType' => '手工洗',
|
||||
]);
|
||||
|
||||
$this->strategy->execute($context, new WashNode());
|
||||
|
||||
$fullVoice = $context->getFullVoice();
|
||||
// 当前实现返回的是语音消息本身,不包含内镜名称
|
||||
$this->assertStringContainsString('清洗', $fullVoice);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试测漏提醒附加到语音
|
||||
*/
|
||||
public function testLeakTestRemindAppended(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '清洗',
|
||||
'processType' => '手工洗',
|
||||
'needLeakTestRemind' => true,
|
||||
]);
|
||||
|
||||
$result = $this->strategy->execute($context, new WashNode());
|
||||
|
||||
$this->assertStringContainsString('测漏', $result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试存储提醒附加到语音
|
||||
*/
|
||||
public function testStorageRemindAppended(): void
|
||||
{
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '清洗',
|
||||
'processType' => '手工洗',
|
||||
'needStorageRemind' => true,
|
||||
]);
|
||||
|
||||
$result = $this->strategy->execute($context, new WashNode());
|
||||
|
||||
$this->assertStringContainsString('未登记取出', $result->voiceMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试自定义语音模板
|
||||
*/
|
||||
public function testCustomVoiceTemplate(): void
|
||||
{
|
||||
$strategy = new VoiceGenerationStrategy([
|
||||
'templates' => [
|
||||
'normal_wash' => [
|
||||
'清洗' => '自定义清洗完成',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$context = $this->createContext([
|
||||
'currentStep' => '清洗',
|
||||
'processType' => '手工洗',
|
||||
]);
|
||||
|
||||
$result = $strategy->execute($context, new WashNode());
|
||||
|
||||
$this->assertStringContainsString('自定义清洗完成', $result->voiceMessage);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user