# Validator 校验器测试模式

## 适用范围

`<模块组>-opplugin` 工程中 `Validator` 目录下的所有校验器类，
即继承 `AbstractValidator` 的类。

## 测试文件位置

```
<模块组>-opplugin/src/test/java/kd/<包路径>/opplugin/validator/XxxValidatorTest.java
```

## 核心思路

校验器的 `validate()` 方法在数据不满足条件时会调用 `addMessage()` / `addErrorMessage()` 等方法。
测试时通过**匿名子类**重写这些方法，将错误消息收集到 List 中，然后断言错误数量和内容。

## 基类选择

| 场景 | 基类 |
|------|------|
| 简单校验器（少量依赖） | 直接继承 `AbstractJunitNoDependenciesResManagerTest` |
| 需要通用校验器框架 | 继承 `ValidatorBaseTest`（提供 registerValidator 方法） |

## 模板结构（直接方式，推荐）

```java
package kd.<包路径>.opplugin.validator;

import kd.bos.dataentity.entity.DynamicObject;
import kd.bos.entity.ExtendedDataEntity;
import kd.bos.entity.validate.ErrorLevel;
import kd.bos.form.unittest.DisplayName;
import kd.bos.test.ext.annotaions.UnittestCaseInfo;
import kd.bos.unittest.AbstractJunitNoDependenciesResManagerTest;
import kd.bos.unittest.mock.DynamicObjectMocker;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import static org.mockito.ArgumentMatchers.*;

public class XxxValidatorTest extends AbstractJunitNoDependenciesResManagerTest {

    // 1. Mock 依赖的静态类
    private MockedStatic<SomeHelper> someHelper;

    // 2. 错误消息收集器
    private final List<String> errorMessages = new ArrayList<>();
    private final List<Integer> errorIndexs = new ArrayList<>();

    @Before
    public void before() {
        someHelper = Mockito.mockStatic(SomeHelper.class);
    }

    @After
    public void after() {
        someHelper.close();
    }

    // 3. 构建校验器（匿名子类重写消息收集方法）
    private XxxValidator buildValidator() {
        return new XxxValidator() {
            @Override
            protected void addMessage(ExtendedDataEntity dataEntity, String content) {
                errorIndexs.add(dataEntity.getDataEntityIndex());
                errorMessages.add(content);
            }
            @Override
            protected void addMessage(ExtendedDataEntity dataEntity, String content, ErrorLevel errorLevel) {
                errorIndexs.add(dataEntity.getDataEntityIndex());
                errorMessages.add(content);
            }
            @Override
            protected void addErrorMessage(ExtendedDataEntity dataEntity, String content) {
                errorIndexs.add(dataEntity.getDataEntityIndex());
                errorMessages.add(content);
            }
            @Override
            protected void addWarningMessage(ExtendedDataEntity dataEntity, String content) {
                errorIndexs.add(dataEntity.getDataEntityIndex());
                errorMessages.add(content);
            }
        };
    }

    // 4. 构建测试数据
    private ExtendedDataEntity[] buildValidatorData() {
        DynamicObject bill = new DynamicObjectMocker()
            .add("id", 1L)
            .add("billstatus", "A")
            .add("entryentity", new DynamicObjectMocker()
                .add("id", 10L)
                .add("qty", BigDecimal.TEN)
                .getCollection())
            .getObject();

        ExtendedDataEntity entity = new ExtendedDataEntity(bill, 0, 0);
        return new ExtendedDataEntity[]{entity};
    }

    // 5. 测试方法
    @UnittestCaseInfo(
        author = "<name> <<name>@kingdee.com>",
        title = "Test validate should pass",
        targetClass = "kd.<包路径>.opplugin.Validator.XxxValidator",
        targetMethod = "validate",
        lastUpdateTime = "yyyy-MM-dd HH:mm:ss",
        lastUpdateAuthor = "<name> <<name>@kingdee.com>",
        methodSignature = "public void validate()",
        testPoints = {"Functionality"},
        description = "Test validate should pass"
    )
    @Test
    @DisplayName("校验通过：数据满足所有条件")
    public void testValidate_ShouldPass() {
        //step 配置 Mock 返回值
        someHelper.when(() -> SomeHelper.getData(any())).thenReturn(validData);

        //step 构建校验器和数据
        ExtendedDataEntity[] dataEntities = buildValidatorData();
        XxxValidator validator = buildValidator();
        validator.setDataEntities(dataEntities);

        //step 执行校验
        validator.validate();

        //assert 无错误消息
        assertEquals(0, errorMessages.size());
    }

    @Test
    @DisplayName("校验失败：数据不满足条件")
    public void testValidate_ShouldFail() {
        //step 配置 Mock 返回值（构造不满足条件的数据环境）
        someHelper.when(() -> SomeHelper.getData(any())).thenReturn(invalidData);

        //step 构建校验器和数据
        ExtendedDataEntity[] dataEntities = buildValidatorData();
        XxxValidator validator = buildValidator();
        validator.setDataEntities(dataEntities);

        //step 执行校验
        validator.validate();

        //assert 应有错误消息
        assertTrue(errorMessages.size() > 0);
    }
}
```

## 使用 ValidatorBaseTest 基类方式

**两种方式的选择：**
- **直接匿名子类**（上方模板）：适用于大多数场景，提供最大灵活性，可自由控制 Mock 和数据构造
- **继承 ValidatorBaseTest**（本节）：适用于校验逻辑简单、仅需验证“是否报错”的场景，提供 `registerValidator()` / `getValidatorErrorMessages()` 等便捷方法，省去手动构建 ExtendedDataEntity 的样板代码

当校验器测试逻辑较为简单且数据构造模式可复用时，推荐继承 `ValidatorBaseTest`：

```java
public class XxxValidatorTest extends ValidatorBaseTest {

    @Override
    protected AbstractValidator buildValidator() {
        return new XxxValidator() {
            @Override
            protected void addErrorMessage(ExtendedDataEntity dataEntity, String content) {
                addValidatorErrorMessage(content); // 基类提供的收集方法
            }
        };
    }

    @Override
    protected DynamicObject[] buildData() {
        return new DynamicObject[]{
            new DynamicObjectMocker()
                .add("id", 1L)
                .add("status", "A")
                .getObject()
        };
    }

    @Test
    public void testValidate() {
        AbstractValidator validator = registerValidator("submit", "提交");
        validator.validate();
        assertEquals(0, getValidatorErrorMessages().size());
    }
}
```

## 关键要点

1. **推荐重写消息方法**：校验器的 `addMessage` / `addErrorMessage` 等方法是 protected 的，
   建议通过匿名子类重写来捕获校验结果
2. **ExtendedDataEntity 包装**：校验器接收 `ExtendedDataEntity[]`，需要用 `new ExtendedDataEntity(dynamicObject, rowIndex, 0)` 包装
3. **设置 entityKey**：部分校验器依赖 `getEntityKey()` 判断单据类型，需要调用 `validator.setEntityKey(formId)`
4. **设置 operateKey**：部分校验器依赖操作类型，需调用 `validator.setOperateKey("submit")` 等
