package jnpf.limsService.provider;
|
|
import org.junit.Test;
|
import jnpf.exception.DataException;
|
|
import java.util.Collections;
|
import java.util.Arrays;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.time.OffsetDateTime;
|
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.stream.Collectors;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.fail;
|
|
public class LimsElnSourceCatalogTest {
|
|
@Test
|
public void savesANewSourceDraftWithoutMakingItSelectable() {
|
LimsElnSourceCatalog catalog = catalog(newStore());
|
|
LimsElnSourceCatalog.Version draft = catalog.createDraft(input("sample.original_batch"));
|
|
assertEquals("DRAFT", draft.getStatus());
|
assertEquals("sample.original_batch", catalog.detail(draft.getId()).getPropertyCode());
|
assertEquals("originalBatch", catalog.definition(draft.getId()).getFieldId());
|
assertTrue(catalog.catalog().isEmpty());
|
}
|
|
@Test
|
public void publishesOnlyAfterReadingAnAuthorizedSampleAndFreezesTheDefinition() {
|
LimsElnSourceCatalog catalog = new LimsElnSourceCatalog(newStore(),
|
() -> new LimsElnSourceCatalog.Actor("0", "field-manager", true),
|
(definition, actor, sampleId) -> {
|
assertEquals("work-item-a", sampleId);
|
assertEquals("originalBatch", definition.getFieldId());
|
return new LimsElnSourceCatalog.Preview(Collections.singletonList("ORIGINAL-001"),
|
"{\"root\":\"task\",\"field\":\"pihao_yuanchang\",\"metadataRevision\":1}");
|
});
|
LimsElnSourceCatalog.Version draft = catalog.createDraft(input("sample.original_batch"));
|
|
LimsElnSourceCatalog.Version published = catalog.publish(draft.getId(), 0, "work-item-a");
|
|
assertEquals("PUBLISHED", published.getStatus());
|
assertEquals(published, catalog.catalog().get(0));
|
assertEquals("work-item-a", published.getValidationSampleId());
|
assertEquals("field-manager", published.getPublishedBy());
|
assertEquals("{\"root\":\"task\",\"field\":\"pihao_yuanchang\",\"metadataRevision\":1}",
|
published.getFrozenDefinitionJson());
|
}
|
|
@Test
|
public void doesNotReuseAnExistingPropertyIdentityAsANewSource() {
|
LimsElnSourceCatalog catalog = catalog(newStore());
|
catalog.createDraft(input("sample.original_batch"));
|
|
try {
|
catalog.createDraft(input("sample.original_batch"));
|
fail("A stable property code must not be reused");
|
} catch (DataException expected) {
|
assertEquals("Source property already exists", expected.getMessage());
|
}
|
}
|
|
@Test
|
public void newPublicationKeepsTheOldDefinitionAndOnlyOffersTheLatestVersion() {
|
LimsElnSourceCatalog catalog = publishingCatalog(newStore());
|
LimsElnSourceCatalog.Version first = catalog.publish(
|
catalog.createDraft(input("sample.original_batch")).getId(), 0, "work-item-a");
|
LimsElnSourceCatalog.Draft changed = input("sample.original_batch");
|
changed.getDefinition().setFieldId("supplierBatch");
|
|
LimsElnSourceCatalog.Version next = catalog.newVersion(first.getId(), changed);
|
assertEquals(first, catalog.catalog().get(0));
|
LimsElnSourceCatalog.Version second = catalog.publish(next.getId(), 0, "work-item-a");
|
|
assertEquals(2, second.getRevision());
|
assertEquals(Collections.singletonList(second), catalog.catalog());
|
assertEquals(first, catalog.published("sample.original_batch", 1));
|
assertEquals("originalBatch", catalog.definition(first.getId()).getFieldId());
|
assertEquals("supplierBatch", catalog.definition(second.getId()).getFieldId());
|
}
|
|
@Test
|
public void disablingRemovesNewChoicesButKeepsPublishedVersionsReadable() {
|
LimsElnSourceCatalog catalog = publishingCatalog(newStore());
|
LimsElnSourceCatalog.Version published = catalog.publish(
|
catalog.createDraft(input("sample.original_batch")).getId(), 0, "work-item-a");
|
|
catalog.setEnabled("sample.original_batch", false);
|
|
assertTrue(catalog.catalog().isEmpty());
|
assertEquals(published, catalog.published("sample.original_batch", 1));
|
catalog.setEnabled("sample.original_batch", true);
|
assertEquals(Collections.singletonList(published), catalog.catalog());
|
}
|
|
@Test
|
public void updatesDraftsWithOptimisticLockingAndNeverUpdatesPublishedDefinitions() {
|
LimsElnSourceCatalog catalog = publishingCatalog(newStore());
|
LimsElnSourceCatalog.Version draft = catalog.createDraft(input("sample.original_batch"));
|
LimsElnSourceCatalog.Draft changed = input("sample.original_batch");
|
changed.getDefinition().setFieldId("supplierBatch");
|
|
LimsElnSourceCatalog.Version updated = catalog.updateDraft(draft.getId(), 0, changed);
|
|
assertEquals(1, updated.getEditVersion());
|
assertEquals("supplierBatch", catalog.definition(draft.getId()).getFieldId());
|
try {
|
catalog.updateDraft(draft.getId(), 0, input("sample.original_batch"));
|
fail("Stale editors must not overwrite a draft");
|
} catch (DataException expected) {
|
assertEquals("Source version has changed", expected.getMessage());
|
}
|
LimsElnSourceCatalog.Version published = catalog.publish(draft.getId(), updated.getEditVersion(), "work-item-a");
|
try {
|
catalog.updateDraft(draft.getId(), published.getEditVersion(), changed);
|
fail("A published source must not be edited in place");
|
} catch (DataException expected) {
|
assertEquals("Published source is immutable", expected.getMessage());
|
}
|
assertEquals(published, catalog.published("sample.original_batch", 1));
|
}
|
|
@Test
|
public void recordCallersCanResolveFixedVersionsWithoutReceivingMaintenancePermission() {
|
LimsElnSourceCatalog.Store store = newStore();
|
LimsElnSourceCatalog manager = publishingCatalog(store);
|
LimsElnSourceCatalog.Version published = manager.publish(
|
manager.createDraft(input("sample.original_batch")).getId(), 0, "work-item-a");
|
LimsElnSourceCatalog recordCaller = new LimsElnSourceCatalog(store,
|
() -> new LimsElnSourceCatalog.Actor("0", "inspector", false));
|
|
assertEquals(published, recordCaller.published("sample.original_batch", 1));
|
|
try {
|
recordCaller.createDraft(input("sample.other"));
|
fail("A record caller must not gain field management permission");
|
} catch (DataException expected) {
|
assertEquals("Field management permission required", expected.getMessage());
|
}
|
}
|
|
@Test
|
public void validatesNativeScalarTypesWithoutReinterpretingTextOrRoundingNumbers() {
|
Map<String, Object> samples = new LinkedHashMap<>();
|
samples.put("DECIMAL", new BigDecimal("1.234567890123456789"));
|
samples.put("DATE", LocalDate.of(2026, 9, 5));
|
samples.put("DATETIME", OffsetDateTime.parse("2026-09-05T12:30:00+08:00"));
|
samples.put("BOOLEAN", Boolean.TRUE);
|
for (Map.Entry<String, Object> sample : samples.entrySet()) {
|
LimsElnSourceCatalog catalog = new LimsElnSourceCatalog(newStore(),
|
() -> new LimsElnSourceCatalog.Actor("0", "field-manager", true),
|
(definition, actor, sampleId) -> new LimsElnSourceCatalog.Preview(
|
Collections.singletonList(sample.getValue()), "{\"metadataRevision\":1}"));
|
LimsElnSourceCatalog.Draft input = input("sample.typed_value_" + sample.getKey());
|
LimsElnSourceCatalog.Version draft = catalog.createDraft(new LimsElnSourceCatalog.Draft(
|
input.getPropertyCode(), input.getDisplayName(), sample.getKey(), input.getDefinition()));
|
|
assertEquals(sample.getKey(), catalog.publish(draft.getId(), 0, "work-item-a").getDataType());
|
}
|
}
|
|
@Test
|
public void blankSampleValuesDoNotCountAsSuccessfulPublicationValidation() {
|
LimsElnSourceCatalog catalog = new LimsElnSourceCatalog(newStore(),
|
() -> new LimsElnSourceCatalog.Actor("0", "field-manager", true),
|
(definition, actor, sampleId) -> new LimsElnSourceCatalog.Preview(
|
Collections.singletonList(" "), "{\"metadataRevision\":1}"));
|
LimsElnSourceCatalog.Version draft = catalog.createDraft(input("sample.original_batch"));
|
|
try {
|
catalog.publish(draft.getId(), 0, "work-item-a");
|
fail("Blank data is not a successful sample");
|
} catch (DataException expected) {
|
assertEquals("Source preview must resolve one value", expected.getMessage());
|
}
|
assertEquals(draft, catalog.detail(draft.getId()));
|
assertTrue(catalog.catalog().isEmpty());
|
}
|
|
@Test
|
public void anOldEditorCannotPublishSomeoneElsesUpdatedDraft() {
|
LimsElnSourceCatalog catalog = publishingCatalog(newStore());
|
LimsElnSourceCatalog.Version draft = catalog.createDraft(input("sample.original_batch"));
|
LimsElnSourceCatalog.Draft changed = input("sample.original_batch");
|
changed.getDefinition().setFieldId("supplierBatch");
|
LimsElnSourceCatalog.Version updated = catalog.updateDraft(draft.getId(), 0, changed);
|
|
try {
|
catalog.publish(draft.getId(), 0, "work-item-a");
|
fail("Publication must check the editor version before reading data");
|
} catch (DataException expected) {
|
assertEquals("Source version has changed", expected.getMessage());
|
}
|
assertEquals(updated, catalog.detail(draft.getId()));
|
}
|
|
@Test
|
public void failedOrAmbiguousPreviewsNeverPublishTheDraft() {
|
List<LimsElnSourceCatalog.SourceReader> readers = Arrays.asList(
|
(definition, actor, sampleId) -> { throw new DataException("Sample is not authorized"); },
|
(definition, actor, sampleId) -> new LimsElnSourceCatalog.Preview(
|
Arrays.asList("same-value", "same-value"), "{\"metadataRevision\":1}"),
|
(definition, actor, sampleId) -> new LimsElnSourceCatalog.Preview(
|
Collections.singletonList(123), "{\"metadataRevision\":1}"),
|
(definition, actor, sampleId) -> new LimsElnSourceCatalog.Preview(
|
Collections.singletonList("value"), " "),
|
(definition, actor, sampleId) -> new LimsElnSourceCatalog.Preview(
|
Collections.singletonList("value"), "{}"));
|
for (int i = 0; i < readers.size(); i++) {
|
LimsElnSourceCatalog catalog = new LimsElnSourceCatalog(newStore(),
|
() -> new LimsElnSourceCatalog.Actor("0", "field-manager", true), readers.get(i));
|
LimsElnSourceCatalog.Version draft = catalog.createDraft(input("sample.invalid_" + i));
|
try {
|
catalog.publish(draft.getId(), 0, "work-item-a");
|
fail("Unsuccessful validation must never publish a source");
|
} catch (DataException expected) {
|
assertEquals(draft, catalog.detail(draft.getId()));
|
}
|
assertTrue(catalog.catalog().isEmpty());
|
}
|
}
|
|
@Test
|
public void draftMetadataMustHaveABusinessNameAndASupportedScalarType() {
|
LimsElnSourceCatalog catalog = catalog(newStore());
|
LimsElnSourceCatalog.Draft source = input("sample.original_batch");
|
for (LimsElnSourceCatalog.Draft invalid : Arrays.asList(
|
new LimsElnSourceCatalog.Draft(source.getPropertyCode(), " ", "TEXT", source.getDefinition()),
|
new LimsElnSourceCatalog.Draft(source.getPropertyCode(), "Name", "SCRIPT", source.getDefinition()))) {
|
try {
|
catalog.createDraft(invalid);
|
fail("Invalid metadata must be rejected before persistence");
|
} catch (DataException expected) {
|
assertTrue(catalog.catalog().isEmpty());
|
}
|
}
|
assertEquals("DRAFT", catalog.createDraft(source).getStatus());
|
}
|
|
@Test
|
public void editingDuringPreviewInvalidatesThePendingPublication() {
|
AtomicReference<LimsElnSourceCatalog> reference = new AtomicReference<>();
|
AtomicReference<String> draftId = new AtomicReference<>();
|
LimsElnSourceCatalog catalog = new LimsElnSourceCatalog(newStore(),
|
() -> new LimsElnSourceCatalog.Actor("0", "field-manager", true),
|
(definition, actor, sampleId) -> {
|
LimsElnSourceCatalog.Draft changed = input("sample.original_batch");
|
changed.getDefinition().setFieldId("supplierBatch");
|
reference.get().updateDraft(draftId.get(), 0, changed);
|
return new LimsElnSourceCatalog.Preview(
|
Collections.singletonList("ORIGINAL-001"), "{\"metadataRevision\":1}");
|
});
|
reference.set(catalog);
|
draftId.set(catalog.createDraft(input("sample.original_batch")).getId());
|
|
try {
|
catalog.publish(draftId.get(), 0, "work-item-a");
|
fail("Validation of an old definition must not publish an updated draft");
|
} catch (DataException expected) {
|
assertEquals("Source version has changed", expected.getMessage());
|
}
|
assertEquals("supplierBatch", catalog.definition(draftId.get()).getFieldId());
|
assertTrue(catalog.catalog().isEmpty());
|
}
|
|
private static LimsElnSourceCatalog publishingCatalog(LimsElnSourceCatalog.Store store) {
|
return new LimsElnSourceCatalog(store,
|
() -> new LimsElnSourceCatalog.Actor("0", "field-manager", true),
|
(definition, actor, sampleId) -> new LimsElnSourceCatalog.Preview(
|
Collections.singletonList("ORIGINAL-001"), "{\"metadataRevision\":1}"));
|
}
|
|
private static LimsElnSourceCatalog catalog(LimsElnSourceCatalog.Store store) {
|
return new LimsElnSourceCatalog(store,
|
() -> new LimsElnSourceCatalog.Actor("0", "field-manager", true));
|
}
|
|
private static LimsElnSourceCatalog.Draft input(String code) {
|
LimsElnSourceCatalog.Definition definition = new LimsElnSourceCatalog.Definition();
|
definition.setObjectId("testRequest");
|
definition.setFieldId("originalBatch");
|
return new LimsElnSourceCatalog.Draft(code, "Original batch", "TEXT", definition);
|
}
|
|
protected LimsElnSourceCatalog.Store newStore() {
|
return new MemoryStore();
|
}
|
|
private static final class MemoryStore implements LimsElnSourceCatalog.Store {
|
private final Map<String, LimsElnSourceCatalog.Version> versions = new LinkedHashMap<>();
|
private final Map<String, Boolean> enabled = new LinkedHashMap<>();
|
|
@Override
|
public boolean enabled(String tenantId, String code) {
|
return enabled.getOrDefault(tenantId + ":" + code, true);
|
}
|
|
@Override
|
public Map<String, Boolean> enabledStates(String tenantId) {
|
Map<String, Boolean> result = new LinkedHashMap<>();
|
versions.values().stream().filter(version -> tenantId.equals(version.getTenantId()))
|
.forEach(version -> result.put(version.getPropertyCode(), enabled(tenantId, version.getPropertyCode())));
|
return result;
|
}
|
|
@Override
|
public void setEnabled(String tenantId, String code, boolean value) {
|
enabled.put(tenantId + ":" + code, value);
|
}
|
|
@Override
|
public void insert(LimsElnSourceCatalog.Version version) {
|
versions.put(version.getId(), version);
|
}
|
|
@Override
|
public boolean replaceDraft(LimsElnSourceCatalog.Version expected,
|
LimsElnSourceCatalog.Version replacement) {
|
return versions.replace(expected.getId(), expected, replacement);
|
}
|
|
@Override
|
public LimsElnSourceCatalog.Version find(String tenantId, String id) {
|
LimsElnSourceCatalog.Version version = versions.get(id);
|
return version != null && tenantId.equals(version.getTenantId()) ? version : null;
|
}
|
|
@Override
|
public List<LimsElnSourceCatalog.Version> list(String tenantId) {
|
return versions.values().stream().filter(v -> tenantId.equals(v.getTenantId()))
|
.collect(Collectors.toList());
|
}
|
}
|
}
|