# 5A 最小 PluginLoader 實作測試案例與停止線稽核表

本文件承接 [5A 最小 PluginLoader 實作前最後確認表](phase-five-a-pluginloader-final-preimplementation-checklist.md) 與 [5A 最小 PluginLoader 最後確認表線上驗收紀錄](phase-five-a-pluginloader-final-preimplementation-online-acceptance-record.md)，用於在等待使用者七項最後確認期間，先固定程式實作後的測試案例、停止線掃描與驗收基準。

本文件不修改程式 repo，也不代表使用者已同意 5A 最後確認七項。只要七項尚未確認，就不得新增 `IPluginLoader`、Loader DTO、Loader service 或 `ApplicationErrorCodes` public constant。

## 0. 文件狀態

| 項目 | 內容 |
|---|---|
| 文件狀態 | 已完成 |
| 建立日期 | 2026-06-07 |
| 對應節點 | 5A 程式實作測試矩陣與停止線稽核 |
| 程式 repo | `hs-device-control-template` |
| 程式 branch | `poc/nmodbus-tcp` |
| 程式 commit | `6f34bb9`，4D 最小 Application plugin contract |
| 遠端基準 | `main=a8b522b`，`poc/nmodbus-tcp=6f34bb9` |
| 工作樹 | 乾淨 |
| 是否修改程式 repo | 否，只做唯讀 refresh 與文件化 |

## 1. 唯讀 refresh 結論

| 檢查項目 | 結論 |
|---|---|
| 5A 候選類別 | `IPluginLoader`、`PluginLoadRequest`、`PluginLoadResult`、`PluginLoaderOptions`、`PluginLoaderService`、`PluginLoaderServiceTests` 仍未存在。 |
| Loader 錯誤碼 | `APP-0341` 到 `APP-0345` 仍未建立；目前 `ApplicationErrorCodes` 只到 `APP-0335` plugin source invalid。 |
| 狀態語意 | `PluginLoadState.Loaded` 已存在但註解明確保留給未來 Loader lifecycle；5A 第一版不應產生真正 `Loaded`。 |
| production assembly loading | 未發現 `Assembly.Load`、`LoadFrom`、`LoadFile`、`AssemblyLoadContext`。 |
| `.dll` 字串 | 只在既有 Application 測試用 `C:\plugins\mock-light.dll` 驗證 unsafe source，production code 未命中。 |
| Application.Tests | `dotnet test tests\HS.DeviceControl.Application.Tests\HS.DeviceControl.Application.Tests.csproj --no-restore` 通過 40 tests。 |

## 2. 實作後必跑測試

| 測試層級 | 指令 | 預期 |
|---|---|---|
| Application.Tests | `dotnet test tests\HS.DeviceControl.Application.Tests\HS.DeviceControl.Application.Tests.csproj --no-restore` | 需通過，且新增 `PluginLoaderServiceTests`。 |
| Solution tests | `dotnet test HS.DeviceControl.sln --no-restore` | 需通過，確認 5A 不破壞 Core、Adapters、ConsoleHost、WebApi、ServiceHost 與既有 Application tests。 |
| 停止線掃描 | `rg -n "Assembly\.Load|LoadFrom|LoadFile|AssemblyLoadContext" src tests samples scripts` | 不得命中新增 production code 或新增測試中的 assembly loading 呼叫。 |
| DLL 字串掃描 | `rg -n "\.dll|plugin folder" src tests samples scripts` | production code 不得命中；既有測試 mock path 可保留但不可擴大成真實載入。 |
| config schema 掃描 | 檢查 `samples/config/appsettings.json`、`AppSettingsConfig.cs`、`ConfigValidator.cs` | 5A 不得新增 plugin path、plugin folder、trust policy 或 loader config schema。 |

## 3. `PluginLoaderServiceTests` 建議案例

