summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop.Tests/P71ApiDocsTests.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop.Tests/P71ApiDocsTests.fs')
-rw-r--r--src/LivingVillage.Desktop.Tests/P71ApiDocsTests.fs93
1 files changed, 93 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop.Tests/P71ApiDocsTests.fs b/src/LivingVillage.Desktop.Tests/P71ApiDocsTests.fs
new file mode 100644
index 0000000..8aa3c71
--- /dev/null
+++ b/src/LivingVillage.Desktop.Tests/P71ApiDocsTests.fs
@@ -0,0 +1,93 @@
+namespace LivingVillage.Desktop.Tests
+
+open System.IO
+open Microsoft.VisualStudio.TestTools.UnitTesting
+
+/// P71(中文 API/模块说明,docs-only):锁定 docs/api-reference/ 的存在、分节与关键口径,
+/// 防止说明文档被误删或章节漂移。
+[<TestClass>]
+type P71ApiDocsTests () =
+
+ let repoRoot () : string =
+ let mutable dir = System.AppContext.BaseDirectory
+ let mutable found = None
+ while found.IsNone && not (System.String.IsNullOrEmpty dir) do
+ if File.Exists(Path.Combine(dir, "LivingVillage.sln")) then found <- Some dir
+ else
+ let parent = Path.GetDirectoryName dir
+ if parent = dir then dir <- "" else dir <- parent
+ match found with
+ | Some d -> d
+ | None -> failwith "找不到仓库根(LivingVillage.sln)"
+
+ let apiDir () = Path.Combine(repoRoot (), "docs", "api-reference")
+
+ let expectedFiles =
+ [ "README.md"
+ "sim-kernel.md"
+ "map-and-mapgen.md"
+ "rendering-desktop.md"
+ "interaction-and-trade.md"
+ "occupation.md"
+ "world-save.md" ]
+
+ let requiredSections =
+ [ "## 职责"
+ "## 对外 API 概览"
+ "## 单位与量纲"
+ "## 不变量"
+ "## 失败分支"
+ "## 算法与性能取舍" ]
+
+ let allDocs () : string =
+ expectedFiles
+ |> List.filter (fun name -> File.Exists(Path.Combine(apiDir (), name)))
+ |> List.map (fun name -> File.ReadAllText(Path.Combine(apiDir (), name)))
+ |> String.concat "\n"
+
+ [<TestMethod>]
+ member _.ApiReferenceDirectoryHasAllModuleFiles () =
+ Assert.IsTrue(Directory.Exists(apiDir ()), sprintf "缺少目录 %s" (apiDir ()))
+ let missing = expectedFiles |> List.filter (fun name -> not (File.Exists(Path.Combine(apiDir (), name))))
+ CollectionAssert.AreEqual(
+ [||],
+ missing |> List.toArray,
+ sprintf "docs/api-reference 缺少文件: %s" (String.concat ", " missing))
+
+ [<TestMethod>]
+ member _.EveryModuleSectionHasTheRequiredHeadings () =
+ let docs = allDocs ()
+ let missing = requiredSections |> List.filter (fun section -> not (docs.Contains section))
+ CollectionAssert.AreEqual(
+ [||],
+ missing |> List.toArray,
+ sprintf "api-reference 缺少规定章节: %s" (String.concat ", " missing))
+ // 六个模块文件各一个「## 职责」,确保每个模块都有齐六个小节。
+ let responsibilities = requiredSections.[0]
+ let count =
+ docs.Split('\n')
+ |> Array.filter (fun line -> line.Trim() = responsibilities)
+ |> Array.length
+ Assert.IsTrue(count >= 6, sprintf "「## 职责」出现 %d 次,应 >= 6(每模块一节)" count)
+
+ [<TestMethod>]
+ member _.ApiReferencePinsKeyInterfacesAndUnits () =
+ let docs = allDocs ()
+ let anchors =
+ [ "ticksPerDay"
+ "configureBounds"
+ "953775FAEB2FDDE97289491AA260BD8D390C571E48A7A13AD2CB6FB7124F7F6C"
+ "WorldSave.save"
+ "LV_WORLD_SAVE_V2"
+ "LV_WORLD_SAVE_V3"
+ "MapGen.paramsForSize"
+ "MapSnapshot.renderPng"
+ "CjkGlyphAtlas"
+ "M5Command"
+ "PlayerTrade.sell"
+ "Occupation.saveToken" ]
+ let missing = anchors |> List.filter (fun anchor -> not (docs.Contains anchor))
+ CollectionAssert.AreEqual(
+ [||],
+ missing |> List.toArray,
+ sprintf "api-reference 未钉住关键接口/单位: %s" (String.concat ", " missing))