summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop.Tests/P71ApiDocsTests.fs
blob: 8aa3c71d294cd276248c9a1d3c4cf059346b7761 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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))