package jnpf.elnService.formula.impl;
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import jnpf.audit.model.AuditEventDTO;
|
import jnpf.elnEntity.formula.ElnFormulaDefEntity;
|
import jnpf.elnEntity.formula.ElnFormulaForms;
|
import jnpf.elnEntity.formula.ElnFormulaParameterSlotEntity;
|
import jnpf.elnEntity.formula.ElnFormulaVersionEntity;
|
import jnpf.elnEntity.formula.ElnFormulaVos;
|
import jnpf.elnEntity.formula.ElnTemplateFormulaBindingEntity;
|
import jnpf.elnEntity.template.ElnTemplateVersionEntity;
|
import jnpf.elnService.formula.ElnFormulaCatalogService;
|
import jnpf.elnService.impl.ElnFieldCodeGenerator;
|
import jnpf.exception.DataException;
|
import org.junit.Test;
|
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
|
import java.lang.reflect.Method;
|
import java.lang.reflect.Field;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collection;
|
import java.util.Comparator;
|
import java.util.LinkedHashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.function.Supplier;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertNotEquals;
|
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.fail;
|
|
public class ElnFormulaCatalogServiceImplTest {
|
|
@Test
|
public void createsFormulaWithGeneratedCodeVersionOneSlotsAndAudit() {
|
Fixture fixture = new Fixture();
|
|
ElnFormulaVos.Detail detail = fixture.service.create(formula("含量计算", "${样品质量}",
|
parameter("样品质量", "DECIMAL")));
|
|
assertTrue(detail.getGongshiBianma().matches("hanliang_jisuan_[a-z0-9]{8}"));
|
assertEquals("enabled", detail.getBizEnabled());
|
assertNull(detail.getDangqianGongshiBanbenId());
|
assertEquals(1, detail.getVersions().size());
|
ElnFormulaVos.Version version = detail.getVersions().get(0);
|
assertEquals(Integer.valueOf(1), version.getBanbenHao());
|
assertEquals("DRAFT", version.getBizStatus());
|
assertTrue(version.getParameters().get(0).getCanshuBianma()
|
.matches("yang_pin_zhi_liang_[a-z0-9]{8}"));
|
assertEquals(Arrays.asList("ELN_FORMULA_CREATE", "ELN_FORMULA_VERSION_CREATE"),
|
fixture.auditActions());
|
}
|
|
@Test
|
public void createsFormulaCodesWithWordLevelPinyinForDomainAndMixedNames() {
|
Fixture fixture = new Fixture();
|
|
ElnFormulaVos.Detail domain = fixture.service.create(formula("有关物质计算", "1"));
|
ElnFormulaVos.Detail mixed = fixture.service.create(formula("HPLC含量计算", "1"));
|
|
assertTrue(domain.getGongshiBianma().matches("youguan_wuzhi_jisuan_[a-z0-9]{8}"));
|
assertTrue(mixed.getGongshiBianma().matches("hplc_hanliang_jisuan_[a-z0-9]{8}"));
|
}
|
|
@Test
|
public void createsDraftWithStableParameterCodesAndRejectsEmptyExpressionAtPreRelease() {
|
Fixture fixture = new Fixture();
|
ElnFormulaForms.CreateDraft draft = new ElnFormulaForms.CreateDraft();
|
draft.setGongshiMingcheng("含量计算草稿");
|
draft.setParameters(Arrays.asList(parameter("样品质量", "DECIMAL"), parameter("稀释倍数", "DECIMAL")));
|
|
ElnFormulaVos.Detail detail = fixture.service.createDraft(draft);
|
|
ElnFormulaVos.Version version = detail.getVersions().get(0);
|
assertEquals("DRAFT", version.getBizStatus());
|
assertEquals("", version.getBiaodashi());
|
assertEquals("DECIMAL", version.getJieguoLeixing());
|
assertNull(version.getXiuyueMoshi());
|
assertNull(version.getXiuyueXiaoshuWei());
|
assertEquals(2, version.getParameters().size());
|
assertTrue(version.getParameters().get(0).getCanshuBianma().matches("yang_pin_zhi_liang_[a-z0-9]{8}"));
|
assertTrue(version.getParameters().get(1).getCanshuBianma().matches("xi_shi_bei_shu_[a-z0-9]{8}"));
|
assertNotEquals(version.getParameters().get(0).getCanshuBianma(), version.getParameters().get(1).getCanshuBianma());
|
assertEquals(Arrays.asList("ELN_FORMULA_CREATE", "ELN_FORMULA_VERSION_CREATE"), fixture.auditActions());
|
|
expectDataException(() -> fixture.service.preRelease(version.getId()));
|
assertEquals("DRAFT", fixture.service.detail(detail.getId()).getVersions().get(0).getBizStatus());
|
}
|
|
@Test
|
public void updatesAuditableDefinitionWithoutChangingCode() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail created = fixture.service.create(formula("原名称", "1"));
|
ElnFormulaForms.UpdateDefinition update = new ElnFormulaForms.UpdateDefinition();
|
update.setGongshiMingcheng("新名称");
|
update.setYewuYongtu("用于复核");
|
update.setBeizhu("变更说明");
|
|
ElnFormulaVos.Detail updated = fixture.service.updateDefinition(created.getId(), update);
|
|
assertEquals(created.getGongshiBianma(), updated.getGongshiBianma());
|
assertEquals("新名称", updated.getGongshiMingcheng());
|
assertEquals("用于复核", updated.getYewuYongtu());
|
assertEquals("ELN_FORMULA_UPDATE", fixture.auditActions().get(2));
|
assertTrue(fixture.audits.get(2).getDataSnapshot().contains("原名称"));
|
assertTrue(fixture.audits.get(2).getDataSnapshot().contains("新名称"));
|
}
|
|
@Test
|
public void copiesSlotCodesAndDraftEditOnlyDeletesCurrentVersionSlots() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail created = fixture.service.create(formula("含量", "1",
|
parameter("质量", "DECIMAL"), parameter("体积", "DECIMAL")));
|
ElnFormulaVos.Version v1 = created.getVersions().get(0);
|
fixture.service.preRelease(v1.getId());
|
fixture.service.publish(v1.getId());
|
|
ElnFormulaVos.Version v2 = fixture.service.createVersion(created.getId(), copyOf(v1.getId()));
|
ElnFormulaForms.Parameter kept = parameterFrom(v2.getParameters().get(0));
|
ElnFormulaForms.Parameter added = parameter("稀释倍数", "DECIMAL");
|
ElnFormulaForms.UpdateVersion update = version("${" + keptCode(v2, 0) + "}", kept, added);
|
ElnFormulaVos.Version updated = fixture.service.updateDraft(v2.getId(), update);
|
|
assertEquals(v1.getParameters().get(0).getCanshuBianma(),
|
updated.getParameters().get(0).getCanshuBianma());
|
assertTrue(updated.getParameters().get(1).getCanshuBianma()
|
.matches("xi_shi_bei_shu_[a-z0-9]{8}"));
|
assertEquals(2, fixture.service.detail(created.getId()).getVersions().get(0)
|
.getParameters().size());
|
assertEquals(2, updated.getParameters().size());
|
assertNotEquals(v1.getParameters().get(1).getId(), updated.getParameters().get(1).getId());
|
}
|
|
@Test
|
public void neverReusesCancelledVersionNumberAndPublishedContentIsImmutable() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail created = fixture.service.create(formula("平均值", "1"));
|
String v1 = created.getVersions().get(0).getId();
|
fixture.service.preRelease(v1);
|
fixture.service.publish(v1);
|
ElnFormulaVos.Version v2 = fixture.service.createVersion(created.getId(), copyOf(v1));
|
fixture.service.cancel(v2.getId(), "不再使用");
|
ElnFormulaVos.Version v3 = fixture.service.createVersion(created.getId(), copyOf(v1));
|
|
assertEquals(Integer.valueOf(2), v2.getBanbenHao());
|
assertEquals(Integer.valueOf(3), v3.getBanbenHao());
|
expectDataException(() -> fixture.service.updateDraft(v1, version("2")));
|
}
|
|
@Test
|
public void followsLifecyclePublishesCurrentAndSwitchesOnlySamePublishedFormula() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail first = fixture.service.create(formula("公式一", "1"));
|
String v1 = first.getVersions().get(0).getId();
|
ElnFormulaVos.Detail other = fixture.service.create(formula("公式二", "2"));
|
String otherV1 = other.getVersions().get(0).getId();
|
|
expectDataException(() -> fixture.service.publish(v1));
|
fixture.service.preRelease(v1);
|
expectDataException(() -> fixture.service.updateDraft(v1, version("3")));
|
fixture.service.publish(v1);
|
assertEquals(v1, fixture.service.detail(first.getId()).getDangqianGongshiBanbenId());
|
expectDataException(() -> fixture.service.switchCurrent(first.getId(), otherV1, "错误切换"));
|
expectDataException(() -> fixture.service.switchCurrent(other.getId(), otherV1, "尚未发布"));
|
}
|
|
@Test
|
public void blocksCancellingReferencedPreReleaseVersion() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail created = fixture.service.create(formula("引用公式", "1"));
|
String versionId = created.getVersions().get(0).getId();
|
fixture.service.preRelease(versionId);
|
fixture.hasDraftReference = true;
|
|
expectDataException(() -> fixture.service.cancel(versionId, "尝试作废"));
|
|
assertEquals("PRE_RELEASE", fixture.service.detail(created.getId())
|
.getVersions().get(0).getBizStatus());
|
}
|
|
@Test
|
public void blocksCancellingPreReleaseWhenReferenceCheckerIsUnavailable() {
|
Fixture fixture = new Fixture(false, new String[0]);
|
ElnFormulaVos.Detail created = fixture.service.create(formula("引用检查不可用", "1"));
|
String versionId = created.getVersions().get(0).getId();
|
fixture.service.preRelease(versionId);
|
|
expectDataException("引用检查能力不可用",
|
() -> fixture.service.cancel(versionId, "尝试作废"));
|
|
assertEquals("PRE_RELEASE", fixture.service.detail(created.getId())
|
.getVersions().get(0).getBizStatus());
|
}
|
|
@Test
|
public void allowsCancellingPreReleaseWhenCheckerConfirmsNoDraftReference() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail created = fixture.service.create(formula("无引用公式", "1"));
|
String versionId = created.getVersions().get(0).getId();
|
fixture.service.preRelease(versionId);
|
|
fixture.service.cancel(versionId, "确认无引用后作废");
|
|
assertEquals("CANCELLED", fixture.service.detail(created.getId())
|
.getVersions().get(0).getBizStatus());
|
}
|
|
@Test
|
public void preReleaseReloadsStoredVersionAndSlotsForValidation() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail created = fixture.service.create(formula("数据库校验", "1",
|
parameter("质量", "DECIMAL")));
|
String versionId = created.getVersions().get(0).getId();
|
fixture.store.versions.get(versionId).setBiaodashi("${unknown_a1}");
|
|
expectDataException(() -> fixture.service.preRelease(versionId));
|
|
assertEquals("DRAFT", fixture.store.versions.get(versionId).getBizStatus());
|
}
|
|
@Test
|
public void rejectsCancelReasonLongerThanDatabaseColumn() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail created = fixture.service.create(formula("原因长度", "1"));
|
String versionId = created.getVersions().get(0).getId();
|
fixture.service.preRelease(versionId);
|
|
expectDataException(() -> fixture.service.cancel(versionId, repeat("原", 501)));
|
|
assertEquals("PRE_RELEASE", fixture.service.detail(created.getId())
|
.getVersions().get(0).getBizStatus());
|
}
|
|
@Test
|
public void generatesBoundedValidCodesAndRetriesFormulaAndParameterCollisions() {
|
Fixture fixture = new Fixture("same0001", "same0001", "next0002",
|
"slot0001", "slot0001", "slot0002");
|
fixture.service.create(formula("同名", "1"));
|
ElnFormulaVos.Detail second = fixture.service.create(formula("同名", "${参数}",
|
parameter("参数", "DECIMAL"), parameter("参数", "DECIMAL")));
|
ElnFormulaVos.Detail longName = fixture.service.create(formula(repeat("超长公式", 20), "1"));
|
ElnFormulaVos.Detail numeric = fixture.service.create(formula("123公式", "1"));
|
ElnFormulaVos.Detail fallback = fixture.service.create(formula("***", "1"));
|
|
assertTrue(second.getGongshiBianma().endsWith("next0002"));
|
assertTrue(longName.getGongshiBianma().length() <= 80);
|
assertTrue(numeric.getGongshiBianma().startsWith("f_123_gongshi_"));
|
assertTrue(fallback.getGongshiBianma().startsWith("formula_"));
|
for (ElnFormulaVos.Parameter parameter : second.getVersions().get(0).getParameters()) {
|
assertTrue(parameter.getCanshuBianma().length() <= 80);
|
assertTrue(parameter.getCanshuBianma().matches("[a-z][a-z0-9_]*"));
|
}
|
assertNotEquals(second.getVersions().get(0).getParameters().get(0).getCanshuBianma(),
|
second.getVersions().get(0).getParameters().get(1).getCanshuBianma());
|
|
ElnFormulaVos.Detail parameterEdges = fixture.service.create(formula("参数边界", "1",
|
parameter(repeat("超长参数", 20), "DECIMAL"),
|
parameter("123参数", "DECIMAL"), parameter("***", "DECIMAL")));
|
List<ElnFormulaVos.Parameter> edgeParameters = parameterEdges.getVersions().get(0)
|
.getParameters();
|
assertTrue(edgeParameters.get(0).getCanshuBianma().length() <= 80);
|
assertTrue(edgeParameters.get(1).getCanshuBianma().startsWith("p_123_can_shu_"));
|
assertTrue(edgeParameters.get(2).getCanshuBianma().startsWith("parameter_"));
|
for (ElnFormulaVos.Parameter parameter : edgeParameters) {
|
assertTrue(parameter.getCanshuBianma().matches("[a-z][a-z0-9_]*"));
|
}
|
}
|
|
@Test
|
public void activeTransactionDelegatesAuditsImmediatelyWithoutRegisteringSynchronization() {
|
Fixture fixture = new Fixture();
|
TransactionSynchronizationManager.setActualTransactionActive(true);
|
TransactionSynchronizationManager.initSynchronization();
|
try {
|
fixture.service.create(formula("回滚公式", "1"));
|
assertEquals(Arrays.asList("ELN_FORMULA_CREATE", "ELN_FORMULA_VERSION_CREATE"),
|
fixture.auditActions());
|
assertTrue(TransactionSynchronizationManager.getSynchronizations().isEmpty());
|
} finally {
|
TransactionSynchronizationManager.clearSynchronization();
|
TransactionSynchronizationManager.setActualTransactionActive(false);
|
}
|
}
|
|
@Test
|
public void catalogContractHasNoDeleteMethod() {
|
for (Method method : ElnFormulaCatalogService.class.getMethods()) {
|
assertFalse("delete".equals(method.getName()));
|
}
|
}
|
|
@Test
|
public void pageCountsOnlyPendingAndActiveTemplateFormulaBindings() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail formula = fixture.service.create(formula("引用统计", "1"));
|
fixture.store.addFormulaBinding(binding("binding-active", "tenant-1", "template-1",
|
formula.getId(), "version-1", "ACTIVE"));
|
fixture.store.addFormulaBinding(binding("binding-pending", "tenant-1", "template-2",
|
formula.getId(), "version-2", "PENDING"));
|
fixture.store.addFormulaBinding(binding("binding-cancelled", "tenant-1", "template-3",
|
formula.getId(), "version-3", "CANCELLED"));
|
fixture.store.addFormulaBinding(binding("binding-removed", "tenant-1", "template-4",
|
formula.getId(), "version-4", "REMOVED"));
|
fixture.store.addFormulaBinding(binding("binding-foreign", "tenant-2", "template-5",
|
formula.getId(), "version-5", "ACTIVE"));
|
|
ElnFormulaVos.ListItem item = fixture.service.page(query()).getRecords().get(0);
|
|
assertEquals(Integer.valueOf(2), property(item, "mubanYinyongShu"));
|
}
|
|
@Test
|
public void pagePrefetchesVersionSummariesWithoutPerFormulaQueries() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail first = fixture.service.create(formula("批量公式一", "1"));
|
ElnFormulaVos.Detail second = fixture.service.create(formula("批量公式二", "1"));
|
ElnFormulaVersionEntity firstVersion = fixture.store.versions.get(
|
first.getVersions().get(0).getId());
|
firstVersion.setBizStatus("PUBLISHED");
|
fixture.store.definitions.get(first.getId())
|
.setDangqianGongshiBanbenId(firstVersion.getId());
|
ElnFormulaVersionEntity secondVersion = fixture.store.versions.get(
|
second.getVersions().get(0).getId());
|
fixture.store.definitions.get(second.getId())
|
.setDangqianGongshiBanbenId(secondVersion.getId());
|
|
ElnFormulaDefEntity empty = new ElnFormulaDefEntity();
|
empty.setId("formula-empty");
|
empty.setFTenantId("tenant-1");
|
empty.setGongshiBianma("formula_empty");
|
empty.setGongshiMingcheng("无版本公式");
|
empty.setBizEnabled("enabled");
|
fixture.store.definitions.put(empty.getId(), empty);
|
|
ElnFormulaVersionEntity foreign = new ElnFormulaVersionEntity();
|
foreign.setId("foreign-version");
|
foreign.setFTenantId("tenant-2");
|
foreign.setGongshiDingyiId(first.getId());
|
foreign.setBanbenHao(99);
|
foreign.setBizStatus("PUBLISHED");
|
fixture.store.versions.put(foreign.getId(), foreign);
|
fixture.store.listVersionsCalls = 0;
|
|
List<ElnFormulaVos.ListItem> records = fixture.service.page(query()).getRecords();
|
Map<String, ElnFormulaVos.ListItem> byId = new LinkedHashMap<>();
|
for (ElnFormulaVos.ListItem item : records) {
|
byId.put(item.getId(), item);
|
}
|
|
assertEquals(0, fixture.store.listVersionsCalls);
|
assertEquals(1, fixture.store.batchVersionCalls);
|
assertEquals("tenant-1", fixture.store.batchVersionTenant);
|
assertEquals(new LinkedHashSet<>(Arrays.asList(
|
first.getId(), second.getId(), empty.getId())),
|
fixture.store.batchVersionDefinitionIds);
|
assertEquals(Integer.valueOf(1), byId.get(first.getId()).getDangqianBanbenHao());
|
assertEquals(Integer.valueOf(1), byId.get(first.getId()).getZuixinFabuBanbenHao());
|
assertEquals(Integer.valueOf(1), byId.get(second.getId()).getDangqianBanbenHao());
|
assertNull(byId.get(second.getId()).getZuixinFabuBanbenHao());
|
assertNull(byId.get(empty.getId()).getDangqianBanbenHao());
|
assertNull(byId.get(empty.getId()).getZuixinFabuBanbenHao());
|
}
|
|
@Test
|
public void detailReturnsStableTemplateReferenceSummariesWithMetadata() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail formula = fixture.service.create(formula("引用摘要", "1"));
|
fixture.store.addTemplate(template("template-b", "tenant-1", "模板-B", "PUBLISHED"));
|
fixture.store.addTemplate(template("template-a", "tenant-1", "模板-A", "DRAFT"));
|
fixture.store.addFormulaBinding(binding("binding-b", "tenant-1", "template-b",
|
formula.getId(), "version-2", "PENDING"));
|
fixture.store.addFormulaBinding(binding("binding-a", "tenant-1", "template-a",
|
formula.getId(), "version-1", "ACTIVE"));
|
|
ElnFormulaVos.Detail detail = fixture.service.detail(formula.getId());
|
List<?> references = (List<?>) property(detail, "templateReferences");
|
|
assertEquals(2, references.size());
|
assertEquals("binding-a", property(references.get(0), "bindingId"));
|
assertEquals("template-a", property(references.get(0), "templateId"));
|
assertEquals("模板-A", property(references.get(0), "templateName"));
|
assertEquals("DRAFT", property(references.get(0), "templateStatus"));
|
assertEquals("ACTIVE", property(references.get(0), "bindingStatus"));
|
assertEquals("version-1", property(references.get(0), "formulaVersionId"));
|
assertEquals("template-b", property(references.get(1), "templateId"));
|
}
|
|
@Test
|
public void detailReturnsReferenceCountAndAtMostFivePreviews() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail formula = fixture.service.create(formula("大量引用", "1"));
|
for (int index = 7; index >= 1; index--) {
|
String templateId = "template-" + index;
|
fixture.store.addTemplate(template(templateId, "tenant-1",
|
"模板-" + index, "PUBLISHED"));
|
fixture.store.addFormulaBinding(binding("binding-" + index, "tenant-1",
|
templateId, formula.getId(), "version-" + index, "ACTIVE"));
|
}
|
|
ElnFormulaVos.Detail detail = fixture.service.detail(formula.getId());
|
List<?> references = (List<?>) property(detail, "templateReferences");
|
|
assertEquals(Integer.valueOf(7), property(detail, "mubanYinyongShu"));
|
assertEquals(5, references.size());
|
assertEquals("template-1", property(references.get(0), "templateId"));
|
assertEquals("template-5", property(references.get(4), "templateId"));
|
}
|
|
@Test
|
public void pagesLiveTemplateReferencesWithStableOrderAndTenantIsolation() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail formula = fixture.service.create(formula("分页引用", "1"));
|
for (int index = 7; index >= 1; index--) {
|
String templateId = "template-" + index;
|
fixture.store.addTemplate(template(templateId, "tenant-1",
|
"模板-" + index, "PUBLISHED"));
|
fixture.store.addFormulaBinding(binding("binding-" + index, "tenant-1",
|
templateId, formula.getId(), "version-" + index, "ACTIVE"));
|
}
|
fixture.store.addFormulaBinding(binding("binding-foreign", "tenant-2",
|
"template-0", formula.getId(), "version-0", "ACTIVE"));
|
fixture.store.addFormulaBinding(binding("binding-cancelled", "tenant-1",
|
"template-8", formula.getId(), "version-8", "CANCELLED"));
|
ElnFormulaForms.ReferenceQuery query = new ElnFormulaForms.ReferenceQuery();
|
query.setCurrentPage(2);
|
query.setPageSize(3);
|
|
IPage<ElnFormulaVos.ReferenceSummary> page =
|
fixture.service.pageTemplateReferences(formula.getId(), query);
|
|
assertEquals(7, page.getTotal());
|
assertEquals(3, page.getRecords().size());
|
assertEquals("template-4", page.getRecords().get(0).getTemplateId());
|
assertEquals("template-6", page.getRecords().get(2).getTemplateId());
|
}
|
|
@Test
|
public void usesBindingIdAsTheFinalStableReferenceOrder() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail formula = fixture.service.create(formula("同键引用", "1"));
|
fixture.store.addTemplate(template("template-1", "tenant-1",
|
"模板-1", "PUBLISHED"));
|
fixture.store.addFormulaBinding(binding("binding-z", "tenant-1",
|
"template-1", formula.getId(), "version-1", "ACTIVE"));
|
fixture.store.addFormulaBinding(binding("binding-a", "tenant-1",
|
"template-1", formula.getId(), "version-1", "ACTIVE"));
|
|
IPage<ElnFormulaVos.ReferenceSummary> page =
|
fixture.service.pageTemplateReferences(
|
formula.getId(), new ElnFormulaForms.ReferenceQuery());
|
|
assertEquals("binding-a", page.getRecords().get(0).getBindingId());
|
assertEquals("binding-z", page.getRecords().get(1).getBindingId());
|
}
|
|
@Test
|
public void filtersTemplateReferencesByTrimmedTemplateNameOrId() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail formula = fixture.service.create(formula("搜索引用", "1"));
|
fixture.store.addTemplate(template("template-alpha", "tenant-1",
|
"含量测定模板", "PUBLISHED"));
|
fixture.store.addTemplate(template("template-beta", "tenant-1",
|
"有关物质模板", "DRAFT"));
|
fixture.store.addFormulaBinding(binding("binding-alpha", "tenant-1",
|
"template-alpha", formula.getId(), "version-1", "ACTIVE"));
|
fixture.store.addFormulaBinding(binding("binding-beta", "tenant-1",
|
"template-beta", formula.getId(), "version-2", "PENDING"));
|
ElnFormulaForms.ReferenceQuery byName = new ElnFormulaForms.ReferenceQuery();
|
byName.setKeyword(" 含量测定 ");
|
ElnFormulaForms.ReferenceQuery byId = new ElnFormulaForms.ReferenceQuery();
|
byId.setKeyword(" beta ");
|
|
IPage<ElnFormulaVos.ReferenceSummary> namePage =
|
fixture.service.pageTemplateReferences(formula.getId(), byName);
|
IPage<ElnFormulaVos.ReferenceSummary> idPage =
|
fixture.service.pageTemplateReferences(formula.getId(), byId);
|
|
assertEquals(1, namePage.getTotal());
|
assertEquals("template-alpha", namePage.getRecords().get(0).getTemplateId());
|
assertEquals(1, idPage.getTotal());
|
assertEquals("template-beta", idPage.getRecords().get(0).getTemplateId());
|
}
|
|
@Test
|
public void detailKeepsLiveReferenceWhenTemplateRecordIsMissingWithoutCrossTenantLeak() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail formula = fixture.service.create(formula("缺失模板", "1"));
|
fixture.store.addFormulaBinding(binding("binding-missing", "tenant-1", "missing-template",
|
formula.getId(), "version-1", "ACTIVE"));
|
fixture.store.addFormulaBinding(binding("binding-foreign", "tenant-2", "foreign-template",
|
formula.getId(), "version-2", "PENDING"));
|
|
List<?> references = (List<?>) property(fixture.service.detail(formula.getId()),
|
"templateReferences");
|
|
assertEquals(1, references.size());
|
assertEquals("missing-template", property(references.get(0), "templateId"));
|
assertNull(property(references.get(0), "templateName"));
|
assertNull(property(references.get(0), "templateStatus"));
|
}
|
|
@Test
|
public void optimisticUpdateCountZeroFailsAndSuccessAdvancesEntityVersion() {
|
expectDataException(() -> ElnFormulaCatalogServiceImpl.nextVersionAfterUpdate(
|
0, 7, "版本冲突"));
|
|
assertEquals(8, ElnFormulaCatalogServiceImpl.nextVersionAfterUpdate(
|
1, 7, "版本冲突"));
|
}
|
|
@Test
|
public void disableBlocksVersionAndPublishThenEnableRestoresLifecycle() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail created = fixture.service.create(formula("停用公式", "1"));
|
String v1 = created.getVersions().get(0).getId();
|
fixture.service.preRelease(v1);
|
fixture.service.disable(created.getId(), "暂时停用");
|
|
expectDataException(() -> fixture.service.publish(v1));
|
expectDataException(() -> fixture.service.createVersion(created.getId(), copyOf(v1)));
|
assertEquals("disabled", fixture.service.detail(created.getId()).getBizEnabled());
|
|
fixture.service.enable(created.getId(), "恢复使用");
|
fixture.service.publish(v1);
|
assertEquals("enabled", fixture.service.detail(created.getId()).getBizEnabled());
|
}
|
|
@Test
|
public void exposesTenantScopedAvailabilityForTemplateBindingGate() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail created = fixture.service.create(formula("引用门禁", "1"));
|
|
assertTrue(fixture.service.isEnabled("tenant-1", created.getId()));
|
assertFalse(fixture.service.isEnabled("other-tenant", created.getId()));
|
fixture.service.disable(created.getId(), "停用");
|
assertFalse(fixture.service.isEnabled("tenant-1", created.getId()));
|
}
|
|
@Test
|
public void emitsEveryFixedLifecycleAuditAction() {
|
Fixture fixture = new Fixture();
|
ElnFormulaVos.Detail created = fixture.service.create(formula("审计公式", "1"));
|
fixture.service.updateDefinition(created.getId(), definition("审计公式二"));
|
String v1 = created.getVersions().get(0).getId();
|
fixture.service.updateDraft(v1, version("2"));
|
fixture.service.preRelease(v1);
|
fixture.service.publish(v1);
|
ElnFormulaVos.Version v2 = fixture.service.createVersion(created.getId(), copyOf(v1));
|
fixture.service.cancel(v2.getId(), "废弃 V2");
|
fixture.service.disable(created.getId(), "停用");
|
fixture.service.enable(created.getId(), "启用");
|
fixture.service.switchCurrent(created.getId(), v1, "切回 V1");
|
|
assertEquals(Arrays.asList("ELN_FORMULA_CREATE", "ELN_FORMULA_VERSION_CREATE",
|
"ELN_FORMULA_UPDATE", "ELN_FORMULA_VERSION_UPDATE",
|
"ELN_FORMULA_VERSION_PRE_RELEASE", "ELN_FORMULA_VERSION_PUBLISH",
|
"ELN_FORMULA_VERSION_CREATE", "ELN_FORMULA_VERSION_CANCEL",
|
"ELN_FORMULA_DISABLE", "ELN_FORMULA_ENABLE",
|
"ELN_FORMULA_SWITCH_CURRENT"), fixture.auditActions());
|
for (AuditEventDTO audit : fixture.audits) {
|
assertTrue("eln_formula".equals(audit.getBizType())
|
|| "eln_formula_version".equals(audit.getBizType()));
|
}
|
}
|
|
private static ElnFormulaForms.Create formula(String name, String expression,
|
ElnFormulaForms.Parameter... parameters) {
|
ElnFormulaForms.Create form = new ElnFormulaForms.Create();
|
form.setGongshiMingcheng(name);
|
form.setYewuYongtu("测试用途");
|
form.setBiaodashi(expression);
|
form.setJieguoLeixing("DECIMAL");
|
form.setParameters(new ArrayList<>(Arrays.asList(parameters)));
|
return form;
|
}
|
|
private static ElnFormulaForms.Parameter parameter(String name, String type) {
|
ElnFormulaForms.Parameter form = new ElnFormulaForms.Parameter();
|
form.setCanshuMingcheng(name);
|
form.setShujuLeixing(type);
|
form.setShifouBitian(1);
|
return form;
|
}
|
|
private static ElnFormulaForms.Parameter parameterFrom(ElnFormulaVos.Parameter source) {
|
ElnFormulaForms.Parameter form = parameter(source.getCanshuMingcheng(), source.getShujuLeixing());
|
form.setId(source.getId());
|
form.setDanwei(source.getDanwei());
|
form.setPaixu(source.getPaixu());
|
return form;
|
}
|
|
private static ElnFormulaForms.CreateVersion copyOf(String sourceVersionId) {
|
ElnFormulaForms.CreateVersion form = new ElnFormulaForms.CreateVersion();
|
form.setSourceVersionId(sourceVersionId);
|
return form;
|
}
|
|
private static ElnFormulaForms.UpdateVersion version(String expression,
|
ElnFormulaForms.Parameter... parameters) {
|
ElnFormulaForms.UpdateVersion form = new ElnFormulaForms.UpdateVersion();
|
form.setBiaodashi(expression);
|
form.setJieguoLeixing("DECIMAL");
|
form.setParameters(new ArrayList<>(Arrays.asList(parameters)));
|
return form;
|
}
|
|
private static ElnFormulaForms.UpdateDefinition definition(String name) {
|
ElnFormulaForms.UpdateDefinition form = new ElnFormulaForms.UpdateDefinition();
|
form.setGongshiMingcheng(name);
|
form.setYewuYongtu("更新用途");
|
return form;
|
}
|
|
private static String keptCode(ElnFormulaVos.Version version, int index) {
|
return version.getParameters().get(index).getCanshuBianma();
|
}
|
|
private static void expectDataException(Runnable action) {
|
try {
|
action.run();
|
fail("预期抛出 DataException");
|
} catch (DataException expected) {
|
assertFalse(expected.getMessage().trim().isEmpty());
|
}
|
}
|
|
private static void expectDataException(String message, Runnable action) {
|
try {
|
action.run();
|
fail("预期抛出 DataException");
|
} catch (DataException expected) {
|
assertTrue(expected.getMessage().contains(message));
|
}
|
}
|
|
private static String repeat(String value, int count) {
|
StringBuilder result = new StringBuilder();
|
for (int i = 0; i < count; i++) {
|
result.append(value);
|
}
|
return result.toString();
|
}
|
|
private static ElnFormulaForms.Query query() {
|
ElnFormulaForms.Query query = new ElnFormulaForms.Query();
|
query.setCurrentPage(1);
|
query.setPageSize(20);
|
return query;
|
}
|
|
private static ElnTemplateFormulaBindingEntity binding(String id, String tenantId,
|
String templateId, String formulaId,
|
String formulaVersionId, String status) {
|
ElnTemplateFormulaBindingEntity result = new ElnTemplateFormulaBindingEntity();
|
result.setId(id);
|
result.setFTenantId(tenantId);
|
result.setMubanId(templateId);
|
result.setGongshiDingyiId(formulaId);
|
result.setGongshiBanbenId(formulaVersionId);
|
result.setBangdingZhuangtai(status);
|
return result;
|
}
|
|
private static ElnTemplateVersionEntity template(String id, String tenantId,
|
String name, String status) {
|
ElnTemplateVersionEntity result = new ElnTemplateVersionEntity();
|
result.setId(id);
|
result.setFTenantId(tenantId);
|
result.setJianyanXiangmu(name);
|
result.setBizStatus(status);
|
return result;
|
}
|
|
private static Object property(Object source, String name) {
|
try {
|
Field field = source.getClass().getDeclaredField(name);
|
field.setAccessible(true);
|
return field.get(source);
|
} catch (ReflectiveOperationException exception) {
|
fail("缺少属性: " + name);
|
return null;
|
}
|
}
|
|
private static final class Fixture {
|
private final MemoryStore store = new MemoryStore();
|
private final List<AuditEventDTO> audits = new ArrayList<>();
|
private final AtomicInteger ids = new AtomicInteger();
|
private boolean hasDraftReference;
|
private final ElnFormulaCatalogServiceImpl service;
|
|
private Fixture(boolean checkerAvailable, String... suffixes) {
|
Supplier<String> suffixSupplier = suffixSupplier(suffixes);
|
service = new ElnFormulaCatalogServiceImpl(store,
|
new FormulaValidationServiceImpl(),
|
checkerAvailable ? (tenantId, versionId) -> hasDraftReference : null,
|
() -> new ElnFormulaCatalogServiceImpl.UserContext("tenant-1", "user-1"),
|
() -> "id-" + ids.incrementAndGet(),
|
new ElnFieldCodeGenerator(suffixSupplier), audits::add, () -> true);
|
}
|
|
private Fixture(String... suffixes) {
|
this(true, suffixes);
|
}
|
|
private Fixture() {
|
this(true, new String[0]);
|
}
|
|
private List<String> auditActions() {
|
List<String> actions = new ArrayList<>();
|
for (AuditEventDTO audit : audits) {
|
actions.add(audit.getActionCode());
|
}
|
return actions;
|
}
|
|
private static Supplier<String> suffixSupplier(String[] configured) {
|
AtomicInteger sequence = new AtomicInteger();
|
return () -> {
|
int index = sequence.getAndIncrement();
|
return index < configured.length
|
? configured[index] : String.format("s%07d", index + 1);
|
};
|
}
|
}
|
|
private static final class MemoryStore implements ElnFormulaCatalogServiceImpl.FormulaCatalogStore {
|
private final Map<String, ElnFormulaDefEntity> definitions = new LinkedHashMap<>();
|
private final Map<String, ElnFormulaVersionEntity> versions = new LinkedHashMap<>();
|
private final Map<String, ElnFormulaParameterSlotEntity> slots = new LinkedHashMap<>();
|
private final Map<String, ElnTemplateFormulaBindingEntity> formulaBindings = new LinkedHashMap<>();
|
private final Map<String, ElnTemplateVersionEntity> templates = new LinkedHashMap<>();
|
private int listVersionsCalls;
|
private int batchVersionCalls;
|
private String batchVersionTenant;
|
private Set<String> batchVersionDefinitionIds = new LinkedHashSet<>();
|
|
private void addFormulaBinding(ElnTemplateFormulaBindingEntity binding) {
|
formulaBindings.put(binding.getId(), binding);
|
}
|
|
private void addTemplate(ElnTemplateVersionEntity template) {
|
templates.put(template.getId(), template);
|
}
|
|
@Override
|
public void insertDefinition(ElnFormulaDefEntity entity) {
|
for (ElnFormulaDefEntity existing : definitions.values()) {
|
if (existing.getFTenantId().equals(entity.getFTenantId())
|
&& existing.getGongshiBianma().equals(entity.getGongshiBianma())) {
|
throw new DuplicateKeyException("duplicate formula code");
|
}
|
}
|
definitions.put(entity.getId(), entity);
|
}
|
|
@Override
|
public void insertVersion(ElnFormulaVersionEntity entity) {
|
versions.put(entity.getId(), entity);
|
}
|
|
@Override
|
public void insertSlot(ElnFormulaParameterSlotEntity entity) {
|
for (ElnFormulaParameterSlotEntity existing : slots.values()) {
|
if (existing.getFTenantId().equals(entity.getFTenantId())
|
&& existing.getGongshiBanbenId().equals(entity.getGongshiBanbenId())
|
&& existing.getCanshuBianma().equals(entity.getCanshuBianma())) {
|
throw new DuplicateKeyException("duplicate slot code");
|
}
|
}
|
slots.put(entity.getId(), entity);
|
}
|
|
@Override
|
public ElnFormulaDefEntity findDefinition(String tenantId, String id, boolean forUpdate) {
|
ElnFormulaDefEntity value = definitions.get(id);
|
return value != null && tenantId.equals(value.getFTenantId()) ? value : null;
|
}
|
|
@Override
|
public ElnFormulaVersionEntity findVersion(String tenantId, String id, boolean forUpdate) {
|
ElnFormulaVersionEntity value = versions.get(id);
|
return value != null && tenantId.equals(value.getFTenantId()) ? value : null;
|
}
|
|
@Override
|
public List<ElnFormulaVersionEntity> listVersions(String tenantId, String definitionId) {
|
listVersionsCalls++;
|
List<ElnFormulaVersionEntity> result = new ArrayList<>();
|
for (ElnFormulaVersionEntity value : versions.values()) {
|
if (tenantId.equals(value.getFTenantId())
|
&& definitionId.equals(value.getGongshiDingyiId())) {
|
result.add(value);
|
}
|
}
|
return result;
|
}
|
|
@Override
|
public List<ElnFormulaVersionEntity> listVersionsByDefinitionIds(
|
String tenantId, Collection<String> definitionIds) {
|
batchVersionCalls++;
|
batchVersionTenant = tenantId;
|
batchVersionDefinitionIds = new LinkedHashSet<>(definitionIds);
|
List<ElnFormulaVersionEntity> result = new ArrayList<>();
|
for (ElnFormulaVersionEntity value : versions.values()) {
|
if (tenantId.equals(value.getFTenantId())
|
&& definitionIds.contains(value.getGongshiDingyiId())) {
|
result.add(value);
|
}
|
}
|
return result;
|
}
|
|
@Override
|
public List<ElnFormulaParameterSlotEntity> listSlots(String tenantId, String versionId) {
|
List<ElnFormulaParameterSlotEntity> result = new ArrayList<>();
|
for (ElnFormulaParameterSlotEntity value : slots.values()) {
|
if (tenantId.equals(value.getFTenantId())
|
&& versionId.equals(value.getGongshiBanbenId())) {
|
result.add(value);
|
}
|
}
|
return result;
|
}
|
|
@Override
|
public List<ElnTemplateFormulaBindingEntity> listLiveTemplateFormulaBindings(
|
String tenantId, java.util.Collection<String> definitionIds) {
|
List<ElnTemplateFormulaBindingEntity> result = new ArrayList<>();
|
for (ElnTemplateFormulaBindingEntity binding : formulaBindings.values()) {
|
if (tenantId.equals(binding.getFTenantId())
|
&& definitionIds.contains(binding.getGongshiDingyiId())
|
&& ("ACTIVE".equals(binding.getBangdingZhuangtai())
|
|| "PENDING".equals(binding.getBangdingZhuangtai()))) {
|
result.add(binding);
|
}
|
}
|
return result;
|
}
|
|
@Override
|
public IPage<ElnTemplateFormulaBindingEntity> pageLiveTemplateFormulaBindings(
|
String tenantId, String definitionId, ElnFormulaForms.ReferenceQuery query) {
|
List<ElnTemplateFormulaBindingEntity> result = new ArrayList<>();
|
String keyword = query.getKeyword() == null ? null : query.getKeyword().trim();
|
for (ElnTemplateFormulaBindingEntity binding : formulaBindings.values()) {
|
ElnTemplateVersionEntity template = templates.get(binding.getMubanId());
|
boolean matchesKeyword = keyword == null || keyword.isEmpty()
|
|| binding.getMubanId().contains(keyword)
|
|| template != null && template.getJianyanXiangmu().contains(keyword);
|
if (tenantId.equals(binding.getFTenantId())
|
&& definitionId.equals(binding.getGongshiDingyiId())
|
&& ("ACTIVE".equals(binding.getBangdingZhuangtai())
|
|| "PENDING".equals(binding.getBangdingZhuangtai()))
|
&& matchesKeyword) {
|
result.add(binding);
|
}
|
}
|
result.sort(Comparator.comparing(ElnTemplateFormulaBindingEntity::getMubanId)
|
.thenComparing(ElnTemplateFormulaBindingEntity::getGongshiBanbenId)
|
.thenComparing(ElnTemplateFormulaBindingEntity::getBangdingZhuangtai)
|
.thenComparing(ElnTemplateFormulaBindingEntity::getId));
|
int from = Math.min((query.getCurrentPage() - 1) * query.getPageSize(), result.size());
|
int to = Math.min(from + query.getPageSize(), result.size());
|
return new Page<ElnTemplateFormulaBindingEntity>(
|
query.getCurrentPage(), query.getPageSize(), result.size())
|
.setRecords(new ArrayList<>(result.subList(from, to)));
|
}
|
|
@Override
|
public List<ElnTemplateVersionEntity> listTemplates(
|
String tenantId, java.util.Collection<String> templateIds) {
|
List<ElnTemplateVersionEntity> result = new ArrayList<>();
|
for (ElnTemplateVersionEntity template : templates.values()) {
|
if (tenantId.equals(template.getFTenantId()) && templateIds.contains(template.getId())) {
|
result.add(template);
|
}
|
}
|
return result;
|
}
|
|
@Override
|
public IPage<ElnFormulaDefEntity> page(String tenantId, ElnFormulaForms.Query query) {
|
List<ElnFormulaDefEntity> result = new ArrayList<>();
|
for (ElnFormulaDefEntity value : definitions.values()) {
|
if (tenantId.equals(value.getFTenantId())) {
|
result.add(value);
|
}
|
}
|
return new Page<ElnFormulaDefEntity>(query.getCurrentPage(), query.getPageSize())
|
.setRecords(result).setTotal(result.size());
|
}
|
|
@Override
|
public void updateDefinition(ElnFormulaDefEntity entity) {
|
definitions.put(entity.getId(), entity);
|
}
|
|
@Override
|
public void updateVersion(ElnFormulaVersionEntity entity) {
|
versions.put(entity.getId(), entity);
|
}
|
|
@Override
|
public void updateSlot(ElnFormulaParameterSlotEntity entity) {
|
slots.put(entity.getId(), entity);
|
}
|
|
@Override
|
public void deleteSlot(ElnFormulaParameterSlotEntity entity) {
|
slots.remove(entity.getId());
|
}
|
}
|
}
|