From 9d1660270272180cd71147d6ddd53d1382643d2e Mon Sep 17 00:00:00 2001
From: wenxiangchao <wenxiangchao@ihengxin.com.cn>
Date: 星期一, 02 十一月 2026 18:00:14 +0800
Subject: [PATCH] merge: 并入远程 Initial commit 作为历史根节点

---
 Plugin.IntegrationTest/MainWindow.xaml.cs |  840 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 840 insertions(+), 0 deletions(-)

diff --git a/Plugin.IntegrationTest/MainWindow.xaml.cs b/Plugin.IntegrationTest/MainWindow.xaml.cs
new file mode 100644
index 0000000..3e80e2d
--- /dev/null
+++ b/Plugin.IntegrationTest/MainWindow.xaml.cs
@@ -0,0 +1,840 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Threading;
+using Aging.Infrastructure.Drivers;
+using CanSimulator;
+using log4net;
+using Plugin.DeviceConfig.Entity;
+using Plugin.DeviceConfig.Model;
+using Plugin.DeviceDebug.Entity;
+using Plugin.DeviceDebug.Model;
+using Plugin.DeviceDebug.Panels;
+
+namespace Plugin.IntegrationTest
+{
+    public partial class MainWindow : Window
+    {
+        private int _passed;
+        private int _failed;
+        private int _totalSteps;
+        private int _currentStep;
+        private string _testDir = string.Empty;
+        private string _logFilePath = string.Empty;
+        private readonly StringBuilder _logBuffer = new();
+
+        // 宓屽叆鐨勬彃浠跺疄渚�
+        private Plugin.DeviceConfig.DeviceConfig? _deviceConfigPlugin;
+        private Plugin.DeviceDebug.DeviceDebug? _deviceDebugPlugin;
+        private static readonly ILog _testLog = LogManager.GetLogger("IntegrationTest");
+
+        // CAN 妯℃嫙鍣�
+        private CanBusSimulator? _canBus;
+        private CanDebugPanel? _canDebugPanel;
+
+        public MainWindow()
+        {
+            InitializeComponent();
+        }
+
+        private void Window_Loaded(object sender, RoutedEventArgs e)
+        {
+            InitializePlugins();
+            InitializeCanSimulator();
+        }
+
+        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+        {
+            // 娓呯悊 CAN 妯℃嫙鍣�
+            _canBus?.Dispose();
+            CanDriverAdapter.ResetAllBuses();
+
+            // 涓嶅垹闄ゆ祴璇曠洰褰曪紝淇濈暀鏁版嵁浠ヤ究閲嶅惎鍚庢仮澶�
+            // 锛堝闇�鎵嬪姩閲嶇疆锛屼娇鐢ㄥ伐鍏锋爮涓殑 "閲嶇疆鏁版嵁" 鎸夐挳锛�
+        }
+
+        /// <summary>
+        /// 鍒濆鍖栦袱涓彃浠跺苟宓屽叆鍒板搴� Tab 涓�
+        /// </summary>
+        private void InitializePlugins()
+        {
+            // 浣跨敤鍥哄畾鐩綍淇濆瓨鏁版嵁锛堥噸鍚悗涓嶄涪澶憋級
+            string appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+            _testDir = Path.Combine(appData, "PluginIntegrationTest");
+            Directory.CreateDirectory(_testDir);
+            _logFilePath = Path.Combine(_testDir, "test_result.log");
+
+            txtStatus.Text = $"鏁版嵁鐩綍: {_testDir}";
+
+            try
+            {
+                // 鍒濆鍖栦华鍣ㄤ华琛ㄦ敞鍐屾彃浠�
+                _deviceConfigPlugin = new Plugin.DeviceConfig.DeviceConfig();
+                _deviceConfigPlugin.SetBaseFolder(_testDir);
+                _deviceConfigPlugin.SetLogger(_testLog);
+                _deviceConfigPlugin.InitFinish();
+                deviceConfigHost.Child = _deviceConfigPlugin;
+
+                // 鍒濆鍖栫‖浠惰皟璇曟彃浠�
+                _deviceDebugPlugin = new Plugin.DeviceDebug.DeviceDebug();
+                _deviceDebugPlugin.SetBaseFolder(_testDir);
+                _deviceDebugPlugin.SetLogger(_testLog);
+                _deviceDebugPlugin.InitFinish();
+                deviceDebugHost.Child = _deviceDebugPlugin;
+
+                // 棰勭疆 CAN 娴嬭瘯璁惧锛堟柟渚跨洿鎺ラ獙璇侀┍鍔ㄤ笌闈㈡澘锛�
+                SeedPresetCanDevices();
+
+                // 鍒锋柊鎻掍欢鍐呴儴鍒楄〃锛堥渶鎵嬪姩璋冪敤鎻掍欢鏂规硶锛�
+                _deviceConfigPlugin.ForceRefreshForTest();
+                _deviceDebugPlugin.ForceRefreshForTest();
+
+                txtStatus.Text = "鎻掍欢鍒濆鍖栧畬鎴� - 鍙垏鎹� Tab 鎵嬪姩娴嬭瘯";
+                Log("[INFO] 鎻掍欢鍒濆鍖栨垚鍔�");
+                Log($"[INFO] 娴嬭瘯鐩綍: {_testDir}");
+                Log("[INFO] DeviceConfig 宸插祵鍏ュ埌 '浠櫒浠〃娉ㄥ唽' Tab");
+                Log("[INFO] DeviceDebug 宸插祵鍏ュ埌 '纭欢璋冭瘯' Tab");
+                Log("[INFO] 棰勭疆娴嬭瘯璁惧: CAN-001, CAN-002 (Transport=Can)");
+                Log("");
+                Log("[TIP] 浣跨敤娴佺▼:");
+                Log("  1. 鍏堝垏鎹㈠埌 '浠櫒浠〃娉ㄥ唽' Tab锛屾坊鍔犺澶囥�侀�氶亾銆丼lot鏄犲皠绛�");
+                Log("  2. 宸查缃� CAN-001 (CAN_BUS_1:0) 鍜� CAN-002 (CAN_BUS_1:1) 渚涙祴璇�");
+                Log("  3. 鍒囨崲鍒� '纭欢璋冭瘯' Tab锛岄�夋嫨 CAN-001 浼氳嚜鍔ㄦ墦寮� CAN 闈㈡澘骞惰繛鎺ユ�荤嚎");
+                Log("  4. 鍒囨崲鍒� 'CAN 妯℃嫙鍣�' Tab 鍙墜鍔ㄦ帶鍒舵�荤嚎鎴栧彟涓�涓妭鐐瑰彂閫佹暟鎹�");
+                Log("  5. 鍙殢鏃剁偣鍑� '杩愯鑷姩鍖栨祴璇�' 楠岃瘉鏍稿績閫昏緫");
+            }
+            catch (Exception ex)
+            {
+                txtStatus.Text = $"鎻掍欢鍒濆鍖栧け璐�: {ex.Message}";
+                Log($"[ERROR] 鎻掍欢鍒濆鍖栧け璐�: {ex.Message}");
+                Log(ex.StackTrace ?? "");
+            }
+        }
+
+        /// <summary>
+        /// 棰勭疆 CAN 娴嬭瘯璁惧锛堜粎褰撲笉瀛樺湪鏃舵彃鍏ワ紝閬垮厤瑕嗙洊鐢ㄦ埛淇敼锛�
+        /// </summary>
+        private void SeedPresetCanDevices()
+        {
+            try
+            {
+                var model = new DeviceConfigModel();
+                string dbPath = Path.Combine(_testDir, "data.db");
+                model.InitDatabase(dbPath);
+
+                int inserted = 0;
+
+                // CAN-001
+                if (model.GetDeviceById("CAN-001") == null)
+                {
+                    model.SaveDevice(new DeviceDefinition
+                    {
+                        DeviceId = "CAN-001",
+                        Kind = DeviceKind.CanDevice,
+                        DriverType = "CAN",
+                        Transport = TransportType.Can,
+                        Endpoint = "CAN_BUS_1:0",
+                        EffectiveVersion = "V1.0"
+                    });
+                    inserted++;
+                }
+
+                // CAN-002
+                if (model.GetDeviceById("CAN-002") == null)
+                {
+                    model.SaveDevice(new DeviceDefinition
+                    {
+                        DeviceId = "CAN-002",
+                        Kind = DeviceKind.CanDevice,
+                        DriverType = "CAN",
+                        Transport = TransportType.Can,
+                        Endpoint = "CAN_BUS_1:1",
+                        EffectiveVersion = "V1.0"
+                    });
+                    inserted++;
+                }
+
+                Log(inserted > 0
+                    ? $"[INFO] 棰勭疆 CAN 璁惧: 鏂板 {inserted} 涓�"
+                    : "[INFO] 棰勭疆 CAN 璁惧宸插瓨鍦紝鏈鐩�");
+            }
+            catch (Exception ex)
+            {
+                Log($"[WARN] 棰勭疆 CAN 璁惧澶辫触: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 閲嶇疆娴嬭瘯鏁版嵁锛堝垹闄ゆ湰鍦扮洰褰曚腑鐨� db 鏂囦欢锛�
+        /// </summary>
+        private void ResetTestData()
+        {
+            try
+            {
+                if (MessageBox.Show("纭畾瑕佸垹闄ゆ湰鍦颁繚瀛樼殑璁惧鏁版嵁鍚楋紵\n姝ゆ搷浣滀笉鍙仮澶嶏紒",
+                    "纭閲嶇疆", MessageBoxButton.YesNo, MessageBoxImage.Warning) != MessageBoxResult.Yes)
+                {
+                    return;
+                }
+
+                // 閲婃斁鎻掍欢鍚庡垹闄ゆ暟鎹�
+                _deviceConfigPlugin = null;
+                _deviceDebugPlugin = null;
+
+                foreach (var file in Directory.GetFiles(_testDir, "*.db", SearchOption.AllDirectories))
+                {
+                    try { File.Delete(file); } catch { }
+                }
+
+                GC.Collect();
+                GC.WaitForPendingFinalizers();
+
+                MessageBox.Show("鏁版嵁宸查噸缃紝璇烽噸鏂板惎鍔ㄧ▼搴忋��", "鎻愮ず", MessageBoxButton.OK, MessageBoxImage.Information);
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"閲嶇疆澶辫触: {ex.Message}", "閿欒", MessageBoxButton.OK, MessageBoxImage.Error);
+            }
+        }
+
+        private void BtnClear_Click(object sender, RoutedEventArgs e)
+        {
+            txtLog.Clear();
+            txtSummary.Text = "灏辩华 - 璇峰垏鎹㈠埌瀵瑰簲 Tab 杩涜鎵嬪姩娴嬭瘯";
+            txtPassed.Text = "";
+            txtFailed.Text = "";
+            progressBar.Value = 0;
+            _passed = 0;
+            _failed = 0;
+            _currentStep = 0;
+            btnRun.IsEnabled = true;
+        }
+
+        private void BtnReset_Click(object sender, RoutedEventArgs e)
+        {
+            ResetTestData();
+        }
+
+        private async void BtnRun_Click(object sender, RoutedEventArgs e)
+        {
+            btnRun.IsEnabled = false;
+            txtLog.Clear();
+            _passed = 0;
+            _failed = 0;
+            _currentStep = 0;
+
+            _totalSteps = CountTestSteps();
+            UpdateProgress();
+
+            Log("========================================");
+            Log("  浠櫒浠〃娉ㄥ唽 & 纭欢璋冭瘯 闆嗘垚娴嬭瘯");
+            Log($"  鏃堕棿: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
+            Log("========================================");
+            Log("");
+
+            // 涓鸿嚜鍔ㄥ寲娴嬭瘯鍒涘缓鐙珛鐨勬暟鎹簱鐩綍
+            string autoTestDir = Path.Combine(_testDir, "auto_test_" + Guid.NewGuid().ToString("N").Substring(0, 8));
+            Directory.CreateDirectory(autoTestDir);
+
+            await Task.Run(() => RunAllTests(autoTestDir));
+
+            Log("");
+            Log("========================================");
+            string result = _failed == 0 ? "鍏ㄩ儴閫氳繃 鉁�" : $"鏈� {_failed} 椤瑰け璐� 鉁�";
+            Log($"  娴嬭瘯缁撴灉: {result}");
+            Log($"  閫氳繃: {_passed}  澶辫触: {_failed}  鎬昏: {_passed + _failed}");
+            Log("========================================");
+
+            Dispatcher.Invoke(() =>
+            {
+                txtSummary.Text = _failed == 0 ? "鉁� 鑷姩鍖栨祴璇曞叏閮ㄩ�氳繃" : $"鉁� 鏈� {_failed} 椤规祴璇曞け璐�";
+                txtSummary.Foreground = _failed == 0
+                    ? System.Windows.Media.Brushes.Green
+                    : System.Windows.Media.Brushes.Red;
+                txtPassed.Text = $"閫氳繃: {_passed}";
+                txtFailed.Text = $"澶辫触: {_failed}";
+                btnRun.IsEnabled = true;
+
+                // 澶嶅埗鏃ュ織鍒拌緭鍑虹洰褰�
+                try
+                {
+                    string appDir = AppDomain.CurrentDomain.BaseDirectory;
+                    string copyPath = Path.Combine(appDir, "test_result.log");
+                    File.Copy(_logFilePath, copyPath, overwrite: true);
+                }
+                catch { }
+            });
+        }
+
+        private int CountTestSteps() => 22;
+
+        private void RunAllTests(string testDir)
+        {
+            string configDbPath = Path.Combine(testDir, "device_config.db");
+            string debugDbPath = Path.Combine(testDir, "device_debug.db");
+
+            // ===== 绗竴閮ㄥ垎锛氫华鍣ㄤ华琛ㄦ敞鍐� =====
+            RunSection("绗竴閮ㄥ垎锛氫华鍣ㄤ华琛ㄦ敞鍐� (DeviceConfig)");
+
+            var configModel = TestInit("鍒濆鍖栬澶囬厤缃暟鎹簱", () =>
+            {
+                var model = new DeviceConfigModel();
+                model.InitDatabase(configDbPath);
+                return model;
+            });
+
+            if (configModel == null)
+            {
+                SkipRemainingSection(9, "DeviceConfig 鏁版嵁搴撳垵濮嬪寲澶辫触");
+            }
+            else
+            {
+                var device = TestInit("鍒涘缓璁惧瀹氫箟锛堢數婧愯澶囷級", () =>
+                {
+                    var dev = new DeviceDefinition
+                    {
+                        DeviceId = "PS-001",
+                        Kind = DeviceKind.PowerSupply,
+                        DriverType = "SCPI",
+                        Transport = TransportType.Tcp,
+                        Endpoint = "192.168.1.100:5025",
+                        EffectiveVersion = "V1.0",
+                        CreatedAt = DateTime.Now,
+                        UpdatedAt = DateTime.Now
+                    };
+                    configModel.SaveDevice(dev);
+                    return dev;
+                });
+
+                TestStep("璇诲彇璁惧瀹氫箟骞堕獙璇�", () =>
+                {
+                    var read = configModel.GetDeviceById("PS-001");
+                    Assert(read != null, "璁惧搴斿瓨鍦�");
+                    Assert(read!.Kind == DeviceKind.PowerSupply, "绉嶇被搴斾负PowerSupply");
+                    Assert(read.Transport == TransportType.Tcp, "浼犺緭鏂瑰紡搴斾负Tcp");
+                    Assert(read.Endpoint == "192.168.1.100:5025", "绔偣鍦板潃搴斿尮閰�");
+                });
+
+                TestStep("鏇存柊璁惧瀹氫箟", () =>
+                {
+                    Assert(device != null, "璁惧瀵硅薄涓嶅簲涓虹┖");
+                    device!.Endpoint = "192.168.1.200:5025";
+                    device.EffectiveVersion = "V2.0";
+                    configModel.SaveDevice(device);
+                    var updated = configModel.GetDeviceById("PS-001");
+                    Assert(updated!.Endpoint == "192.168.1.200:5025", "绔偣搴斿凡鏇存柊");
+                    Assert(updated.EffectiveVersion == "V2.0", "鐗堟湰搴斿凡鏇存柊");
+                });
+
+                TestStep("娣诲姞璁惧閫氶亾", () =>
+                {
+                    for (int i = 1; i <= 4; i++)
+                    {
+                        var ch = new DeviceChannel
+                        {
+                            DeviceId = "PS-001",
+                            ChannelId = i,
+                            Capability = i <= 2 ? "Voltage" : "Current",
+                            MaxVoltage = 30.0,
+                            MaxCurrent = 5.0,
+                            Limits = $"{{\"min_v\":0,\"max_v\":30,\"min_i\":0,\"max_i\":5}}",
+                            SessionKey = ""
+                        };
+                        configModel.SaveChannel(ch);
+                    }
+                    var channels = configModel.GetChannelsByDevice("PS-001");
+                    Assert(channels.Count == 4, $"搴旀湁4涓�氶亾锛屽疄闄厈channels.Count}涓�");
+                });
+
+                TestStep("娣诲姞Slot鏄犲皠", () =>
+                {
+                    var mapping = new SlotDeviceMapping
+                    {
+                        SlotId = "SLOT-01",
+                        Capability = "Power",
+                        Version = "V2.0",
+                        DeviceId = "PS-001",
+                        ChannelId = 1,
+                        MappingVersion = "MV-001"
+                    };
+                    configModel.SaveSlotMapping(mapping);
+                    var mappings = configModel.GetSlotMappings();
+                    Assert(mappings.Count == 1, "搴旀湁1鏉lot鏄犲皠");
+                    Assert(mappings[0].DeviceId == "PS-001", "鏄犲皠璁惧ID搴斿尮閰�");
+                });
+
+                TestStep("鍒涘缓閰嶇疆鐗堟湰", () =>
+                {
+                    var ver = new DeviceConfigVersion
+                    {
+                        VersionId = "VER-001",
+                        Status = VersionStatus.Draft,
+                        Hash = configModel.ComputeConfigHash(),
+                        Author = "TestUser",
+                        Timestamp = DateTime.Now,
+                        DiffDescription = "鍒濆閰嶇疆"
+                    };
+                    configModel.SaveConfigVersion(ver);
+                    var versions = configModel.GetConfigVersions();
+                    Assert(versions.Count == 1, "搴旀湁1涓増鏈�");
+                    Assert(versions[0].Status == VersionStatus.Draft, "鐗堟湰鐘舵�佸簲涓篋raft");
+                    Assert(!string.IsNullOrEmpty(versions[0].Hash), "鍝堝笇涓嶅簲涓虹┖");
+                });
+
+                TestStep("鍐茬獊妫�娴嬶紙搴旀棤鍐茬獊锛�", () =>
+                {
+                    var conflicts = configModel.DetectConflicts();
+                    Assert(conflicts.Count == 0, $"涓嶅簲鏈夊啿绐侊紝瀹為檯: {string.Join("; ", conflicts)}");
+                });
+
+                TestStep("娣诲姞绗簩涓澶囧苟妫�娴婭P鍐茬獊", () =>
+                {
+                    var dev2 = new DeviceDefinition
+                    {
+                        DeviceId = "PS-002",
+                        Kind = DeviceKind.PowerSupply,
+                        DriverType = "SCPI",
+                        Transport = TransportType.Tcp,
+                        Endpoint = "192.168.1.200:5025",
+                        EffectiveVersion = "V1.0",
+                        CreatedAt = DateTime.Now,
+                        UpdatedAt = DateTime.Now
+                    };
+                    configModel.SaveDevice(dev2);
+
+                    var dev3 = new DeviceDefinition
+                    {
+                        DeviceId = "PS-003",
+                        Kind = DeviceKind.PowerSupply,
+                        DriverType = "ModbusTCP",
+                        Transport = TransportType.Tcp,
+                        Endpoint = "192.168.1.200:5025",
+                        EffectiveVersion = "V1.0",
+                        CreatedAt = DateTime.Now,
+                        UpdatedAt = DateTime.Now
+                    };
+                    configModel.SaveDevice(dev3);
+
+                    var conflicts = configModel.DetectConflicts();
+                    Assert(conflicts.Count > 0, "搴旀娴嬪埌IP鍐茬獊");
+                    Log($"  [鍐茬獊] {conflicts[0]}");
+                });
+
+                TestStep("鍒犻櫎璁惧鍜屾竻鐞�", () =>
+                {
+                    configModel.DeleteDevice("PS-003");
+                    configModel.DeleteChannel("PS-001", 4);
+                    var allDevices = configModel.GetAllDevices();
+                    Assert(allDevices.Count == 2, $"鍒犻櫎鍚庡簲鏈�2涓澶囷紝瀹為檯{allDevices.Count}");
+                    var channels = configModel.GetChannelsByDevice("PS-001");
+                    Assert(channels.Count == 3, $"鍒犻櫎閫氶亾鍚庡簲鏈�3涓紝瀹為檯{channels.Count}");
+                });
+            }
+
+            Log("");
+
+            // ===== 绗簩閮ㄥ垎锛氱‖浠惰皟璇� =====
+            RunSection("绗簩閮ㄥ垎锛氱‖浠惰皟璇� (DeviceDebug)");
+
+            var debugModel = TestInit("鍒濆鍖栬皟璇曟暟鎹簱", () =>
+            {
+                var model = new DeviceDebugModel();
+                model.InitDatabase(debugDbPath);
+                return model;
+            });
+
+            DebugSessionManager? sessionMgr = null;
+            if (debugModel == null)
+            {
+                SkipRemainingSection(8, "DeviceDebug 鏁版嵁搴撳垵濮嬪寲澶辫触");
+            }
+            else
+            {
+                sessionMgr = TestInit("鍒涘缓浼氳瘽绠$悊鍣�", () =>
+                {
+                    return new DebugSessionManager(debugModel, resourceManager: null);
+                });
+
+                if (sessionMgr == null)
+                {
+                    SkipRemainingSection(7, "浼氳瘽绠$悊鍣ㄥ垱寤哄け璐�");
+                }
+                else
+                {
+                    string? sessionId = null;
+                    TestStep("鎵撳紑璋冭瘯浼氳瘽锛圧eadOnly锛�", () =>
+                    {
+                        var session = sessionMgr.OpenSession("PS-001", "OP001", "寮犲伐", "V2.0");
+                        sessionId = session.SessionId;
+                        Assert(!string.IsNullOrEmpty(sessionId), "浼氳瘽ID涓嶅簲涓虹┖");
+                        Assert(session.State == DebugSessionState.ReadOnly, "鍒濆鐘舵�佸簲涓篟eadOnly");
+                        Assert(session.DeviceId == "PS-001", "璁惧ID搴斿尮閰�");
+                        Log($"  浼氳瘽ID: {sessionId}");
+                    });
+
+                    if (sessionId == null)
+                    {
+                        SkipRemainingSection(6, "鎵撳紑浼氳瘽澶辫触");
+                    }
+                    else
+                    {
+                        TestStep("璇锋眰缁存姢妯″紡锛圧eadOnly 鈫� AcquiringMaintenance锛�", () =>
+                        {
+                            bool result = sessionMgr.RequestMaintenance(sessionId!);
+                            Assert(result, "璇锋眰缁存姢搴旀垚鍔�");
+                            var state = sessionMgr.GetSessionState(sessionId!);
+                            Assert(state == DebugSessionState.AcquiringMaintenance, $"鐘舵�佸簲涓篈cquiringMaintenance锛屽疄闄厈state}");
+                        });
+
+                        TestStep("婵�娲荤淮鎶ゆā寮忥紙AcquiringMaintenance 鈫� MaintenanceActive锛�", () =>
+                        {
+                            sessionMgr.ActivateMaintenance(sessionId!);
+                            var state = sessionMgr.GetSessionState(sessionId!);
+                            Assert(state == DebugSessionState.MaintenanceActive, $"鐘舵�佸簲涓篗aintenanceActive锛屽疄闄厈state}");
+                        });
+
+                        TestStep("楠岃瘉鍛戒护鎵ц鏉冮檺", () =>
+                        {
+                            var cmd = new PowerSetCommand { Voltage = 12.5, CurrentLimit = 3.0, Output = true };
+                            bool allowed = sessionMgr.ValidateCommand(sessionId!, cmd);
+                            Assert(allowed, "MaintenanceActive鐘舵�佷笅搴斿厑璁稿懡浠ゆ墽琛�");
+
+                            var readOnlySession = sessionMgr.OpenSession("PS-002", "OP002", "鏉庡伐", "V1.0");
+                            bool readOnlyAllowed = sessionMgr.ValidateCommand(readOnlySession.SessionId, cmd);
+                            Assert(!readOnlyAllowed, "ReadOnly鐘舵�佷笅搴旀嫆缁濆懡浠ゆ墽琛�");
+                        });
+
+                        TestStep("璁板綍璋冭瘯鎿嶄綔瀹¤", () =>
+                        {
+                            var op = new DebugOperation
+                            {
+                                SessionId = sessionId!,
+                                CommandType = "PowerSet",
+                                Parameters = "{\"voltage\":12.5,\"current\":3.0,\"output\":true}",
+                                Outcome = "Success",
+                                DeviceReadback = "V=12.48,I=2.99",
+                                ErrorCode = "",
+                                ErrorMessage = ""
+                            };
+                            debugModel.RecordOperation(op);
+
+                            var ops = debugModel.GetOperationsBySession(sessionId!);
+                            Assert(ops.Count == 1, $"搴旀湁1鏉℃搷浣滆褰曪紝瀹為檯{ops.Count}");
+                            Assert(ops[0].CommandType == "PowerSet", "鍛戒护绫诲瀷搴斿尮閰�");
+                            Assert(ops[0].Outcome == "Success", "缁撴灉搴斾负Success");
+                        });
+
+                        TestStep("鍏抽棴浼氳瘽锛圡aintenanceActive 鈫� Closing 鈫� Closed锛�", () =>
+                        {
+                            sessionMgr.CloseSession(sessionId!);
+                            var state = sessionMgr.GetSessionState(sessionId!);
+                            Assert(state == DebugSessionState.Closed, $"鐘舵�佸簲涓篊losed锛屽疄闄厈state}");
+
+                            var session = debugModel.GetSession(sessionId!);
+                            Assert(session != null, "浼氳瘽璁板綍搴斾粛瀛樺湪");
+                            Assert(session!.ClosedAt.HasValue, "鍏抽棴鏃堕棿搴斿凡璁剧疆");
+                        });
+
+                        TestStep("鏁呴殰鍜屾仮澶嶆祴璇�", () =>
+                        {
+                            var faultSession = sessionMgr.OpenSession("PS-001", "OP001", "寮犲伐", "V2.0");
+                            string faultId = faultSession.SessionId;
+                            sessionMgr.RequestMaintenance(faultId);
+                            sessionMgr.ActivateMaintenance(faultId);
+
+                            sessionMgr.FaultSession(faultId, "閫氫俊瓒呮椂");
+                            var state = sessionMgr.GetSessionState(faultId);
+                            Assert(state == DebugSessionState.Faulted, $"鐘舵�佸簲涓篎aulted锛屽疄闄厈state}");
+
+                            sessionMgr.MarkRecoveryRequired(faultId);
+                            state = sessionMgr.GetSessionState(faultId);
+                            Assert(state == DebugSessionState.RecoveryRequired, $"鐘舵�佸簲涓篟ecoveryRequired锛屽疄闄厈state}");
+                        });
+                    }
+                }
+            }
+
+            Log("");
+
+            // ===== 绗笁閮ㄥ垎锛氳法妯″潡鑱斿姩楠岃瘉 =====
+            RunSection("绗笁閮ㄥ垎锛氳法妯″潡鑱斿姩楠岃瘉");
+
+            if (configModel == null || debugModel == null)
+            {
+                SkipRemainingSection(4, "鍓嶇疆妯″潡鍒濆鍖栧け璐ワ紝璺宠繃鑱斿姩娴嬭瘯");
+            }
+            else
+            {
+                TestStep("璁惧閰嶇疆 鈫� 璋冭瘯浼氳瘽鏁版嵁涓�鑷存��", () =>
+                {
+                    var configDevice = configModel.GetDeviceById("PS-001");
+                    Assert(configDevice != null, "閰嶇疆涓簲瀛樺湪PS-001");
+
+                    var activeSessions = debugModel.GetActiveSessionsByDevice("PS-001");
+                    Assert(activeSessions.Count > 0, "PS-001搴旀湁娲昏穬浼氳瘽璁板綍");
+                    Assert(activeSessions.All(s => s.DeviceId == "PS-001"), "鎵�鏈変細璇濆簲寮曠敤PS-001");
+                });
+
+                TestStep("閰嶇疆鐗堟湰鍝堝笇涓�鑷存��", () =>
+                {
+                    var currentHash = configModel.ComputeConfigHash();
+                    var versions = configModel.GetConfigVersions();
+                    Assert(versions.Count > 0, "搴旀湁鐗堟湰璁板綍");
+
+                    string originalHash = versions[0].Hash;
+                    Log($"  鍘熷鍝堝笇: {originalHash.Substring(0, 16)}...");
+                    Log($"  褰撳墠鍝堝笇: {currentHash.Substring(0, 16)}...");
+                    Log($"  鍝堝笇鍙樺寲: {(originalHash != currentHash ? "鏄紙閰嶇疆宸叉洿鏂帮級" : "鍚�")}");
+                    Assert(true, "鍝堝笇璁$畻娴佺▼姝e父");
+                });
+
+                TestStep("鎿嶄綔瀹¤瀹屾暣鎬ч獙璇�", () =>
+                {
+                    var allSessions = debugModel.GetAllSessions();
+                    int totalOps = 0;
+                    foreach (var s in allSessions)
+                    {
+                        var ops = debugModel.GetOperationsBySession(s.SessionId);
+                        totalOps += ops.Count;
+                    }
+                    Assert(totalOps > 0, $"搴旀湁鎿嶄綔瀹¤璁板綍锛屽疄闄厈totalOps}鏉�");
+                    Log($"  鎬讳細璇濇暟: {allSessions.Count}, 鎬绘搷浣滆褰�: {totalOps}");
+                });
+
+                TestStep("璁惧缁勬ā鏉垮姛鑳介獙璇�", () =>
+                {
+                    var template = new DeviceGroupTemplate
+                    {
+                        TemplateId = "TPL-001",
+                        DeviceType = "PowerSupply",
+                        DriverType = "SCPI",
+                        Transport = "Tcp",
+                        DeviceCount = 4,
+                        ChannelsPerDevice = 4,
+                        AddressRule = "192.168.1.{0}",
+                        SlotStart = 1
+                    };
+                    configModel.SaveGroupTemplate(template);
+                    var templates = configModel.GetGroupTemplates();
+                    Assert(templates.Count == 1, "搴旀湁1涓ā鏉�");
+                    Assert(templates[0].DeviceCount == 4, "璁惧鏁伴噺搴斾负4");
+                });
+            }
+        }
+
+        #region CAN 妯℃嫙鍣�
+
+        /// <summary>
+        /// 鍒濆鍖� CAN 妯℃嫙鍣�
+        /// </summary>
+        private void InitializeCanSimulator()
+        {
+            try
+            {
+                // 鍒涘缓 CAN 璋冭瘯闈㈡澘骞跺祵鍏ュ埌 Tab 涓�
+                _canDebugPanel = new CanDebugPanel();
+                canSimulatorHost.Child = _canDebugPanel;
+
+                Log("[INFO] CAN 妯℃嫙鍣ㄥ凡宓屽叆鍒� 'CAN 妯℃嫙鍣�' Tab");
+            }
+            catch (Exception ex)
+            {
+                Log($"[ERROR] CAN 妯℃嫙鍣ㄥ垵濮嬪寲澶辫触: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 鍚姩鎬荤嚎鎸夐挳鐐瑰嚮
+        /// </summary>
+        private void CanStartBtn_Click(object sender, RoutedEventArgs e)
+        {
+            try
+            {
+                string busName = canBusNameInput.Text.Trim();
+                if (string.IsNullOrEmpty(busName))
+                {
+                    MessageBox.Show("璇疯緭鍏ユ�荤嚎鍚嶇О", "鎻愮ず", MessageBoxButton.OK, MessageBoxImage.Warning);
+                    return;
+                }
+
+                // 閫氳繃 DriverFactory 鍒涘缓 CAN 椹卞姩鏉ュ惎鍔ㄦ�荤嚎
+                var driver = DriverFactory.CreateDriver("CAN_SIM_0", "CAN", $"{busName}:0");
+                driver.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
+
+                // 鑾峰彇鎬荤嚎瀹炰緥
+                var buses = CanDriverAdapter.GetAllBuses();
+                if (buses.TryGetValue(busName, out var bus))
+                {
+                    _canBus = bus;
+                    bus.Start();
+
+                    // 鏇存柊 UI
+                    canBusIndicator.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4CAF50"));
+                    canBusStatusText.Text = $"杩愯涓� ({busName})";
+                    canStartBtn.IsEnabled = false;
+                    canStopBtn.IsEnabled = true;
+
+                    Log($"[CAN] 鎬荤嚎 {busName} 宸插惎鍔�");
+                }
+            }
+            catch (Exception ex)
+            {
+                Log($"[CAN] 鍚姩鎬荤嚎澶辫触: {ex.Message}");
+                MessageBox.Show($"鍚姩澶辫触: {ex.Message}", "閿欒", MessageBoxButton.OK, MessageBoxImage.Error);
+            }
+        }
+
+        /// <summary>
+        /// 鍋滄鎬荤嚎鎸夐挳鐐瑰嚮
+        /// </summary>
+        private void CanStopBtn_Click(object sender, RoutedEventArgs e)
+        {
+            try
+            {
+                _canBus?.Stop();
+
+                // 鏇存柊 UI
+                canBusIndicator.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#9E9E9E"));
+                canBusStatusText.Text = "宸插仠姝�";
+                canStartBtn.IsEnabled = true;
+                canStopBtn.IsEnabled = false;
+
+                Log("[CAN] 鎬荤嚎宸插仠姝�");
+            }
+            catch (Exception ex)
+            {
+                Log($"[CAN] 鍋滄鎬荤嚎澶辫触: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 閲嶇疆鎵�鏈夋寜閽偣鍑�
+        /// </summary>
+        private void CanResetBtn_Click(object sender, RoutedEventArgs e)
+        {
+            try
+            {
+                CanDriverAdapter.ResetAllBuses();
+                _canBus = null;
+
+                // 鏇存柊 UI
+                canBusIndicator.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#9E9E9E"));
+                canBusStatusText.Text = "鏈惎鍔�";
+                canStartBtn.IsEnabled = true;
+                canStopBtn.IsEnabled = false;
+
+                Log("[CAN] 鎵�鏈夋�荤嚎宸查噸缃�");
+            }
+            catch (Exception ex)
+            {
+                Log($"[CAN] 閲嶇疆澶辫触: {ex.Message}");
+            }
+        }
+
+        #endregion
+
+        #region 娴嬭瘯杈呭姪鏂规硶
+
+        private T? TestInit<T>(string name, Func<T> action) where T : class
+        {
+            _currentStep++;
+            UpdateProgress();
+            try
+            {
+                var result = action();
+                _passed++;
+                LogStep("PASS", name);
+                return result;
+            }
+            catch (Exception ex)
+            {
+                _failed++;
+                LogStep("FAIL", name, ex.Message);
+                return default;
+            }
+        }
+
+        private void TestStep(string name, Action action)
+        {
+            _currentStep++;
+            UpdateProgress();
+            try
+            {
+                action();
+                _passed++;
+                LogStep("PASS", name);
+            }
+            catch (Exception ex)
+            {
+                _failed++;
+                LogStep("FAIL", name, ex.Message);
+            }
+        }
+
+        private void Assert(bool condition, string message)
+        {
+            if (!condition)
+                throw new Exception($"鏂█澶辫触: {message}");
+        }
+
+        private void RunSection(string title)
+        {
+            Log($"--- {title} ---");
+            Log("");
+        }
+
+        private void SkipRemainingSection(int count, string reason)
+        {
+            for (int i = 0; i < count; i++)
+            {
+                _currentStep++;
+                _failed++;
+                UpdateProgress();
+                LogStep("FAIL", $"[SKIP] 渚濊禆澶辫触: {reason}");
+            }
+        }
+
+        private void LogStep(string status, string name, string? error = null)
+        {
+            string line = status == "PASS"
+                ? $"  [PASS] {name}"
+                : $"  [FAIL] {name} 鈫� {error}";
+            Log(line);
+        }
+
+        private void Log(string message)
+        {
+            _logBuffer.AppendLine(message);
+            Dispatcher.Invoke(() =>
+            {
+                txtLog.AppendText(message + Environment.NewLine);
+                txtLog.ScrollToEnd();
+            });
+            if (!string.IsNullOrEmpty(_logFilePath))
+            {
+                try { File.AppendAllText(_logFilePath, message + Environment.NewLine); }
+                catch { }
+            }
+        }
+
+        private void UpdateProgress()
+        {
+            Dispatcher.Invoke(() =>
+            {
+                if (_totalSteps > 0)
+                {
+                    double pct = (_currentStep * 100.0) / _totalSteps;
+                    progressBar.Value = Math.Min(pct, 100);
+                }
+            });
+        }
+
+        #endregion
+    }
+}

--
Gitblit v1.9.1