namespace LivingVillage.Desktop.Tests open System.IO open Microsoft.VisualStudio.TestTools.UnitTesting /// P71(中文 API/模块说明,docs-only):锁定 docs/api-reference/ 的存在、分节与关键口径, /// 防止说明文档被误删或章节漂移。 [] 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" [] 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)) [] 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) [] 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))