package jnpf.elnService.impl;
|
|
import jnpf.audit.model.AuditEventDTO;
|
import jnpf.eln.RecordFillSaveCommand;
|
import jnpf.eln.RecordFillSaveOperation;
|
import jnpf.elnEntity.runtime.ElnRecordEntity;
|
import jnpf.elnEntity.runtime.ElnRecordForms;
|
import jnpf.elnEntity.runtime.ElnRecordRepeatRowEntity;
|
import jnpf.elnEntity.runtime.ElnRecordRevisionEntity;
|
import jnpf.elnEntity.runtime.ElnRecordVos;
|
import jnpf.elnEntity.template.ElnTemplateVos;
|
import jnpf.elnMapper.ElnRecordMapper;
|
import jnpf.elnMapper.ElnRecordRepeatRowMapper;
|
import jnpf.elnMapper.ElnRecordRevisionMapper;
|
import jnpf.elnService.ElnRecordFillAccessService;
|
import jnpf.elnService.ElnRecordFillTicketService;
|
import jnpf.elnService.template.ElnTemplateRepeatGroupService;
|
import jnpf.exception.DataException;
|
import jnpf.util.JsonUtil;
|
import org.junit.Test;
|
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.TransactionDefinition;
|
import org.springframework.transaction.TransactionStatus;
|
import org.springframework.transaction.support.SimpleTransactionStatus;
|
|
import java.lang.reflect.Proxy;
|
import java.util.Arrays;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.List;
|
import java.util.LinkedHashMap;
|
import java.util.Map;
|
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotEquals;
|
import static org.junit.Assert.fail;
|
|
public class ElnRecordRepeatServiceImplTest {
|
|
@Test
|
public void appendsOnceAndReplaysThePreparedRowForTheSameOperationCode() {
|
ElnRecordEntity record = new ElnRecordEntity();
|
record.setId("record-1");
|
record.setMubanId("template-1");
|
record.setDangqianXiudingId("revision-1");
|
ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity();
|
revision.setId("revision-1");
|
revision.setDianziJiluId("record-1");
|
revision.setJiluWendang("file-1");
|
revision.setBizStatus("DRAFT");
|
AtomicReference<ElnRecordRepeatRowEntity> inserted = new AtomicReference<>();
|
AtomicInteger rowSelections = new AtomicInteger();
|
AtomicInteger inserts = new AtomicInteger();
|
ElnRecordRepeatRowMapper rows = mapper(ElnRecordRepeatRowMapper.class,
|
(method, arguments) -> {
|
if ("selectOne".equals(method)) {
|
return rowSelections.incrementAndGet() >= 3 ? inserted.get() : null;
|
}
|
if ("insert".equals(method)) {
|
inserted.set((ElnRecordRepeatRowEntity) arguments[0]);
|
inserts.incrementAndGet();
|
return 1;
|
}
|
if ("selectCount".equals(method)) return 0L;
|
return null;
|
});
|
RecordingTransactionManager transactions = new RecordingTransactionManager();
|
ElnRecordRepeatServiceImpl service = new ElnRecordRepeatServiceImpl(
|
mapper(ElnRecordMapper.class,
|
(method, arguments) -> "selectOne".equals(method) ? record : null),
|
mapper(ElnRecordRevisionMapper.class,
|
(method, arguments) -> "selectOne".equals(method) ? revision : null),
|
rows, repeatGroups(group("measurements", 0, 10),
|
group("results", 0, 2)), allowFill(), transactions,
|
ignored -> { },
|
new ElnRecordRepeatServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
});
|
ElnRecordForms.RepeatAppend form = new ElnRecordForms.RepeatAppend();
|
form.setGroupCode("results");
|
form.setOperationCode("operation-1");
|
|
ElnRecordVos.RepeatRow first = service.append(ticket(), form);
|
ElnRecordVos.RepeatRow replay = service.append(ticket(), form);
|
|
assertNotNull(first.getRowId());
|
assertNotEquals(inserted.get().getId(), inserted.get().getRowId());
|
assertEquals(first.getRowId(), replay.getRowId());
|
assertEquals(Integer.valueOf(1), first.getRowOrder());
|
assertEquals("PREPARED", first.getStatus());
|
assertEquals(1, inserts.get());
|
assertEquals(2, transactions.commits.get());
|
}
|
|
@Test
|
public void allocatesAnotherPreparedRowForADifferentOperationCode() {
|
ElnRecordEntity record = new ElnRecordEntity();
|
record.setId("record-1");
|
record.setMubanId("template-1");
|
record.setDangqianXiudingId("revision-1");
|
ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity();
|
revision.setId("revision-1");
|
revision.setDianziJiluId("record-1");
|
revision.setJiluWendang("file-1");
|
revision.setBizStatus("DRAFT");
|
ElnRecordRepeatRowEntity pending = new ElnRecordRepeatRowEntity();
|
pending.setRowId("row-pending");
|
pending.setDianziJiluId("record-1");
|
pending.setMubanId("template-1");
|
pending.setRepeatGroupCode("results");
|
pending.setRowOrder(1);
|
pending.setOperationStatus("PREPARED");
|
AtomicInteger selections = new AtomicInteger();
|
AtomicInteger inserts = new AtomicInteger();
|
ElnRecordRepeatServiceImpl service = new ElnRecordRepeatServiceImpl(
|
mapper(ElnRecordMapper.class,
|
(method, arguments) -> "selectOne".equals(method) ? record : null),
|
mapper(ElnRecordRevisionMapper.class,
|
(method, arguments) -> "selectOne".equals(method) ? revision : null),
|
mapper(ElnRecordRepeatRowMapper.class, (method, arguments) -> {
|
if ("selectOne".equals(method)) {
|
return selections.incrementAndGet() == 2 ? pending : null;
|
}
|
if ("insert".equals(method)) {
|
inserts.incrementAndGet();
|
return 1;
|
}
|
if ("selectCount".equals(method)) return 1L;
|
return null;
|
}), repeatGroups(group("results", 0, 2)), allowFill(),
|
new RecordingTransactionManager(), ignored -> { },
|
new ElnRecordRepeatServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
});
|
ElnRecordForms.RepeatAppend form = new ElnRecordForms.RepeatAppend();
|
form.setGroupCode("results");
|
form.setOperationCode("operation-after-reopen");
|
|
ElnRecordVos.RepeatRow appended = service.append(ticket(), form);
|
|
assertNotEquals("row-pending", appended.getRowId());
|
assertEquals(Integer.valueOf(2), appended.getRowOrder());
|
assertEquals("PREPARED", appended.getStatus());
|
assertEquals(1, inserts.get());
|
}
|
|
@Test
|
public void rejectsAppendWhenAllocatedRowsReachTheTargetGroupMaximum() {
|
ElnRecordEntity record = record();
|
ElnRecordRevisionEntity revision = revision();
|
AtomicInteger inserts = new AtomicInteger();
|
ElnRecordRepeatRowMapper rows = mapper(ElnRecordRepeatRowMapper.class,
|
(method, arguments) -> {
|
if ("selectOne".equals(method)) return null;
|
if ("selectCount".equals(method)) return 2L;
|
if ("insert".equals(method)) inserts.incrementAndGet();
|
return null;
|
});
|
ElnRecordRepeatServiceImpl service = new ElnRecordRepeatServiceImpl(
|
mapper(ElnRecordMapper.class,
|
(method, arguments) -> "selectOne".equals(method) ? record : null),
|
mapper(ElnRecordRevisionMapper.class,
|
(method, arguments) -> "selectOne".equals(method) ? revision : null),
|
rows, repeatGroups(group("results", 0, 2)), allowFill(),
|
new RecordingTransactionManager(), ignored -> { },
|
new ElnRecordRepeatServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
});
|
ElnRecordForms.RepeatAppend form = new ElnRecordForms.RepeatAppend();
|
form.setGroupCode("results");
|
form.setOperationCode("operation-over-limit");
|
|
try {
|
service.append(ticket(), form);
|
fail("allocated rows at maxRows must reject append");
|
} catch (DataException expected) {
|
assertEquals("重复组已达到最大行数 2", expected.getMessage());
|
}
|
assertEquals(0, inserts.get());
|
}
|
|
@Test
|
public void preparesAndRestoresDeleteIntentIdempotentlyWithAudit() {
|
ElnRecordEntity record = record();
|
ElnRecordRevisionEntity revision = revision();
|
ElnRecordRepeatRowEntity row = activeRow();
|
AtomicInteger selections = new AtomicInteger();
|
AtomicInteger updates = new AtomicInteger();
|
ElnRecordRepeatRowMapper rows = mapper(ElnRecordRepeatRowMapper.class,
|
(method, arguments) -> {
|
if ("selectOne".equals(method)) {
|
int selection = selections.incrementAndGet();
|
if (selection == 1) return null;
|
return row;
|
}
|
if ("selectCount".equals(method)) return 2L;
|
if ("updateById".equals(method)) {
|
updates.incrementAndGet();
|
return 1;
|
}
|
return null;
|
});
|
List<AuditEventDTO> audits = new ArrayList<>();
|
ElnRecordRepeatServiceImpl service = new ElnRecordRepeatServiceImpl(
|
mapper(ElnRecordMapper.class,
|
(method, arguments) -> "selectOne".equals(method) ? record : null),
|
mapper(ElnRecordRevisionMapper.class,
|
(method, arguments) -> "selectOne".equals(method) ? revision : null),
|
rows, repeatGroups(group("results", 1, 10)), allowFill(),
|
new RecordingTransactionManager(), audits::add,
|
new ElnRecordRepeatServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
});
|
ElnRecordForms.RepeatDelete delete = new ElnRecordForms.RepeatDelete();
|
delete.setGroupCode("results");
|
delete.setRowId("row-1");
|
delete.setOperationCode("delete-1");
|
delete.setReason("录入错误");
|
|
ElnRecordVos.RepeatRow prepared = service.requestDelete(ticket(), delete);
|
ElnRecordVos.RepeatRow replay = service.requestDelete(ticket(), delete);
|
|
assertEquals("DELETE_PREPARED", prepared.getStatus());
|
assertEquals("delete-1", prepared.getDeleteOperationCode());
|
assertEquals("录入错误", replay.getDeleteReason());
|
assertEquals(1, updates.get());
|
|
ElnRecordForms.RepeatRestore restore = new ElnRecordForms.RepeatRestore();
|
restore.setGroupCode("results");
|
restore.setRowId("row-1");
|
restore.setOperationCode("delete-1");
|
ElnRecordVos.RepeatRow restored = service.restoreDelete(ticket(), restore);
|
|
assertEquals("ACTIVE", restored.getStatus());
|
assertEquals(null, restored.getDeleteOperationCode());
|
assertEquals(2, updates.get());
|
assertEquals(Arrays.asList("ELN_RECORD_REPEAT_DELETE_PREPARE",
|
"ELN_RECORD_REPEAT_DELETE_RESTORE"),
|
Arrays.asList(audits.get(0).getActionCode(), audits.get(1).getActionCode()));
|
assertNotEquals(audits.get(0).getClientEventId(), audits.get(1).getClientEventId());
|
}
|
|
@Test
|
public void rejectsDeleteIntentWhenItWouldFallBelowTheGroupMinimum() {
|
ElnRecordRepeatRowEntity row = activeRow();
|
AtomicInteger selections = new AtomicInteger();
|
ElnRecordRepeatServiceImpl service = new ElnRecordRepeatServiceImpl(
|
mapper(ElnRecordMapper.class,
|
(method, arguments) -> "selectOne".equals(method) ? record() : null),
|
mapper(ElnRecordRevisionMapper.class,
|
(method, arguments) -> "selectOne".equals(method) ? revision() : null),
|
mapper(ElnRecordRepeatRowMapper.class, (method, arguments) -> {
|
if ("selectOne".equals(method)) {
|
return selections.incrementAndGet() == 1 ? null : row;
|
}
|
if ("selectCount".equals(method)) return 1L;
|
return null;
|
}), repeatGroups(group("results", 1, 10)), allowFill(),
|
new RecordingTransactionManager(), ignored -> { },
|
new ElnRecordRepeatServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
});
|
ElnRecordForms.RepeatDelete delete = new ElnRecordForms.RepeatDelete();
|
delete.setGroupCode("results");
|
delete.setRowId("row-1");
|
delete.setOperationCode("delete-min");
|
delete.setReason("录入错误");
|
|
try {
|
service.requestDelete(ticket(), delete);
|
fail("delete intent below minRows must be rejected");
|
} catch (DataException expected) {
|
assertEquals("重复组至少保留 1 行", expected.getMessage());
|
}
|
}
|
|
private static ElnRecordRepeatRowEntity activeRow() {
|
ElnRecordRepeatRowEntity row = new ElnRecordRepeatRowEntity();
|
row.setId("snapshot-1");
|
row.setRowId("row-1");
|
row.setDianziJiluId("record-1");
|
row.setXiudingId("revision-1");
|
row.setMubanId("template-1");
|
row.setRepeatGroupCode("results");
|
row.setRowOrder(1);
|
row.setOperationCode("append-1");
|
row.setOperationStatus("ACTIVE");
|
row.setFTenantId("tenant-1");
|
row.setFDeleteMark(0);
|
return row;
|
}
|
|
private static ElnRecordEntity record() {
|
ElnRecordEntity record = new ElnRecordEntity();
|
record.setId("record-1");
|
record.setMubanId("template-1");
|
record.setDangqianXiudingId("revision-1");
|
return record;
|
}
|
|
private static ElnRecordRevisionEntity revision() {
|
ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity();
|
revision.setId("revision-1");
|
revision.setDianziJiluId("record-1");
|
revision.setJiluWendang("file-1");
|
revision.setBizStatus("DRAFT");
|
return revision;
|
}
|
|
private static ElnTemplateVos.RepeatGroup group(String code, int minRows,
|
int maxRows) {
|
ElnTemplateVos.RepeatGroup group = new ElnTemplateVos.RepeatGroup();
|
group.setGroupCode(code);
|
group.setMinRows(minRows);
|
group.setMaxRows(maxRows);
|
return group;
|
}
|
|
private static ElnTemplateRepeatGroupService repeatGroups(
|
ElnTemplateVos.RepeatGroup... groups) {
|
List<ElnTemplateVos.RepeatGroup> configured = Arrays.asList(groups);
|
return new ElnTemplateRepeatGroupService() {
|
@Override public List<ElnTemplateVos.RepeatGroup> list(
|
String tenantId, String templateId) { return configured; }
|
@Override public ElnTemplateVos.RepeatGroup ensureConfigured(
|
String tenantId, String templateId, String groupCode,
|
Integer minRows, Integer maxRows, String stepId,
|
String operatorId) { return null; }
|
@Override public void copyForVersion(String tenantId, String sourceTemplateId,
|
String targetTemplateId) { }
|
@Override public void afterBindingsActivated(String tenantId,
|
String sourceTemplateId, String targetTemplateId,
|
Map<String, String> bindingIdMap) { }
|
};
|
}
|
|
private static ElnRecordFillAccessService allowFill() {
|
return new ElnRecordFillAccessService() {
|
@Override public boolean canFill(String tenantId, String userId,
|
String recordId, String fileId) { return true; }
|
@Override public RecordFillSaveOperation preflight(RecordFillSaveCommand command) { return null; }
|
@Override public RecordFillSaveOperation commit(RecordFillSaveCommand command) { return null; }
|
@Override public RecordFillSaveOperation abort(RecordFillSaveCommand command) { return null; }
|
@Override public RecordFillSaveOperation saveStatus(String tenantId,
|
String operationCode) { return null; }
|
};
|
}
|
|
private static ElnRecordFillTicketService.FillTicketContext ticket() {
|
Map<String, String> payload = new LinkedHashMap<>();
|
payload.put("scope", ElnRecordFillTicketService.SCOPE);
|
payload.put("tenantId", "tenant-1");
|
payload.put("userId", "user-1");
|
payload.put("fileId", "file-1");
|
payload.put("bizDataId", "record-1");
|
payload.put("bizModule", ElnRecordFillTicketService.BIZ_MODULE);
|
payload.put("bizScene", ElnRecordFillTicketService.BIZ_SCENE);
|
payload.put("pluginGuid", "asc.{7F4E8F35-5D66-47F8-A5B4-6AB3FAACF565}");
|
return new ElnRecordFillTicketService(
|
ignored -> JsonUtil.getObjectToString(payload)).parse("ticket");
|
}
|
|
@SuppressWarnings("unchecked")
|
private static <T> T mapper(Class<T> type, MapperHandler handler) {
|
return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type},
|
(proxy, method, arguments) -> {
|
if (method.getDeclaringClass() == Object.class) {
|
if ("toString".equals(method.getName())) return type.getSimpleName() + "Fake";
|
if ("hashCode".equals(method.getName())) return System.identityHashCode(proxy);
|
if ("equals".equals(method.getName())) return proxy == arguments[0];
|
}
|
Object result = handler.invoke(method.getName(), arguments);
|
if (result != null) return result;
|
if (method.getReturnType().isPrimitive()) return 0;
|
return null;
|
});
|
}
|
|
@FunctionalInterface
|
private interface MapperHandler {
|
Object invoke(String method, Object[] arguments);
|
}
|
|
private static final class RecordingTransactionManager
|
implements PlatformTransactionManager {
|
private final AtomicInteger commits = new AtomicInteger();
|
|
@Override
|
public TransactionStatus getTransaction(TransactionDefinition definition) {
|
return new SimpleTransactionStatus();
|
}
|
|
@Override
|
public void commit(TransactionStatus status) {
|
commits.incrementAndGet();
|
}
|
|
@Override
|
public void rollback(TransactionStatus status) { }
|
}
|
}
|