| # | 測試案例 | 建議測試名稱 | 預期結果 |
|---:|---|---|---|
| 1 | 有效 metadata-only plugin 可被選取並回 `Verified`。 | `LoadPlugin_ShouldReturnVerifiedForValidMetadata` | `Success=true`、`PluginId` 正確、`State=Verified`、不產生 `Loaded`。 |
| 2 | request 為 `null` 或缺少 `PluginId`。 | `LoadPlugin_ShouldRejectInvalidRequest` | `Success=false`、`Code=PluginLoadRequestInvalid`、不丟 exception。 |
| 3 | catalog 找不到指定 plugin id。 | `LoadPlugin_ShouldReturnNotFoundWhenPluginIsMissing` | `Success=false`、`Code=PluginLoadPluginNotFound`。 |
| 4 | plugin disabled 且 options 未允許 disabled。 | `LoadPlugin_ShouldRejectDisabledPluginByDefault` | `Success=false`、`State=Disabled` 或 rejected result，訊息需保留 disabled reason 的安全摘要。 |
| 5 | expected contract version 與 metadata 不相容。 | `LoadPlugin_ShouldRejectContractMismatch` | `Success=false`、`Code=PluginLoadContractMismatch`、`State=Rejected`。 |
| 6 | expected checksum 與 metadata checksum 不一致。 | `LoadPlugin_ShouldRejectChecksumMismatch` | `Success=false`、`Code=PluginLoadChecksumMismatch`、不讀實體 DLL。 |
| 7 | catalog 回傳 unsafe source 或敏感 metadata。 | `LoadPlugin_ShouldRejectUnsafeCatalogResult` | 回標準錯誤，不暴露正式 path、password、token。 |
| 8 | catalog service 回傳失敗。 | `LoadPlugin_ShouldReturnFaultedWhenCatalogFails` | `Success=false`、`State=Faulted` 或 rejected result，需保留標準錯誤 code。 |
| 9 | options 允許 disabled 只做 metadata review。 | `LoadPlugin_ShouldReturnDisabledWhenOptionsAllowDisabledReview` | 可回 `State=Disabled`，但仍不得視為可載入或 `Loaded`。 |
| 10 | Loader result 需包含 verification checks。 | `LoadPlugin_ShouldIncludeVerificationChecks` | checks / warnings 可追溯，且不包含敏感資訊。 |

## 4. 實作後停止線稽核表

| 停止線 | 稽核方式 | 必須結果 |
|---|---|---|
| 不載入外部 DLL | 搜尋 `Assembly.Load`、`LoadFrom`、`LoadFile`、`AssemblyLoadContext` | 5A 新增檔案不得命中。 |
| 不掃描 plugin folder | 搜尋 `Directory.GetFiles`、`EnumerateFiles`、`plugin folder`、`*.dll` | 5A 新增檔案不得建立 folder scan。 |
| 不改 config schema | 比對 `samples/config/appsettings.json` 與 config 類別 | 不新增 loader path、plugin path、trust policy schema。 |
| 不寫 DB audit | 搜尋 DB audit table、DDL、`CREATE TABLE`、`ALTER TABLE` | 5A 不得新增 DB DDL 或寫入入口。 |
| 不改 WebApi auth / Swagger | 比對 WebApi 專案與設定 | 5A 不得新增 auth、Swagger 或正式部署設定。 |
| 不建立 Windows Service | 比對 ServiceHost 與 project file | 5A 不得新增 service installer、sc.exe、Windows Service package。 |
| 不接真實硬體 | 搜尋 Adapter / hardware call | 5A 不得直接操作 PLC、COM、TCP、UDP、Modbus 或真實設備。 |

## 5. 實作完成後驗收紀錄需包含

| 證據 | 內容 |
|---|---|
| 新增 / 修改檔案清單 | 明列 `IPluginLoader`、request、result、options、service、tests、error codes。 |
| 測試結果 | Application.Tests 與 solution tests 的通過數。 |
| 停止線掃描 | assembly loading、DLL、plugin folder、config、DB、auth、Windows Service 的掃描結果。 |
| 行為摘要 | valid metadata、invalid request、not found、disabled、contract mismatch、checksum mismatch、unsafe source 的結果。 |
| 程式 repo 狀態 | stage / commit 前確認與工作樹狀態。 |
| 文件同步 | 文件 repo 更新實作紀錄、驗收紀錄、status / progress / public。 |

## 6. 下一步門檻

目前仍需等待使用者確認：

```text
5A 最小 PluginLoader 實作前最後確認：
1. 同意
2. 同意
3. 同意
4. 同意
5. 同意
6. 同意
7. 同意
```

確認前不可修改程式 repo；確認後才能依本測試矩陣進入 5A metadata-only PluginLoader 程式實作。
