File size: 4,269 Bytes
6d1fe92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
94
95
/**
 * Task #228 — PATCH /api/admin/evolution/networks/:id/strategy 入参校验单测。
 *
 * 这一组测试专门覆盖 patchStrategy handler 中可被纯函数化的入参校验段
 * (`validatePatchStrategyBody`),不依赖 Postgres / Express,跑得很快,
 * 用来回归 reviewer (Task #228 code review) 提出的 "PATCH 路由
 * success + unknown_strategy 路径需要小测试覆盖" 高影响项。
 *
 * 覆盖契约:
 *   1. 缺 strategy 字段        → 400 strategy_required
 *   2. strategy 字段非字符串   → 400 strategy_required
 *   3. strategy 字段空字符串   → 400 strategy_required
 *   4. 未注册的 strategy 名    → 400 unknown_strategy + 列出可选名
 *   5. 已注册的 strategy 名    → ok=true, status=200, 回显 strategy
 *
 * 副作用段(requireNetwork → db.update → recordEvent)走另外的 e2e
 * 测试覆盖,避免单测里再起一遍 ephemeral schema。
 */
import { test } from "node:test";
import assert from "node:assert/strict";

process.env.DATABASE_URL ??= "postgres://test:test@127.0.0.1:5432/test";

const { validatePatchStrategyBody } = await import("../evolutionFlywheel");
const { listEvolutionStrategyNames, DEFAULT_EVOLUTION_STRATEGY } = await import(
  "../../lib/evolution/strategies"
);

test("validatePatchStrategyBody: 缺 strategy 字段 → 400 strategy_required", () => {
  const r = validatePatchStrategyBody({});
  assert.equal(r.ok, false);
  assert.equal(r.status, 400);
  assert.equal(r.body.error, "strategy_required");
  assert.equal(r.body.strategy, undefined);
  assert.equal(r.body.available, undefined);
});

test("validatePatchStrategyBody: body 为 null → 400 strategy_required", () => {
  const r = validatePatchStrategyBody(null);
  assert.equal(r.ok, false);
  assert.equal(r.status, 400);
  assert.equal(r.body.error, "strategy_required");
});

test("validatePatchStrategyBody: strategy 非字符串 → 400 strategy_required", () => {
  for (const bad of [42, true, [], {}, { foo: "bar" }]) {
    const r = validatePatchStrategyBody({ strategy: bad });
    assert.equal(r.ok, false, `strategy=${JSON.stringify(bad)}`);
    assert.equal(r.status, 400);
    assert.equal(r.body.error, "strategy_required");
  }
});

test("validatePatchStrategyBody: 空字符串 → 400 strategy_required", () => {
  const r = validatePatchStrategyBody({ strategy: "" });
  assert.equal(r.ok, false);
  assert.equal(r.status, 400);
  assert.equal(r.body.error, "strategy_required");
});

test("validatePatchStrategyBody: 未注册策略名 → 400 unknown_strategy + 列出可选名", () => {
  const r = validatePatchStrategyBody({ strategy: "definitely_not_registered" });
  assert.equal(r.ok, false);
  assert.equal(r.status, 400);
  assert.equal(r.body.error, "unknown_strategy");
  // 列表必须非空且与注册表一致,前端要据此渲染下拉。
  assert.ok(Array.isArray(r.body.available));
  assert.deepEqual(r.body.available, listEvolutionStrategyNames());
  assert.ok((r.body.available as string[]).length > 0);
  // 默认策略名永远在列表里(回退路径的兜底实例)。
  assert.ok((r.body.available as string[]).includes(DEFAULT_EVOLUTION_STRATEGY));
});

test("validatePatchStrategyBody: 已注册策略名 → ok=true, 回显 strategy", () => {
  // hyperparameter_grid (默认) 与 fitness_guided 都已在 Task #228 注册。
  for (const name of ["hyperparameter_grid", "fitness_guided"]) {
    const r = validatePatchStrategyBody({ strategy: name });
    assert.equal(r.ok, true, `strategy=${name}`);
    assert.equal(r.status, 200);
    assert.equal(r.body.strategy, name);
    assert.equal(r.body.error, undefined);
    assert.equal(r.body.available, undefined);
  }
});

test("validatePatchStrategyBody: 未注册名 + 默认策略名永远可用作后续切换", () => {
  // 这条测试锚定 reviewer 提到的 "未知名安全回退" 语义在 admin 层的体现:
  // 即便用户写了未知名, 我们至少能把回退目标 (DEFAULT_EVOLUTION_STRATEGY)
  // 列在 available 里,前端可一键切回安全态。
  const r = validatePatchStrategyBody({ strategy: "unknown_xyz" });
  assert.equal(r.ok, false);
  const available = r.body.available as string[];
  assert.ok(available.includes(DEFAULT_EVOLUTION_STRATEGY));
});