using System;
using IvfTl.AutoFocus.Layout;
using Xunit;
namespace IvfTl.AutoFocus.Tests
{
///
/// M2-02 PhotoLayerCalculator 单元测试(重建被删单测)。
/// 覆盖 §2.4 层位置公式 + §2.5 就近优先/钳位/异常/边界。
/// 所有期望值均按 PhotoLayerCalculator.cs 当前公式现算,并在注释里写出推导:
/// start = focusZ - LayerDown*spacing; 第 i 层 = start + i*spacing。
///
public class PhotoLayerCalculatorTests
{
// 小工具:构造一个已解析好的有效配置。
private static FocusLayerConfig Cfg(int spacing, int count, int down) =>
new FocusLayerConfig
{
LayerSpacingPulse = spacing,
LayerCount = count,
LayerDown = down,
};
// 小工具:构造原始就近优先入参。
private static FocusLayerRawConfig Raw(
int? wellSpacing = null, int? deviceSpacing = null,
int? wellCount = null, int? deviceCount = null,
int? wellDown = null, int? deviceDown = null,
string tlSn = "TL-TEST", int houseSn = 4, int wellSn = 1) =>
new FocusLayerRawConfig
{
TlSn = tlSn,
HouseSn = houseSn,
WellSn = wellSn,
WellSpacingPulse = wellSpacing,
DeviceSpacingPulse = deviceSpacing,
WellLayerCount = wellCount,
DeviceLayerCount = deviceCount,
WellMoveDownLayer = wellDown,
DeviceLayerDown = deviceDown,
};
// ===== ① 层公式:期望值如 [88178,88306,88434,88562,88690] =====
// 推导(按代码公式现算):
// focusZ=88434, down=2, spacing=128, count=5
// start = 88434 - 2*128 = 88434 - 256 = 88178
// 层: 88178, 88178+128=88306, +128=88434, +128=88562, +128=88690
// (清晰层 FocusZ=88434 恰落在第 down=2 层 — 符合设计语义)
[Fact]
public void 层公式_计划样例_应得88178到88690()
{
var cfg = Cfg(spacing: 128, count: 5, down: 2);
int[] pos = PhotoLayerCalculator.ComputeLayerPositions(88434, cfg, pulseMax: 0);
Assert.Equal(new[] { 88178, 88306, 88434, 88562, 88690 }, pos);
// 清晰层(第 down 层)必须 == focusZ
Assert.Equal(88434, pos[2]);
}
// 通用公式校验:任意取值,逐层与 start + i*spacing 对齐。
[Theory]
[InlineData(50000, 200, 7, 3)] // start=50000-3*200=49400
[InlineData(0, 10, 4, 0)] // down=0 → start=focusZ=0
[InlineData(-100, 50, 3, 1)] // 负 focusZ(纯算术,不钳位)→ start=-150
public void 层公式_逐层等于起点加i乘间距(int focusZ, int spacing, int count, int down)
{
var cfg = Cfg(spacing, count, down);
int[] pos = PhotoLayerCalculator.ComputeLayerPositions(focusZ, cfg, pulseMax: 0);
int start = focusZ - down * spacing;
Assert.Equal(count, pos.Length);
for (int i = 0; i < count; i++)
Assert.Equal(start + i * spacing, pos[i]);
// 第 down 层永远等于 focusZ(锚点语义)
if (down < count) Assert.Equal(focusZ, pos[down]);
}
// ===== ② 层间距双空(well 级和设备级都没配)→ FocusConfigMissingException =====
[Fact]
public void 间距双空_应抛FocusConfigMissingException_默认消息为层间距未配置()
{
var raw = Raw(wellSpacing: null, deviceSpacing: null,
deviceCount: 5, deviceDown: 2, tlSn: "TL-9", houseSn: 4, wellSn: 1);
var ex = Assert.Throws(() => PhotoLayerCalculator.Resolve(raw));
Assert.Equal("TL-9", ex.TlSn);
Assert.Equal(4, ex.HouseSn);
Assert.Equal(1, ex.WellSn);
Assert.Contains("层间距未配置", ex.Message); // 默认消息
}
// 层数双空 / 下移双空 也各自抛(带定制消息),验证不会兜底魔法数。
[Fact]
public void 层数双空_应抛异常且消息为层数未配置()
{
var raw = Raw(deviceSpacing: 128, wellCount: null, deviceCount: null, deviceDown: 2);
var ex = Assert.Throws(() => PhotoLayerCalculator.Resolve(raw));
Assert.Contains("层数未配置", ex.Message);
}
[Fact]
public void 下移双空_应抛异常且消息为下移层数未配置()
{
var raw = Raw(deviceSpacing: 128, deviceCount: 5, wellDown: null, deviceDown: null);
var ex = Assert.Throws(() => PhotoLayerCalculator.Resolve(raw));
Assert.Contains("下移层数未配置", ex.Message);
}
// ===== ③ 就近优先:well 级覆盖设备级 =====
[Fact]
public void 就近优先_well级间距覆盖设备级()
{
var raw = Raw(wellSpacing: 200, deviceSpacing: 128, deviceCount: 5, deviceDown: 2);
var cfg = PhotoLayerCalculator.Resolve(raw);
Assert.Equal(200, cfg.LayerSpacingPulse); // well 赢
}
[Fact]
public void 就近优先_well级层数与下移覆盖设备级()
{
var raw = Raw(deviceSpacing: 128,
wellCount: 9, deviceCount: 5,
wellDown: 3, deviceDown: 2);
var cfg = PhotoLayerCalculator.Resolve(raw);
Assert.Equal(9, cfg.LayerCount); // well 赢
Assert.Equal(3, cfg.LayerDown); // well 赢
}
[Fact]
public void 就近优先_well级为空时回退设备级()
{
var raw = Raw(wellSpacing: null, deviceSpacing: 128,
wellCount: null, deviceCount: 5,
wellDown: null, deviceDown: 2);
var cfg = PhotoLayerCalculator.Resolve(raw);
Assert.Equal(128, cfg.LayerSpacingPulse);
Assert.Equal(5, cfg.LayerCount);
Assert.Equal(2, cfg.LayerDown);
}
// well 级下移 = 0 必须被当成"有效覆盖"(0 是合法 down,不能被当 null)。
[Fact]
public void 就近优先_well级下移为0应视为有效覆盖而非回退()
{
var raw = Raw(deviceSpacing: 128, deviceCount: 5,
wellDown: 0, deviceDown: 2);
var cfg = PhotoLayerCalculator.Resolve(raw);
Assert.Equal(0, cfg.LayerDown); // 不应回退成设备级 2
}
// ===== ④ 下移层数 down 覆盖 — 改变起点位置 =====
// down 越大,起点越往下(Z 越小),整组层整体下移。
[Theory]
[InlineData(0)] // start = focusZ
[InlineData(2)] // start = focusZ - 2*spacing
[InlineData(4)] // start = focusZ - 4*spacing (= 末层之外,清晰层落最末)
public void 下移层数_控制起点为focusZ减down乘间距(int down)
{
int focusZ = 90000, spacing = 100, count = 5;
var cfg = Cfg(spacing, count, down);
int[] pos = PhotoLayerCalculator.ComputeLayerPositions(focusZ, cfg, 0);
Assert.Equal(focusZ - down * spacing, pos[0]); // 第0层=起点
}
// ===== ⑤ pulseMax 钳位 =====
// 起点为负 → 第0层钳到 0;末层超上限 → 钳到 pulseMax。
[Fact]
public void 钳位_下越界钳到0_上越界钳到pulseMax()
{
// focusZ=150, down=2, spacing=100 → start=150-200=-50(负,会被钳)
// 原始层: -50, 50, 150, 250, 350 ; pulseMax=300
// 钳后: 0, 50, 150, 250, 300
var cfg = Cfg(spacing: 100, count: 5, down: 2);
int[] pos = PhotoLayerCalculator.ComputeLayerPositions(150, cfg, pulseMax: 300);
Assert.Equal(new[] { 0, 50, 150, 250, 300 }, pos);
}
[Fact]
public void 钳位_pulseMax为0时不钳位允许负值()
{
var cfg = Cfg(spacing: 100, count: 3, down: 2);
// start = 150 - 200 = -50 → 不钳位
int[] pos = PhotoLayerCalculator.ComputeLayerPositions(150, cfg, pulseMax: 0);
Assert.Equal(new[] { -50, 50, 150 }, pos);
}
[Fact]
public void 钳位_全部在范围内时不改变()
{
var cfg = Cfg(spacing: 100, count: 3, down: 1);
// start=88434-100=88334 → 88334,88434,88534 ; pulseMax=100000 不触发
int[] pos = PhotoLayerCalculator.ComputeLayerPositions(88434, cfg, pulseMax: 100000);
Assert.Equal(new[] { 88334, 88434, 88534 }, pos);
}
// ===== ⑥ 边界 / 防御性 =====
[Fact]
public void 边界_单层count1_只返回起点一层()
{
var cfg = Cfg(spacing: 128, count: 1, down: 0);
int[] pos = PhotoLayerCalculator.ComputeLayerPositions(5000, cfg, 0);
Assert.Single(pos);
Assert.Equal(5000, pos[0]);
}
[Fact]
public void 边界_LayerCount为0应抛ArgumentOutOfRange()
{
var cfg = Cfg(spacing: 128, count: 0, down: 0);
Assert.Throws(
() => PhotoLayerCalculator.ComputeLayerPositions(5000, cfg, 0));
}
[Fact]
public void 边界_负LayerCount应抛ArgumentOutOfRange()
{
var cfg = Cfg(spacing: 128, count: -3, down: 0);
Assert.Throws(
() => PhotoLayerCalculator.ComputeLayerPositions(5000, cfg, 0));
}
[Fact]
public void 边界_cfg为null应抛ArgumentNull()
{
Assert.Throws(
() => PhotoLayerCalculator.ComputeLayerPositions(5000, null, 0));
}
[Fact]
public void 边界_Resolve入参null应抛ArgumentNull()
{
Assert.Throws(() => PhotoLayerCalculator.Resolve(null));
}
// 可疑边界探针:down >= count(清晰层落在拍摄范围之外)。
// 注:down