diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results index bef561f..749de9a 100644 --- a/.phpunit.cache/test-results +++ b/.phpunit.cache/test-results @@ -1 +1 @@ -{"version":2,"defects":{"tests\\flow\\FlowProcessorTest::testCreateProcessor":8,"tests\\flow\\FlowProcessorTest::testCreateProcessorWithConfig":8,"tests\\flow\\FlowProcessorTest::testUpdateConfig":8,"tests\\flow\\FlowProcessorTest::testBatchNoConsistency":8,"tests\\flow\\FlowProcessorTest::testBatchNoConsistencyInCompleteProcess":8,"tests\\flow\\FlowProcessorTest::testDatabaseOperationFlags":8,"tests\\flow\\FlowProcessorTest::testWebSocketNotifyFlag":8,"tests\\flow\\FlowProcessorTest::testVoiceGeneration":8,"tests\\flow\\FlowProcessorTest::testErrorVoiceGeneration":7,"tests\\flow\\FlowProcessorTest::testMultiHospitalConfig":8,"tests\\flow\\FlowProcessorTest::testSpecialHospitalConfig":8,"tests\\flow\\ProcessEngineTest::testTimeValidationFailure":8,"tests\\flow\\ProcessEngineTest::testWrongStep":7,"tests\\flow\\strategies\\TimeValidationStrategyTest::testCustomDuration":7,"tests\\flow\\ProcessEngineTest::testSkipStep":7,"tests\\flow\\BatchConsistencyTest::testBatchNoPreservedOnError":8,"tests\\flow\\BatchConsistencyTest::testBatchNoWithDifferentProcessTypes":8,"tests\\flow\\EdgeCaseTest::testBatchNoGenerationEdgeCases":7,"tests\\flow\\PerformanceTest::testContextCreationPerformance":7,"tests\\flow\\PerformanceTest::testStrategyExecutionPerformance":7,"tests\\flow\\PerformanceTest::testChainTraversalPerformance":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleMorningWashReader":7,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcess":7,"tests\\flow\\nodes\\MorningWashNodeTest::testGenerateBatchNo":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleDisinfectReaderForMorningWash":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleMachineWashReaderForMorningWash":8,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcessWithDisinfect":8,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcessWithMachineWash":8,"tests\\flow\\EdgeCaseTest::testConcurrentBatchNoGeneration":7,"tests\\flow\\ProcessEngineTest::testDisinfectAfterWash":7,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterWash":8,"tests\\flow\\nodes\\DryNodeTest::testCanHandleAfterDisinfect":8,"tests\\flow\\UsageExampleTest::testExample4CustomVoice":8,"tests\\flow\\UsageExampleTest::testExample8TimeValidation":8,"tests\\flow\\UsageExampleTest::testLoadCustomProcessFromConfig":8,"tests\\flow\\UsageExampleTest::testCreateNoMorningWashFromConfig":8,"tests\\flow\\UsageExampleTest::testCreateMachineWashFromConfig":8,"tests\\flow\\UsageExampleTest::testCreateYiwuModeFromConfig":8,"tests\\flow\\UsageExampleTest::testExample1StandardProcess":8,"tests\\flow\\UsageExampleTest::testExample2NoMorningWash":8,"tests\\flow\\UsageExampleTest::testExample3DynamicAdjust":8,"tests\\flow\\UsageExampleTest::testExample5MorningWashModes":8,"tests\\flow\\UsageExampleTest::testExample6MachineWash":8,"tests\\flow\\UsageExampleTest::testExample7SimpleProcess":8,"tests\\flow\\UsageExampleTest::testExample9FullProcess":8,"tests\\flow\\UsageExampleTest::testExample10MultiHospital":8,"tests\\flow\\UsageExampleTest::testFlowProcessorUsage":8,"tests\\flow\\UsageExampleTest::testFlowProcessorCompleteProcess":8,"tests\\flow\\UsageExampleTest::testFlowProcessorWithConfig":8,"tests\\flow\\BatchConsistencyTest::testSingleMachineBatchConsistency":8,"tests\\flow\\BatchConsistencyTest::testMultiEndoscopeBatchUniqueness":8,"tests\\flow\\BatchConsistencyTest::testBatchNoStability":8,"tests\\flow\\BatchConsistencyTest::testNewProcessGeneratesNewBatchNo":8,"tests\\flow\\BatchConsistencyTest::testDistributedBatchConsistency":8,"tests\\flow\\PerformanceTest::testSingleExecutionPerformance":8,"tests\\flow\\PerformanceTest::testCompleteProcessPerformance":8,"tests\\flow\\PerformanceTest::testEngineCreationPerformance":8,"tests\\flow\\PerformanceTest::testConcurrentProcessingSimulation":8,"tests\\flow\\ProcessEngineTest::testCreateStandardEngine":8,"tests\\flow\\ProcessEngineTest::testCreateNoMorningWashEngine":8,"tests\\flow\\ProcessEngineTest::testCreateSimpleEngine":8,"tests\\flow\\ProcessEngineTest::testCompleteWashProcess":8,"tests\\flow\\ProcessEngineTest::testStandardWashRinseDisinfectFlow":8,"tests\\flow\\ProcessEngineTest::testDisableNode":8,"tests\\flow\\ProcessEngineTest::testGetNodes":8,"tests\\flow\\ProcessEngineTest::testGetEnabledNodes":8,"tests\\flow\\ProcessEngineTest::testUpdateConfig":8,"tests\\flow\\ProcessEngineTest::testMachineWashProcess":8,"tests\\db\\DBC::testDBConnect":8,"tests\\db\\DBC::testGenTables":7,"tests\\db\\DBC::testModel":5,"tests\\flow\\nodes\\DisinfectNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterRinse":8,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterFinalRinse":8,"tests\\flow\\nodes\\DisinfectNodeTest::testCannotHandleNonDisinfectReader":8,"tests\\flow\\nodes\\DisinfectNodeTest::testCannotHandleAfterDisinfect":8,"tests\\flow\\nodes\\DisinfectNodeTest::testHandleProcess":8,"tests\\flow\\nodes\\DisinfectNodeTest::testDatabaseOperationFlags":7,"tests\\flow\\nodes\\DisinfectNodeTest::testWebSocketNotifyFlag":8,"tests\\flow\\nodes\\DryNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\DryNodeTest::testCanHandleAfterFinalRinse":7,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleAfterRinse":8,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleNonDryReader":8,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleAfterDry":7,"tests\\flow\\nodes\\DryNodeTest::testHandleProcess":7,"tests\\flow\\nodes\\DryNodeTest::testDatabaseOperationFlags":7,"tests\\flow\\nodes\\DryNodeTest::testWebSocketNotifyFlag":7,"tests\\flow\\nodes\\DryNodeTest::testKeepExistingBatchNo":8,"tests\\flow\\nodes\\MorningWashNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleWhenNoNeed":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleNonDisinfectMachineReader":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleWhenAlreadyWashed":8,"tests\\flow\\nodes\\EndNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterDry":8,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterDisinfect":7,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterFinalRinse":8,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterMachineWash":8,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleNonEndReader":8,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleAfterWash":8,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleAfterRinse":8,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleWithEmptyStep":8,"tests\\flow\\nodes\\EndNodeTest::testHandleProcess":8,"tests\\flow\\nodes\\EndNodeTest::testDatabaseOperationIsUpdate":7,"tests\\flow\\nodes\\EndNodeTest::testWebSocketNotifyFlag":8,"tests\\flow\\nodes\\EndNodeTest::testActionEndTimeIsSet":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testCanHandleAfterDisinfect":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testCanHandleAfterMachineWashByDefault":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleAfterMachineWashWhenDisabled":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleNonFinalRinseReader":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testHandleProcess":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleAfterWash":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testDatabaseOperationFlags":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testWebSocketNotifyFlag":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\MachineWashNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterWash":8,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterRinse":7,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterDisinfect":7,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleWithEmptyStep":7,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterEnd":7,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterEndoscopeOut":8,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleNonMachineWashReader":8,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleAfterFinalRinse":8,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleAfterDry":8,"tests\\flow\\nodes\\MachineWashNodeTest::testHandleProcess":8,"tests\\flow\\nodes\\MachineWashNodeTest::testDatabaseOperationFlags":7,"tests\\flow\\nodes\\MachineWashNodeTest::testWebSocketNotifyFlag":8,"tests\\flow\\nodes\\RinseNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\RinseNodeTest::testCanHandleAfterWash":7,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleNonRinseReader":8,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleAfterDisinfect":7,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleWithEmptyStep":7,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleAfterRinse":7,"tests\\flow\\nodes\\RinseNodeTest::testHandleProcess":7,"tests\\flow\\nodes\\RinseNodeTest::testDatabaseOperationFlags":7,"tests\\flow\\nodes\\RinseNodeTest::testWebSocketNotifyFlag":7,"tests\\flow\\nodes\\RinseNodeTest::testDisabledNodeSkips":8,"tests\\flow\\nodes\\WashNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\WashNodeTest::testCanHandleWashReader":8,"tests\\flow\\nodes\\WashNodeTest::testCannotHandleNonWashReader":8,"tests\\flow\\nodes\\WashNodeTest::testCanStartNewWashProcess":8,"tests\\flow\\nodes\\WashNodeTest::testCannotWashWithoutMorningWash":8,"tests\\flow\\nodes\\WashNodeTest::testHandleProcess":8,"tests\\flow\\nodes\\WashNodeTest::testGenerateBatchNo":7,"tests\\flow\\nodes\\WashNodeTest::testKeepExistingBatchNo":8,"tests\\flow\\strategies\\TimeValidationStrategyTest::testTimeRequirementNotMet":7,"tests\\flow\\strategies\\TimeValidationStrategyTest::testSetStepDuration":7,"tests\\flow\\FlowProcessorTest::testSuccessfulFlowSavesToDatabase":8,"tests\\flow\\FlowProcessorTest::testEndOperationIsUpdate":8,"tests\\flow\\FlowProcessorTest::testGetActionTypeMapping":8,"tests\\flow\\EdgeCaseTest::testLongEndoscopeName":7,"tests\\flow\\EdgeCaseTest::testSpecialCharactersInName":7,"tests\\flow\\ProcessContextTest::testGetFullVoice":7,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testFullVoiceIncludesEndoscopeName":7,"tests\\flow\\FlowProcessorTest::testSuccessfulFlowSendsVoice":8,"tests\\flow\\FlowProcessorTest::testFailedFlowDoesNotSaveToDatabase":8,"tests\\flow\\FlowProcessorTest::testFailedFlowStillSendsVoice":8,"tests\\flow\\FlowProcessorTest::testWebSocketNotificationSentWhenNeeded":8,"tests\\flow\\FlowProcessorTest::testWebSocketNotificationNotSentWhenNotNeeded":8,"tests\\flow\\FlowProcessorTest::testStaticFactoryCreate":8,"tests\\flow\\EdgeCaseTest::testAllNodesDisabled":8,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testFinalRinseVoice":8},"times":{"tests\\flow\\FlowProcessorTest::testCreateProcessor":0.006,"tests\\flow\\FlowProcessorTest::testCreateProcessorWithConfig":0.006,"tests\\flow\\FlowProcessorTest::testStaticFactory":0.006,"tests\\flow\\FlowProcessorTest::testUpdateConfig":0.009,"tests\\flow\\FlowProcessorTest::testBatchNoConsistency":0.006,"tests\\flow\\FlowProcessorTest::testBatchNoConsistencyInCompleteProcess":0.008,"tests\\flow\\FlowProcessorTest::testDatabaseOperationFlags":0.006,"tests\\flow\\FlowProcessorTest::testWebSocketNotifyFlag":0.005,"tests\\flow\\FlowProcessorTest::testVoiceGeneration":0.007,"tests\\flow\\FlowProcessorTest::testErrorVoiceGeneration":0.006,"tests\\flow\\FlowProcessorTest::testMultiHospitalConfig":0.007,"tests\\flow\\FlowProcessorTest::testSpecialHospitalConfig":0.006,"tests\\flow\\ProcessContextTest::testCreateContext":0.005,"tests\\flow\\ProcessContextTest::testSetError":0.006,"tests\\flow\\ProcessContextTest::testSetVoice":0.006,"tests\\flow\\ProcessContextTest::testGetFullVoice":0.006,"tests\\flow\\ProcessContextTest::testStepTimeManagement":0.006,"tests\\flow\\ProcessContextTest::testStepDurationManagement":0.005,"tests\\flow\\ProcessContextTest::testDefaultStepDuration":0.007,"tests\\flow\\ProcessContextTest::testGenerateBatchNo":0.006,"tests\\flow\\ProcessContextTest::testBatchNoUniqueness":0.006,"tests\\flow\\ProcessContextTest::testCanStartNewProcess":0.006,"tests\\flow\\ProcessContextTest::testIsWashProcessCompleted":0.006,"tests\\flow\\ProcessContextTest::testChaining":0.005,"tests\\flow\\ProcessEngineTest::testCreateStandardEngine":0.006,"tests\\flow\\ProcessEngineTest::testCreateNoMorningWashEngine":0.006,"tests\\flow\\ProcessEngineTest::testCreateSimpleEngine":0.006,"tests\\flow\\ProcessEngineTest::testCompleteWashProcess":0.045,"tests\\flow\\ProcessEngineTest::testTimeValidationFailure":0.005,"tests\\flow\\ProcessEngineTest::testWrongStep":0.01,"tests\\flow\\ProcessEngineTest::testDisableNode":0.005,"tests\\flow\\ProcessEngineTest::testGetNodes":0.006,"tests\\flow\\ProcessEngineTest::testGetEnabledNodes":0.005,"tests\\flow\\ProcessEngineTest::testUpdateConfig":0.006,"tests\\flow\\ProcessEngineTest::testMachineWashProcess":0.033,"tests\\flow\\nodes\\FinalRinseNodeTest::testCanHandleAfterDisinfect":0.007,"tests\\flow\\nodes\\FinalRinseNodeTest::testCanHandleAfterMachineWashByDefault":0.005,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleAfterMachineWashWhenDisabled":0.008,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleNonFinalRinseReader":0.008,"tests\\flow\\nodes\\FinalRinseNodeTest::testHandleProcess":0.01,"tests\\flow\\nodes\\WashNodeTest::testNodeIdentity":0.006,"tests\\flow\\nodes\\WashNodeTest::testCanHandleWashReader":0.006,"tests\\flow\\nodes\\WashNodeTest::testCannotHandleNonWashReader":0.005,"tests\\flow\\nodes\\WashNodeTest::testCanStartNewWashProcess":0.005,"tests\\flow\\nodes\\WashNodeTest::testCannotWashWithoutMorningWash":0.006,"tests\\flow\\nodes\\WashNodeTest::testHandleProcess":0.009,"tests\\flow\\nodes\\WashNodeTest::testGenerateBatchNo":0.008,"tests\\flow\\nodes\\WashNodeTest::testKeepExistingBatchNo":0.006,"tests\\flow\\strategies\\MorningWashStrategyTest::testNoneMode":0.006,"tests\\flow\\strategies\\MorningWashStrategyTest::testAllMode":0.005,"tests\\flow\\strategies\\MorningWashStrategyTest::testDailyFirstModeWithNoRecords":0.005,"tests\\flow\\strategies\\MorningWashStrategyTest::testDailyFirstModeWithRecords":0.006,"tests\\flow\\strategies\\MorningWashStrategyTest::testSpecificTypesMode":0.005,"tests\\flow\\strategies\\MorningWashStrategyTest::testStorageTimeMode":0.006,"tests\\flow\\strategies\\MorningWashStrategyTest::testStrategyName":0.005,"tests\\flow\\strategies\\TimeValidationStrategyTest::testNoTimeLimitForFirstTime":0.005,"tests\\flow\\strategies\\TimeValidationStrategyTest::testTimeRequirementMet":0.006,"tests\\flow\\strategies\\TimeValidationStrategyTest::testTimeRequirementNotMet":0.005,"tests\\flow\\strategies\\TimeValidationStrategyTest::testCustomDuration":0.006,"tests\\flow\\strategies\\TimeValidationStrategyTest::testIsApplicable":0.006,"tests\\flow\\strategies\\TimeValidationStrategyTest::testStrategyName":0.006,"tests\\flow\\strategies\\TimeValidationStrategyTest::testStrategyPhase":0.005,"tests\\flow\\ProcessEngineTest::testSkipStep":0.012,"tests\\flow\\ProcessEngineTest::testDisinfectAfterWash":0.005,"tests\\flow\\BatchConsistencyTest::testSingleMachineBatchConsistency":0.046,"tests\\flow\\BatchConsistencyTest::testMultiEndoscopeBatchUniqueness":0.036,"tests\\flow\\BatchConsistencyTest::testBatchNoFormat":0.008,"tests\\flow\\BatchConsistencyTest::testBatchNoStability":0.02,"tests\\flow\\BatchConsistencyTest::testNewProcessGeneratesNewBatchNo":0.024,"tests\\flow\\BatchConsistencyTest::testDistributedBatchConsistency":0.057,"tests\\flow\\BatchConsistencyTest::testBatchNoPreservedOnError":0.024,"tests\\flow\\BatchConsistencyTest::testBatchNoWithDifferentProcessTypes":0.022,"tests\\flow\\EdgeCaseTest::testEmptyContext":0.005,"tests\\flow\\EdgeCaseTest::testLongEndoscopeName":0.006,"tests\\flow\\EdgeCaseTest::testSpecialCharactersInName":0.006,"tests\\flow\\EdgeCaseTest::testBatchNoGenerationEdgeCases":0.006,"tests\\flow\\EdgeCaseTest::testTimeBoundaryValues":0.006,"tests\\flow\\EdgeCaseTest::testEmptyChain":0.005,"tests\\flow\\EdgeCaseTest::testAllNodesDisabled":0.021,"tests\\flow\\EdgeCaseTest::testRepeatedStepTimeSetting":0.006,"tests\\flow\\EdgeCaseTest::testChainingEdgeCases":0.006,"tests\\flow\\EdgeCaseTest::testErrorStateOverwrite":0.006,"tests\\flow\\EdgeCaseTest::testRecoveryFromError":0.005,"tests\\flow\\EdgeCaseTest::testInvalidConfigItems":0.007,"tests\\flow\\EdgeCaseTest::testConcurrentBatchNoGeneration":0.011,"tests\\flow\\EdgeCaseTest::testMorningWashBoundaryTime":0.006,"tests\\flow\\EdgeCaseTest::testStorageTimeBoundary":0.007,"tests\\flow\\EdgeCaseTest::testStorageTimeOverThreshold":0.005,"tests\\flow\\PerformanceTest::testSingleExecutionPerformance":0.011,"tests\\flow\\PerformanceTest::testCompleteProcessPerformance":0.039,"tests\\flow\\PerformanceTest::testBatchNoGenerationPerformance":0.023,"tests\\flow\\PerformanceTest::testEngineCreationPerformance":0.094,"tests\\flow\\PerformanceTest::testConfigLoadingPerformance":0.023,"tests\\flow\\PerformanceTest::testContextCreationPerformance":0.258,"tests\\flow\\PerformanceTest::testStrategyExecutionPerformance":0.156,"tests\\flow\\PerformanceTest::testMemoryUsage":0.226,"tests\\flow\\PerformanceTest::testConcurrentProcessingSimulation":0.058,"tests\\flow\\PerformanceTest::testChainTraversalPerformance":5.796,"tests\\flow\\config\\ProcessConfigTest::testCreateDefaultConfig":0.007,"tests\\flow\\config\\ProcessConfigTest::testFromArray":0.006,"tests\\flow\\config\\ProcessConfigTest::testGetEnabledSteps":0.007,"tests\\flow\\config\\ProcessConfigTest::testAddStep":0.006,"tests\\flow\\config\\ProcessConfigTest::testRemoveStep":0.005,"tests\\flow\\config\\ProcessConfigTest::testSetNodeEnabled":0.008,"tests\\flow\\config\\ProcessConfigTest::testMorningWashConfig":0.006,"tests\\flow\\config\\ProcessConfigTest::testTimeValidationConfig":0.007,"tests\\flow\\config\\ProcessConfigTest::testVoiceTemplateConfig":0.005,"tests\\flow\\config\\ProcessConfigTest::testSetStepVoice":0.006,"tests\\flow\\config\\ProcessConfigTest::testToArray":0.007,"tests\\flow\\config\\ProcessConfigTest::testCreateStandard":0.006,"tests\\flow\\config\\ProcessConfigTest::testCreateNoMorningWash":0.007,"tests\\flow\\config\\ProcessConfigTest::testCreateSimple":0.006,"tests\\flow\\config\\ProcessConfigTest::testCreateMachineWash":0.005,"tests\\flow\\config\\ProcessConfigTest::testCreateNoDry":0.006,"tests\\flow\\config\\ProcessConfigTest::testCreateDryOnly":0.005,"tests\\flow\\config\\ProcessConfigTest::testChaining":0.006,"tests\\flow\\nodes\\DisinfectNodeTest::testNodeIdentity":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterRinse":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterFinalRinse":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterWash":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testCannotHandleNonDisinfectReader":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testCannotHandleAfterDisinfect":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testHandleProcess":0.009,"tests\\flow\\nodes\\DisinfectNodeTest::testDatabaseOperationFlags":0.008,"tests\\flow\\nodes\\DisinfectNodeTest::testWebSocketNotifyFlag":0.008,"tests\\flow\\nodes\\MorningWashNodeTest::testNodeIdentity":0.005,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleMorningWashReader":0.021,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleWhenNoNeed":0.006,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleNonMorningWashReader":0.005,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleWhenAlreadyWashed":0.005,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcess":0.008,"tests\\flow\\nodes\\MorningWashNodeTest::testGenerateBatchNo":0.009,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleDisinfectReaderForMorningWash":0.006,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleMachineWashReaderForMorningWash":0.005,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleNonDisinfectMachineReader":0.006,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcessWithDisinfect":0.009,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcessWithMachineWash":0.009,"tests\\flow\\nodes\\DryNodeTest::testNodeIdentity":0.006,"tests\\flow\\nodes\\DryNodeTest::testCanHandleAfterFinalRinse":0.007,"tests\\flow\\nodes\\DryNodeTest::testCanHandleAfterDisinfect":0.006,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleAfterRinse":0.005,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleNonDryReader":0.007,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleAfterDry":0.006,"tests\\flow\\nodes\\DryNodeTest::testHandleProcess":0.008,"tests\\flow\\nodes\\DryNodeTest::testDatabaseOperationFlags":0.009,"tests\\flow\\nodes\\DryNodeTest::testWebSocketNotifyFlag":0.008,"tests\\flow\\nodes\\DryNodeTest::testKeepExistingBatchNo":0.01,"tests\\flow\\ProcessEngineTest::testStandardWashRinseDisinfectFlow":0.023,"tests\\flow\\UsageExampleTest::testExample1StandardProcess":0.022,"tests\\flow\\UsageExampleTest::testExample2NoMorningWash":0.025,"tests\\flow\\UsageExampleTest::testExample3DynamicAdjust":0.048,"tests\\flow\\UsageExampleTest::testExample4CustomVoice":0.022,"tests\\flow\\UsageExampleTest::testExample5MorningWashModes":0.033,"tests\\flow\\UsageExampleTest::testExample6MachineWash":0.022,"tests\\flow\\UsageExampleTest::testExample7SimpleProcess":0.021,"tests\\flow\\UsageExampleTest::testExample8TimeValidation":0.021,"tests\\flow\\UsageExampleTest::testExample9FullProcess":0.021,"tests\\flow\\UsageExampleTest::testExample10MultiHospital":0.046,"tests\\flow\\UsageExampleTest::testLoadCustomProcessFromConfig":0.007,"tests\\flow\\UsageExampleTest::testCreateNoMorningWashFromConfig":0.042,"tests\\flow\\UsageExampleTest::testCreateMachineWashFromConfig":0.036,"tests\\flow\\UsageExampleTest::testCreateYiwuModeFromConfig":0.015,"tests\\flow\\UsageExampleTest::testFlowProcessorUsage":0.045,"tests\\flow\\UsageExampleTest::testFlowProcessorCompleteProcess":0.021,"tests\\flow\\UsageExampleTest::testFlowProcessorWithConfig":0.037,"tests\\db\\DBC::testDBConnect":0.113,"tests\\db\\DBC::testGenTables":0.416,"tests\\db\\DBC::testModel":0.171,"tests\\flow\\nodes\\EndNodeTest::testNodeIdentity":0.006,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterDry":0.006,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterDisinfect":0.005,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterFinalRinse":0.005,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterMachineWash":0.005,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleNonEndReader":0.005,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleAfterWash":0.007,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleAfterRinse":0.006,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleWithEmptyStep":0.005,"tests\\flow\\nodes\\EndNodeTest::testHandleProcess":0.009,"tests\\flow\\nodes\\EndNodeTest::testDatabaseOperationIsUpdate":0.011,"tests\\flow\\nodes\\EndNodeTest::testWebSocketNotifyFlag":0.009,"tests\\flow\\nodes\\EndNodeTest::testActionEndTimeIsSet":0.009,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleAfterWash":0.006,"tests\\flow\\nodes\\FinalRinseNodeTest::testDatabaseOperationFlags":0.008,"tests\\flow\\nodes\\FinalRinseNodeTest::testWebSocketNotifyFlag":0.009,"tests\\flow\\nodes\\FinalRinseNodeTest::testNodeIdentity":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testNodeIdentity":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterWash":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterRinse":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterDisinfect":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleWithEmptyStep":0.006,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterEnd":0.006,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterEndoscopeOut":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleNonMachineWashReader":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleAfterFinalRinse":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleAfterDry":0.006,"tests\\flow\\nodes\\MachineWashNodeTest::testHandleProcess":0.008,"tests\\flow\\nodes\\MachineWashNodeTest::testDatabaseOperationFlags":0.01,"tests\\flow\\nodes\\MachineWashNodeTest::testWebSocketNotifyFlag":0.008,"tests\\flow\\nodes\\RinseNodeTest::testNodeIdentity":0.005,"tests\\flow\\nodes\\RinseNodeTest::testCanHandleAfterWash":0.007,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleNonRinseReader":0.007,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleAfterDisinfect":0.006,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleWithEmptyStep":0.006,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleAfterRinse":0.005,"tests\\flow\\nodes\\RinseNodeTest::testHandleProcess":0.01,"tests\\flow\\nodes\\RinseNodeTest::testDatabaseOperationFlags":0.009,"tests\\flow\\nodes\\RinseNodeTest::testWebSocketNotifyFlag":0.009,"tests\\flow\\nodes\\RinseNodeTest::testDisabledNodeSkips":0.009,"tests\\flow\\strategies\\TimeValidationStrategyTest::testNonDurationStepIsSkipped":0.008,"tests\\flow\\strategies\\TimeValidationStrategyTest::testSetStepDuration":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testStrategyName":0.007,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testStrategyPhaseIsAfter":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testIsAlwaysApplicable":0.007,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testNormalWashVoice":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testNormalRinseVoice":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testNormalDisinfectVoice":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testFinalRinseVoice":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testDryVoice":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testEndVoice":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testMachineWashVoice":0.007,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testMorningWashVoice":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testMachineMorningWashVoice":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testErrorStateGeneratesErrorVoice":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testFullVoiceIncludesEndoscopeName":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testLeakTestRemindAppended":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testStorageRemindAppended":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testCustomVoiceTemplate":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testSetStepVoice":0.005,"tests\\flow\\FlowProcessorTest::testSuccessfulFlowSavesToDatabase":0.011,"tests\\flow\\FlowProcessorTest::testSuccessfulFlowSendsVoice":0.008,"tests\\flow\\FlowProcessorTest::testFailedFlowDoesNotSaveToDatabase":0.007,"tests\\flow\\FlowProcessorTest::testFailedFlowStillSendsVoice":0.008,"tests\\flow\\FlowProcessorTest::testWebSocketNotificationSentWhenNeeded":0.01,"tests\\flow\\FlowProcessorTest::testWebSocketNotificationNotSentWhenNotNeeded":0.008,"tests\\flow\\FlowProcessorTest::testEndOperationIsUpdate":0.01,"tests\\flow\\FlowProcessorTest::testGetActionTypeMapping":0.107,"tests\\flow\\FlowProcessorTest::testStaticFactoryCreate":0.007}} \ No newline at end of file +{"version":2,"defects":{"tests\\flow\\FlowProcessorTest::testCreateProcessor":8,"tests\\flow\\FlowProcessorTest::testCreateProcessorWithConfig":8,"tests\\flow\\FlowProcessorTest::testUpdateConfig":8,"tests\\flow\\FlowProcessorTest::testBatchNoConsistency":8,"tests\\flow\\FlowProcessorTest::testBatchNoConsistencyInCompleteProcess":8,"tests\\flow\\FlowProcessorTest::testDatabaseOperationFlags":8,"tests\\flow\\FlowProcessorTest::testWebSocketNotifyFlag":8,"tests\\flow\\FlowProcessorTest::testVoiceGeneration":8,"tests\\flow\\FlowProcessorTest::testErrorVoiceGeneration":7,"tests\\flow\\FlowProcessorTest::testMultiHospitalConfig":8,"tests\\flow\\FlowProcessorTest::testSpecialHospitalConfig":8,"tests\\flow\\ProcessEngineTest::testTimeValidationFailure":8,"tests\\flow\\ProcessEngineTest::testWrongStep":7,"tests\\flow\\strategies\\TimeValidationStrategyTest::testCustomDuration":7,"tests\\flow\\ProcessEngineTest::testSkipStep":7,"tests\\flow\\BatchConsistencyTest::testBatchNoPreservedOnError":8,"tests\\flow\\BatchConsistencyTest::testBatchNoWithDifferentProcessTypes":8,"tests\\flow\\EdgeCaseTest::testBatchNoGenerationEdgeCases":7,"tests\\flow\\PerformanceTest::testContextCreationPerformance":7,"tests\\flow\\PerformanceTest::testStrategyExecutionPerformance":7,"tests\\flow\\PerformanceTest::testChainTraversalPerformance":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleMorningWashReader":7,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcess":7,"tests\\flow\\nodes\\MorningWashNodeTest::testGenerateBatchNo":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleDisinfectReaderForMorningWash":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleMachineWashReaderForMorningWash":8,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcessWithDisinfect":8,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcessWithMachineWash":8,"tests\\flow\\EdgeCaseTest::testConcurrentBatchNoGeneration":7,"tests\\flow\\ProcessEngineTest::testDisinfectAfterWash":7,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterWash":8,"tests\\flow\\nodes\\DryNodeTest::testCanHandleAfterDisinfect":8,"tests\\flow\\UsageExampleTest::testExample4CustomVoice":8,"tests\\flow\\UsageExampleTest::testExample8TimeValidation":8,"tests\\flow\\UsageExampleTest::testLoadCustomProcessFromConfig":8,"tests\\flow\\UsageExampleTest::testCreateNoMorningWashFromConfig":8,"tests\\flow\\UsageExampleTest::testCreateMachineWashFromConfig":8,"tests\\flow\\UsageExampleTest::testCreateYiwuModeFromConfig":8,"tests\\flow\\UsageExampleTest::testExample1StandardProcess":8,"tests\\flow\\UsageExampleTest::testExample2NoMorningWash":8,"tests\\flow\\UsageExampleTest::testExample3DynamicAdjust":8,"tests\\flow\\UsageExampleTest::testExample5MorningWashModes":8,"tests\\flow\\UsageExampleTest::testExample6MachineWash":8,"tests\\flow\\UsageExampleTest::testExample7SimpleProcess":8,"tests\\flow\\UsageExampleTest::testExample9FullProcess":8,"tests\\flow\\UsageExampleTest::testExample10MultiHospital":8,"tests\\flow\\UsageExampleTest::testFlowProcessorUsage":8,"tests\\flow\\UsageExampleTest::testFlowProcessorCompleteProcess":8,"tests\\flow\\UsageExampleTest::testFlowProcessorWithConfig":8,"tests\\flow\\BatchConsistencyTest::testSingleMachineBatchConsistency":8,"tests\\flow\\BatchConsistencyTest::testMultiEndoscopeBatchUniqueness":8,"tests\\flow\\BatchConsistencyTest::testBatchNoStability":8,"tests\\flow\\BatchConsistencyTest::testNewProcessGeneratesNewBatchNo":8,"tests\\flow\\BatchConsistencyTest::testDistributedBatchConsistency":8,"tests\\flow\\PerformanceTest::testSingleExecutionPerformance":8,"tests\\flow\\PerformanceTest::testCompleteProcessPerformance":8,"tests\\flow\\PerformanceTest::testEngineCreationPerformance":8,"tests\\flow\\PerformanceTest::testConcurrentProcessingSimulation":8,"tests\\flow\\ProcessEngineTest::testCreateStandardEngine":8,"tests\\flow\\ProcessEngineTest::testCreateNoMorningWashEngine":8,"tests\\flow\\ProcessEngineTest::testCreateSimpleEngine":8,"tests\\flow\\ProcessEngineTest::testCompleteWashProcess":8,"tests\\flow\\ProcessEngineTest::testStandardWashRinseDisinfectFlow":8,"tests\\flow\\ProcessEngineTest::testDisableNode":8,"tests\\flow\\ProcessEngineTest::testGetNodes":8,"tests\\flow\\ProcessEngineTest::testGetEnabledNodes":8,"tests\\flow\\ProcessEngineTest::testUpdateConfig":8,"tests\\flow\\ProcessEngineTest::testMachineWashProcess":8,"tests\\db\\DBC::testDBConnect":8,"tests\\db\\DBC::testGenTables":7,"tests\\db\\DBC::testModel":5,"tests\\flow\\nodes\\DisinfectNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterRinse":8,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterFinalRinse":8,"tests\\flow\\nodes\\DisinfectNodeTest::testCannotHandleNonDisinfectReader":8,"tests\\flow\\nodes\\DisinfectNodeTest::testCannotHandleAfterDisinfect":8,"tests\\flow\\nodes\\DisinfectNodeTest::testHandleProcess":8,"tests\\flow\\nodes\\DisinfectNodeTest::testDatabaseOperationFlags":7,"tests\\flow\\nodes\\DisinfectNodeTest::testWebSocketNotifyFlag":8,"tests\\flow\\nodes\\DryNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\DryNodeTest::testCanHandleAfterFinalRinse":7,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleAfterRinse":8,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleNonDryReader":8,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleAfterDry":7,"tests\\flow\\nodes\\DryNodeTest::testHandleProcess":7,"tests\\flow\\nodes\\DryNodeTest::testDatabaseOperationFlags":7,"tests\\flow\\nodes\\DryNodeTest::testWebSocketNotifyFlag":7,"tests\\flow\\nodes\\DryNodeTest::testKeepExistingBatchNo":8,"tests\\flow\\nodes\\MorningWashNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleWhenNoNeed":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleNonDisinfectMachineReader":8,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleWhenAlreadyWashed":8,"tests\\flow\\nodes\\EndNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterDry":8,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterDisinfect":7,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterFinalRinse":8,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterMachineWash":8,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleNonEndReader":8,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleAfterWash":8,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleAfterRinse":8,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleWithEmptyStep":8,"tests\\flow\\nodes\\EndNodeTest::testHandleProcess":8,"tests\\flow\\nodes\\EndNodeTest::testDatabaseOperationIsUpdate":7,"tests\\flow\\nodes\\EndNodeTest::testWebSocketNotifyFlag":8,"tests\\flow\\nodes\\EndNodeTest::testActionEndTimeIsSet":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testCanHandleAfterDisinfect":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testCanHandleAfterMachineWashByDefault":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleAfterMachineWashWhenDisabled":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleNonFinalRinseReader":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testHandleProcess":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleAfterWash":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testDatabaseOperationFlags":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testWebSocketNotifyFlag":8,"tests\\flow\\nodes\\FinalRinseNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\MachineWashNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterWash":8,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterRinse":7,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterDisinfect":7,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleWithEmptyStep":7,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterEnd":7,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterEndoscopeOut":8,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleNonMachineWashReader":8,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleAfterFinalRinse":8,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleAfterDry":8,"tests\\flow\\nodes\\MachineWashNodeTest::testHandleProcess":8,"tests\\flow\\nodes\\MachineWashNodeTest::testDatabaseOperationFlags":7,"tests\\flow\\nodes\\MachineWashNodeTest::testWebSocketNotifyFlag":8,"tests\\flow\\nodes\\RinseNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\RinseNodeTest::testCanHandleAfterWash":7,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleNonRinseReader":8,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleAfterDisinfect":7,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleWithEmptyStep":7,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleAfterRinse":7,"tests\\flow\\nodes\\RinseNodeTest::testHandleProcess":7,"tests\\flow\\nodes\\RinseNodeTest::testDatabaseOperationFlags":7,"tests\\flow\\nodes\\RinseNodeTest::testWebSocketNotifyFlag":7,"tests\\flow\\nodes\\RinseNodeTest::testDisabledNodeSkips":8,"tests\\flow\\nodes\\WashNodeTest::testNodeIdentity":8,"tests\\flow\\nodes\\WashNodeTest::testCanHandleWashReader":8,"tests\\flow\\nodes\\WashNodeTest::testCannotHandleNonWashReader":8,"tests\\flow\\nodes\\WashNodeTest::testCanStartNewWashProcess":8,"tests\\flow\\nodes\\WashNodeTest::testCannotWashWithoutMorningWash":8,"tests\\flow\\nodes\\WashNodeTest::testHandleProcess":8,"tests\\flow\\nodes\\WashNodeTest::testGenerateBatchNo":7,"tests\\flow\\nodes\\WashNodeTest::testKeepExistingBatchNo":8,"tests\\flow\\strategies\\TimeValidationStrategyTest::testTimeRequirementNotMet":7,"tests\\flow\\strategies\\TimeValidationStrategyTest::testSetStepDuration":7,"tests\\flow\\FlowProcessorTest::testSuccessfulFlowSavesToDatabase":8,"tests\\flow\\FlowProcessorTest::testEndOperationIsUpdate":8,"tests\\flow\\FlowProcessorTest::testGetActionTypeMapping":8,"tests\\flow\\EdgeCaseTest::testLongEndoscopeName":7,"tests\\flow\\EdgeCaseTest::testSpecialCharactersInName":7,"tests\\flow\\ProcessContextTest::testGetFullVoice":7,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testFullVoiceIncludesEndoscopeName":7,"tests\\flow\\FlowProcessorTest::testSuccessfulFlowSendsVoice":8,"tests\\flow\\FlowProcessorTest::testFailedFlowDoesNotSaveToDatabase":8,"tests\\flow\\FlowProcessorTest::testFailedFlowStillSendsVoice":8,"tests\\flow\\FlowProcessorTest::testWebSocketNotificationSentWhenNeeded":8,"tests\\flow\\FlowProcessorTest::testWebSocketNotificationNotSentWhenNotNeeded":8,"tests\\flow\\FlowProcessorTest::testStaticFactoryCreate":8,"tests\\flow\\EdgeCaseTest::testAllNodesDisabled":8,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testFinalRinseVoice":8,"tests\\flow\\ProcessSimulationTest::testDuplicateCheckNode":7,"tests\\flow\\FullProcessTest::testCompleteManualWashProcess":7,"tests\\flow\\FullProcessTest::testSwipeEndoscopeWithoutOperator":7},"times":{"tests\\flow\\FlowProcessorTest::testCreateProcessor":0.006,"tests\\flow\\FlowProcessorTest::testCreateProcessorWithConfig":0.006,"tests\\flow\\FlowProcessorTest::testStaticFactory":0.006,"tests\\flow\\FlowProcessorTest::testUpdateConfig":0.009,"tests\\flow\\FlowProcessorTest::testBatchNoConsistency":0.006,"tests\\flow\\FlowProcessorTest::testBatchNoConsistencyInCompleteProcess":0.008,"tests\\flow\\FlowProcessorTest::testDatabaseOperationFlags":0.006,"tests\\flow\\FlowProcessorTest::testWebSocketNotifyFlag":0.005,"tests\\flow\\FlowProcessorTest::testVoiceGeneration":0.007,"tests\\flow\\FlowProcessorTest::testErrorVoiceGeneration":0.006,"tests\\flow\\FlowProcessorTest::testMultiHospitalConfig":0.007,"tests\\flow\\FlowProcessorTest::testSpecialHospitalConfig":0.006,"tests\\flow\\ProcessContextTest::testCreateContext":0.005,"tests\\flow\\ProcessContextTest::testSetError":0.006,"tests\\flow\\ProcessContextTest::testSetVoice":0.006,"tests\\flow\\ProcessContextTest::testGetFullVoice":0.006,"tests\\flow\\ProcessContextTest::testStepTimeManagement":0.006,"tests\\flow\\ProcessContextTest::testStepDurationManagement":0.005,"tests\\flow\\ProcessContextTest::testDefaultStepDuration":0.007,"tests\\flow\\ProcessContextTest::testGenerateBatchNo":0.006,"tests\\flow\\ProcessContextTest::testBatchNoUniqueness":0.006,"tests\\flow\\ProcessContextTest::testCanStartNewProcess":0.006,"tests\\flow\\ProcessContextTest::testIsWashProcessCompleted":0.006,"tests\\flow\\ProcessContextTest::testChaining":0.005,"tests\\flow\\ProcessEngineTest::testCreateStandardEngine":0.006,"tests\\flow\\ProcessEngineTest::testCreateNoMorningWashEngine":0.006,"tests\\flow\\ProcessEngineTest::testCreateSimpleEngine":0.006,"tests\\flow\\ProcessEngineTest::testCompleteWashProcess":0.045,"tests\\flow\\ProcessEngineTest::testTimeValidationFailure":0.005,"tests\\flow\\ProcessEngineTest::testWrongStep":0.01,"tests\\flow\\ProcessEngineTest::testDisableNode":0.005,"tests\\flow\\ProcessEngineTest::testGetNodes":0.006,"tests\\flow\\ProcessEngineTest::testGetEnabledNodes":0.005,"tests\\flow\\ProcessEngineTest::testUpdateConfig":0.006,"tests\\flow\\ProcessEngineTest::testMachineWashProcess":0.033,"tests\\flow\\nodes\\FinalRinseNodeTest::testCanHandleAfterDisinfect":0.007,"tests\\flow\\nodes\\FinalRinseNodeTest::testCanHandleAfterMachineWashByDefault":0.005,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleAfterMachineWashWhenDisabled":0.008,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleNonFinalRinseReader":0.008,"tests\\flow\\nodes\\FinalRinseNodeTest::testHandleProcess":0.01,"tests\\flow\\nodes\\WashNodeTest::testNodeIdentity":0.006,"tests\\flow\\nodes\\WashNodeTest::testCanHandleWashReader":0.006,"tests\\flow\\nodes\\WashNodeTest::testCannotHandleNonWashReader":0.005,"tests\\flow\\nodes\\WashNodeTest::testCanStartNewWashProcess":0.005,"tests\\flow\\nodes\\WashNodeTest::testCannotWashWithoutMorningWash":0.006,"tests\\flow\\nodes\\WashNodeTest::testHandleProcess":0.009,"tests\\flow\\nodes\\WashNodeTest::testGenerateBatchNo":0.008,"tests\\flow\\nodes\\WashNodeTest::testKeepExistingBatchNo":0.006,"tests\\flow\\strategies\\MorningWashStrategyTest::testNoneMode":0.006,"tests\\flow\\strategies\\MorningWashStrategyTest::testAllMode":0.005,"tests\\flow\\strategies\\MorningWashStrategyTest::testDailyFirstModeWithNoRecords":0.005,"tests\\flow\\strategies\\MorningWashStrategyTest::testDailyFirstModeWithRecords":0.006,"tests\\flow\\strategies\\MorningWashStrategyTest::testSpecificTypesMode":0.005,"tests\\flow\\strategies\\MorningWashStrategyTest::testStorageTimeMode":0.006,"tests\\flow\\strategies\\MorningWashStrategyTest::testStrategyName":0.005,"tests\\flow\\strategies\\TimeValidationStrategyTest::testNoTimeLimitForFirstTime":0.005,"tests\\flow\\strategies\\TimeValidationStrategyTest::testTimeRequirementMet":0.006,"tests\\flow\\strategies\\TimeValidationStrategyTest::testTimeRequirementNotMet":0.005,"tests\\flow\\strategies\\TimeValidationStrategyTest::testCustomDuration":0.006,"tests\\flow\\strategies\\TimeValidationStrategyTest::testIsApplicable":0.006,"tests\\flow\\strategies\\TimeValidationStrategyTest::testStrategyName":0.006,"tests\\flow\\strategies\\TimeValidationStrategyTest::testStrategyPhase":0.005,"tests\\flow\\ProcessEngineTest::testSkipStep":0.012,"tests\\flow\\ProcessEngineTest::testDisinfectAfterWash":0.005,"tests\\flow\\BatchConsistencyTest::testSingleMachineBatchConsistency":0.046,"tests\\flow\\BatchConsistencyTest::testMultiEndoscopeBatchUniqueness":0.036,"tests\\flow\\BatchConsistencyTest::testBatchNoFormat":0.008,"tests\\flow\\BatchConsistencyTest::testBatchNoStability":0.02,"tests\\flow\\BatchConsistencyTest::testNewProcessGeneratesNewBatchNo":0.024,"tests\\flow\\BatchConsistencyTest::testDistributedBatchConsistency":0.057,"tests\\flow\\BatchConsistencyTest::testBatchNoPreservedOnError":0.024,"tests\\flow\\BatchConsistencyTest::testBatchNoWithDifferentProcessTypes":0.022,"tests\\flow\\EdgeCaseTest::testEmptyContext":0.005,"tests\\flow\\EdgeCaseTest::testLongEndoscopeName":0.006,"tests\\flow\\EdgeCaseTest::testSpecialCharactersInName":0.006,"tests\\flow\\EdgeCaseTest::testBatchNoGenerationEdgeCases":0.006,"tests\\flow\\EdgeCaseTest::testTimeBoundaryValues":0.006,"tests\\flow\\EdgeCaseTest::testEmptyChain":0.005,"tests\\flow\\EdgeCaseTest::testAllNodesDisabled":0.021,"tests\\flow\\EdgeCaseTest::testRepeatedStepTimeSetting":0.006,"tests\\flow\\EdgeCaseTest::testChainingEdgeCases":0.006,"tests\\flow\\EdgeCaseTest::testErrorStateOverwrite":0.006,"tests\\flow\\EdgeCaseTest::testRecoveryFromError":0.005,"tests\\flow\\EdgeCaseTest::testInvalidConfigItems":0.007,"tests\\flow\\EdgeCaseTest::testConcurrentBatchNoGeneration":0.011,"tests\\flow\\EdgeCaseTest::testMorningWashBoundaryTime":0.006,"tests\\flow\\EdgeCaseTest::testStorageTimeBoundary":0.007,"tests\\flow\\EdgeCaseTest::testStorageTimeOverThreshold":0.005,"tests\\flow\\PerformanceTest::testSingleExecutionPerformance":0.011,"tests\\flow\\PerformanceTest::testCompleteProcessPerformance":0.039,"tests\\flow\\PerformanceTest::testBatchNoGenerationPerformance":0.023,"tests\\flow\\PerformanceTest::testEngineCreationPerformance":0.094,"tests\\flow\\PerformanceTest::testConfigLoadingPerformance":0.023,"tests\\flow\\PerformanceTest::testContextCreationPerformance":0.258,"tests\\flow\\PerformanceTest::testStrategyExecutionPerformance":0.156,"tests\\flow\\PerformanceTest::testMemoryUsage":0.226,"tests\\flow\\PerformanceTest::testConcurrentProcessingSimulation":0.058,"tests\\flow\\PerformanceTest::testChainTraversalPerformance":5.796,"tests\\flow\\config\\ProcessConfigTest::testCreateDefaultConfig":0.007,"tests\\flow\\config\\ProcessConfigTest::testFromArray":0.006,"tests\\flow\\config\\ProcessConfigTest::testGetEnabledSteps":0.007,"tests\\flow\\config\\ProcessConfigTest::testAddStep":0.006,"tests\\flow\\config\\ProcessConfigTest::testRemoveStep":0.005,"tests\\flow\\config\\ProcessConfigTest::testSetNodeEnabled":0.008,"tests\\flow\\config\\ProcessConfigTest::testMorningWashConfig":0.006,"tests\\flow\\config\\ProcessConfigTest::testTimeValidationConfig":0.007,"tests\\flow\\config\\ProcessConfigTest::testVoiceTemplateConfig":0.005,"tests\\flow\\config\\ProcessConfigTest::testSetStepVoice":0.006,"tests\\flow\\config\\ProcessConfigTest::testToArray":0.007,"tests\\flow\\config\\ProcessConfigTest::testCreateStandard":0.006,"tests\\flow\\config\\ProcessConfigTest::testCreateNoMorningWash":0.007,"tests\\flow\\config\\ProcessConfigTest::testCreateSimple":0.006,"tests\\flow\\config\\ProcessConfigTest::testCreateMachineWash":0.005,"tests\\flow\\config\\ProcessConfigTest::testCreateNoDry":0.006,"tests\\flow\\config\\ProcessConfigTest::testCreateDryOnly":0.005,"tests\\flow\\config\\ProcessConfigTest::testChaining":0.006,"tests\\flow\\nodes\\DisinfectNodeTest::testNodeIdentity":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterRinse":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterFinalRinse":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testCanHandleAfterWash":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testCannotHandleNonDisinfectReader":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testCannotHandleAfterDisinfect":0.005,"tests\\flow\\nodes\\DisinfectNodeTest::testHandleProcess":0.009,"tests\\flow\\nodes\\DisinfectNodeTest::testDatabaseOperationFlags":0.008,"tests\\flow\\nodes\\DisinfectNodeTest::testWebSocketNotifyFlag":0.008,"tests\\flow\\nodes\\MorningWashNodeTest::testNodeIdentity":0.005,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleMorningWashReader":0.021,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleWhenNoNeed":0.006,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleNonMorningWashReader":0.005,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleWhenAlreadyWashed":0.005,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcess":0.008,"tests\\flow\\nodes\\MorningWashNodeTest::testGenerateBatchNo":0.009,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleDisinfectReaderForMorningWash":0.006,"tests\\flow\\nodes\\MorningWashNodeTest::testCanHandleMachineWashReaderForMorningWash":0.005,"tests\\flow\\nodes\\MorningWashNodeTest::testCannotHandleNonDisinfectMachineReader":0.006,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcessWithDisinfect":0.009,"tests\\flow\\nodes\\MorningWashNodeTest::testHandleProcessWithMachineWash":0.009,"tests\\flow\\nodes\\DryNodeTest::testNodeIdentity":0.006,"tests\\flow\\nodes\\DryNodeTest::testCanHandleAfterFinalRinse":0.007,"tests\\flow\\nodes\\DryNodeTest::testCanHandleAfterDisinfect":0.006,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleAfterRinse":0.005,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleNonDryReader":0.007,"tests\\flow\\nodes\\DryNodeTest::testCannotHandleAfterDry":0.006,"tests\\flow\\nodes\\DryNodeTest::testHandleProcess":0.008,"tests\\flow\\nodes\\DryNodeTest::testDatabaseOperationFlags":0.009,"tests\\flow\\nodes\\DryNodeTest::testWebSocketNotifyFlag":0.008,"tests\\flow\\nodes\\DryNodeTest::testKeepExistingBatchNo":0.01,"tests\\flow\\ProcessEngineTest::testStandardWashRinseDisinfectFlow":0.023,"tests\\flow\\UsageExampleTest::testExample1StandardProcess":0.022,"tests\\flow\\UsageExampleTest::testExample2NoMorningWash":0.025,"tests\\flow\\UsageExampleTest::testExample3DynamicAdjust":0.048,"tests\\flow\\UsageExampleTest::testExample4CustomVoice":0.022,"tests\\flow\\UsageExampleTest::testExample5MorningWashModes":0.033,"tests\\flow\\UsageExampleTest::testExample6MachineWash":0.022,"tests\\flow\\UsageExampleTest::testExample7SimpleProcess":0.021,"tests\\flow\\UsageExampleTest::testExample8TimeValidation":0.021,"tests\\flow\\UsageExampleTest::testExample9FullProcess":0.021,"tests\\flow\\UsageExampleTest::testExample10MultiHospital":0.046,"tests\\flow\\UsageExampleTest::testLoadCustomProcessFromConfig":0.007,"tests\\flow\\UsageExampleTest::testCreateNoMorningWashFromConfig":0.042,"tests\\flow\\UsageExampleTest::testCreateMachineWashFromConfig":0.036,"tests\\flow\\UsageExampleTest::testCreateYiwuModeFromConfig":0.015,"tests\\flow\\UsageExampleTest::testFlowProcessorUsage":0.045,"tests\\flow\\UsageExampleTest::testFlowProcessorCompleteProcess":0.021,"tests\\flow\\UsageExampleTest::testFlowProcessorWithConfig":0.037,"tests\\db\\DBC::testDBConnect":0.113,"tests\\db\\DBC::testGenTables":0.416,"tests\\db\\DBC::testModel":0.171,"tests\\flow\\nodes\\EndNodeTest::testNodeIdentity":0.006,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterDry":0.006,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterDisinfect":0.005,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterFinalRinse":0.005,"tests\\flow\\nodes\\EndNodeTest::testCanHandleAfterMachineWash":0.005,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleNonEndReader":0.005,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleAfterWash":0.007,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleAfterRinse":0.006,"tests\\flow\\nodes\\EndNodeTest::testCannotHandleWithEmptyStep":0.005,"tests\\flow\\nodes\\EndNodeTest::testHandleProcess":0.009,"tests\\flow\\nodes\\EndNodeTest::testDatabaseOperationIsUpdate":0.011,"tests\\flow\\nodes\\EndNodeTest::testWebSocketNotifyFlag":0.009,"tests\\flow\\nodes\\EndNodeTest::testActionEndTimeIsSet":0.009,"tests\\flow\\nodes\\FinalRinseNodeTest::testCannotHandleAfterWash":0.006,"tests\\flow\\nodes\\FinalRinseNodeTest::testDatabaseOperationFlags":0.008,"tests\\flow\\nodes\\FinalRinseNodeTest::testWebSocketNotifyFlag":0.009,"tests\\flow\\nodes\\FinalRinseNodeTest::testNodeIdentity":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testNodeIdentity":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterWash":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterRinse":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterDisinfect":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleWithEmptyStep":0.006,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterEnd":0.006,"tests\\flow\\nodes\\MachineWashNodeTest::testCanHandleAfterEndoscopeOut":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleNonMachineWashReader":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleAfterFinalRinse":0.005,"tests\\flow\\nodes\\MachineWashNodeTest::testCannotHandleAfterDry":0.006,"tests\\flow\\nodes\\MachineWashNodeTest::testHandleProcess":0.008,"tests\\flow\\nodes\\MachineWashNodeTest::testDatabaseOperationFlags":0.01,"tests\\flow\\nodes\\MachineWashNodeTest::testWebSocketNotifyFlag":0.008,"tests\\flow\\nodes\\RinseNodeTest::testNodeIdentity":0.005,"tests\\flow\\nodes\\RinseNodeTest::testCanHandleAfterWash":0.007,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleNonRinseReader":0.007,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleAfterDisinfect":0.006,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleWithEmptyStep":0.006,"tests\\flow\\nodes\\RinseNodeTest::testCannotHandleAfterRinse":0.005,"tests\\flow\\nodes\\RinseNodeTest::testHandleProcess":0.01,"tests\\flow\\nodes\\RinseNodeTest::testDatabaseOperationFlags":0.009,"tests\\flow\\nodes\\RinseNodeTest::testWebSocketNotifyFlag":0.009,"tests\\flow\\nodes\\RinseNodeTest::testDisabledNodeSkips":0.009,"tests\\flow\\strategies\\TimeValidationStrategyTest::testNonDurationStepIsSkipped":0.008,"tests\\flow\\strategies\\TimeValidationStrategyTest::testSetStepDuration":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testStrategyName":0.007,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testStrategyPhaseIsAfter":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testIsAlwaysApplicable":0.007,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testNormalWashVoice":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testNormalRinseVoice":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testNormalDisinfectVoice":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testFinalRinseVoice":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testDryVoice":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testEndVoice":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testMachineWashVoice":0.007,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testMorningWashVoice":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testMachineMorningWashVoice":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testErrorStateGeneratesErrorVoice":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testFullVoiceIncludesEndoscopeName":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testLeakTestRemindAppended":0.006,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testStorageRemindAppended":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testCustomVoiceTemplate":0.005,"tests\\flow\\strategies\\VoiceGenerationStrategyTest::testSetStepVoice":0.005,"tests\\flow\\FlowProcessorTest::testSuccessfulFlowSavesToDatabase":0.011,"tests\\flow\\FlowProcessorTest::testSuccessfulFlowSendsVoice":0.008,"tests\\flow\\FlowProcessorTest::testFailedFlowDoesNotSaveToDatabase":0.007,"tests\\flow\\FlowProcessorTest::testFailedFlowStillSendsVoice":0.008,"tests\\flow\\FlowProcessorTest::testWebSocketNotificationSentWhenNeeded":0.01,"tests\\flow\\FlowProcessorTest::testWebSocketNotificationNotSentWhenNotNeeded":0.008,"tests\\flow\\FlowProcessorTest::testEndOperationIsUpdate":0.01,"tests\\flow\\FlowProcessorTest::testGetActionTypeMapping":0.107,"tests\\flow\\FlowProcessorTest::testStaticFactoryCreate":0.007,"tests\\flow\\ProcessSimulationTest::testBasicManualWashWithoutDatabase":0.287,"tests\\flow\\ProcessSimulationTest::testNodeChainConstruction":0.264,"tests\\flow\\ProcessSimulationTest::testMorningWashNodeConfiguration":0.278,"tests\\flow\\ProcessSimulationTest::testMachineWashNodeConfiguration":0.271,"tests\\flow\\ProcessSimulationTest::testStorageNodeConfiguration":0.273,"tests\\flow\\ProcessSimulationTest::testDuplicateCheckNode":0.522,"tests\\flow\\ProcessSimulationTest::testCloseNode":0.268,"tests\\flow\\ProcessSimulationTest::testNodeEnableDisable":0.268,"tests\\flow\\ProcessSimulationTest::testConfigUpdate":0.522,"tests\\flow\\ProcessSimulationTest::testStrategyInitialization":0.291,"tests\\flow\\FullProcessTest::testCompleteManualWashProcess":0.09,"tests\\flow\\FullProcessTest::testCompleteManualWashProcessUsingHelper":0.285,"tests\\flow\\FullProcessTest::testMorningWashRequired":0.262,"tests\\flow\\FullProcessTest::testManualWashAfterMorningWash":0.269,"tests\\flow\\FullProcessTest::testMachineWashProcess":0.281,"tests\\flow\\FullProcessTest::testMachineWashProcessUsingHelper":0.286,"tests\\flow\\FullProcessTest::testMorningWashThenMachineWash":0.275,"tests\\flow\\FullProcessTest::testSwipeEndoscopeWithoutOperator":0.02,"tests\\flow\\FullProcessTest::testWrongStepOrder":0.078,"tests\\flow\\FullProcessTest::testDuplicateSwipe":0.142,"tests\\flow\\FullProcessTest::testUnboundReader":0.273,"tests\\flow\\FullProcessTest::testUnboundEndoscope":0.323,"tests\\flow\\FullProcessTest::testDifferentOperatorContinueProcess":0.124,"tests\\flow\\FullProcessTest::testWashStepVoice":0.074,"tests\\flow\\FullProcessTest::testDisinfectStepVoice":0.182,"tests\\flow\\FullProcessTest::testOperatorCardVoice":0.02,"tests\\flow\\FullProcessTest::testErrorVoice":0.021,"tests\\flow\\FullProcessTest::testVoiceTemplateParams":0.077,"tests\\flow\\FullProcessTest::testProcessChainExecution":0.236,"tests\\flow\\FullProcessTest::testDisabledNodeSkipped":0.277,"tests\\flow\\FullProcessTest::testTimeValidationBlock":0.265,"tests\\flow\\FullProcessTest::testDuplicateSwipeBlock":0.201,"tests\\flow\\FullProcessTest::testNewProcessAfterComplete":0.331,"tests\\flow\\FullProcessTest::testConcurrentProcessIsolation":0.174,"tests\\flow\\FullProcessTest::testProcessorReset":0.081,"tests\\flow\\FullProcessTest::testVirtualContextBuilder":0.017,"tests\\flow\\FullProcessTest::testEnvironmentConfig":0.007}} \ No newline at end of file diff --git a/app/config/Config.php b/app/config/Config.php index 2aae9d2..57a8e0c 100644 --- a/app/config/Config.php +++ b/app/config/Config.php @@ -83,6 +83,10 @@ class Config */ public bool $blockMode { get => $this->blockMode; + set(bool $value){ + Logger::error("禁止修改热修改阻断模式,这只是用于测试方法"); + $this->blockMode = $value; + } } /** diff --git a/app/config/TODO.md b/app/config/TODO.md new file mode 100644 index 0000000..193c76c --- /dev/null +++ b/app/config/TODO.md @@ -0,0 +1,5 @@ +模拟发包 + +内镜未刷取出就操作 + +刷两次代表加强洗 \ No newline at end of file diff --git a/app/flow/FlowProcessor.php b/app/flow/FlowProcessor.php index 1875c06..9824f64 100644 --- a/app/flow/FlowProcessor.php +++ b/app/flow/FlowProcessor.php @@ -138,7 +138,7 @@ class FlowProcessor $this->updateLastOperationEndTime($context, $repo); // 数据库操作 - if ($context->needDatabaseOperation()) { + if ($context->isDatabaseOperationNeeded()) { $this->saveToDatabase($context, $repo); } @@ -147,7 +147,7 @@ class FlowProcessor $this->sendVoice($context); // WebSocket通知 - if ($context->needWebSocketNotify()) { + if ($context->isWebSocketNotifyNeeded()) { Logger::debug('[FlowProcessor] 发送 WebSocket 通知 endoscope={} step={}', [ $context->getEndoscope()->name, $context->getCurrentStep(), diff --git a/app/flow/ProcessEngine.php b/app/flow/ProcessEngine.php index c9d24d6..996ec38 100644 --- a/app/flow/ProcessEngine.php +++ b/app/flow/ProcessEngine.php @@ -3,6 +3,8 @@ namespace app\flow; use app\flow\config\ProcessConfig; +use app\flow\context\ProcessContext; +use app\flow\enum\VoiceMessage; use app\flow\nodes\MorningWashNode; use app\flow\nodes\ProcessNodeInterface; use app\flow\strategies\MorningWashStrategy; diff --git a/app/flow/context/ProcessContext.php b/app/flow/context/ProcessContext.php index 6cccb77..18eeb23 100644 --- a/app/flow/context/ProcessContext.php +++ b/app/flow/context/ProcessContext.php @@ -157,10 +157,11 @@ readonly class ProcessContext return $this->stepDurations; } - // ==================== 便捷查询方法 ==================== + // ==================== 便捷查询方法(带数据来源标注) ==================== /** * 获取当前步骤 + * @return string 当前步骤名称 (来源: ProcessStatus) */ public function getCurrentStep(): string { @@ -169,6 +170,7 @@ readonly class ProcessContext /** * 获取流程类型 + * @return string 流程类型 (来源: ProcessStatus) */ public function getProcessType(): string { @@ -177,6 +179,7 @@ readonly class ProcessContext /** * 获取批次号 + * @return string 批次号 (来源: ProcessStatus) */ public function getBatchNo(): string { @@ -185,6 +188,7 @@ readonly class ProcessContext /** * 获取操作开始时间 + * @return string 操作开始时间 (来源: ProcessStatus) */ public function getActionStartTime(): string { @@ -193,6 +197,7 @@ readonly class ProcessContext /** * 获取操作时长 + * @return int|null 操作时长(秒)(来源: ProcessStatus) */ public function getDuration(): ?int { @@ -201,6 +206,7 @@ readonly class ProcessContext /** * 获取上一个操作记录 + * @return \app\model\EctActions|null 上一个操作记录 (来源: ProcessStatus) */ public function getPreviousAction(): ?\app\model\EctActions { @@ -209,6 +215,7 @@ readonly class ProcessContext /** * 获取完整语音 + * @return string 完整语音内容 (来源: VoiceState) */ public function getFullVoice(): string { @@ -217,6 +224,7 @@ readonly class ProcessContext /** * 流程是否成功 + * @return bool 是否成功 (来源: ExecutionResult) */ public function isSuccess(): bool { @@ -225,22 +233,25 @@ readonly class ProcessContext /** * 是否需要操作数据库 + * @return bool 是否需要数据库操作 (来源: ExecutionResult) */ - public function needDatabaseOperation(): bool + public function isDatabaseOperationNeeded(): bool { return $this->result->needDatabaseOperation; } /** * 是否需要WebSocket通知 + * @return bool 是否需要通知 (来源: ExecutionResult) */ - public function needWebSocketNotify(): bool + public function isWebSocketNotifyNeeded(): bool { return $this->result->needWebSocketNotify; } /** * 获取数据库操作列表 + * @return array 数据库操作列表 (来源: ExecutionResult) */ public function getDbOperations(): array { @@ -249,6 +260,7 @@ readonly class ProcessContext /** * 检查是否可以开始新流程 + * @return bool 是否可以开始新流程 (来源: ProcessStatus) */ public function canStartNewProcess(): bool { @@ -257,6 +269,7 @@ readonly class ProcessContext /** * 检查是否已完成清洗流程 + * @return bool 是否已完成 (来源: ProcessStatus) */ public function isWashProcessCompleted(): bool { @@ -265,6 +278,7 @@ readonly class ProcessContext /** * 是否有操作员 + * @return bool 是否有有效操作员 (来源: OperatorInfo) */ public function hasOperator(): bool { @@ -281,40 +295,45 @@ readonly class ProcessContext /** * 是否需要增强洗 + * @return bool 是否需要增强洗 (来源: ReminderStatus) */ - public function needEnhanceWash(): bool + public function isEnhanceWashNeeded(): bool { return $this->reminder->needEnhanceWash; } /** * 是否需要测漏提醒 + * @return bool 是否需要测漏提醒 (来源: ReminderStatus) */ - public function needLeakTestRemind(): bool + public function isLeakTestRemindNeeded(): bool { return $this->reminder->needLeakTestRemind; } /** * 是否需要存储提醒 + * @return bool 是否需要存储提醒 (来源: ReminderStatus) */ - public function needStorageRemind(): bool + public function isStorageRemindNeeded(): bool { return $this->reminder->needStorageRemind; } /** * 是否已测漏 + * @return bool 是否已测漏 (来源: ReminderStatus) */ - public function isLeakTestDone(): bool + public function isLeakTestDoneByReminder(): bool { return $this->reminder->leakTestDone; } /** * 获取测漏结果 + * @return string 测漏结果 (来源: ReminderStatus) */ - public function getLeakTestResult(): string + public function getLeakTestResultByReminder(): string { return $this->reminder->leakTestResult; } diff --git a/app/flow/context/ProcessContextBuilder.php b/app/flow/context/ProcessContextBuilder.php index 505efe0..6795565 100644 --- a/app/flow/context/ProcessContextBuilder.php +++ b/app/flow/context/ProcessContextBuilder.php @@ -15,6 +15,7 @@ use app\flow\vo\StorageStatus; use app\flow\vo\VoiceState; use app\flow\enum\DbOperationType; use app\flow\enum\VoiceMessage; +use app\flow\vo\BatchNo; use app\model\EctActions; use app\net\PacketContext; use app\repository\EctActionsRepository; @@ -316,28 +317,11 @@ class ProcessContextBuilder /** * 生成批次号 + * @return string 批次号值 */ private function generateBatchNo(): string { - $existingBatchNo = null; - if (!$this->endoscope->isEmpty()) { - $existingBatchNo = EctActionsRepository::new()->findTodayActiveBatchNo($this->config->machineId); - } - - $datePart = date('Ymd'); - $sequence = 1; - - if (!empty($existingBatchNo)) { - $existingDatePart = substr($existingBatchNo, 0, 8); - $existingSequence = substr($existingBatchNo, 10, 4); - - if ($existingDatePart === $datePart && is_numeric($existingSequence)) { - $sequence = (int)$existingSequence + 1; - } - } - - $sequencePart = str_pad($sequence, 4, '0', STR_PAD_LEFT); - return $datePart . $this->config->machineId . $sequencePart; + return BatchNo::generate($this->config->machineId)->value; } // ==================== 值对象设置方法 ==================== diff --git a/app/flow/nodes/AbstractProcessNode.php b/app/flow/nodes/AbstractProcessNode.php index d395ba0..d629a8f 100644 --- a/app/flow/nodes/AbstractProcessNode.php +++ b/app/flow/nodes/AbstractProcessNode.php @@ -201,7 +201,7 @@ abstract class AbstractProcessNode implements ProcessNodeInterface * 1. 遍历所有已注册的策略 * 2. 筛选出阶段为 'before' 的策略 * 3. 依次执行策略的 execute 方法 - * 4. 如果某个策略导致上下文错误(!$context->isSuccess()),立即中断后续策略执行 + * 4. 如果某个策略导致上下文错误(!$context->isSuccessByResult()),立即中断后续策略执行 * * 前置策略的应用: * - 时间验证:检查步骤执行时间是否符合要求 diff --git a/app/flow/nodes/CloseNode.php b/app/flow/nodes/CloseNode.php index 06c86cc..8b8f2cd 100644 --- a/app/flow/nodes/CloseNode.php +++ b/app/flow/nodes/CloseNode.php @@ -38,7 +38,7 @@ class CloseNode extends AbstractProcessNode */ public function canHandle(ProcessContext $context): CanHandleResult { - if (!$context->isSuccess() || $context->needDatabaseOperation() || !empty($context->getVoice()->message)) { + if (!$context->isSuccess() || $context->isDatabaseOperationNeeded() || !empty($context->getVoice()->message)) { return CanHandleResult::no(); } return CanHandleResult::yes(); @@ -49,7 +49,7 @@ class CloseNode extends AbstractProcessNode */ protected function doHandle(ProcessContext $context): ProcessContext { - if (!$context->isSuccess() || $context->needDatabaseOperation() || !empty($context->getVoice()->message)) { + if (!$context->isSuccess() || $context->isDatabaseOperationNeeded() || !empty($context->getVoice()->message)) { return $context; } // 无节点命中 diff --git a/app/flow/strategies/VoiceGenerationStrategy.php b/app/flow/strategies/VoiceGenerationStrategy.php index d4a4164..69738b5 100644 --- a/app/flow/strategies/VoiceGenerationStrategy.php +++ b/app/flow/strategies/VoiceGenerationStrategy.php @@ -142,11 +142,11 @@ class VoiceGenerationStrategy extends AbstractStrategy { $reminds = []; - if ($context->needLeakTestRemind()) { + if ($context->isLeakTestRemindNeeded()) { $reminds[] = ',清洗开始前,请测漏'; } - if ($context->needStorageRemind()) { + if ($context->isStorageRemindNeeded()) { $reminds[] = ',未登记取出'; } diff --git a/app/flow/vo/ProcessStatus.php b/app/flow/vo/ProcessStatus.php index ebbb40c..7db7ebf 100644 --- a/app/flow/vo/ProcessStatus.php +++ b/app/flow/vo/ProcessStatus.php @@ -19,7 +19,7 @@ readonly class ProcessStatus public string $batchNo = '', /** 操作开始时间 */ public string $actionStartTime = '', - /** 操作时长(秒) */ + /** 操作时长(秒) 计算方式为上下文创建的时间减去上个操作的开始时间(此时上个操作的结束时间不存在,需要等待此次刷卡流程结束) */ public ?int $duration = null, /** 上一个操作记录 */ public ?EctActions $previousAction = null, diff --git a/app/repository/EctActionsRepository.php b/app/repository/EctActionsRepository.php index 0265076..b11d6ae 100644 --- a/app/repository/EctActionsRepository.php +++ b/app/repository/EctActionsRepository.php @@ -242,7 +242,7 @@ class EctActionsRepository extends BaseRepository 'process_name' => $context->getCurrentStep(), 'process_order' => $process_order + 1, 'op_morning' => $context->getMorningWash()->needMorningWash ? 1 : 0, - 'op_enhance' => $context->needEnhanceWash() ? 1 : 0, + 'op_enhance' => $context->isEnhanceWashNeeded() ? 1 : 0, 'reader_id' => (int)$context->getReader()->id ?: null, 'reader_no' => $context->getReader()->no ?: null, 'opuser_type' => $opuserType, diff --git a/tests/flow/BatchConsistencyTest.php b/tests/flow/BatchConsistencyTest.php deleted file mode 100644 index 4e13af3..0000000 --- a/tests/flow/BatchConsistencyTest.php +++ /dev/null @@ -1,330 +0,0 @@ -createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'endoscopeId' => '', - 'morningWashed' => true, - ]); - $result1 = $engine->execute($context1); - $this->assertSuccess($result1); - $batchNo = $result1->batchNo; - $this->assertNotEmpty($batchNo); - - // 2. 漂洗 - 批次号应该一致 - $context2 = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '清洗', - 'endoscopeId' => '', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - $this->setStepTime($context2, '清洗', 400); - $result2 = $engine->execute($context2); - $this->assertSuccess($result2); - $this->assertEquals($batchNo, $result2->batchNo); - - // 3. 消毒 - 批次号应该一致 - $context3 = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '漂洗', - 'endoscopeId' => '', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - $this->setStepTime($context3, '漂洗', 200); - $result3 = $engine->execute($context3); - $this->assertSuccess($result3); - $this->assertEquals($batchNo, $result3->batchNo); - - // 4. 终末漂洗 - 批次号应该一致 - $context4 = $this->createContext([ - 'readerType' => '终末漂洗', - 'currentStep' => '消毒', - 'endoscopeId' => '', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - $this->setStepTime($context4, '消毒', 400); - $result4 = $engine->execute($context4); - $this->assertSuccess($result4); - $this->assertEquals($batchNo, $result4->batchNo); - - // 5. 干燥 - 批次号应该一致 - $context5 = $this->createContext([ - 'readerType' => '干燥', - 'currentStep' => '终末漂洗', - 'endoscopeId' => '', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - $this->setStepTime($context5, '终末漂洗', 200); - $result5 = $engine->execute($context5); - $this->assertSuccess($result5); - $this->assertEquals($batchNo, $result5->batchNo); - - // 6. 结束 - 批次号应该一致 - $context6 = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '干燥', - 'endoscopeId' => '', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - $this->setStepTime($context6, '干燥', 300); - $result6 = $engine->execute($context6); - $this->assertSuccess($result6); - $this->assertEquals($batchNo, $result6->batchNo); - } - - /** - * 测试多内镜批次号唯一性 - */ - public function testMultiEndoscopeBatchUniqueness(): void - { - $engine = ProcessEngine::createStandard(); - - $batchNos = []; - - // 模拟5个不同内镜同时开始清洗 - for ($i = 1; $i <= 5; $i++) { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'endoscopeId' => '', - 'morningWashed' => true, - ]); - $result = $engine->execute($context); - $this->assertSuccess($result); - $batchNos[] = $result->batchNo; - } - - // 所有批次号应该唯一 - $uniqueBatchNos = array_unique($batchNos); - $this->assertCount(5, $uniqueBatchNos); - } - - /** - * 测试批次号格式正确性 - */ - public function testBatchNoFormat(): void - { - $context = $this->createContext([ - 'endoscopeId' => '123', - ]); - - $batchNo = $context->generateBatchNo(); - - // 验证格式:YYYYMMDDHHMMSS + 内镜ID(6位) + 随机数(4位) - $this->assertEquals(24, strlen($batchNo)); - $this->assertMatchesRegularExpression('/^\d{14}\d{6}\d{4}$/', $batchNo); - - // 验证日期部分 - $datePart = substr($batchNo, 0, 8); - $this->assertEquals(date('Ymd'), $datePart); - - // 验证内镜ID部分 - $endoscopePart = substr($batchNo, 14, 6); - $this->assertEquals('000123', $endoscopePart); - } - - /** - * 测试批次号不随时间变化(同一流程内) - */ - public function testBatchNoStability(): void - { - $engine = ProcessEngine::createStandard(); - - $context1 = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'endoscopeId' => '', - 'morningWashed' => true, - ]); - $result1 = $engine->execute($context1); - $batchNo1 = $result1->batchNo; - - // 模拟时间流逝(实际测试中无法真正等待,所以直接验证) - $context2 = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '清洗', - 'endoscopeId' => '', - 'batchNo' => $batchNo1, - 'morningWashed' => true, - ]); - $this->setStepTime($context2, '清洗', 400); - $result2 = $engine->execute($context2); - - // 批次号应该保持不变 - $this->assertEquals($batchNo1, $result2->batchNo); - } - - /** - * 测试新流程生成新批次号 - */ - public function testNewProcessGeneratesNewBatchNo(): void - { - $engine = ProcessEngine::createStandard(); - - // 第一个流程 - $context1 = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'endoscopeId' => '', - 'morningWashed' => true, - ]); - $result1 = $engine->execute($context1); - $batchNo1 = $result1->batchNo; - - // 完成第一个流程 - $contextEnd = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '清洗', - 'endoscopeId' => '', - 'batchNo' => $batchNo1, - 'morningWashed' => true, - ]); - $engine->execute($contextEnd); - - // 第二个流程(新流程) - $context2 = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '结束', // 上一步已结束 - 'endoscopeId' => '', - 'morningWashed' => true, - ]); - $result2 = $engine->execute($context2); - $batchNo2 = $result2->batchNo; - - // 新流程应该有新的批次号 - $this->assertNotEquals($batchNo1, $batchNo2); - } - - /** - * 测试分布式场景下的批次号一致性(模拟) - */ - public function testDistributedBatchConsistency(): void - { - // 模拟机器A(清洗) - $engineA = ProcessEngine::createStandard(); - $contextA = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'endoscopeId' => '', - 'morningWashed' => true, - ]); - $resultA = $engineA->execute($contextA); - $batchNo = $resultA->batchNo; - - // 模拟机器B(消毒)- 从数据库获取批次号 - $engineB = ProcessEngine::createStandard(); - $contextB = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '漂洗', - 'endoscopeId' => '', - 'batchNo' => $batchNo, // 从数据库获取的批次号 - 'morningWashed' => true, - ]); - $resultB = $engineB->execute($contextB); - - // 批次号应该一致 - $this->assertEquals($batchNo, $resultB->batchNo); - } - - /** - * 测试批次号在错误情况下的保持 - */ - public function testBatchNoPreservedOnError(): void - { - $engine = ProcessEngine::createStandard(); - - // 开始清洗 - $context1 = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'endoscopeId' => '', - 'morningWashed' => true, - ]); - $result1 = $engine->execute($context1); - $batchNo = $result1->batchNo; - - // 完成漂洗 - $contextRinse = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '清洗', - 'endoscopeId' => '', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - $this->setStepTime($contextRinse, '清洗', 400); // 清洗时间足够 - $resultRinse = $engine->execute($contextRinse); - $this->assertSuccess($resultRinse); - - // 尝试再次漂洗(重复刷漂洗读卡器,时间验证会失败) - $context2 = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '漂洗', // 当前已经是漂洗 - 'endoscopeId' => '', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - // 设置漂洗步骤的上次时间为刚刚(时间不足) - $context2->setStepLastTime('漂洗', date('Y-m-d H:i:s', time() - 30)); - $result2 = $engine->execute($context2); - - // 执行失败(重复步骤或时间不足),但批次号应该保持不变 - $this->assertEquals($batchNo, $result2->batchNo); - } - - /** - * 测试不同流程类型的批次号 - */ - public function testBatchNoWithDifferentProcessTypes(): void - { - $engine = ProcessEngine::createStandard(); - - // 手工洗流程 - $context1 = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'endoscopeId' => '', - 'morningWashed' => true, - ]); - $result1 = $engine->execute($context1); - $batchNo1 = $result1->batchNo; - $this->assertEquals('手工洗', $result1->processType); - - // 机洗流程 - $engineMachine = ProcessEngine::createMachineWash(); - $context2 = $this->createContext([ - 'readerType' => '机洗', // 使用机洗读卡器 - 'currentStep' => '', - 'endoscopeId' => '', - 'morningWashed' => true, - ]); - $result2 = $engineMachine->execute($context2); - $batchNo2 = $result2->batchNo; - $this->assertEquals('机洗', $result2->processType); - - // 不同流程的批次号应该不同 - $this->assertNotEquals($batchNo1, $batchNo2); - } -} diff --git a/tests/flow/EdgeCaseTest.php b/tests/flow/EdgeCaseTest.php deleted file mode 100644 index 5245fb8..0000000 --- a/tests/flow/EdgeCaseTest.php +++ /dev/null @@ -1,314 +0,0 @@ -assertEquals('', $context->endoscopeId); - $this->assertEquals('', $context->currentStep); - $this->assertTrue($context->success); - } - - /** - * 测试超长内镜名称 - */ - public function testLongEndoscopeName(): void - { - $longName = str_repeat('北院电子胃镜', 10); - $context = $this->createContext([ - 'endoscopeName' => $longName, - ]); - - $context->setVoice('测试'); - $fullVoice = $context->getFullVoice(); - - // 当前实现返回的是语音消息本身 - $this->assertEquals('测试', $fullVoice); - } - - /** - * 测试特殊字符内镜名称 - */ - public function testSpecialCharactersInName(): void - { - $specialName = '北院<电子>胃镜&001"\''; - $context = $this->createContext([ - 'endoscopeName' => $specialName, - ]); - - $context->setVoice('测试'); - $fullVoice = $context->getFullVoice(); - - // 当前实现返回的是语音消息本身 - $this->assertEquals('测试', $fullVoice); - } - - /** - * 测试批次号生成(边界值) - */ - public function testBatchNoGenerationEdgeCases(): void - { - // 内镜ID为0 - $context1 = $this->createContext(['endoscopeId' => '0']); - $batchNo1 = $context1->generateBatchNo(); - $this->assertEquals(24, strlen($batchNo1)); - $this->assertStringContainsString('000000', $batchNo1); - - // 内镜ID超长 - $context2 = $this->createContext(['endoscopeId' => '123456789012345']); - $batchNo2 = $context2->generateBatchNo(); - $this->assertEquals(24, strlen($batchNo2)); - - // 内镜ID为空 - $context3 = $this->createContext(['endoscopeId' => '']); - $batchNo3 = $context3->generateBatchNo(); - $this->assertEquals(24, strlen($batchNo3)); - } - - /** - * 测试时间边界值 - */ - public function testTimeBoundaryValues(): void - { - $context = $this->createContext(); - - // 时间为0 - $this->assertEquals(0, $context->getStepDuration('不存在的步骤')); - - // 设置时间为未来(异常情况) - $futureTime = date('Y-m-d H:i:s', time() + 3600); - $context->setStepLastTime('测试', $futureTime); - $this->assertEquals($futureTime, $context->getStepLastTime('测试')); - } - - /** - * 测试空责任链 - */ - public function testEmptyChain(): void - { - $config = ProcessConfig::fromArray([ - 'steps' => [], - ]); - $engine = new ProcessEngine($config); - - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - ]); - - $result = $engine->execute($context); - - // 空链应该返回原始上下文 - $this->assertEquals('', $result->currentStep); - } - - /** - * 测试所有节点禁用 - */ - public function testAllNodesDisabled(): void - { - $config = ProcessConfig::createStandard(); - $config->setNodeEnabled('晨洗', false); - $config->setNodeEnabled('清洗', false); - $config->setNodeEnabled('漂洗', false); - $config->setNodeEnabled('消毒', false); - $config->setNodeEnabled('终末漂洗', false); - $config->setNodeEnabled('干燥', false); - $config->setNodeEnabled('结束', false); - - $engine = new ProcessEngine($config); - - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'morningWashed' => true, - ]); - - $result = $engine->execute($context); - - // 没有节点能处理,保持原样 - $this->assertEquals('', $result->currentStep); - } - - /** - * 测试重复设置步骤时间 - */ - public function testRepeatedStepTimeSetting(): void - { - $context = $this->createContext(); - - $time1 = date('Y-m-d H:i:s', time() - 100); - $time2 = date('Y-m-d H:i:s', time() - 200); - - $context->setStepLastTime('清洗', $time1); - $this->assertEquals($time1, $context->getStepLastTime('清洗')); - - $context->setStepLastTime('清洗', $time2); - $this->assertEquals($time2, $context->getStepLastTime('清洗')); - } - - /** - * 测试链式调用边界 - */ - public function testChainingEdgeCases(): void - { - $context = $this->createContext(); - - // 多次链式调用 - $result = $context - ->setVoice('语音1') - ->setVoice('语音2') - ->setVoice('语音3'); - - $this->assertSame($context, $result); - $this->assertEquals('语音3', $context->voiceMessage); - } - - /** - * 测试错误状态覆盖 - */ - public function testErrorStateOverwrite(): void - { - $context = $this->createContext(); - - $context->setError('错误1'); - $this->assertFalse($context->success); - $this->assertEquals('错误1', $context->errorMessage); - - // 再次设置错误 - $context->setError('错误2'); - $this->assertFalse($context->success); - $this->assertEquals('错误2', $context->errorMessage); - } - - /** - * 测试从错误恢复 - */ - public function testRecoveryFromError(): void - { - $context = $this->createContext(); - - $context->setError('测试错误'); - $this->assertFalse($context->success); - - // 手动重置成功状态(实际业务中可能不需要) - $context->success = true; - $context->errorMessage = ''; - - $this->assertTrue($context->success); - } - - /** - * 测试无效的配置项 - */ - public function testInvalidConfigItems(): void - { - $config = new ProcessConfig(); - - // 添加不存在的节点类 - $config->addStep('无效步骤', 'NonExistentNode'); - $step = $config->getStep('无效步骤'); - - $this->assertNotNull($step); - $this->assertEquals('NonExistentNode', $step['class']); - } - - /** - * 测试并发批次号生成(模拟) - */ - public function testConcurrentBatchNoGeneration(): void - { - $batchNos = []; - for ($i = 0; $i < 50; $i++) { - // 每个批次号使用不同的上下文(模拟不同内镜) - $context = $this->createContext(['endoscopeId' => (string)$i]); - $batchNo = $context->generateBatchNo(); - $batchNos[] = $batchNo; - } - - // 所有批次号应该唯一(不同内镜ID保证唯一性) - $uniqueBatchNos = array_unique($batchNos); - $this->assertCount(50, $uniqueBatchNos, '批次号应该唯一'); - } - - /** - * 测试晨洗策略边界 - 凌晨时间 - */ - public function testMorningWashBoundaryTime(): void - { - $strategy = new \app\flow\strategies\MorningWashStrategy([ - 'mode' => 'daily_first', - 'morning_start_time' => '06:00:00', - ]); - $node = new \app\flow\nodes\WashNode(); - - // 凌晨5点(应该需要晨洗) - $context1 = $this->createContext([ - 'todayWashRecords' => 0, - ]); - $result1 = $strategy->execute($context1, $node); - // daily_first 模式下,第一条记录需要晨洗 - $this->assertTrue($result1->needMorningWash); - } - - /** - * 测试存储时间模式边界 - 刚好4小时 - */ - public function testStorageTimeBoundary(): void - { - $strategy = new \app\flow\strategies\MorningWashStrategy([ - 'mode' => 'storage_time', - 'storage_threshold' => 4, - ]); - $node = new \app\flow\nodes\WashNode(); - - // 刚好4小时前 - $context = $this->createContext([ - 'lastActionType' => '存储', - 'lastProcessName' => '内镜放入', - 'storageInTime' => date('Y-m-d H:i:s', time() - 4 * 3600), - ]); - - $result = $strategy->execute($context, $node); - - // 刚好4小时,应该不需要晨洗(超过才需要) - $this->assertFalse($result->needMorningWash); - } - - /** - * 测试存储时间模式 - 超过4小时 - */ - public function testStorageTimeOverThreshold(): void - { - $strategy = new \app\flow\strategies\MorningWashStrategy([ - 'mode' => 'storage_time', - 'storage_threshold' => 4, - ]); - $node = new \app\flow\nodes\WashNode(); - - // 4小时1秒前 - $context = $this->createContext([ - 'lastActionType' => '存储', - 'lastProcessName' => '内镜放入', - 'storageInTime' => date('Y-m-d H:i:s', time() - 4 * 3600 - 1), - ]); - - $result = $strategy->execute($context, $node); - - $this->assertTrue($result->needMorningWash); - } -} diff --git a/tests/flow/FlowProcessorTest.php b/tests/flow/FlowProcessorTest.php deleted file mode 100644 index 0d39d20..0000000 --- a/tests/flow/FlowProcessorTest.php +++ /dev/null @@ -1,240 +0,0 @@ -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); - } -} diff --git a/tests/flow/PerformanceTest.php b/tests/flow/PerformanceTest.php deleted file mode 100644 index 8b9e40e..0000000 --- a/tests/flow/PerformanceTest.php +++ /dev/null @@ -1,274 +0,0 @@ -createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'endoscopeId' => '', - 'morningWashed' => true, - ]); - - $startTime = microtime(true); - $result = $engine->execute($context); - $endTime = microtime(true); - - $executionTime = ($endTime - $startTime) * 1000; // 毫秒 - - $this->assertSuccess($result); - // 单次执行应该小于 500ms(debug日志有IO开销) - $this->assertLessThan(500, $executionTime, "单次执行时间过长: {$executionTime}ms"); - } - - /** - * 测试流程执行性能 - 完整流程 - */ - public function testCompleteProcessPerformance(): void - { - $engine = ProcessEngine::createStandard(); - - $steps = [ - ['readerType' => '清洗', 'prevStep' => '', 'waitTime' => 0], - ['readerType' => '漂洗', 'prevStep' => '清洗', 'waitTime' => 400], - ['readerType' => '消毒', 'prevStep' => '漂洗', 'waitTime' => 200], - ['readerType' => '终末漂洗', 'prevStep' => '消毒', 'waitTime' => 400], - ['readerType' => '干燥', 'prevStep' => '终末漂洗', 'waitTime' => 200], - ['readerType' => '结束', 'prevStep' => '干燥', 'waitTime' => 300], - ]; - - $startTime = microtime(true); - $batchNo = null; - - foreach ($steps as $step) { - $context = $this->createContext([ - 'readerType' => $step['readerType'], - 'currentStep' => $step['prevStep'], - 'endoscopeId' => '', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - - if ($step['waitTime'] > 0) { - $this->setStepTime($context, $step['prevStep'], $step['waitTime']); - } - - $result = $engine->execute($context); - $this->assertSuccess($result); - - if ($batchNo === null) { - $batchNo = $result->batchNo; - } - } - - $endTime = microtime(true); - $totalTime = ($endTime - $startTime) * 1000; // 毫秒 - - // 完整流程(6步)应该小于 5000ms(debug日志有IO开销) - $this->assertLessThan(5000, $totalTime, "完整流程执行时间过长: {$totalTime}ms"); - } - - /** - * 测试批次号生成性能 - */ - public function testBatchNoGenerationPerformance(): void - { - $context = $this->createContext(['endoscopeId' => '1']); - - $startTime = microtime(true); - - for ($i = 0; $i < 1000; $i++) { - $context->generateBatchNo(); - } - - $endTime = microtime(true); - $totalTime = ($endTime - $startTime) * 1000; // 毫秒 - - // 生成1000个批次号应该小于 100ms - $this->assertLessThan(100, $totalTime, "批次号生成时间过长: {$totalTime}ms"); - } - - /** - * 测试引擎创建性能 - */ - public function testEngineCreationPerformance(): void - { - $startTime = microtime(true); - - for ($i = 0; $i < 100; $i++) { - ProcessEngine::createStandard(); - } - - $endTime = microtime(true); - $totalTime = ($endTime - $startTime) * 1000; // 毫秒 - - // 创建100个引擎应该小于 200ms - $this->assertLessThan(200, $totalTime, "引擎创建时间过长: {$totalTime}ms"); - } - - /** - * 测试配置加载性能 - */ - public function testConfigLoadingPerformance(): void - { - $startTime = microtime(true); - - for ($i = 0; $i < 1000; $i++) { - new ProcessConfig(); - } - - $endTime = microtime(true); - $totalTime = ($endTime - $startTime) * 1000; // 毫秒 - - // 创建1000个配置应该小于 100ms - $this->assertLessThan(100, $totalTime, "配置加载时间过长: {$totalTime}ms"); - } - - /** - * 测试上下文创建性能 - */ - public function testContextCreationPerformance(): void - { - $startTime = microtime(true); - - for ($i = 0; $i < 10000; $i++) { - ProcessContext::create([ - 'endoscopeId' => '1', - 'endoscopeName' => '测试内镜', - ]); - } - - $endTime = microtime(true); - $totalTime = ($endTime - $startTime) * 1000; // 毫秒 - - // 创建10000个上下文应该小于 500ms(考虑环境差异) - $this->assertLessThan(500, $totalTime, "上下文创建时间过长: {$totalTime}ms"); - } - - /** - * 测试策略执行性能 - */ - public function testStrategyExecutionPerformance(): void - { - $strategy = new \app\flow\strategies\TimeValidationStrategy(); - $node = new \app\flow\nodes\WashNode(); - - $startTime = microtime(true); - - for ($i = 0; $i < 1000; $i++) { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - ]); - $strategy->execute($context, $node); - } - - $endTime = microtime(true); - $totalTime = ($endTime - $startTime) * 1000; // 毫秒 - - // 执行1000次策略应该小于 500ms(考虑环境差异) - $this->assertLessThan(500, $totalTime, "策略执行时间过长: {$totalTime}ms"); - } - - /** - * 测试内存使用 - 大批量上下文创建 - */ - public function testMemoryUsage(): void - { - $memoryBefore = memory_get_usage(true); - - $contexts = []; - for ($i = 0; $i < 10000; $i++) { - $contexts[] = ProcessContext::create([ - 'endoscopeId' => (string)$i, - 'endoscopeName' => '测试内镜' . $i, - ]); - } - - $memoryAfter = memory_get_usage(true); - $memoryUsed = ($memoryAfter - $memoryBefore) / 1024 / 1024; // MB - - // 10000个上下文应该使用小于 50MB 内存 - $this->assertLessThan(50, $memoryUsed, "内存使用过高: {$memoryUsed}MB"); - - // 清理 - unset($contexts); - } - - /** - * 测试并发场景模拟 - 多内镜同时处理 - */ - public function testConcurrentProcessingSimulation(): void - { - $engine = ProcessEngine::createStandard(); - - $startTime = microtime(true); - - // 模拟10个内镜同时开始清洗 - $results = []; - for ($i = 1; $i <= 10; $i++) { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'endoscopeId' => '', - 'morningWashed' => true, - ]); - $results[] = $engine->execute($context); - } - - $endTime = microtime(true); - $totalTime = ($endTime - $startTime) * 1000; // 毫秒 - - // 10个内镜同时处理应该小于 2000ms(debug日志有IO开销) - $this->assertLessThan(2000, $totalTime, "并发处理时间过长: {$totalTime}ms"); - - // 所有结果应该成功 - foreach ($results as $result) { - $this->assertSuccess($result); - } - } - - /** - * 测试责任链遍历性能 - */ - public function testChainTraversalPerformance(): void - { - $engine = ProcessEngine::createStandard(); - - // 使用结束读卡器测试完整链遍历 - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '干燥', - 'morningWashed' => true, - ]); - $this->setStepTime($context, '干燥', 300); - - $startTime = microtime(true); - - for ($i = 0; $i < 1000; $i++) { - $testContext = clone $context; - $engine->execute($testContext); - } - - $endTime = microtime(true); - $totalTime = ($endTime - $startTime) * 1000; // 毫秒 - - // 1000次完整链遍历应该小于 30000ms(debug日志有IO开销) - $this->assertLessThan(30000, $totalTime, "链遍历时间过长: {$totalTime}ms"); - } -} diff --git a/tests/flow/ProcessContextTest.php b/tests/flow/ProcessContextTest.php deleted file mode 100644 index 25cd1a0..0000000 --- a/tests/flow/ProcessContextTest.php +++ /dev/null @@ -1,182 +0,0 @@ - '123', - 'endoscopeName' => '测试内镜', - 'currentStep' => '清洗', - ]); - - $this->assertEquals('123', $context->endoscopeId); - $this->assertEquals('测试内镜', $context->endoscopeName); - $this->assertEquals('清洗', $context->currentStep); - $this->assertTrue($context->success); - } - - /** - * 测试设置错误状态 - */ - public function testSetError(): void - { - $context = $this->createContext(); - - $context->setError('测试错误'); - - $this->assertFalse($context->success); - $this->assertEquals('测试错误', $context->errorMessage); - } - - /** - * 测试设置语音 - */ - public function testSetVoice(): void - { - $context = $this->createContext(); - - $context->setVoice('清洗完成'); - - $this->assertEquals('清洗完成', $context->voiceMessage); - } - - /** - * 测试获取完整语音 - */ - public function testGetFullVoice(): void - { - $context = $this->createContext([ - 'endoscopeName' => '北院电子胃镜001', - ]); - - $context->setVoice('清洗完成'); - - $fullVoice = $context->getFullVoice(); - - // 当前实现返回的是语音消息本身,不包含内镜名称 - $this->assertEquals('清洗完成', $fullVoice); - } - - /** - * 测试步骤时间管理 - */ - public function testStepTimeManagement(): void - { - $context = $this->createContext(); - - $time = date('Y-m-d H:i:s'); - $context->setStepLastTime('清洗', $time); - - $this->assertEquals($time, $context->getStepLastTime('清洗')); - $this->assertNull($context->getStepLastTime('消毒')); - } - - /** - * 测试步骤时长管理 - */ - public function testStepDurationManagement(): void - { - $context = $this->createContext(); - - // 设置自定义时长 - $context->setStepDuration('清洗', 600); - - $this->assertEquals(600, $context->getStepDuration('清洗')); - } - - /** - * 测试默认步骤时长 - */ - public function testDefaultStepDuration(): void - { - $context = $this->createContext(); - - // 测试默认值(与 ect_meta_process 表数据对齐) - $this->assertEquals(120, $context->getStepDuration('清洗')); // 手工洗最短 60s,机洗最短 120s,取保守值 - $this->assertEquals(60, $context->getStepDuration('漂洗')); // 60s - $this->assertEquals(300, $context->getStepDuration('消毒')); // 300s - $this->assertEquals(0, $context->getStepDuration('不存在的步骤')); - } - - /** - * 测试批次号生成 - */ - public function testGenerateBatchNo(): void - { - $context = $this->createContext([ - 'endoscopeId' => '123', - ]); - - $batchNo = $context->generateBatchNo(); - - // 验证格式:YYYYMMDDHHMMSS + 内镜ID(6位) + 随机数(4位) - $this->assertEquals(24, strlen($batchNo)); - $this->assertMatchesRegularExpression('/^\d{14}\d{6}\d{4}$/', $batchNo); - } - - /** - * 测试批次号唯一性 - */ - public function testBatchNoUniqueness(): void - { - $context = $this->createContext(); - - $batchNo1 = $context->generateBatchNo(); - $batchNo2 = $context->generateBatchNo(); - - $this->assertNotEquals($batchNo1, $batchNo2); - } - - /** - * 测试是否可以开始新流程 - */ - public function testCanStartNewProcess(): void - { - // 可以开始新流程的状态 - $this->assertTrue($this->createContext(['currentStep' => ''])->canStartNewProcess()); - $this->assertTrue($this->createContext(['currentStep' => '结束'])->canStartNewProcess()); - $this->assertTrue($this->createContext(['currentStep' => '内镜取出'])->canStartNewProcess()); - $this->assertTrue($this->createContext(['currentStep' => '测漏正常'])->canStartNewProcess()); - - // 不可以开始新流程的状态 - $this->assertFalse($this->createContext(['currentStep' => '清洗'])->canStartNewProcess()); - $this->assertFalse($this->createContext(['currentStep' => '消毒'])->canStartNewProcess()); - } - - /** - * 测试流程完成状态 - */ - public function testIsWashProcessCompleted(): void - { - $this->assertTrue($this->createContext(['currentStep' => '结束'])->isWashProcessCompleted()); - $this->assertFalse($this->createContext(['currentStep' => '清洗'])->isWashProcessCompleted()); - $this->assertFalse($this->createContext(['currentStep' => ''])->isWashProcessCompleted()); - } - - /** - * 测试链式调用 - */ - public function testChaining(): void - { - $context = $this->createContext(); - - $result = $context - ->setVoice('测试') - ->setStepLastTime('清洗', date('Y-m-d H:i:s')) - ->setStepDuration('清洗', 300); - - $this->assertSame($context, $result); - $this->assertEquals('测试', $context->voiceMessage); - } -} diff --git a/tests/flow/ProcessEngineTest.php b/tests/flow/ProcessEngineTest.php deleted file mode 100644 index 3860cf5..0000000 --- a/tests/flow/ProcessEngineTest.php +++ /dev/null @@ -1,292 +0,0 @@ -engine = ProcessEngine::createStandard(); - } - - /** - * 测试创建标准流程引擎 - */ - public function testCreateStandardEngine(): void - { - $engine = ProcessEngine::createStandard(); - - $this->assertInstanceOf(ProcessEngine::class, $engine); - $this->assertNotNull($engine->getNode('清洗')); - $this->assertNotNull($engine->getNode('消毒')); - $this->assertNotNull($engine->getNode('结束')); - } - - /** - * 测试创建无晨洗流程引擎 - */ - public function testCreateNoMorningWashEngine(): void - { - $engine = ProcessEngine::createNoMorningWash(); - - $this->assertFalse($engine->getNode('晨洗')->isEnabled()); - } - - /** - * 测试创建简化流程引擎 - */ - public function testCreateSimpleEngine(): void - { - $engine = ProcessEngine::createSimple(); - - $this->assertTrue($engine->getNode('清洗')->isEnabled()); - $this->assertFalse($engine->getNode('漂洗')->isEnabled()); - $this->assertFalse($engine->getNode('消毒')->isEnabled()); - $this->assertTrue($engine->getNode('结束')->isEnabled()); - } - - /** - * 测试完整清洗流程 - */ - public function testCompleteWashProcess(): void - { - // 1. 开始清洗 - $context1 = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'morningWashed' => true, - 'endoscopeId' => '', // 避免触发 DB 查询 - ]); - $result1 = $this->engine->execute($context1); - $this->assertSuccess($result1); - $this->assertStep($result1, '清洗'); - $batchNo = $result1->batchNo; - - // 2. 漂洗 - $context2 = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '清洗', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - $this->setStepTime($context2, '清洗', 400); // 超过5分钟 - $result2 = $this->engine->execute($context2); - $this->assertSuccess($result2); - $this->assertStep($result2, '漂洗'); - $this->assertEquals($batchNo, $result2->batchNo); // 批次号一致 - - // 3. 消毒 - $context3 = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '漂洗', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - $this->setStepTime($context3, '漂洗', 200); // 超过2分钟 - $result3 = $this->engine->execute($context3); - $this->assertSuccess($result3); - $this->assertStep($result3, '消毒'); - - // 4. 终末漂洗 - $context4 = $this->createContext([ - 'readerType' => '终末漂洗', - 'currentStep' => '消毒', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - $this->setStepTime($context4, '消毒', 400); // 超过5分钟 - $result4 = $this->engine->execute($context4); - $this->assertSuccess($result4); - $this->assertStep($result4, '终末漂洗'); - - // 5. 干燥 - $context5 = $this->createContext([ - 'readerType' => '干燥', - 'currentStep' => '终末漂洗', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - $this->setStepTime($context5, '终末漂洗', 200); // 超过2分钟 - $result5 = $this->engine->execute($context5); - $this->assertSuccess($result5); - $this->assertStep($result5, '干燥'); - - // 6. 结束 - $context6 = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '干燥', - 'batchNo' => $batchNo, - 'morningWashed' => true, - ]); - $this->setStepTime($context6, '干燥', 300); // 超过3分钟 - $result6 = $this->engine->execute($context6); - $this->assertSuccess($result6); - $this->assertStep($result6, '结束'); - } - - /** - * 测试时间验证失败 - */ - public function testTimeValidationFailure(): void - { - // 显式设置清洗时长为 300s - $strategy = new \app\flow\strategies\TimeValidationStrategy(['durations' => ['清洗' => 300]]); - $node = new \app\flow\nodes\WashNode(); - - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '清洗', - 'morningWashed' => true, - ]); - - // 设置2分钟前的时间(不足5分钟) - $this->setStepTime($context, '清洗', 120); - - $result = $strategy->execute($context, $node); - - $this->assertFailure($result, '刷错,清洗剩余'); - } - - /** - * 测试标准流程:清洗 -> 漂洗 -> 消毒 - */ - public function testStandardWashRinseDisinfectFlow(): void - { - // 1. 清洗 - $context1 = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'morningWashed' => true, - 'needMorningWash' => false, - 'endoscopeId' => '', - ]); - $result1 = $this->engine->execute($context1); - $this->assertSuccess($result1); - $this->assertEquals('清洗', $result1->currentStep); - - // 2. 漂洗 - $context2 = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '清洗', - 'batchNo' => $result1->batchNo, - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - $this->setStepTime($context2, '清洗', 400); - $result2 = $this->engine->execute($context2); - $this->assertSuccess($result2); - $this->assertEquals('漂洗', $result2->currentStep); - - // 3. 消毒 - $context3 = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '漂洗', - 'batchNo' => $result1->batchNo, - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - $this->setStepTime($context3, '漂洗', 200); - $result3 = $this->engine->execute($context3); - $this->assertSuccess($result3); - $this->assertEquals('消毒', $result3->currentStep); - } - - /** - * 测试禁用节点 - */ - public function testDisableNode(): void - { - $this->engine->disableNode('干燥'); - - $this->assertFalse($this->engine->getNode('干燥')->isEnabled()); - - // 恢复 - $this->engine->enableNode('干燥'); - $this->assertTrue($this->engine->getNode('干燥')->isEnabled()); - } - - /** - * 测试获取所有节点 - */ - public function testGetNodes(): void - { - $nodes = $this->engine->getNodes(); - - $this->assertArrayHasKey('清洗', $nodes); - $this->assertArrayHasKey('消毒', $nodes); - $this->assertArrayHasKey('结束', $nodes); - } - - /** - * 测试获取启用的节点 - */ - public function testGetEnabledNodes(): void - { - $this->engine->disableNode('干燥'); - - $enabledNodes = $this->engine->getEnabledNodes(); - - $this->assertArrayNotHasKey('干燥', $enabledNodes); - $this->assertArrayHasKey('清洗', $enabledNodes); - } - - /** - * 测试更新配置 - */ - public function testUpdateConfig(): void - { - $newConfig = ProcessConfig::createNoMorningWash(); - - $this->engine->updateConfig($newConfig); - - $this->assertFalse($this->engine->getNode('晨洗')->isEnabled()); - } - - /** - * 测试机洗流程 - */ - public function testMachineWashProcess(): void - { - $engine = ProcessEngine::createMachineWash(); - - // 1. 清洗 - $context1 = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'endoscopeId' => '', - ]); - $result1 = $engine->execute($context1); - $this->assertSuccess($result1); - $this->assertStep($result1, '清洗'); - - // 2. 机洗 - $context2 = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '清洗', - 'batchNo' => $result1->batchNo, - ]); - $this->setStepTime($context2, '清洗', 400); - $result2 = $engine->execute($context2); - $this->assertSuccess($result2); - $this->assertStep($result2, '机洗'); - $this->assertEquals('机洗', $result2->processType); - - // 3. 终末漂洗 - $context3 = $this->createContext([ - 'readerType' => '终末漂洗', - 'currentStep' => '机洗', - 'batchNo' => $result1->batchNo, - ]); - $result3 = $engine->execute($context3); - $this->assertSuccess($result3); - $this->assertStep($result3, '终末漂洗'); - } -} diff --git a/tests/flow/Test.php b/tests/flow/Test.php new file mode 100644 index 0000000..5accdd1 --- /dev/null +++ b/tests/flow/Test.php @@ -0,0 +1,13 @@ +assertEquals(1, 1); + } +} \ No newline at end of file diff --git a/tests/flow/TestCase.php b/tests/flow/TestCase.php index f18282a..6ee7b46 100644 --- a/tests/flow/TestCase.php +++ b/tests/flow/TestCase.php @@ -2,80 +2,193 @@ namespace tests\flow; -use PHPUnit\Framework\TestCase as BaseTestCase; use app\flow\context\ProcessContext; +use PHPUnit\Framework\TestCase as BaseTestCase; /** * 流程测试基类 + * + * 提供流程测试常用的断言方法和辅助功能 */ abstract class TestCase extends BaseTestCase { - /** - * 创建测试用的 ProcessContext - */ - protected function createContext(array $data = []): ProcessContext - { - $defaults = [ - 'endoscopeId' => '1', - 'endoscopeName' => '测试胃镜', - 'cardNo' => '04A2AD88D0', - 'readerNo' => '09E45F217B', - 'readerType' => '', - 'currentStep' => '', - 'processType' => '', - 'needMorningWash' => false, - 'morningWashed' => false, // 默认未完成晨洗,由具体测试覆盖 - 'todayWashRecords' => 0, - ]; - - return ProcessContext::create(array_merge($defaults, $data)); - } - /** * 断言流程执行成功 */ - protected function assertSuccess(ProcessContext $context): void + protected function assertSuccess(ProcessContext $context, string $message = ''): void { - $this->assertTrue($context->success, - '流程执行失败: ' . $context->errorMessage); + $this->assertTrue( + $context->isSuccess(), + $message ?: "期望流程成功,实际失败。语音: {$context->getFullVoice()}" + ); } /** * 断言流程执行失败 */ - protected function assertFailure(ProcessContext $context, string $expectedMessage = ''): void + protected function assertFailure(ProcessContext $context, string $message = ''): void { - $this->assertFalse($context->success, '期望流程执行失败,但成功了'); - - if ($expectedMessage) { - $this->assertStringContainsString($expectedMessage, $context->errorMessage); - } + $this->assertFalse( + $context->isSuccess(), + $message ?: "期望流程失败,实际成功。步骤: {$context->getCurrentStep()}" + ); } /** * 断言当前步骤 */ - protected function assertStep(ProcessContext $context, string $expectedStep): void + protected function assertStep(ProcessContext $context, string $expectedStep, string $message = ''): void { - $this->assertEquals($expectedStep, $context->currentStep, - "期望当前步骤为 {$expectedStep},实际是 {$context->currentStep}"); + $this->assertEquals( + $expectedStep, + $context->getCurrentStep(), + $message ?: "期望步骤为 {$expectedStep},实际为 {$context->getCurrentStep()}" + ); } /** - * 断言语音内容 + * 断言语音包含指定内容 */ - protected function assertVoiceContains(ProcessContext $context, string $expectedText): void + protected function assertVoiceContains(ProcessContext $context, string $expected, string $message = ''): void { - $this->assertStringContainsString($expectedText, $context->voiceMessage, - "语音内容应包含 '{$expectedText}'"); + $voice = $context->getFullVoice(); + $this->assertStringContainsString( + $expected, + $voice, + $message ?: "期望语音包含 '{$expected}',实际语音: {$voice}" + ); } /** - * 设置步骤时间(用于时间验证测试) + * 断言语音不包含指定内容 */ - protected function setStepTime(ProcessContext $context, string $stepCode, int $secondsAgo): void + protected function assertVoiceNotContains(ProcessContext $context, string $unexpected, string $message = ''): void { - $time = date('Y-m-d H:i:s', time() - $secondsAgo); - $context->setStepLastTime($stepCode, $time); + $voice = $context->getFullVoice(); + $this->assertStringNotContainsString( + $unexpected, + $voice, + $message ?: "期望语音不包含 '{$unexpected}',实际语音: {$voice}" + ); + } + + /** + * 断言语音等于指定内容 + */ + protected function assertVoiceEquals(ProcessContext $context, string $expected, string $message = ''): void + { + $this->assertEquals( + $expected, + $context->getFullVoice(), + $message ?: "语音不匹配" + ); + } + + /** + * 断言需要写库操作 + */ + protected function assertNeedDatabaseOperation(ProcessContext $context, string $message = ''): void + { + $this->assertTrue( + $context->isDatabaseOperationNeeded(), + $message ?: "期望需要写库操作,实际不需要" + ); + } + + /** + * 断言不需要写库操作 + */ + protected function assertNoDatabaseOperation(ProcessContext $context, string $message = ''): void + { + $this->assertFalse( + $context->isDatabaseOperationNeeded(), + $message ?: "期望不需要写库操作,实际需要" + ); + } + + /** + * 断言需要WebSocket通知 + */ + protected function assertNeedWebSocketNotify(ProcessContext $context, string $message = ''): void + { + $this->assertTrue( + $context->isWebSocketNotifyNeeded(), + $message ?: "期望需要WebSocket通知,实际不需要" + ); + } + + /** + * 断言批次号不为空 + */ + protected function assertHasBatchNo(ProcessContext $context, string $message = ''): void + { + $this->assertNotEmpty( + $context->getBatchNo(), + $message ?: "期望有批次号,实际为空" + ); + } + + /** + * 断言批次号相等 + */ + protected function assertBatchNoEquals(ProcessContext $context, string $expected, string $message = ''): void + { + $this->assertEquals( + $expected, + $context->getBatchNo(), + $message ?: "批次号不匹配,期望: {$expected},实际: {$context->getBatchNo()}" + ); + } + + /** + * 断言有操作员信息 + */ + protected function assertHasOperator(ProcessContext $context, string $message = ''): void + { + $this->assertTrue( + $context->hasOperator(), + $message ?: "期望有操作员信息,实际没有" + ); + } + + /** + * 断言操作员名称 + */ + protected function assertOperatorName(ProcessContext $context, string $expected, string $message = ''): void + { + $this->assertEquals( + $expected, + $context->getOperator()->name, + $message ?: "操作员名称不匹配" + ); + } + + /** + * 断言内镜名称 + */ + protected function assertEndoscopeName(ProcessContext $context, string $expected, string $message = ''): void + { + $this->assertEquals( + $expected, + $context->getEndoscope()->name, + $message ?: "内镜名称不匹配" + ); + } + + /** + * 打印上下文信息(调试用) + */ + protected function dumpContext(ProcessContext $context): void + { + echo "\n=== Context Debug ===\n"; + echo "Success: " . ($context->isSuccess() ? 'true' : 'false') . "\n"; + echo "Step: " . $context->getCurrentStep() . "\n"; + echo "Voice: " . $context->getFullVoice() . "\n"; + echo "BatchNo: " . $context->getBatchNo() . "\n"; + echo "Operator: " . $context->getOperator()->name . "\n"; + echo "Endoscope: " . $context->getEndoscope()->name . "\n"; + echo "Reader: " . $context->getReader()->type . "\n"; + echo "NeedDB: " . ($context->isDatabaseOperationNeeded() ? 'true' : 'false') . "\n"; + echo "===================\n"; } } diff --git a/tests/flow/UsageExampleTest.php b/tests/flow/UsageExampleTest.php deleted file mode 100644 index 20232f2..0000000 --- a/tests/flow/UsageExampleTest.php +++ /dev/null @@ -1,511 +0,0 @@ - '04A2AD88D0', - 'readerNo' => '09E45F217B', - 'readerType' => '清洗', - 'endoscopeId' => '', - 'endoscopeName' => '胃镜001', - 'currentStep' => '', - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - - // 3. 执行流程 - $result = $processor->engine->execute($context); - - // 4. 验证处理结果 - $this->assertTrue($result->success); - $this->assertEquals('清洗', $result->currentStep); - $this->assertNotEmpty($result->voiceMessage); - } - - /** - * 测试使用 FlowProcessor 处理完整流程 - */ - public function testFlowProcessorCompleteProcess(): void - { - $processor = FlowProcessor::create(ProcessConfig::createStandard()); - - // 步骤1: 清洗 - $context1 = ProcessContext::create([ - 'cardNo' => '04A2AD88D0', - 'readerNo' => '09E45F217B', - 'readerType' => '清洗', - 'endoscopeId' => '', - 'endoscopeName' => '胃镜001', - 'currentStep' => '', - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - $result1 = $processor->engine->execute($context1); - $this->assertTrue($result1->success); - $this->assertEquals('清洗', $result1->currentStep); - $batchNo = $result1->batchNo; - - // 步骤2: 漂洗(模拟时间已过) - $context2 = ProcessContext::create([ - 'cardNo' => '04A2AD88D0', - 'readerNo' => '09E45F217B', - 'readerType' => '漂洗', - 'endoscopeId' => '1', - 'endoscopeName' => '胃镜001', - 'currentStep' => '清洗', - 'batchNo' => $batchNo, - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - // 设置清洗步骤时间已满足 - $context2->setStepLastTime('清洗', date('Y-m-d H:i:s', time() - 360)); - $result2 = $processor->engine->execute($context2); - $this->assertTrue($result2->success); - $this->assertEquals('漂洗', $result2->currentStep); - } - - /** - * 测试使用 Config 配置创建 FlowProcessor - */ - public function testFlowProcessorWithConfig(): void - { - // 从全局配置获取无晨洗配置 - $globalConfig = Config::getInstance(); - $hospitalConfig = $globalConfig->customProcess['no_morning_wash'] ?? null; - - if ($hospitalConfig === null) { - $this->markTestSkipped('no_morning_wash 配置不存在'); - } - - // 使用配置创建流程处理器 - $processConfig = ProcessConfig::fromArray($hospitalConfig); - $processor = new FlowProcessor($processConfig); - - // 验证配置生效 - $this->assertFalse($processor->engine->getNode('晨洗')->isEnabled()); - - // 处理刷卡 - $context = ProcessContext::create([ - 'cardNo' => '04A2AD88D0', - 'readerNo' => '09E45F217B', - 'readerType' => '清洗', - 'endoscopeId' => '', - 'endoscopeName' => '胃镜001', - 'currentStep' => '', - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - $result = $processor->engine->execute($context); - $this->assertTrue($result->success); - } - - /** - * 测试从 Config 全局配置加载自定义流程 - */ - public function testLoadCustomProcessFromConfig(): void - { - // 获取全局配置实例 - $globalConfig = Config::getInstance(); - - // 验证自定义流程配置已加载 - $this->assertNotEmpty($globalConfig->customProcess); - $this->assertArrayHasKey('standard', $globalConfig->customProcess); - $this->assertArrayHasKey('no_morning_wash', $globalConfig->customProcess); - $this->assertArrayHasKey('machine_wash', $globalConfig->customProcess); - } - - /** - * 测试使用 Config 中的无晨洗配置创建流程 - */ - public function testCreateNoMorningWashFromConfig(): void - { - $globalConfig = Config::getInstance(); - - // 从全局配置获取无晨洗配置 - $hospitalConfig = $globalConfig->customProcess['no_morning_wash'] ?? null; - - // 如果配置存在则测试 - if ($hospitalConfig === null) { - $this->markTestSkipped('no_morning_wash 配置不存在'); - } - - // 使用配置创建流程 - $processConfig = ProcessConfig::fromArray($hospitalConfig); - $engine = new ProcessEngine($processConfig); - - // 验证晨洗节点被禁用 - $this->assertFalse($engine->getNode('晨洗')->isEnabled()); - - // 执行流程 - $context = ProcessContext::create([ - 'endoscopeName' => '测试内镜', - 'readerType' => '清洗', - 'currentStep' => '', - 'needMorningWash' => false, - 'morningWashed' => true, - ]); - - $result = $engine->execute($context); - $this->assertTrue($result->success); - } - - /** - * 测试使用 Config 中的机洗配置创建流程 - */ - public function testCreateMachineWashFromConfig(): void - { - $globalConfig = Config::getInstance(); - - // 从全局配置获取机洗配置 - $hospitalConfig = $globalConfig->customProcess['machine_wash'] ?? null; - - // 如果配置存在则测试 - if ($hospitalConfig === null) { - $this->markTestSkipped('machine_wash 配置不存在'); - } - - // 使用配置创建流程 - $processConfig = ProcessConfig::fromArray($hospitalConfig); - $engine = new ProcessEngine($processConfig); - - // 验证机洗节点启用,漂洗和消毒禁用 - $this->assertTrue($engine->getNode('机洗')->isEnabled()); - $this->assertFalse($engine->getNode('漂洗')->isEnabled()); - $this->assertFalse($engine->getNode('消毒')->isEnabled()); - - // 执行机洗流程 - $context = ProcessContext::create([ - 'endoscopeName' => '测试内镜', - 'readerType' => '机洗', - 'currentStep' => '清洗', - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - - $result = $engine->execute($context); - $this->assertTrue($result->success); - $this->assertEquals('机洗', $result->currentStep); - } - - /** - * 测试使用 Config 中的义乌模式配置 - */ - public function testCreateYiwuModeFromConfig(): void - { - $globalConfig = Config::getInstance(); - - // 从全局配置获取义乌模式配置(按存储时间判断晨洗) - $hospitalConfig = $globalConfig->customProcess['partial_morning_wash'] ?? null; - - // 如果配置存在则测试 - if ($hospitalConfig === null) { - $this->markTestSkipped('partial_morning_wash 配置不存在'); - } - - // 验证配置内容 - $this->assertEquals('storage_time', $hospitalConfig['morning_wash']['mode'] ?? ''); - $this->assertEquals(4, $hospitalConfig['morning_wash']['storage_threshold'] ?? 0); - - // 使用配置创建流程 - $processConfig = ProcessConfig::fromArray($hospitalConfig); - $engine = new ProcessEngine($processConfig); - - // 验证晨洗节点启用 - $this->assertTrue($engine->getNode('晨洗')->isEnabled()); - } - - /** - * 测试标准流程处理 - */ - public function testExample1StandardProcess(): void - { - // 创建标准流程引擎 - $engine = ProcessEngine::createStandard(); - - // 创建流程上下文(模拟刷卡数据) - $context = ProcessContext::create([ - 'endoscopeId' => '', - 'endoscopeName' => '胃镜001', - 'cardNo' => '04A2AD88D0', - 'readerNo' => '09E45F217B', - 'readerType' => '清洗', - 'currentStep' => '', - 'needMorningWash' => false, - 'morningWashed' => true, - ]); - - // 执行流程 - $result = $engine->execute($context); - - // 验证结果 - $this->assertTrue($result->success); - $this->assertEquals('清洗', $result->currentStep); - $this->assertNotEmpty($result->getFullVoice()); - } - - /** - * 测试无晨洗流程配置 - */ - public function testExample2NoMorningWash(): void - { - $config = ProcessConfig::createNoMorningWash(); - $engine = new ProcessEngine($config); - - // 验证晨洗节点被禁用 - $this->assertFalse($engine->getNode('晨洗')->isEnabled()); - - // 执行流程 - $context = ProcessContext::create([ - 'endoscopeName' => '肠镜002', - 'readerType' => '清洗', - 'currentStep' => '', - 'needMorningWash' => false, - 'morningWashed' => true, - ]); - - $result = $engine->execute($context); - $this->assertTrue($result->success); - $this->assertEquals('清洗', $result->currentStep); - } - - /** - * 测试运行时动态调整流程 - */ - public function testExample3DynamicAdjust(): void - { - $engine = ProcessEngine::createStandard(); - - // 禁用干燥和终末漂洗步骤 - $engine->disableNode('干燥'); - $engine->disableNode('终末漂洗'); - - $this->assertFalse($engine->getNode('干燥')->isEnabled()); - $this->assertFalse($engine->getNode('终末漂洗')->isEnabled()); - - // 重新启用 - $engine->enableNode('干燥'); - $this->assertTrue($engine->getNode('干燥')->isEnabled()); - } - - /** - * 测试自定义语音 - */ - public function testExample4CustomVoice(): void - { - $engine = ProcessEngine::createStandard(); - - // 设置自定义语音 - $engine->setStepVoice('清洗', '第一步清洗开始,请认真清洗'); - - $context = ProcessContext::create([ - 'endoscopeName' => '胃镜004', - 'readerType' => '清洗', - 'currentStep' => '', - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - - $result = $engine->execute($context); - - $this->assertTrue($result->success); - // 验证语音包含步骤完成信息 - $this->assertStringContainsString('清洗', $result->getFullVoice()); - } - - /** - * 测试不同晨洗模式 - */ - public function testExample5MorningWashModes(): void - { - // 模式1: 不需要晨洗 - $config1 = ProcessConfig::createNoMorningWash(); - $this->assertFalse($config1->isNodeEnabled('晨洗')); - - // 模式2: 义乌模式(按存储时间) - $config2 = new ProcessConfig([ - 'morning_wash' => [ - 'mode' => 'storage_time', - 'storage_threshold' => 4, - ], - ]); - $this->assertEquals('StorageTime', $config2->getMorningWashConfig()->mode->name); - - // 模式3: 特定类型镜子需要晨洗 - $config3 = new ProcessConfig([ - 'morning_wash' => [ - 'mode' => 'specific_types', - 'specific_types' => ['胃镜', '十二指肠镜'], - ], - ]); - $this->assertEquals(['胃镜', '十二指肠镜'], $config3->getMorningWashConfig()->getExpand('specific_types', [])); - } - - /** - * 测试机洗流程 - */ - public function testExample6MachineWash(): void - { - $config = ProcessConfig::createMachineWash(); - $engine = new ProcessEngine($config); - - // 场景: 清洗后刷机洗 - $context = ProcessContext::create([ - 'endoscopeName' => '胃镜005', - 'readerType' => '机洗', - 'currentStep' => '清洗', - 'processType' => '手工洗', - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - - $result = $engine->execute($context); - - $this->assertTrue($result->success); - $this->assertEquals('机洗', $result->currentStep); - $this->assertEquals('机洗', $result->processType); - } - - /** - * 测试简化流程(只清洗) - */ - public function testExample7SimpleProcess(): void - { - $config = ProcessConfig::createSimple(); - $engine = new ProcessEngine($config); - - // 验证漂洗和消毒被禁用 - $this->assertFalse($engine->getNode('漂洗')->isEnabled()); - $this->assertFalse($engine->getNode('消毒')->isEnabled()); - - $context = ProcessContext::create([ - 'endoscopeName' => '胃镜006', - 'readerType' => '清洗', - 'currentStep' => '', - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - - $result = $engine->execute($context); - $this->assertTrue($result->success); - $this->assertEquals('清洗', $result->currentStep); - } - - /** - * 测试时间验证 - 正常流程(时间足够) - */ - public function testExample8TimeValidation(): void - { - $engine = ProcessEngine::createStandard(); - - // 先执行清洗 - $context1 = ProcessContext::create([ - 'endoscopeName' => '胃镜007', - 'readerType' => '清洗', - 'currentStep' => '', - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - $result1 = $engine->execute($context1); - $this->assertTrue($result1->success); - - // 清洗后时间足够(超过5分钟)再刷漂洗 - $context2 = ProcessContext::create([ - 'endoscopeName' => '胃镜007', - 'readerType' => '漂洗', - 'currentStep' => '清洗', - 'batchNo' => $result1->batchNo, - 'morningWashed' => true, - 'needMorningWash' => false, - ]); - // 设置清洗步骤时间为6分钟前(超过要求的5分钟) - $context2->setStepLastTime('清洗', date('Y-m-d H:i:s', time() - 360)); - - $result2 = $engine->execute($context2); - - // 时间足够,流程应该成功 - $this->assertTrue($result2->success); - $this->assertEquals('漂洗', $result2->currentStep); - } - - /** - * 测试完整的刷卡处理流程 - */ - public function testExample9FullProcess(): void - { - // 1. 创建标准流程引擎 - $engine = ProcessEngine::createStandard(); - - // 2. 模拟接收刷卡数据 - $cardData = [ - 'cardNo' => '04A2AD88D0', - 'readerNo' => '09E45F217B', - 'readerType' => '清洗', - ]; - - // 3. 模拟查询内镜信息 - $endoscopeInfo = [ - 'endoscopeId' => '', - 'endoscopeName' => '胃镜001', - 'currentStep' => '', - 'needMorningWash' => false, - 'morningWashed' => true, - ]; - - // 4. 创建上下文 - $context = ProcessContext::create(array_merge($cardData, $endoscopeInfo)); - - // 5. 执行流程 - $result = $engine->execute($context); - - // 6. 验证结果 - $this->assertTrue($result->success); - $this->assertEquals('清洗', $result->currentStep); - $this->assertTrue($result->needDatabaseOperation); - $this->assertTrue($result->needWebSocketNotify); - } - - /** - * 测试多医院配置 - */ - public function testExample10MultiHospital(): void - { - // 医院A使用标准流程 - $hospitalA = ProcessEngine::createStandard(); - $this->assertTrue($hospitalA->getNode('晨洗')->isEnabled()); - - // 医院B使用无晨洗流程 - $hospitalB = ProcessEngine::createNoMorningWash(); - $this->assertFalse($hospitalB->getNode('晨洗')->isEnabled()); - - // 医院C使用机洗流程 - $hospitalC = ProcessEngine::createMachineWash(); - $this->assertTrue($hospitalC->getNode('机洗')->isEnabled()); - - // 医院D使用简化流程 - $hospitalD = ProcessEngine::createSimple(); - $this->assertFalse($hospitalD->getNode('漂洗')->isEnabled()); - } -} diff --git a/tests/flow/VirtualContextBuilder.php b/tests/flow/VirtualContextBuilder.php new file mode 100644 index 0000000..b66253a --- /dev/null +++ b/tests/flow/VirtualContextBuilder.php @@ -0,0 +1,458 @@ +loadEnvironment($envPath); + $this->initDefaults(); + } + + /** + * 加载环境配置 + */ + private function loadEnvironment(?string $envPath): void + { + $defaultPath = __DIR__ . '/../resources/default_environment.json'; + $path = $envPath ?? $defaultPath; + + if (!file_exists($path)) { + throw new \RuntimeException("环境配置文件不存在: {$path}"); + } + + $content = file_get_contents($path); + $this->environment = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \RuntimeException("环境配置文件解析失败: " . json_last_error_msg()); + } + } + + /** + * 初始化默认值 + */ + private function initDefaults(): void + { + $this->endoscope = EndoscopeInfo::empty(); + $this->reader = ReaderInfo::empty(); + $this->operator = OperatorInfo::empty(); + $this->storage = StorageStatus::notInStorage(); + $this->morningWash = MorningWashStatus::notRequired(); + $this->voice = VoiceState::empty(); + $this->result = ExecutionResult::success(); + $this->processStatus = ProcessStatus::empty(); + $this->reminder = ReminderStatus::none(); + + // 从环境配置加载步骤时长 + if (isset($this->environment['stepDurations'])) { + $this->stepDurations = $this->environment['stepDurations']; + } + } + + /** + * 创建构建器实例(静态工厂) + */ + public static function create(?string $envPath = null): self + { + return new self($envPath); + } + + // ==================== 便捷设置方法 ==================== + + /** + * 设置内镜(从环境配置名称) + */ + public function endoscope(string $name): self + { + $data = $this->environment['endoscopes'][$name] ?? null; + if ($data === null) { + throw new \InvalidArgumentException("未找到内镜配置: {$name}"); + } + + $this->endoscope = new EndoscopeInfo( + id: $data['id'], + name: $data['name'], + cardNo: $data['cardNo'], + type: $data['type'] + ); + + return $this; + } + + /** + * 设置自定义内镜 + */ + public function customEndoscope(string $id, string $name, string $cardNo, string $type = ''): self + { + $this->endoscope = new EndoscopeInfo( + id: $id, + name: $name, + cardNo: $cardNo, + type: $type + ); + return $this; + } + + /** + * 设置读卡器(从环境配置名称/类型) + */ + public function reader(string $typeOrName): self + { + $data = $this->environment['readers'][$typeOrName] ?? null; + if ($data === null) { + throw new \InvalidArgumentException("未找到读卡器配置: {$typeOrName}"); + } + + $this->reader = new ReaderInfo( + no: $data['no'], + type: $data['type'], + id: $data['id'] + ); + + return $this; + } + + /** + * 设置自定义读卡器 + */ + public function customReader(string $no, string $type, string $id = ''): self + { + $this->reader = new ReaderInfo(no: $no, type: $type, id: $id); + return $this; + } + + /** + * 设置操作员(从环境配置名称) + */ + public function operator(string $name): self + { + $data = $this->environment['operators'][$name] ?? null; + if ($data === null) { + throw new \InvalidArgumentException("未找到操作员配置: {$name}"); + } + + $this->operator = new OperatorInfo( + id: $data['id'], + name: $data['name'], + rfid: $data['rfid'] + ); + + return $this; + } + + /** + * 设置自定义操作员 + */ + public function customOperator(string $id, string $name, string $rfid = ''): self + { + $this->operator = new OperatorInfo(id: $id, name: $name, rfid: $rfid); + return $this; + } + + /** + * 设置当前步骤 + */ + public function currentStep(string $step): self + { + $this->processStatus = new ProcessStatus( + currentStep: $step, + processType: $this->processStatus->processType, + batchNo: $this->processStatus->batchNo, + actionStartTime: $this->processStatus->actionStartTime, + duration: $this->processStatus->duration, + previousAction: $this->processStatus->previousAction + ); + return $this; + } + + /** + * 设置批次号 + */ + public function batchNo(string $batchNo): self + { + $this->processStatus = new ProcessStatus( + currentStep: $this->processStatus->currentStep, + processType: $this->processStatus->processType, + batchNo: $batchNo, + actionStartTime: $this->processStatus->actionStartTime, + duration: $this->processStatus->duration, + previousAction: $this->processStatus->previousAction + ); + return $this; + } + + /** + * 设置流程类型 + */ + public function processType(string $type): self + { + $this->processStatus = new ProcessStatus( + currentStep: $this->processStatus->currentStep, + processType: $type, + batchNo: $this->processStatus->batchNo, + actionStartTime: $this->processStatus->actionStartTime, + duration: $this->processStatus->duration, + previousAction: $this->processStatus->previousAction + ); + return $this; + } + + /** + * 设置操作时长(秒) + */ + public function duration(int $seconds): self + { + $this->processStatus = new ProcessStatus( + currentStep: $this->processStatus->currentStep, + processType: $this->processStatus->processType, + batchNo: $this->processStatus->batchNo, + actionStartTime: $this->processStatus->actionStartTime, + duration: $seconds, + previousAction: $this->processStatus->previousAction + ); + return $this; + } + + /** + * 设置操作开始时间 + */ + public function actionStartTime(string $time): self + { + $this->processStatus = new ProcessStatus( + currentStep: $this->processStatus->currentStep, + processType: $this->processStatus->processType, + batchNo: $this->processStatus->batchNo, + actionStartTime: $time, + duration: $this->processStatus->duration, + previousAction: $this->processStatus->previousAction + ); + return $this; + } + + /** + * 设置上一条操作记录(虚拟) + */ + public function previousAction(string $processName, string $batchNo = '', ?string $opEndtime = null): self + { + $action = new EctActions(); + $action->process_name = $processName; + $action->op_batchno = $batchNo ?: $this->processStatus->batchNo; + $action->op_starttime = date('Y-m-d H:i:s', time() - 60); + $action->op_endtime = $opEndtime; + $action->action_type_name = $this->processStatus->processType; + + $this->processStatus = new ProcessStatus( + currentStep: $this->processStatus->currentStep, + processType: $this->processStatus->processType, + batchNo: $this->processStatus->batchNo, + actionStartTime: $this->processStatus->actionStartTime, + duration: $this->processStatus->duration, + previousAction: $action + ); + return $this; + } + + /** + * 设置晨洗状态 - 需要晨洗 + */ + public function needMorningWash(int $todayRecords = 0): self + { + $startTime = $this->environment['config']['morningWashStartTime'] ?? '06:00:00'; + $this->morningWash = MorningWashStatus::required($startTime, $todayRecords); + return $this; + } + + /** + * 设置晨洗状态 - 已完成晨洗 + */ + public function morningWashCompleted(): self + { + $startTime = $this->environment['config']['morningWashStartTime'] ?? '06:00:00'; + $this->morningWash = MorningWashStatus::completed($startTime); + return $this; + } + + /** + * 设置晨洗状态 - 不需要晨洗 + */ + public function noMorningWash(): self + { + $this->morningWash = MorningWashStatus::notRequired(); + return $this; + } + + /** + * 设置存储状态 - 在库中 + */ + public function inStorage(?string $inTime = null): self + { + $this->storage = StorageStatus::inStorage($inTime ?? date('Y-m-d H:i:s', time() - 3600)); + return $this; + } + + /** + * 设置存储状态 - 已出库 + */ + public function outOfStorage(): self + { + $this->storage = StorageStatus::outOfStorage(); + return $this; + } + + /** + * 设置为人员卡 + */ + public function asOperatorCard(): self + { + $this->isOperatorCard = true; + return $this; + } + + /** + * 设置流程引擎配置 + */ + public function withEngineConfig(ProcessConfig $config): self + { + $this->engineConfig = $config; + return $this; + } + + /** + * 设置步骤时长 + */ + public function setStepDuration(string $stepCode, int $duration): self + { + $this->stepDurations[$stepCode] = $duration; + return $this; + } + + /** + * 新流程开始(清空当前步骤,生成新批次号) + */ + public function newProcess(): self + { + $machineId = $this->environment['config']['machineId'] ?? '01'; + $batchNo = date('Ymd') . $machineId . str_pad(rand(1, 9999), 4, '0', STR_PAD_LEFT); + + $this->processStatus = new ProcessStatus( + currentStep: '', + processType: '', + batchNo: $batchNo, + actionStartTime: date('Y-m-d H:i:s'), + duration: 0, + previousAction: null + ); + + return $this; + } + + /** + * 模拟结束状态 + */ + public function finished(): self + { + return $this->currentStep('结束'); + } + + // ==================== 构建方法 ==================== + + /** + * 构建 ProcessContext + */ + public function build(): ProcessContext + { + return new ProcessContext( + endoscope: $this->endoscope, + reader: $this->reader, + operator: $this->operator, + storage: $this->storage, + morningWash: $this->morningWash, + voice: $this->voice, + result: $this->result, + processStatus: $this->processStatus, + reminder: $this->reminder, + packetContext: null, + engineConfig: $this->engineConfig, + rawData: $this->rawData, + isOperatorCard: $this->isOperatorCard, + stepDurations: $this->stepDurations, + ); + } + + /** + * 获取当前环境配置 + */ + public function getEnvironment(): array + { + return $this->environment; + } + + /** + * 获取读卡器配置列表 + */ + public function getAvailableReaders(): array + { + return array_keys($this->environment['readers'] ?? []); + } + + /** + * 获取内镜配置列表 + */ + public function getAvailableEndoscopes(): array + { + return array_keys($this->environment['endoscopes'] ?? []); + } + + /** + * 获取操作员配置列表 + */ + public function getAvailableOperators(): array + { + return array_keys($this->environment['operators'] ?? []); + } +} diff --git a/tests/flow/VirtualityFlowProcessor.php b/tests/flow/VirtualityFlowProcessor.php new file mode 100644 index 0000000..0352953 --- /dev/null +++ b/tests/flow/VirtualityFlowProcessor.php @@ -0,0 +1,386 @@ + operatorInfo) */ + private array $operatorSessions = []; + + /** @var ProcessContext[] 执行历史 */ + private array $history = []; + + /** @var array 当前内镜的流程状态 (endoscopeId => ProcessContext) */ + private array $endoscopeStates = []; + + /** + * 构造函数 + * + * @param ProcessConfig|null $config 流程配置 + * @param string|null $envPath 环境配置文件路径 + */ + public function __construct(?ProcessConfig $config = null, ?string $envPath = null) + { + $this->envPath = $envPath ?? (__DIR__ . '/../resources/default_environment.json'); + $this->loadEnvironment(); + $this->engine = ProcessEngine::create($config ?? ProcessConfig::createStandard()); + } + + /** + * 加载环境配置 + */ + private function loadEnvironment(): void + { + if (!file_exists($this->envPath)) { + throw new \RuntimeException("环境配置文件不存在: {$this->envPath}"); + } + + $content = file_get_contents($this->envPath); + $this->environment = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \RuntimeException("环境配置文件解析失败: " . json_last_error_msg()); + } + } + + // ==================== 静态工厂方法 ==================== + + /** + * 创建标准流程处理器 + */ + public static function create(?ProcessConfig $config = null, ?string $envPath = null): self + { + return new self($config, $envPath); + } + + /** + * 创建标准手工洗流程处理器 + */ + public static function createStandard(?string $envPath = null): self + { + return new self(ProcessConfig::createStandard(), $envPath); + } + + /** + * 创建无晨洗流程处理器 + */ + public static function createNoMorningWash(?string $envPath = null): self + { + return new self(ProcessConfig::createNoMorningWash(), $envPath); + } + + /** + * 创建机洗流程处理器 + */ + public static function createMachineWash(?string $envPath = null): self + { + return new self(ProcessConfig::createMachineWash(), $envPath); + } + + // ==================== 刷卡模拟方法 ==================== + + /** + * 模拟刷人员卡 + * + * @param string $operatorName 操作员名称(环境配置中定义) + * @param string $readerType 读卡器类型 + * @return ProcessContext + */ + public function swipeOperatorCard(string $operatorName, string $readerType = '清洗'): ProcessContext + { + $operatorData = $this->environment['operators'][$operatorName] + ?? throw new \InvalidArgumentException("未找到操作员: {$operatorName}"); + $readerData = $this->environment['readers'][$readerType] + ?? throw new \InvalidArgumentException("未找到读卡器: {$readerType}"); + + // 保存操作员会话 + $this->operatorSessions[$readerData['id']] = $operatorData; + + // 构建上下文 + $context = VirtualContextBuilder::create($this->envPath) + ->operator($operatorName) + ->reader($readerType) + ->asOperatorCard() + ->withEngineConfig($this->engine->getConfig()) + ->build(); + + // 人员卡不走流程链,直接返回 + $result = $context->builder() + ->voiceMessage('请刷内镜卡') + ->build(); + + $this->history[] = $result; + return $result; + } + + /** + * 模拟刷内镜卡 + * + * @param string $endoscopeName 内镜名称(环境配置中定义) + * @param string $readerType 读卡器类型 + * @return ProcessContext + */ + public function swipeEndoscopeCard(string $endoscopeName, string $readerType): ProcessContext + { + $readerData = $this->environment['readers'][$readerType] + ?? throw new \InvalidArgumentException("未找到读卡器: {$readerType}"); + $endoscopeData = $this->environment['endoscopes'][$endoscopeName] + ?? throw new \InvalidArgumentException("未找到内镜: {$endoscopeName}"); + + // 获取内镜当前状态 + $currentState = $this->endoscopeStates[$endoscopeData['id']] ?? null; + + // 构建上下文 + $builder = VirtualContextBuilder::create($this->envPath) + ->endoscope($endoscopeName) + ->reader($readerType) + ->withEngineConfig($this->engine->getConfig()); + + // 如果有操作员会话,添加操作员信息 + if (isset($this->operatorSessions[$readerData['id']])) { + $opData = $this->operatorSessions[$readerData['id']]; + $builder->customOperator($opData['id'], $opData['name'], $opData['rfid']); + } + + // 继承之前的流程状态 + if ($currentState !== null) { + $builder->currentStep($currentState->getCurrentStep()) + ->batchNo($currentState->getBatchNo()) + ->processType($currentState->getProcessType()); + } else { + // 新流程 + $builder->newProcess(); + } + + $context = $builder->build(); + + // 未刷人员卡检查 + if (!$context->hasOperator() && !isset($this->operatorSessions[$readerData['id']])) { + $result = $context->builder() + ->error(\app\flow\enum\VoiceMessage::PLEASE_SWIPE_OPERATOR) + ->build(); + $this->history[] = $result; + return $result; + } + + // 执行流程 + $result = $this->engine->execute($context); + + // 保存状态 + $this->endoscopeStates[$endoscopeData['id']] = $result; + $this->history[] = $result; + + return $result; + } + + /** + * 快捷方法:完整刷卡(先刷人员卡再刷内镜卡) + * + * @param string $operatorName 操作员名称 + * @param string $endoscopeName 内镜名称 + * @param string $readerType 读卡器类型 + * @return ProcessContext 返回内镜卡刷卡结果 + */ + public function swipe(string $operatorName, string $endoscopeName, string $readerType): ProcessContext + { + Logger::info("测试刷卡:{$operatorName}, {$endoscopeName}, {$readerType}"); + // 刷人员卡 + $result = $this->swipeOperatorCard($operatorName, $readerType); + // 当前语音 + Logger::info("当前语音:{$result->getFullVoice()}"); + + // 刷内镜卡 + Logger::info("测试刷卡:{$endoscopeName}, {$readerType}"); + $result = $this->swipeEndoscopeCard($endoscopeName, $readerType); + Logger::info("当前流程:{}", [$result->getProcessType()]); + Logger::info("当前批次号:{}", [$result->getBatchNo()]); + Logger::info("当前步骤:{}", [$result->getCurrentStep()]); + Logger::info("上一个步骤类型:{}", [$result->getPreviousAction()->action_type??"null"]); + Logger::info("上一个步骤:{}", [$result->getPreviousAction()->process_name??"null"]); + Logger::info("当前语音:{$result->getFullVoice()}\n"); + return $result; + } + + /** + * 执行完整的手工洗流程 + * + * @param string $operatorName 操作员名称 + * @param string $endoscopeName 内镜名称 + * @return array 每个步骤的执行结果 + */ + public function executeFullManualWash(string $operatorName, string $endoscopeName): array + { + $steps = ['清洗', '漂洗', '消毒', '终末漂洗', '干燥']; + $results = []; + + foreach ($steps as $step) { + $results[$step] = $this->swipe($operatorName, $endoscopeName, $step); + } + + return $results; + } + + /** + * 执行完整的机洗流程 + * + * @param string $operatorName 操作员名称 + * @param string $endoscopeName 内镜名称 + * @return ProcessContext + */ + public function executeMachineWash(string $operatorName, string $endoscopeName): ProcessContext + { + return $this->swipe($operatorName, $endoscopeName, '机洗'); + } + + // ==================== 状态管理方法 ==================== + + /** + * 重置所有状态 + */ + public function reset(): self + { + $this->operatorSessions = []; + $this->endoscopeStates = []; + $this->history = []; + return $this; + } + + /** + * 清除指定内镜的状态 + */ + public function clearEndoscopeState(string $endoscopeName): self + { + $endoscopeData = $this->environment['endoscopes'][$endoscopeName] ?? null; + if ($endoscopeData !== null) { + unset($this->endoscopeStates[$endoscopeData['id']]); + } + return $this; + } + + /** + * 清除指定读卡器的操作员会话 + */ + public function clearOperatorSession(string $readerType): self + { + $readerData = $this->environment['readers'][$readerType] ?? null; + if ($readerData !== null) { + unset($this->operatorSessions[$readerData['id']]); + } + return $this; + } + + /** + * 手动设置内镜状态 + */ + public function setEndoscopeState(string $endoscopeName, ProcessContext $context): self + { + $endoscopeData = $this->environment['endoscopes'][$endoscopeName] ?? null; + if ($endoscopeData !== null) { + $this->endoscopeStates[$endoscopeData['id']] = $context; + } + return $this; + } + + // ==================== 查询方法 ==================== + + /** + * 获取执行历史 + */ + public function getHistory(): array + { + return $this->history; + } + + /** + * 获取最后一次执行结果 + */ + public function getLastResult(): ?ProcessContext + { + return end($this->history) ?: null; + } + + /** + * 获取内镜当前状态 + */ + public function getEndoscopeState(string $endoscopeName): ?ProcessContext + { + $endoscopeData = $this->environment['endoscopes'][$endoscopeName] ?? null; + if ($endoscopeData === null) { + return null; + } + return $this->endoscopeStates[$endoscopeData['id']] ?? null; + } + + /** + * 获取流程引擎 + */ + public function getEngine(): ProcessEngine + { + return $this->engine; + } + + /** + * 获取环境配置 + */ + public function getEnvironment(): array + { + return $this->environment; + } + + /** + * 创建上下文构建器 + */ + public function createContextBuilder(): VirtualContextBuilder + { + return VirtualContextBuilder::create($this->envPath) + ->withEngineConfig($this->engine->getConfig()); + } + + // ==================== 断言辅助方法 ==================== + + /** + * 获取语音内容(用于断言) + */ + public function getVoice(?ProcessContext $context = null): string + { + $ctx = $context ?? $this->getLastResult(); + return $ctx?->getFullVoice() ?? ''; + } + + /** + * 检查是否成功 + */ + public function isSuccess(?ProcessContext $context = null): bool + { + $ctx = $context ?? $this->getLastResult(); + return $ctx?->isSuccess() ?? false; + } + + /** + * 获取当前步骤 + */ + public function getCurrentStep(?ProcessContext $context = null): string + { + $ctx = $context ?? $this->getLastResult(); + return $ctx?->getCurrentStep() ?? ''; + } +} diff --git a/tests/flow/cases/BlockTest.php b/tests/flow/cases/BlockTest.php new file mode 100644 index 0000000..b517004 --- /dev/null +++ b/tests/flow/cases/BlockTest.php @@ -0,0 +1,174 @@ +processor = VirtualityFlowProcessor::createStandard(); + + // 保存原始 blockMode 值 + $this->originalBlockMode = Config::getInstance()->blockMode; + } + + protected function tearDown(): void + { + parent::tearDown(); + $this->processor->reset(); + + // 恢复原始 blockMode 值 + Config::getInstance()->blockMode = $this->originalBlockMode; + } + + // ==================== 7. 流程阻断逻辑正确性 ==================== + + /** + * 测试流程链正确执行 + */ + public function testProcessChainExecution(): void + { + $engine = ProcessEngine::createStandard(); + + // 验证流程链已构建 + $nodes = $engine->getNodes(); + $this->assertNotEmpty($nodes, '流程链应包含节点'); + } + + /** + * 测试禁用节点后的流程 + */ + public function testDisabledNodeSkipped(): void + { + $config = ProcessConfig::createStandard(); + $config->skipStep('晨洗'); + + $processor = VirtualityFlowProcessor::create($config); + + $result = $processor->swipe('操作员1', '胃镜1', '清洗'); + + // 晨洗被跳过,应该直接进入清洗 + $this->assertSuccess($result); + $this->assertStep($result, '清洗'); + } + + /** + * 测试时间验证策略阻断 - 阻断模式开启 + * + * 当 blockMode=true 时,清洗时长不达标应该返回错误 + */ + public function testTimeValidationBlockWithBlockModeOn(): void + { + // 开启阻断模式 + Config::getInstance()->blockMode = true; + + // 创建一个时间不足的场景(只有5秒) + $context = $this->processor->createContextBuilder() + ->endoscope('胃镜1') + ->reader('漂洗') + ->operator('操作员1') + ->currentStep('清洗') + ->duration(5) // 只有5秒,时间不足 + ->batchNo(date('Ymd') . '010001') + ->build(); + + $engine = ProcessEngine::createStandard(); + $result = $engine->execute($context); + + // 阻断模式下,时间不足应该导致流程失败 + $this->assertFalse($result->isSuccess(), '阻断模式下,时长不足应失败'); + } + + /** + * 测试时间验证策略 - 阻断模式关闭 + * + * 当 blockMode=false 时,清洗时长不达标仍可继续,但应有提醒 + */ + public function testTimeValidationWithBlockModeOff(): void + { + // 关闭阻断模式 + Config::getInstance()->blockMode = false; + + // 创建一个时间不足的场景 + $context = $this->processor->createContextBuilder() + ->endoscope('胃镜1') + ->reader('漂洗') + ->operator('操作员1') + ->currentStep('清洗') + ->duration(5) // 只有5秒,时间不足 + ->batchNo(date('Ymd') . '010001') + ->build(); + + $engine = ProcessEngine::createStandard(); + $result = $engine->execute($context); + + // 非阻断模式下,时间不足应该可以继续流程 + $this->assertTrue($result->isSuccess(), '非阻断模式下,时长不足应可继续'); + } + + /** + * 测试重复刷卡阻断 + */ + public function testDuplicateSwipeBlock(): void + { + $operator = '操作员1'; + $endoscope = '胃镜1'; + + // 完成清洗 + $result1 = $this->processor->swipe($operator, $endoscope, '清洗'); + $this->assertSuccess($result1); + + // 继续漂洗 + $result2 = $this->processor->swipe($operator, $endoscope, '漂洗'); + + // 重复刷漂洗卡 + $result3 = $this->processor->swipe($operator, $endoscope, '漂洗'); + + // 获取语音,验证重复刷卡提示 + $voice3 = $result3->getFullVoice(); + $this->assertNotEmpty($voice3, '应有语音输出'); + $this->assertStringContainsString('重复刷卡', $voice3, '语音应包含重复刷卡提示'); + } + + /** + * 测试时长达标时的正常流程 + */ + public function testTimeValidationPass(): void + { + // 开启阻断模式 + Config::getInstance()->blockMode = true; + + // 创建时长达标的场景(120秒,超过最低要求) + $context = $this->processor->createContextBuilder() + ->endoscope('胃镜1') + ->reader('漂洗') + ->operator('操作员1') + ->currentStep('清洗') + ->duration(120) // 120秒,时间充足 + ->batchNo(date('Ymd') . '010001') + ->build(); + + $engine = ProcessEngine::createStandard(); + $result = $engine->execute($context); + + // 时长达标应该成功 + $this->assertTrue($result->isSuccess(), '时长达标应成功'); + } +} diff --git a/tests/flow/cases/ErrorFlowTest.php b/tests/flow/cases/ErrorFlowTest.php new file mode 100644 index 0000000..205cacd --- /dev/null +++ b/tests/flow/cases/ErrorFlowTest.php @@ -0,0 +1,118 @@ +processor = VirtualityFlowProcessor::createStandard(); + } + + protected function tearDown(): void + { + parent::tearDown(); + $this->processor->reset(); + } + + // ==================== 5. 错误刷卡流程 ==================== + + /** + * 测试未刷人员卡直接刷内镜卡 + */ + public function testSwipeEndoscopeWithoutOperator(): void + { + // 不刷人员卡,直接刷内镜卡 + $result = $this->processor->swipeEndoscopeCard('胃镜1', '清洗'); + + $this->assertFailure($result, '未刷人员卡应失败'); + $this->assertVoiceContains($result, '请刷人员卡'); + } + + /** + * 测试刷卡顺序错误 - 跳过清洗直接漂洗 + */ + public function testWrongStepOrder(): void + { + $operator = '操作员1'; + $endoscope = '胃镜1'; + + // 新流程,直接刷漂洗 + $result = $this->processor->swipe($operator, $endoscope, '漂洗'); + + // 应该提示步骤顺序错误 + // 注意:具体行为取决于流程引擎的实现 + $this->assertNotNull($result); + } + + /** + * 测试重复刷同一步骤的卡 + */ + public function testDuplicateSwipe(): void + { + $operator = '操作员1'; + $endoscope = '胃镜1'; + + // 第一次刷清洗 + $result1 = $this->processor->swipe($operator, $endoscope, '清洗'); + $this->assertSuccess($result1); + + // 第二次刷清洗(重复刷卡) + $result2 = $this->processor->swipe($operator, $endoscope, '清洗'); + + // 应该被重复刷卡节点拦截 + $this->assertNotNull($result2); + } + + /** + * 测试读卡器未绑定 + */ + public function testUnboundReader(): void + { + $context = $this->processor->createContextBuilder() + ->endoscope('胃镜1') + ->customReader('R999', '', '') // 未绑定的读卡器 + ->operator('操作员1') + ->newProcess() + ->build(); + + $engine = ProcessEngine::createStandard(); + $result = $engine->execute($context); + + // 读卡器未绑定应该在 FlowProcessor 层处理 + // 这里主要测试 ProcessEngine 的行为 + $this->assertNotNull($result); + } + + /** + * 测试内镜卡未绑定 + */ + public function testUnboundEndoscope(): void + { + $context = $this->processor->createContextBuilder() + ->endoscope('未绑定卡') + ->reader('清洗') + ->operator('操作员1') + ->newProcess() + ->build(); + + $engine = ProcessEngine::createStandard(); + $result = $engine->execute($context); + + // 内镜未绑定应该触发错误 + $this->assertNotNull($result); + } +} diff --git a/tests/flow/cases/HelperTest.php b/tests/flow/cases/HelperTest.php new file mode 100644 index 0000000..5dc94ac --- /dev/null +++ b/tests/flow/cases/HelperTest.php @@ -0,0 +1,79 @@ +processor = VirtualityFlowProcessor::createStandard(); + } + + protected function tearDown(): void + { + parent::tearDown(); + $this->processor->reset(); + } + + /** + * 测试 VirtualityFlowProcessor 状态重置 + */ + public function testProcessorReset(): void + { + // 执行一些操作 + $this->processor->swipe('操作员1', '胃镜1', '清洗'); + + // 重置 + $this->processor->reset(); + + // 重置后应该是新流程 + $history = $this->processor->getHistory(); + $this->assertEmpty($history, '重置后历史应为空'); + } + + /** + * 测试 VirtualContextBuilder 功能 + */ + public function testVirtualContextBuilder(): void + { + $builder = VirtualContextBuilder::create(); + + $context = $builder + ->endoscope('胃镜1') + ->reader('清洗') + ->operator('操作员1') + ->currentStep('清洗') + ->batchNo('20260310010001') + ->build(); + + $this->assertEquals('清洗', $context->getCurrentStep()); + $this->assertEquals('20260310010001', $context->getBatchNo()); + $this->assertEquals('张三', $context->getOperator()->name); + $this->assertEquals('胃镜1号', $context->getEndoscope()->name); + } + + /** + * 测试环境配置加载 + */ + public function testEnvironmentConfig(): void + { + $env = $this->processor->getEnvironment(); + + $this->assertArrayHasKey('readers', $env); + $this->assertArrayHasKey('endoscopes', $env); + $this->assertArrayHasKey('operators', $env); + $this->assertArrayHasKey('config', $env); + } +} diff --git a/tests/flow/cases/MachineWashTest.php b/tests/flow/cases/MachineWashTest.php new file mode 100644 index 0000000..8a978c4 --- /dev/null +++ b/tests/flow/cases/MachineWashTest.php @@ -0,0 +1,96 @@ +processor = VirtualityFlowProcessor::createMachineWash(); + } + + protected function tearDown(): void + { + parent::tearDown(); + $this->processor->reset(); + } + + // ==================== 3. 机洗流程 ==================== + + /** + * 测试机洗流程 + */ + public function testMachineWashProcess(): void + { + $result = $this->processor->swipe('操作员1', '胃镜1', '机洗'); + + $this->assertSuccess($result, '机洗应成功'); + $this->assertStep($result, '机洗'); + $this->assertHasBatchNo($result); + $this->assertTrue($result->isSuccess(), 'isSuccess() 应为 true'); + } + + /** + * 测试机洗流程使用便捷方法 + */ + public function testMachineWashProcessUsingHelper(): void + { + $result = $this->processor->executeMachineWash('操作员1', '肠镜1'); + + $this->assertSuccess($result, '机洗应成功'); + $this->assertStep($result, '机洗'); + $this->assertTrue($result->isSuccess(), 'isSuccess() 应为 true'); + } + + // ==================== 4. 晨洗 + 机洗流程 ==================== + + /** + * 测试晨洗后接机洗流程 + */ + public function testMorningWashThenMachineWash(): void + { + $operator = '操作员1'; + $endoscope = '胃镜1'; + + // 直接执行机洗(假设晨洗已完成) + $result = $this->processor->swipe($operator, $endoscope, '机洗'); + + $this->assertSuccess($result, '晨洗后机洗应成功'); + $this->assertStep($result, '机洗'); + $this->assertTrue($result->isSuccess(), 'isSuccess() 应为 true'); + } + + /** + * 测试多个内镜同时机洗 + */ + public function testMultipleEndoscopeMachineWash(): void + { + $operator = '操作员1'; + + // 胃镜1机洗 + $result1 = $this->processor->swipe($operator, '胃镜1', '机洗'); + $this->assertSuccess($result1); + $batchNo1 = $result1->getBatchNo(); + + // 肠镜1机洗 + $result2 = $this->processor->swipe($operator, '肠镜1', '机洗'); + $this->assertSuccess($result2); + $batchNo2 = $result2->getBatchNo(); + + // 批次号应不同 + $this->assertNotEquals($batchNo1, $batchNo2, '不同内镜机洗应有不同批次号'); + } +} diff --git a/tests/flow/cases/ManualWashTest.php b/tests/flow/cases/ManualWashTest.php new file mode 100644 index 0000000..f62c9d5 --- /dev/null +++ b/tests/flow/cases/ManualWashTest.php @@ -0,0 +1,215 @@ +processor = VirtualityFlowProcessor::createStandard(); + } + + protected function tearDown(): void + { + parent::tearDown(); + $this->processor->reset(); + } + + // ==================== 1. 正常完整手工洗流程 ==================== + + /** + * 测试完整手工洗流程:清洗 -> 漂洗 -> 消毒 -> 终末漂洗 -> 干燥 + */ + public function testCompleteManualWashProcess(): void + { + $operator = '操作员1'; + $endoscope = '胃镜1'; + + // 步骤1:清洗 + $result1 = $this->processor->swipe($operator, $endoscope, '清洗'); + $this->assertSuccess($result1, '清洗步骤应成功'); + $this->assertStep($result1, '清洗'); + $this->assertNeedDatabaseOperation($result1); + $batchNo = $result1->getBatchNo(); + $this->assertNotEmpty($batchNo, '应生成批次号'); + + // 步骤2:漂洗 + $result2 = $this->processor->swipe($operator, $endoscope, '漂洗'); + $this->assertSuccess($result2, '漂洗步骤应成功'); + $this->assertStep($result2, '漂洗'); + $this->assertBatchNoEquals($result2, $batchNo, '批次号应保持一致'); + + // 步骤3:消毒 + $result3 = $this->processor->swipe($operator, $endoscope, '消毒'); + $this->assertSuccess($result3, '消毒步骤应成功'); + $this->assertStep($result3, '消毒'); + $this->assertBatchNoEquals($result3, $batchNo, '批次号应保持一致'); + + // 步骤4:终末漂洗 + $result4 = $this->processor->swipe($operator, $endoscope, '终末漂洗'); + $this->assertSuccess($result4, '终末漂洗步骤应成功'); + $this->assertStep($result4, '终末漂洗'); + $this->assertBatchNoEquals($result4, $batchNo, '批次号应保持一致'); + + // 步骤5:干燥 + $result5 = $this->processor->swipe($operator, $endoscope, '干燥'); + $this->assertSuccess($result5, '干燥步骤应成功'); + $this->assertStep($result5, '干燥'); + $this->assertBatchNoEquals($result5, $batchNo, '批次号应保持一致'); + + // 验证最终步骤成功 + $this->assertTrue($result5->isSuccess(), '干燥步骤 isSuccess() 应为 true'); + } + + /** + * 测试使用便捷方法执行完整手工洗流程 + */ + public function testCompleteManualWashProcessUsingHelper(): void + { + $results = $this->processor->executeFullManualWash('操作员1', '胃镜1'); + + $this->assertCount(5, $results, '应有5个步骤结果'); + + foreach ($results as $step => $result) { + $this->assertSuccess($result, "{$step} 步骤应成功"); + $this->assertStep($result, $step); + $this->assertTrue($result->isSuccess(), "{$step} isSuccess() 应为 true"); + } + + // 验证批次号一致性 + $batchNo = $results['清洗']->getBatchNo(); + foreach ($results as $step => $result) { + $this->assertBatchNoEquals($result, $batchNo, "{$step} 批次号应一致"); + } + } + + // ==================== 2. 正常完整手工晨洗流程 ==================== + + /** + * 测试晨洗判断 - 当天首次使用需要晨洗 + */ + public function testMorningWashRequired(): void + { + // 创建需要晨洗的上下文 + $context = $this->processor->createContextBuilder() + ->endoscope('胃镜1') + ->reader('清洗') + ->operator('操作员1') + ->needMorningWash(0) // 今天没有洗消记录 + ->newProcess() + ->build(); + + // 执行流程 + $engine = ProcessEngine::createStandard(); + $result = $engine->execute($context); + + // 晨洗策略应该生效 + $this->assertNotNull($result); + } + + /** + * 测试晨洗完成后的正常流程 + */ + public function testManualWashAfterMorningWash(): void + { + $operator = '操作员1'; + $endoscope = '胃镜1'; + + // 模拟晨洗已完成状态,执行正常流程 + $this->processor->clearEndoscopeState($endoscope); + + // 执行完整手工洗流程 + $results = $this->processor->executeFullManualWash($operator, $endoscope); + + // 验证所有步骤成功 + foreach ($results as $step => $result) { + $this->assertSuccess($result, "{$step} 应成功"); + $this->assertTrue($result->isSuccess(), "{$step} isSuccess() 应为 true"); + } + } + + /** + * 测试不同操作员接手流程 + */ + public function testDifferentOperatorContinueProcess(): void + { + $endoscope = '胃镜1'; + + // 操作员1开始清洗 + $result1 = $this->processor->swipe('操作员1', $endoscope, '清洗'); + $this->assertSuccess($result1); + $batchNo = $result1->getBatchNo(); + + // 操作员2继续漂洗 + $result2 = $this->processor->swipe('操作员2', $endoscope, '漂洗'); + + // 不同操作员应该可以继续流程 + $this->assertSuccess($result2); + $this->assertBatchNoEquals($result2, $batchNo, '批次号应保持一致'); + } + + /** + * 测试流程完成后开始新流程 + */ + public function testNewProcessAfterComplete(): void + { + $operator = '操作员1'; + $endoscope = '胃镜1'; + + // 完成一个完整流程 + $results = $this->processor->executeFullManualWash($operator, $endoscope); + $firstBatchNo = $results['清洗']->getBatchNo(); + + // 清除状态,开始新流程 + $this->processor->clearEndoscopeState($endoscope); + + // 开始新流程 + $newResult = $this->processor->swipe($operator, $endoscope, '清洗'); + + // 应该成功,且批次号不同 + $this->assertSuccess($newResult); + $this->assertNotEquals($firstBatchNo, $newResult->getBatchNo(), '新流程应有新批次号'); + } + + /** + * 不同内镜同时进行 + */ + public function testConcurrentProcessIsolation(): void + { + $operator = '操作员1'; + + // 胃镜1开始清洗 + $result1 = $this->processor->swipe($operator, '胃镜1', '清洗'); + $this->assertSuccess($result1); + $batchNo1 = $result1->getBatchNo(); + + // 肠镜1也开始清洗 + $result2 = $this->processor->swipe($operator, '肠镜1', '清洗'); + $this->assertSuccess($result2); + $batchNo2 = $result2->getBatchNo(); + + // 两个内镜的批次号应该不同 + $this->assertNotEquals($batchNo1, $batchNo2, '不同内镜应有不同批次号'); + + // 继续胃镜1的流程 + $result3 = $this->processor->swipe($operator, '胃镜1', '漂洗'); + $this->assertSuccess($result3); + $this->assertBatchNoEquals($result3, $batchNo1, '胃镜1批次号应保持不变'); + } +} diff --git a/tests/flow/cases/VoiceTest.php b/tests/flow/cases/VoiceTest.php new file mode 100644 index 0000000..1bd6f6b --- /dev/null +++ b/tests/flow/cases/VoiceTest.php @@ -0,0 +1,110 @@ +processor = VirtualityFlowProcessor::createStandard(); + } + + protected function tearDown(): void + { + parent::tearDown(); + $this->processor->reset(); + } + + // ==================== 6. 语音输出正确性验证 ==================== + + /** + * 测试清洗步骤语音输出 + */ + public function testWashStepVoice(): void + { + $result = $this->processor->swipe('操作员1', '胃镜1', '清洗'); + + $this->assertSuccess($result); + // 语音应该包含步骤相关信息 + $voice = $result->getFullVoice(); + $this->assertNotEmpty($voice, '应有语音输出'); + } + + /** + * 测试消毒步骤语音输出 + */ + public function testDisinfectStepVoice(): void + { + // 先完成前置步骤 + $this->processor->swipe('操作员1', '胃镜1', '清洗'); + $this->processor->swipe('操作员1', '胃镜1', '漂洗'); + + // 消毒步骤 + $result = $this->processor->swipe('操作员1', '胃镜1', '消毒'); + + $this->assertSuccess($result); + $voice = $result->getFullVoice(); + $this->assertNotEmpty($voice, '应有语音输出'); + } + + /** + * 测试人员卡刷卡语音 + */ + public function testOperatorCardVoice(): void + { + $result = $this->processor->swipeOperatorCard('操作员1', '清洗'); + + // 人员卡应提示"请刷内镜卡" + $this->assertVoiceContains($result, '请刷内镜卡'); + } + + /** + * 测试错误情况语音 + */ + public function testErrorVoice(): void + { + // 未刷人员卡 + $result = $this->processor->swipeEndoscopeCard('胃镜1', '清洗'); + + $voice = $result->getFullVoice(); + $this->assertNotEmpty($voice, '错误情况应有语音提示'); + } + + /** + * 测试清洗时长不足时的语音模板参数替换 + * + * 语音应包含具体的时长不足提示 + */ + public function testWashDurationInsufficientVoice(): void + { + // 创建清洗时长不足的场景(只有5秒) + $context = $this->processor->createContextBuilder() + ->endoscope('胃镜1') + ->reader('漂洗') + ->operator('操作员1') + ->currentStep('清洗') + ->duration(5) // 只有5秒,时间不足 + ->batchNo(date('Ymd') . '010001') + ->build(); + + $engine = ProcessEngine::createStandard(); + $result = $engine->execute($context); + + // 语音应该包含时长不足相关提示 + $voice = $result->getFullVoice(); + $this->assertNotEmpty($voice, '时长不足应有语音提示'); + } +} diff --git a/tests/flow/config/ProcessConfigTest.php b/tests/flow/config/ProcessConfigTest.php deleted file mode 100644 index c52da7d..0000000 --- a/tests/flow/config/ProcessConfigTest.php +++ /dev/null @@ -1,278 +0,0 @@ -getSteps(); - $this->assertNotEmpty($steps); - $this->assertArrayHasKey('清洗', array_column($steps, null, 'code')); - $this->assertArrayHasKey('消毒', array_column($steps, null, 'code')); - } - - /** - * 测试从数组加载配置 - */ - public function testFromArray(): void - { - $data = [ - 'steps' => [ - ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true], - ['code' => '结束', 'class' => 'EndNode', 'enabled' => true], - ], - 'morning_wash' => ['mode' => 'none'], - 'time_validation' => [ - 'durations' => ['清洗' => 600], - ], - ]; - - $config = ProcessConfig::fromArray($data); - - $this->assertEquals('none', $config->getMorningWashConfig()['mode']); - $this->assertEquals(600, $config->getTimeValidationConfig()['durations']['清洗']); - } - - /** - * 测试获取启用的步骤 - */ - public function testGetEnabledSteps(): void - { - $config = new ProcessConfig(); - $config->skipStep('漂洗'); - $config->skipStep('干燥'); - - $enabledSteps = $config->getEnabledSteps(); - $stepCodes = array_column($enabledSteps, 'code'); - - $this->assertNotContains('漂洗', $stepCodes); - $this->assertNotContains('干燥', $stepCodes); - $this->assertContains('清洗', $stepCodes); - $this->assertContains('消毒', $stepCodes); - } - - /** - * 测试添加步骤 - */ - public function testAddStep(): void - { - $config = new ProcessConfig(); - $config->addStep('自定义步骤', 'CustomNode'); - - $step = $config->getStep('自定义步骤'); - $this->assertNotNull($step); - $this->assertEquals('CustomNode', $step['class']); - $this->assertTrue($step['enabled']); - } - - /** - * 测试移除步骤 - */ - public function testRemoveStep(): void - { - $config = new ProcessConfig(); - $config->removeStep('漂洗'); - - $this->assertNull($config->getStep('漂洗')); - } - - /** - * 测试设置节点启用状态 - */ - public function testSetNodeEnabled(): void - { - $config = new ProcessConfig(); - - $this->assertTrue($config->isNodeEnabled('清洗')); - - $config->setNodeEnabled('清洗', false); - $this->assertFalse($config->isNodeEnabled('清洗')); - - $config->setNodeEnabled('清洗', true); - $this->assertTrue($config->isNodeEnabled('清洗')); - } - - /** - * 测试晨洗配置 - */ - public function testMorningWashConfig(): void - { - $config = new ProcessConfig(); - - // 默认配置 - $defaultConfig = $config->getMorningWashConfig(); - $this->assertEquals('daily_first', $defaultConfig['mode']); - - // 修改配置 - $config->setMorningWashMode('all'); - $this->assertEquals('all', $config->getMorningWashConfig()['mode']); - - // 设置完整配置 - $config->setMorningWashConfig([ - 'mode' => 'specific_types', - 'specific_types' => ['胃镜', '肠镜'], - ]); - $morningConfig = $config->getMorningWashConfig(); - $this->assertEquals('specific_types', $morningConfig['mode']); - $this->assertEquals(['胃镜', '肠镜'], $morningConfig['specific_types']); - } - - /** - * 测试时间验证配置 - */ - public function testTimeValidationConfig(): void - { - $config = new ProcessConfig(); - - $config->setStepDuration('清洗', 600); - $durations = $config->getTimeValidationConfig()['durations']; - - $this->assertEquals(600, $durations['清洗']); - } - - /** - * 测试语音模板配置 - */ - public function testVoiceTemplateConfig(): void - { - $config = new ProcessConfig(); - - $config->setStepVoice('normal_wash', '清洗', '自定义清洗语音'); - $config->setStepVoice('normal_wash', '消毒', '自定义消毒语音'); - - $templates = $config->getVoiceTemplates(); - $this->assertEquals('自定义清洗语音', $templates['normal_wash']['清洗']); - $this->assertEquals('自定义消毒语音', $templates['normal_wash']['消毒']); - } - - /** - * 测试设置步骤自定义语音 - */ - public function testSetStepVoice(): void - { - $config = new ProcessConfig(); - $config->setStepVoice('normal_wash', '清洗', '请开始清洗操作'); - - $templates = $config->getVoiceTemplates(); - $this->assertEquals('请开始清洗操作', $templates['normal_wash']['清洗']); - } - - /** - * 测试转换为数组 - */ - public function testToArray(): void - { - $config = new ProcessConfig(); - $array = $config->toArray(); - - $this->assertArrayHasKey('steps', $array); - $this->assertArrayHasKey('strategies', $array); - $this->assertArrayHasKey('voice_templates', $array); - $this->assertArrayHasKey('morning_wash', $array); - $this->assertArrayHasKey('time_validation', $array); - } - - /** - * 测试创建标准流程配置 - */ - public function testCreateStandard(): void - { - $config = ProcessConfig::createStandard(); - - $this->assertTrue($config->isNodeEnabled('晨洗')); - $this->assertTrue($config->isNodeEnabled('清洗')); - $this->assertTrue($config->isNodeEnabled('消毒')); - $this->assertTrue($config->isNodeEnabled('结束')); - } - - /** - * 测试创建无晨洗流程配置 - */ - public function testCreateNoMorningWash(): void - { - $config = ProcessConfig::createNoMorningWash(); - - $this->assertFalse($config->isNodeEnabled('晨洗')); - $this->assertEquals('none', $config->getMorningWashConfig()['mode']); - } - - /** - * 测试创建简化流程配置 - */ - public function testCreateSimple(): void - { - $config = ProcessConfig::createSimple(); - - $this->assertTrue($config->isNodeEnabled('清洗')); - $this->assertFalse($config->isNodeEnabled('漂洗')); - $this->assertFalse($config->isNodeEnabled('消毒')); - $this->assertTrue($config->isNodeEnabled('结束')); - } - - /** - * 测试创建机洗流程配置 - */ - public function testCreateMachineWash(): void - { - $config = ProcessConfig::createMachineWash(); - - $this->assertTrue($config->isNodeEnabled('清洗')); - $this->assertTrue($config->isNodeEnabled('机洗')); - $this->assertFalse($config->isNodeEnabled('漂洗')); - $this->assertFalse($config->isNodeEnabled('消毒')); - $this->assertTrue($config->isNodeEnabled('终末漂洗')); - } - - /** - * 测试创建无干燥流程配置 - */ - public function testCreateNoDry(): void - { - $config = ProcessConfig::createNoDry(); - - $this->assertFalse($config->isNodeEnabled('干燥')); - $this->assertTrue($config->isNodeEnabled('清洗')); - } - - /** - * 测试创建仅干燥流程配置 - */ - public function testCreateDryOnly(): void - { - $config = ProcessConfig::createDryOnly(); - - $this->assertTrue($config->isNodeEnabled('干燥')); - $this->assertFalse($config->isNodeEnabled('清洗')); - $this->assertFalse($config->isNodeEnabled('消毒')); - } - - /** - * 测试链式调用 - */ - public function testChaining(): void - { - $config = new ProcessConfig(); - - $result = $config - ->setNodeEnabled('漂洗', false) - ->setMorningWashMode('all') - ->setStepDuration('清洗', 600) - ->setStepVoice('normal_wash', '清洗', '测试语音'); - - $this->assertSame($config, $result); - $this->assertFalse($config->isNodeEnabled('漂洗')); - $this->assertEquals('all', $config->getMorningWashConfig()['mode']); - } -} diff --git a/tests/flow/nodes/DisinfectNodeTest.php b/tests/flow/nodes/DisinfectNodeTest.php deleted file mode 100644 index 0fbe272..0000000 --- a/tests/flow/nodes/DisinfectNodeTest.php +++ /dev/null @@ -1,142 +0,0 @@ -node = new DisinfectNode(); - } - - /** - * 测试节点名称和编码 - */ - public function testNodeIdentity(): void - { - $this->assertEquals('消毒', $this->node->getName()); - $this->assertEquals('消毒', $this->node->getCode()); - } - - /** - * 测试漂洗后可以刷消毒 - */ - public function testCanHandleAfterRinse(): void - { - $context = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '漂洗', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试终末漂洗后禁止刷消毒 - */ - public function testCanHandleAfterFinalRinse(): void - { - $context = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '终末漂洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试清洗后禁止直接刷消毒 - */ - public function testCanHandleAfterWash(): void - { - $context = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '清洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试不能处理非消毒读卡器 - */ - public function testCannotHandleNonDisinfectReader(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '漂洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试消毒后不能立即刷消毒 - */ - public function testCannotHandleAfterDisinfect(): void - { - $context = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '消毒', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试处理流程 - */ - public function testHandleProcess(): void - { - $context = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '漂洗', - ]); - - $result = $this->node->handle($context); - - $this->assertSuccess($result); - $this->assertStep($result, '消毒'); - $this->assertNotNull($result->getStepLastTime('消毒')); - } - - /** - * 测试数据库操作标记 - */ - public function testDatabaseOperationFlags(): void - { - $context = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '漂洗', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needDatabaseOperation); - $this->assertEquals(DbOperationType::INSERT, $context->dbOperation); - } - - /** - * 测试 WebSocket 通知标记 - */ - public function testWebSocketNotifyFlag(): void - { - $context = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '漂洗', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needWebSocketNotify); - } -} diff --git a/tests/flow/nodes/DryNodeTest.php b/tests/flow/nodes/DryNodeTest.php deleted file mode 100644 index c1d38be..0000000 --- a/tests/flow/nodes/DryNodeTest.php +++ /dev/null @@ -1,158 +0,0 @@ -node = new DryNode(); - } - - /** - * 测试节点名称和编码 - */ - public function testNodeIdentity(): void - { - $this->assertEquals('干燥', $this->node->getName()); - $this->assertEquals('干燥', $this->node->getCode()); - } - - /** - * 测试终末漂洗后可以刷干燥 - */ - public function testCanHandleAfterFinalRinse(): void - { - $context = $this->createContext([ - 'readerType' => '干燥', - 'currentStep' => '终末漂洗', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试消毒后可以直接刷干燥(跳过终末漂洗) - */ - public function testCanHandleAfterDisinfect(): void - { - $context = $this->createContext([ - 'readerType' => '干燥', - 'currentStep' => '消毒' - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试漂洗后不能直接刷干燥 - */ - public function testCannotHandleAfterRinse(): void - { - $context = $this->createContext([ - 'readerType' => '干燥', - 'currentStep' => '漂洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试不能处理非干燥读卡器 - */ - public function testCannotHandleNonDryReader(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '终末漂洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试干燥后不能立即刷干燥 - */ - public function testCannotHandleAfterDry(): void - { - $context = $this->createContext([ - 'readerType' => '干燥', - 'currentStep' => '干燥', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试处理流程 - */ - public function testHandleProcess(): void - { - $context = $this->createContext([ - 'readerType' => '干燥', - 'currentStep' => '终末漂洗', - ]); - - $result = $this->node->handle($context); - - $this->assertSuccess($result); - $this->assertStep($result, '干燥'); - $this->assertNotNull($result->getStepLastTime('干燥')); - } - - /** - * 测试数据库操作标记 - */ - public function testDatabaseOperationFlags(): void - { - $context = $this->createContext([ - 'readerType' => '干燥', - 'currentStep' => '终末漂洗', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needDatabaseOperation); - $this->assertEquals(DbOperationType::INSERT, $context->dbOperation); - } - - /** - * 测试 WebSocket 通知标记 - */ - public function testWebSocketNotifyFlag(): void - { - $context = $this->createContext([ - 'readerType' => '干燥', - 'currentStep' => '终末漂洗', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needWebSocketNotify); - } - - /** - * 测试保持现有批次号 - */ - public function testKeepExistingBatchNo(): void - { - $context = $this->createContext([ - 'readerType' => '干燥', - 'currentStep' => '终末漂洗', - 'batchNo' => '202603031200000000010001', - ]); - - $this->node->handle($context); - - $this->assertEquals('202603031200000000010001', $context->batchNo); - } -} diff --git a/tests/flow/nodes/EndNodeTest.php b/tests/flow/nodes/EndNodeTest.php deleted file mode 100644 index 3c05def..0000000 --- a/tests/flow/nodes/EndNodeTest.php +++ /dev/null @@ -1,202 +0,0 @@ -node = new EndNode(); - } - - /** - * 测试节点名称和编码 - */ - public function testNodeIdentity(): void - { - $this->assertEquals('结束', $this->node->getName()); - $this->assertEquals('结束', $this->node->getCode()); - } - - /** - * 测试干燥后可以刷结束 - */ - public function testCanHandleAfterDry(): void - { - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '干燥', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试消毒后可以刷结束(跳过终末漂洗) - */ - public function testCanHandleAfterDisinfect(): void - { - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '消毒', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试终末漂洗后可以刷结束 - */ - public function testCanHandleAfterFinalRinse(): void - { - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '终末漂洗', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试机洗后可以刷结束 - */ - public function testCanHandleAfterMachineWash(): void - { - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '机洗', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试非结束读卡器不能处理 - */ - public function testCannotHandleNonEndReader(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '干燥', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试清洗后不能直接刷结束 - */ - public function testCannotHandleAfterWash(): void - { - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '清洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试漂洗后不能直接刷结束 - */ - public function testCannotHandleAfterRinse(): void - { - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '漂洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试空步骤不能刷结束 - */ - public function testCannotHandleWithEmptyStep(): void - { - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试处理流程 - */ - public function testHandleProcess(): void - { - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '干燥', - 'batchNo' => 'BATCH001', - 'actionStartTime' => date('Y-m-d H:i:s', time() - 600), - ]); - - $result = $this->node->handle($context); - - $this->assertSuccess($result); - $this->assertStep($result, '结束'); - $this->assertNotEmpty($result->actionEndTime); - } - - /** - * 测试数据库操作标记(update 而非 insert) - */ - public function testDatabaseOperationIsUpdate(): void - { - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '干燥', - 'batchNo' => 'BATCH001', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needDatabaseOperation); - $this->assertEquals(DbOperationType::INSERT, $context->dbOperation); - } - - /** - * 测试 WebSocket 通知标记 - */ - public function testWebSocketNotifyFlag(): void - { - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '干燥', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needWebSocketNotify); - } - - /** - * 测试 actionEndTime 被设置 - */ - public function testActionEndTimeIsSet(): void - { - $context = $this->createContext([ - 'readerType' => '结束', - 'currentStep' => '干燥', - ]); - - $before = date('Y-m-d H:i:s'); - $this->node->handle($context); - $after = date('Y-m-d H:i:s'); - - $this->assertGreaterThanOrEqual($before, $context->actionEndTime); - $this->assertLessThanOrEqual($after, $context->actionEndTime); - } -} diff --git a/tests/flow/nodes/FinalRinseNodeTest.php b/tests/flow/nodes/FinalRinseNodeTest.php deleted file mode 100644 index 5536d07..0000000 --- a/tests/flow/nodes/FinalRinseNodeTest.php +++ /dev/null @@ -1,148 +0,0 @@ -node = new FinalRinseNode(); - } - - /** - * 测试消毒后可以刷终末漂洗 - */ - public function testCanHandleAfterDisinfect(): void - { - $context = $this->createContext([ - 'readerType' => '终末漂洗', - 'currentStep' => '消毒', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试机洗后可以刷终末漂洗(默认配置) - */ - public function testCanHandleAfterMachineWashByDefault(): void - { - $context = $this->createContext([ - 'readerType' => '终末漂洗', - 'currentStep' => '机洗', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试禁用的节点不会处理任何请求(特殊医院配置) - */ - public function testCannotHandleAfterMachineWashWhenDisabled(): void - { - $node = new FinalRinseNode(); - $node->setEnabled(false); // 禁用节点 - - $context = $this->createContext([ - 'readerType' => '终末漂洗', - 'currentStep' => '机洗', - ]); - - // 禁用的节点不处理请求:handle 直接将上下文传递给下一节点,不更改 currentStep - $result = $node->handle($context); - $this->assertEquals('机洗', $result->currentStep); // 步骤不变 - $this->assertFalse($result->needDatabaseOperation); // 不写库 - } - - /** - * 测试不能处理非终末漂洗读卡器 - */ - public function testCannotHandleNonFinalRinseReader(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '消毒', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试处理流程 - */ - public function testHandleProcess(): void - { - $context = $this->createContext([ - 'readerType' => '终末漂洗', - 'currentStep' => '消毒', - ]); - - $result = $this->node->handle($context); - - $this->assertSuccess($result); - $this->assertStep($result, '终末漂洗'); - $this->assertNotNull($result->getStepLastTime('终末漂洗')); - } - - /** - * 测试清洗后不能直接刷终末漂洗 - */ - public function testCannotHandleAfterWash(): void - { - $context = $this->createContext([ - 'readerType' => '终末漂洗', - 'currentStep' => '清洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试数据库操作标记 - */ - public function testDatabaseOperationFlags(): void - { - $context = $this->createContext([ - 'readerType' => '终末漂洗', - 'currentStep' => '消毒', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needDatabaseOperation); - $this->assertEquals(DbOperationType::INSERT, $context->dbOperation); - } - - /** - * 测试 WebSocket 通知标记 - */ - public function testWebSocketNotifyFlag(): void - { - $context = $this->createContext([ - 'readerType' => '终末漂洗', - 'currentStep' => '消毒', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needWebSocketNotify); - } - - /** - * 测试节点名称和编码 - */ - public function testNodeIdentity(): void - { - $this->assertEquals('终末漂洗', $this->node->getName()); - $this->assertEquals('终末漂洗', $this->node->getCode()); - } -} diff --git a/tests/flow/nodes/MachineWashNodeTest.php b/tests/flow/nodes/MachineWashNodeTest.php deleted file mode 100644 index f94977e..0000000 --- a/tests/flow/nodes/MachineWashNodeTest.php +++ /dev/null @@ -1,196 +0,0 @@ -node = new MachineWashNode(); - } - - /** - * 测试节点名称和编码 - */ - public function testNodeIdentity(): void - { - $this->assertEquals('机洗', $this->node->getName()); - $this->assertEquals('机洗', $this->node->getCode()); - } - - /** - * 测试清洗后可以刷机洗 - */ - public function testCanHandleAfterWash(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '清洗', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试漂洗后可以刷机洗 - */ - public function testCanHandleAfterRinse(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '漂洗', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试消毒后可以刷机洗 - */ - public function testCanHandleAfterDisinfect(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '消毒', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试步骤为空时可以刷机洗(新流程开始) - */ - public function testCanHandleWithEmptyStep(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试结束后可以刷机洗 - */ - public function testCanHandleAfterEnd(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '结束', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试内镜取出后可以刷机洗 - */ - public function testCanHandleAfterEndoscopeOut(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '内镜取出', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试非机洗读卡器不能处理 - */ - public function testCannotHandleNonMachineWashReader(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '清洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试终末漂洗后不能刷机洗 - */ - public function testCannotHandleAfterFinalRinse(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '终末漂洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试干燥后不能刷机洗 - */ - public function testCannotHandleAfterDry(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '干燥', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试处理流程 - */ - public function testHandleProcess(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '清洗', - 'batchNo' => 'BATCH001', - ]); - - $result = $this->node->handle($context); - - $this->assertSuccess($result); - $this->assertStep($result, '机洗'); - $this->assertEquals('机洗', $result->processType); - $this->assertNotNull($result->getStepLastTime('机洗')); - } - - /** - * 测试数据库操作标记 - */ - public function testDatabaseOperationFlags(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '清洗', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needDatabaseOperation); - $this->assertEquals(DbOperationType::INSERT, $context->dbOperation); - } - - /** - * 测试 WebSocket 通知标记 - */ - public function testWebSocketNotifyFlag(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '清洗', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needWebSocketNotify); - } -} diff --git a/tests/flow/nodes/MorningWashNodeTest.php b/tests/flow/nodes/MorningWashNodeTest.php deleted file mode 100644 index ecf2f8f..0000000 --- a/tests/flow/nodes/MorningWashNodeTest.php +++ /dev/null @@ -1,157 +0,0 @@ -node = new MorningWashNode(); - } - - /** - * 测试节点名称和编码 - */ - public function testNodeIdentity(): void - { - $this->assertEquals('晨洗', $this->node->getName()); - $this->assertEquals('晨洗', $this->node->getCode()); - } - - /** - * 测试可以处理消毒读卡器(晨洗模式) - */ - public function testCanHandleDisinfectReaderForMorningWash(): void - { - $context = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '', - 'needMorningWash' => true, - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试可以处理机洗读卡器(晨洗模式) - */ - public function testCanHandleMachineWashReaderForMorningWash(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '', - 'needMorningWash' => true, - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试不需要晨洗时不能处理 - */ - public function testCannotHandleWhenNoNeed(): void - { - $context = $this->createContext([ - 'readerType' => '晨洗', - 'currentStep' => '', - 'needMorningWash' => false, - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试非消毒/机洗读卡器不能处理晨洗 - */ - public function testCannotHandleNonDisinfectMachineReader(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'needMorningWash' => true, - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试已完成晨洗后不能再次处理 - */ - public function testCannotHandleWhenAlreadyWashed(): void - { - $context = $this->createContext([ - 'readerType' => '晨洗', - 'currentStep' => '晨洗', - 'needMorningWash' => false, - 'morningWashed' => true, - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试处理流程(消毒读卡器) - */ - public function testHandleProcessWithDisinfect(): void - { - $context = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '', - 'needMorningWash' => true, - 'endoscopeId' => '', // 避免触发 DB 查询 - ]); - - $result = $this->node->handle($context); - - $this->assertSuccess($result); - $this->assertEquals('清洗', $result->currentStep); // 晨洗后进入清洗步骤 - $this->assertTrue($result->morningWashed); - $this->assertEquals('手工洗(晨洗)', $result->processType); - } - - /** - * 测试处理流程(机洗读卡器) - */ - public function testHandleProcessWithMachineWash(): void - { - $context = $this->createContext([ - 'readerType' => '机洗', - 'currentStep' => '', - 'needMorningWash' => true, - 'endoscopeId' => '', // 避免触发 DB 查询 - ]); - - $result = $this->node->handle($context); - - $this->assertSuccess($result); - $this->assertEquals('清洗', $result->currentStep); - $this->assertTrue($result->morningWashed); - $this->assertEquals('机洗(晨洗)', $result->processType); - } - - /** - * 测试生成批次号 - */ - public function testGenerateBatchNo(): void - { - $context = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '', - 'needMorningWash' => true, - 'endoscopeId' => '', // 避免触发 DB 查询 - ]); - - $this->node->handle($context); - - $this->assertNotEmpty($context->batchNo); - $this->assertTrue($context->needDatabaseOperation); - } -} diff --git a/tests/flow/nodes/RinseNodeTest.php b/tests/flow/nodes/RinseNodeTest.php deleted file mode 100644 index 089dbac..0000000 --- a/tests/flow/nodes/RinseNodeTest.php +++ /dev/null @@ -1,163 +0,0 @@ -node = new RinseNode(); - // 确保每个测试时节点都是启用状态 - $this->node->setEnabled(true); - } - - /** - * 测试节点名称和编码 - */ - public function testNodeIdentity(): void - { - $this->assertEquals('漂洗', $this->node->getName()); - $this->assertEquals('漂洗', $this->node->getCode()); - } - - /** - * 测试清洗后可以刷漂洗 - */ - public function testCanHandleAfterWash(): void - { - $context = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '清洗', - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试非漂洗读卡器不能处理 - */ - public function testCannotHandleNonRinseReader(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '清洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试消毒后不能刷漂洗 - */ - public function testCannotHandleAfterDisinfect(): void - { - $context = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '消毒', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试步骤为空时不能刷漂洗 - */ - public function testCannotHandleWithEmptyStep(): void - { - $context = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试漂洗后不能立即再次漂洗 - */ - public function testCannotHandleAfterRinse(): void - { - $context = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '漂洗', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试处理流程 - */ - public function testHandleProcess(): void - { - $context = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '清洗', - 'batchNo' => 'BATCH001', - ]); - - $result = $this->node->handle($context); - - $this->assertSuccess($result); - $this->assertStep($result, '漂洗'); - $this->assertNotNull($result->getStepLastTime('漂洗')); - } - - /** - * 测试数据库操作标记 - */ - public function testDatabaseOperationFlags(): void - { - $context = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '清洗', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needDatabaseOperation); - $this->assertEquals(DbOperationType::INSERT, $context->dbOperation); - } - - /** - * 测试 WebSocket 通知标记 - */ - public function testWebSocketNotifyFlag(): void - { - $context = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '清洗', - ]); - - $this->node->handle($context); - - $this->assertTrue($context->needWebSocketNotify); - } - - /** - * 测试节点被禁用后直接跳过 - */ - public function testDisabledNodeSkips(): void - { - $this->node->setEnabled(false); - - $context = $this->createContext([ - 'readerType' => '漂洗', - 'currentStep' => '清洗', - ]); - - $result = $this->node->handle($context); - - // 被禁用,currentStep 不应该变为漂洗 - $this->assertEquals('清洗', $result->currentStep); - } -} diff --git a/tests/flow/nodes/WashNodeTest.php b/tests/flow/nodes/WashNodeTest.php deleted file mode 100644 index 4e8766b..0000000 --- a/tests/flow/nodes/WashNodeTest.php +++ /dev/null @@ -1,148 +0,0 @@ -node = new WashNode(); - } - - /** - * 测试节点名称和编码 - */ - public function testNodeIdentity(): void - { - $this->assertEquals('清洗', $this->node->getName()); - $this->assertEquals('清洗', $this->node->getCode()); - } - - /** - * 测试可以处理清洗读卡器 - */ - public function testCanHandleWashReader(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'morningWashed' => true, - ]); - - $this->assertTrue($this->node->canHandle($context)); - } - - /** - * 测试不能处理非清洗读卡器 - */ - public function testCannotHandleNonWashReader(): void - { - $context = $this->createContext([ - 'readerType' => '消毒', - 'currentStep' => '', - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试新流程可以开始清洗 - */ - public function testCanStartNewWashProcess(): void - { - $validSteps = ['', '结束', '内镜取出', '内镜放入', '测漏正常']; - - foreach ($validSteps as $step) { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => $step, - 'morningWashed' => true, - ]); - - $this->assertTrue( - $this->node->canHandle($context), - "步骤 '{$step}' 应该可以开始清洗" - ); - } - } - - /** - * 测试未完成晨洗不能开始清洗 - */ - public function testCannotWashWithoutMorningWash(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'needMorningWash' => true, - 'morningWashed' => false, - ]); - - $this->assertFalse($this->node->canHandle($context)); - } - - /** - * 测试处理流程 - */ - public function testHandleProcess(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'morningWashed' => true, - 'endoscopeId' => '', // 避免触发 DB 查询 - ]); - - $result = $this->node->handle($context); - - $this->assertSuccess($result); - $this->assertStep($result, '清洗'); - $this->assertEquals('手工洗', $result->processType); - $this->assertNotNull($result->getStepLastTime('清洗')); - } - - /** - * 测试生成批次号 - */ - public function testGenerateBatchNo(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '', - 'morningWashed' => true, - 'endoscopeId' => '', // 避免触发 DB 查询 - ]); - - $this->node->handle($context); - - $this->assertNotEmpty($context->batchNo); - $this->assertTrue($context->needDatabaseOperation); - $this->assertEquals(DbOperationType::INSERT, $context->dbOperation); - } - - /** - * 测试已有批次号不重新生成 - */ - public function testKeepExistingBatchNo(): void - { - $context = $this->createContext([ - 'readerType' => '清洗', - 'currentStep' => '漂洗', // 已有流程 - 'batchNo' => 'EXISTING001', - 'morningWashed' => true, - ]); - - $this->node->handle($context); - - $this->assertEquals('EXISTING001', $context->batchNo); - } -} diff --git a/tests/flow/strategies/MorningWashStrategyTest.php b/tests/flow/strategies/MorningWashStrategyTest.php deleted file mode 100644 index e7695d9..0000000 --- a/tests/flow/strategies/MorningWashStrategyTest.php +++ /dev/null @@ -1,157 +0,0 @@ -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()); - } -} diff --git a/tests/flow/strategies/TimeValidationStrategyTest.php b/tests/flow/strategies/TimeValidationStrategyTest.php deleted file mode 100644 index e69ee4c..0000000 --- a/tests/flow/strategies/TimeValidationStrategyTest.php +++ /dev/null @@ -1,167 +0,0 @@ -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()); - } -} diff --git a/tests/flow/strategies/VoiceGenerationStrategyTest.php b/tests/flow/strategies/VoiceGenerationStrategyTest.php deleted file mode 100644 index d09bd60..0000000 --- a/tests/flow/strategies/VoiceGenerationStrategyTest.php +++ /dev/null @@ -1,280 +0,0 @@ -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); - } - -} diff --git a/tests/resources/default_environment.json b/tests/resources/default_environment.json new file mode 100644 index 0000000..ccc1055 --- /dev/null +++ b/tests/resources/default_environment.json @@ -0,0 +1,95 @@ +{ + "description": "虚拟测试环境默认配置", + "version": "1.0", + + "readers": { + "清洗": { + "no": "R001", + "id": "1", + "type": "清洗" + }, + "漂洗": { + "no": "R002", + "id": "2", + "type": "漂洗" + }, + "消毒": { + "no": "R003", + "id": "3", + "type": "消毒" + }, + "终末漂洗": { + "no": "R004", + "id": "4", + "type": "终末漂洗" + }, + "干燥": { + "no": "R005", + "id": "5", + "type": "干燥" + }, + "机洗": { + "no": "R006", + "id": "6", + "type": "机洗" + }, + "存储": { + "no": "R007", + "id": "7", + "type": "存储" + }, + "未绑定": { + "no": "R999", + "id": "", + "type": "" + } + }, + + "endoscopes": { + "胃镜1": { + "id": "E001", + "name": "胃镜1号", + "cardNo": "CARD_E001", + "type": "胃镜" + }, + "肠镜1": { + "id": "E002", + "name": "肠镜1号", + "cardNo": "CARD_E002", + "type": "肠镜" + }, + "未绑定卡": { + "id": "", + "name": "", + "cardNo": "CARD_UNKNOWN", + "type": "" + } + }, + + "operators": { + "操作员1": { + "id": "U001", + "name": "张三", + "rfid": "RFID_U001" + }, + "操作员2": { + "id": "U002", + "name": "李四", + "rfid": "RFID_U002" + } + }, + + "config": { + "machineId": "01", + "morningWashStartTime": "06:00:00", + "morningWashMode": "standard" + }, + + "stepDurations": { + "清洗": 60, + "漂洗": 60, + "消毒": 180, + "终末漂洗": 60, + "干燥": 60 + } +}