From 7ed2d9ed3b6743a8a19467bde8b6b71550fe1f2d Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:42:08 +0000 Subject: [PATCH 01/13] x1499 changing how blocks are represented Previously info about a *block* was stored in a slot/sample link table with an int for the highest section taken from the block. Block labware was required to have exactly one slot and exactly one sample. In the future we will support labware representing a block with multiple samples in, where each sample may be spread across multiple slots. We represent that by marking a *sample* as a block by it having a non-null blockHighestSection field. Labware can contain multiple block-samples. Block-samples can be shared across multiple slots in a piece of labware. This commit has a small change to the graphql schema but other outward facing behaviour should be largely unchanged. --- .../uk/ac/sanger/sccp/stan/model/Sample.java | 30 ++++++- .../uk/ac/sanger/sccp/stan/model/Slot.java | 34 +------- .../service/BlockProcessingServiceImp.java | 15 ++-- .../sccp/stan/service/LabwareService.java | 17 ++-- .../service/ParaffinProcessingServiceImp.java | 85 +++++++++++++++---- .../sccp/stan/service/SampleService.java | 42 --------- .../stan/service/UnreleaseServiceImp.java | 41 +++++---- .../confirm/ConfirmSectionServiceImp.java | 24 +++--- .../ConfirmSectionValidationServiceImp.java | 2 +- .../service/register/RegisterServiceImp.java | 4 +- .../releasefile/ReleaseFileService.java | 8 +- .../resources/db/changelog/changelog-4.00.xml | 13 +++ .../db/changelog/changelog-master.xml | 1 + src/main/resources/schema.graphqls | 6 +- .../uk/ac/sanger/sccp/stan/EntityCreator.java | 29 ++++--- .../uk/ac/sanger/sccp/stan/EntityFactory.java | 13 +-- .../TestFileBlockRegister.java | 10 +-- .../TestOrientationQCMutation.java | 4 +- .../TestPlanAndRecordSectionMutations.java | 12 +-- .../TestRegisterOriginalSamplesMutation.java | 2 +- .../integrationtest/TestReleaseMutation.java | 7 +- .../sccp/stan/repo/TestPlanActionRepo.java | 10 +-- .../sccp/stan/repo/TestPlanOperationRepo.java | 3 +- .../sccp/stan/repo/TestReleaseRepo.java | 6 +- .../service/TestBlockProcessingService.java | 1 - .../stan/service/TestLabwareValidator.java | 8 +- .../TestParaffinProcessingService.java | 62 +++++++++----- .../sccp/stan/service/TestSampleService.java | 73 ---------------- .../stan/service/TestUnreleaseService.java | 57 ++++++------- .../service/flag/TestFlagLookupService.java | 6 +- .../service/history/TestHistoryService.java | 8 +- .../label/TestLabwareLabelDataService.java | 2 +- .../service/operation/TestAliquotService.java | 8 +- .../operation/TestInPlaceOpService.java | 6 +- .../confirm/TestConfirmOperationService.java | 6 +- .../confirm/TestConfirmSectionService.java | 40 +++++---- .../TestConfirmSectionValidationService.java | 6 +- .../operation/plan/TestPlanValidation.java | 10 +-- .../service/register/TestRegisterService.java | 20 ++--- .../releasefile/TestReleaseFileService.java | 28 ++---- 40 files changed, 353 insertions(+), 406 deletions(-) delete mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/SampleService.java create mode 100644 src/main/resources/db/changelog/changelog-4.00.xml delete mode 100644 src/test/java/uk/ac/sanger/sccp/stan/service/TestSampleService.java diff --git a/src/main/java/uk/ac/sanger/sccp/stan/model/Sample.java b/src/main/java/uk/ac/sanger/sccp/stan/model/Sample.java index 150878431..0b44af3c0 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/model/Sample.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/model/Sample.java @@ -1,10 +1,10 @@ package uk.ac.sanger.sccp.stan.model; -import com.google.common.base.MoreObjects; - import javax.persistence.*; import java.util.Objects; +import static uk.ac.sanger.sccp.utils.BasicUtils.describe; + /** * A sample is a piece of some tissue that has some particular state and can be located inside slots in labware, * and used in operations. @@ -20,6 +20,7 @@ public class Sample { private Tissue tissue; @ManyToOne private BioState bioState; + private Integer blockHighestSection; public Sample() {} @@ -62,6 +63,24 @@ public void setBioState(BioState bioState) { this.bioState = bioState; } + public Integer getBlockHighestSection() { + return this.blockHighestSection; + } + + public void setBlockHighestSection(Integer blockHighestSection) { + this.blockHighestSection = blockHighestSection; + } + + public boolean isBlock() { + return blockHighestSection != null; + } + + public static Sample newBlock(Integer id, Tissue tissue, BioState bs, Integer blockHighestSection) { + Sample sample = new Sample(id, null, tissue, bs); + sample.setBlockHighestSection(blockHighestSection); + return sample; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -70,7 +89,9 @@ public boolean equals(Object o) { return (Objects.equals(this.id, that.id) && Objects.equals(this.section, that.section) && Objects.equals(this.tissue, that.tissue) - && Objects.equals(this.bioState, that.bioState)); + && Objects.equals(this.bioState, that.bioState) + && Objects.equals(this.blockHighestSection, that.blockHighestSection) + ); } @Override @@ -80,11 +101,12 @@ public int hashCode() { @Override public String toString() { - return MoreObjects.toStringHelper(this) + return describe(this) .add("id", id) .add("section", section) .add("tissue", tissue) .add("bioState", bioState) + .addIfNotNull("blockHighestSection", blockHighestSection) .toString(); } } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/model/Slot.java b/src/main/java/uk/ac/sanger/sccp/stan/model/Slot.java index b67684445..d38a69796 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/model/Slot.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/model/Slot.java @@ -12,7 +12,6 @@ * @author dr6 */ @Entity -@SecondaryTable(name = "block_info", pkJoinColumns = @PrimaryKeyJoinColumn(name = "slot_id")) public class Slot { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -25,22 +24,14 @@ public class Slot { @JoinTable(name = "slot_sample", inverseJoinColumns = @JoinColumn(name="sample_id")) private List samples; - @Column(table = "block_info", name = "sample_id") - private Integer blockSampleId; - @Column(table = "block_info", name = "highest_section") - private Integer blockHighestSection; - public Slot() { this.samples = new ArrayList<>(); } - public Slot(Integer id, Integer labwareId, Address address, List samples, Integer blockSampleId, - Integer blockHighestSection) { + public Slot(Integer id, Integer labwareId, Address address, List samples) { this.id = id; this.labwareId = labwareId; this.address = address; - this.blockSampleId = blockSampleId; - this.blockHighestSection = blockHighestSection; setSamples(samples); } @@ -83,24 +74,8 @@ public void addSample(Sample sample) { this.samples.add(sample); } - public Integer getBlockSampleId() { - return this.blockSampleId; - } - - public void setBlockSampleId(Integer blockSampleId) { - this.blockSampleId = blockSampleId; - } - - public Integer getBlockHighestSection() { - return this.blockHighestSection; - } - - public void setBlockHighestSection(Integer blockHighestSection) { - this.blockHighestSection = blockHighestSection; - } - public boolean isBlock() { - return (this.blockSampleId != null); + return (this.samples.stream().anyMatch(Sample::isBlock)); } @Override @@ -112,8 +87,7 @@ public boolean equals(Object o) { && Objects.equals(this.address, that.address) && Objects.equals(this.labwareId, that.labwareId) && Objects.equals(this.samples, that.samples) - && Objects.equals(this.blockSampleId, that.blockSampleId) - && Objects.equals(this.blockHighestSection, that.blockHighestSection)); + ); } @Override @@ -128,8 +102,6 @@ public String toString() { .add("labwareId", labwareId) .add("address", address) .add("samples", samples) - .add("blockSampleId", blockSampleId) - .add("blockHighestSection", blockHighestSection) .toString(); } } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/BlockProcessingServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/BlockProcessingServiceImp.java index 96781ce80..132f69e9d 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/BlockProcessingServiceImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/BlockProcessingServiceImp.java @@ -12,8 +12,7 @@ import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockLabware; import uk.ac.sanger.sccp.stan.service.store.StoreService; import uk.ac.sanger.sccp.stan.service.work.WorkService; -import uk.ac.sanger.sccp.utils.BasicUtils; -import uk.ac.sanger.sccp.utils.UCMap; +import uk.ac.sanger.sccp.utils.*; import java.util.*; import java.util.function.Function; @@ -406,6 +405,7 @@ public Tissue getOrCreateTissue(Tissue original, String replicate, Medium medium /** * Creates a new sample, from a new tissue if necessary. + * The new sample will represent a new block, so it will have the blockHighestSection field set to 0. * @param block the specification of the block * @param sourceLabware the source labware for the new block * @param bs the bio state for the new block @@ -417,7 +417,7 @@ public Sample createSample(TissueBlockLabware block, Labware sourceLabware, BioS .map(Sample::getTissue) .orElseThrow(); Tissue tissue = getOrCreateTissue(original, block.getReplicate(), medium); - return sampleRepo.save(new Sample(null, null, tissue, bs)); + return sampleRepo.save(Sample.newBlock(null, tissue, bs, 0)); } /** @@ -428,10 +428,9 @@ public Sample createSample(TissueBlockLabware block, Labware sourceLabware, BioS * @return a list of labware for respective blocks of the request */ public List createDestinations(TissueBlockRequest request, List samples, UCMap lwTypes) { - final var sampleIter = samples.iterator(); - return request.getLabware().stream() - .map(block -> createDestination(lwTypes.get(block.getLabwareType()), sampleIter.next(), block.getPreBarcode())) - .collect(toList()); + return Zip.of(request.getLabware().stream(), samples.stream()) + .map((tbl, sam) -> createDestination(lwTypes.get(tbl.getLabwareType()), sam, tbl.getPreBarcode())) + .toList(); } /** @@ -445,8 +444,6 @@ public Labware createDestination(LabwareType lwType, Sample sample, String preBa Labware lw = lwService.create(lwType, preBarcode, preBarcode); Slot slot = lw.getFirstSlot(); slot.addSample(sample); - slot.setBlockSampleId(sample.getId()); - slot.setBlockHighestSection(0); slotRepo.save(slot); return lw; } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/LabwareService.java b/src/main/java/uk/ac/sanger/sccp/stan/service/LabwareService.java index 1a69f1712..514f71104 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/LabwareService.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/LabwareService.java @@ -13,6 +13,7 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static uk.ac.sanger.sccp.utils.BasicUtils.repr; +import static uk.ac.sanger.sccp.utils.BasicUtils.stream; /** * Service to help with labware, including creating labware with appropriate slots. @@ -90,20 +91,20 @@ public List create(LabwareType labwareType, int number) { List barcodes = barcodeIntRepo.createStanBarcodes(number); List newLabware = barcodes.stream() .map(bc -> new Labware(null, bc, labwareType, null)) - .collect(toList()); + .toList(); Iterable savedLabware = labwareRepo.saveAll(newLabware); final int numRows = labwareType.getNumRows(); final int numColumns = labwareType.getNumColumns(); - final List newSlots = Streams.stream(savedLabware).flatMap(lw -> - Address.stream(numRows, numColumns).map(address -> - new Slot(null, lw.getId(), address, null, null, null) - ) - ).collect(toList()); + final List newSlots = stream(savedLabware).flatMap(lw -> { + assert lw != null; + return Address.stream(numRows, numColumns).map(address -> + new Slot(null, lw.getId(), address, null)); + }).toList(); slotRepo.saveAll(newSlots); return Streams.stream(savedLabware) .peek(entityManager::refresh) - .collect(toList()); + .toList(); } /** @@ -121,7 +122,7 @@ public Labware create(Labware unsaved) { final int numRows = labwareType.getNumRows(); final int numColumns = labwareType.getNumColumns(); List newSlots = Address.stream(numRows, numColumns) - .map(address -> new Slot(null, labware.getId(), address, null, null, null)) + .map(address -> new Slot(null, labware.getId(), address, null)) .collect(toList()); slotRepo.saveAll(newSlots); entityManager.refresh(labware); diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/ParaffinProcessingServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/ParaffinProcessingServiceImp.java index b02d116e1..f81b8b2ce 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/ParaffinProcessingServiceImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/ParaffinProcessingServiceImp.java @@ -26,15 +26,16 @@ public class ParaffinProcessingServiceImp implements ParaffinProcessingService { private final OperationTypeRepo opTypeRepo; private final MediumRepo mediumRepo; private final TissueRepo tissueRepo; + private final SlotRepo slotRepo; + private final SampleRepo sampleRepo; private final WorkService workService; private final OperationService opService; private final CommentValidationService commentValidationService; private final LabwareValidatorFactory lwValFactory; - private final SlotRepo slotRepo; @Autowired public ParaffinProcessingServiceImp(LabwareRepo lwRepo, OperationCommentRepo opComRepo, OperationTypeRepo opTypeRepo, - MediumRepo mediumRepo, TissueRepo tissueRepo, SlotRepo slotRepo, + MediumRepo mediumRepo, TissueRepo tissueRepo, SlotRepo slotRepo, SampleRepo sampleRepo, WorkService workService, OperationService opService, CommentValidationService commentValidationService, LabwareValidatorFactory lwValFactory) { @@ -44,6 +45,7 @@ public ParaffinProcessingServiceImp(LabwareRepo lwRepo, OperationCommentRepo opC this.mediumRepo = mediumRepo; this.tissueRepo = tissueRepo; this.slotRepo = slotRepo; + this.sampleRepo = sampleRepo; this.workService = workService; this.opService = opService; this.commentValidationService = commentValidationService; @@ -150,8 +152,8 @@ public Medium loadMedium(Collection problems, String mediumName) { */ public OperationResult record(User user, Collection labware, Work work, Comment comment, Medium medium) { updateMedium(labware, medium); - List ops = createOps(user, labware); - createBlocks(labware); + List changes = createBlocks(labware); + List ops = createOps(user, changes); workService.link(work, ops); recordComments(comment, ops); return new OperationResult(ops, labware); @@ -180,32 +182,80 @@ public void updateMedium(Collection labware, Medium medium) { * Converts labware to blocks, if they aren't already * @param labware the labware to make into blocks, if they aren't already */ - public void createBlocks(Collection labware) { + List createBlocks(Collection labware) { + List samplesToSave = new ArrayList<>(); List slotsToSave = new ArrayList<>(); + List changes = new ArrayList<>(labware.size()); for (Labware lw : labware) { - Slot slot = lw.getFirstSlot(); - if (!slot.isBlock()) { - slot.setBlockSampleId(slot.getSamples().getFirst().getId()); - slot.setBlockHighestSection(0); - slotsToSave.add(slot); + Map newSampleMap = new HashMap<>(); + for (Slot slot : lw.getSlots()) { + if (slot.getSamples().isEmpty()) { + continue; + } + boolean changed = false; + List newSamples = new ArrayList<>(slot.getSamples().size()); + for (Sample sam : slot.getSamples()) { + Sample blockSample; + if (!sam.isBlock()) { + blockSample = newSampleMap.get(sam); + if (blockSample==null) { + blockSample = Sample.newBlock(null, sam.getTissue(), sam.getBioState(), 0); + newSampleMap.put(sam, blockSample); + } + changed = true; + } else { + blockSample = sam; + } + newSamples.add(blockSample); + changes.add(new BlockChange(slot, sam, blockSample)); + } + if (changed) { + slot.setSamples(newSamples); + slotsToSave.add(slot); + } } + samplesToSave.addAll(newSampleMap.values()); + } + if (!samplesToSave.isEmpty()) { + // This should update the sample objects with ids + sampleRepo.saveAll(samplesToSave); } if (!slotsToSave.isEmpty()) { slotRepo.saveAll(slotsToSave); } + return changes; } /** - * Creates operations on the given labware + * Creates operations * @param user user responsible - * @param labware the labware + * @param changes the slots and affected samples * @return the operations created */ - public List createOps(User user, Collection labware) { + List createOps(User user, Collection changes) { OperationType opType = opTypeRepo.getByName("Paraffin processing"); - return labware.stream() - .map(lw -> opService.createOperationInPlace(opType, user, lw, null, null)) - .collect(toList()); + Map> lwChanges = new LinkedHashMap<>(); + for (BlockChange change : changes) { + Integer lwId = change.slot.getLabwareId(); + lwChanges.computeIfAbsent(lwId, id -> new ArrayList<>()).add(change); + } + return lwChanges.values().stream() + .map(cs -> createOp(user, opType, cs)) + .toList(); + } + + /** + * Creates an operation describing the given changes + * @param user the user responsible + * @param opType the operation type + * @param changes slots and samples + * @return the created operation + */ + Operation createOp(User user, OperationType opType, Collection changes) { + List actions = changes.stream() + .map(c -> new Action(null, null, c.slot, c.slot, c.blockSample, c.originalSample)) + .toList(); + return opService.createOperation(opType, user, actions, null); } /** @@ -220,4 +270,7 @@ public void recordComments(Comment comment, Collection ops) { .collect(toList()); opComRepo.saveAll(opComs); } + + /** A slot and the samples involved in its action */ + record BlockChange(Slot slot, Sample originalSample, Sample blockSample) {} } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/SampleService.java b/src/main/java/uk/ac/sanger/sccp/stan/service/SampleService.java deleted file mode 100644 index e0d902417..000000000 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/SampleService.java +++ /dev/null @@ -1,42 +0,0 @@ -package uk.ac.sanger.sccp.stan.service; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import uk.ac.sanger.sccp.stan.model.Sample; -import uk.ac.sanger.sccp.stan.model.Slot; -import uk.ac.sanger.sccp.stan.repo.PlanActionRepo; -import uk.ac.sanger.sccp.stan.repo.SlotRepo; - -/** - * @author dr6 - */ -@Service -public class SampleService { - private final PlanActionRepo planActionRepo; - private final SlotRepo slotRepo; - - @Autowired - public SampleService(PlanActionRepo planActionRepo, SlotRepo slotRepo) { - this.planActionRepo = planActionRepo; - this.slotRepo = slotRepo; - } - - /** - * Figures out the next section number from the given block, and increments its section counter. - * @param block a slot containing a block - * @return the next section number for the given block - */ - public int nextSection(Slot block) { - assert block.isBlock(); - assert block.getSamples().size()==1; - final Sample sample = block.getSamples().get(0); - Integer listedMaxSection = block.getBlockHighestSection(); - int maxSection = (listedMaxSection==null ? 0 : listedMaxSection); - int maxPlanSection = planActionRepo.findMaxPlannedSectionFromSlotId(block.getId()).orElse(0); - int nextSection = Math.max(maxSection, maxPlanSection) + 1; - block.setBlockHighestSection(nextSection); - block.setBlockSampleId(sample.getId()); - slotRepo.save(block); - return nextSection; - } -} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/UnreleaseServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/UnreleaseServiceImp.java index 21a757a54..6a158f620 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/UnreleaseServiceImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/UnreleaseServiceImp.java @@ -23,18 +23,18 @@ public class UnreleaseServiceImp implements UnreleaseService { private final LabwareValidatorFactory labwareValidatorFactory; private final LabwareRepo lwRepo; - private final SlotRepo slotRepo; + private final SampleRepo sampleRepo; private final OperationTypeRepo opTypeRepo; private final OperationService opService; private final WorkService workService; @Autowired public UnreleaseServiceImp(LabwareValidatorFactory labwareValidatorFactory, - LabwareRepo lwRepo, SlotRepo slotRepo, OperationTypeRepo opTypeRepo, + LabwareRepo lwRepo, SampleRepo sampleRepo, OperationTypeRepo opTypeRepo, OperationService opService, WorkService workService) { this.labwareValidatorFactory = labwareValidatorFactory; this.lwRepo = lwRepo; - this.slotRepo = slotRepo; + this.sampleRepo = sampleRepo; this.opTypeRepo = opTypeRepo; this.opService = opService; this.workService = workService; @@ -149,18 +149,22 @@ public void validateRequest(Collection problems, UCMap labware, * @return a string indicating the problem; null if no problem was found. */ public String highestSectionProblem(Labware lw, int highestSection) { - Slot slot = lw.getFirstSlot(); - if (!slot.isBlock()) { + Integer oldValue = lw.getSlots().stream() + .flatMap(slot -> slot.getSamples().stream()) + .map(Sample::getBlockHighestSection) + .filter(Objects::nonNull) + .max(Comparator.naturalOrder()) + .orElse(null); + if (oldValue == null) { return "Cannot set the highest section number from labware "+lw.getBarcode()+" because it is not a block."; } - Integer oldValue = slot.getBlockHighestSection(); - if (oldValue!=null && oldValue > highestSection) { - return String.format("For block %s, cannot reduce the highest section number from %s to %s.", - lw.getBarcode(), slot.getBlockHighestSection(), highestSection); - } if (highestSection < 0) { return "Cannot set the highest section to a negative number."; } + if (oldValue > highestSection) { + return String.format("For block %s, cannot reduce the highest section number from %s to %s.", + lw.getBarcode(), oldValue, highestSection); + } return null; } @@ -191,23 +195,26 @@ public OperationResult perform(User user, UnreleaseRequest request, OperationTyp @NotNull public List updateLabware(List requestLabware, UCMap labwareMap) { List labwareList = new ArrayList<>(requestLabware.size()); - List slotsToUpdate = new ArrayList<>(requestLabware.size()); + Set samplesToUpdate = new HashSet<>(); for (UnreleaseLabware ul : requestLabware) { Labware lw = labwareMap.get(ul.getBarcode()); lw.setReleased(false); if (ul.getHighestSection()!=null) { - Slot slot = lw.getFirstSlot(); - if (slot.getBlockHighestSection()==null || slot.getBlockHighestSection() < ul.getHighestSection()) { - slot.setBlockHighestSection(ul.getHighestSection()); - slotsToUpdate.add(slot); + for (Slot slot : lw.getSlots()) { + for (Sample sample : slot.getSamples()) { + if (sample.isBlock() && !sample.getBlockHighestSection().equals(ul.getHighestSection())) { + sample.setBlockHighestSection(ul.getHighestSection()); + samplesToUpdate.add(sample); + } + } } } labwareList.add(lw); } - if (!slotsToUpdate.isEmpty()) { - slotRepo.saveAll(slotsToUpdate); + if (!samplesToUpdate.isEmpty()) { + sampleRepo.saveAll(new ArrayList<>(samplesToUpdate)); } lwRepo.saveAll(labwareList); return labwareList; diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/operation/confirm/ConfirmSectionServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/operation/confirm/ConfirmSectionServiceImp.java index d6d9cebf7..7e1d91ec0 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/operation/confirm/ConfirmSectionServiceImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/operation/confirm/ConfirmSectionServiceImp.java @@ -367,27 +367,25 @@ public Map getPlanActionMap(Collection planActi * @param ops the operations creating the new sections */ public void updateSourceBlocks(Collection ops) { - Map slotsToUpdate = new HashMap<>(); + Map samplesToUpdate = new HashMap<>(); for (Operation op : ops) { for (Action action : op.getActions()) { - Slot src = action.getSource(); + Sample srcSample = action.getSourceSample(); Sample sample = action.getSample(); - if (src.isBlock() && sample.getSection() != null) { - Integer alreadyHighestSection = src.getBlockHighestSection(); - Slot slotInMap = slotsToUpdate.get(src.getId()); - if (slotInMap!=null) { - if (alreadyHighestSection==null || slotInMap.getBlockHighestSection() > alreadyHighestSection) { - alreadyHighestSection = slotInMap.getBlockHighestSection(); + if (srcSample.isBlock() && sample.getSection() != null) { + Sample sampleInMap = samplesToUpdate.get(srcSample.getId()); + if (sampleInMap != null) { + if (sampleInMap.getBlockHighestSection() < sample.getSection()) { + sampleInMap.setBlockHighestSection(sample.getSection()); } - } - if (alreadyHighestSection == null || alreadyHighestSection < sample.getSection()) { - src.setBlockHighestSection(sample.getSection()); - slotsToUpdate.put(src.getId(), src); + } else if (srcSample.getBlockHighestSection() != null && srcSample.getBlockHighestSection() < sample.getSection()){ + srcSample.setBlockHighestSection(sample.getSection()); + samplesToUpdate.put(srcSample.getId(), srcSample); } } } } - slotRepo.saveAll(slotsToUpdate.values()); + sampleRepo.saveAll(samplesToUpdate.values()); } /** Deduplication key for samples */ diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/operation/confirm/ConfirmSectionValidationServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/operation/confirm/ConfirmSectionValidationServiceImp.java index 6303a3b8c..946877d18 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/operation/confirm/ConfirmSectionValidationServiceImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/operation/confirm/ConfirmSectionValidationServiceImp.java @@ -286,7 +286,7 @@ public void validateSections(Collection problems, List c if (dest.getLabwareId().equals(lw.getId())) { final Integer sampleId = pa.getSample().getId(); plannedSampleIds.computeIfAbsent(dest.getAddress(), ad -> new HashSet<>()).add(sampleId); - Integer lastSection = pa.getSource().getBlockHighestSection(); + Integer lastSection = pa.getSample().getBlockHighestSection(); if (lastSection != null) { Integer max = sampleMaxSection.get(sampleId); if (max == null || max < lastSection) { diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java index b40969c8a..a2c3992f9 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java @@ -155,13 +155,11 @@ public RegisterResult create(RegisterRequest request, User user, RegisterValidat for (BlockRegisterRequest block : request.getBlocks()) { Tissue tissue = tissues.get(block.getExternalIdentifier().toUpperCase()); - Sample sample = sampleRepo.save(new Sample(null, null, tissue, bioState)); + Sample sample = sampleRepo.save(Sample.newBlock(null, tissue, bioState, block.getHighestSection())); LabwareType labwareType = validation.getLabwareType(block.getLabwareType()); Labware labware = labwareService.create(labwareType); Slot slot = labware.getFirstSlot(); slot.getSamples().add(sample); - slot.setBlockSampleId(sample.getId()); - slot.setBlockHighestSection(block.getHighestSection()); slot = slotRepo.save(slot); entityManager.refresh(labware); labwareList.add(labware); diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseFileService.java b/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseFileService.java index 7d228f215..97f96da4a 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseFileService.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseFileService.java @@ -271,15 +271,15 @@ public Map loadSnapshots(Collection releases) { /** * Figures and sets the last section field for release entries. * The last section is only set on entries that specify a block. - * The last section is {@link Slot#getBlockHighestSection} + * The last section is {@link Sample#getBlockHighestSection} * @param entries the release entries under construction */ public void loadLastSection(Collection entries) { for (ReleaseEntry entry : entries) { - if (!entry.getSlot().isBlock() || !entry.getSlot().getBlockSampleId().equals(entry.getSample().getId())) { - continue; + Sample sample = entry.getSample(); + if (sample.isBlock()) { + entry.setLastSection(sample.getBlockHighestSection()); } - entry.setLastSection(entry.getSlot().getBlockHighestSection()); } } diff --git a/src/main/resources/db/changelog/changelog-4.00.xml b/src/main/resources/db/changelog/changelog-4.00.xml new file mode 100644 index 000000000..b76da292a --- /dev/null +++ b/src/main/resources/db/changelog/changelog-4.00.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml index 142d0b0b4..03709b4ca 100644 --- a/src/main/resources/db/changelog/changelog-master.xml +++ b/src/main/resources/db/changelog/changelog-master.xml @@ -43,4 +43,5 @@ + diff --git a/src/main/resources/schema.graphqls b/src/main/resources/schema.graphqls index f9c5b9632..e1c56f7f2 100644 --- a/src/main/resources/schema.graphqls +++ b/src/main/resources/schema.graphqls @@ -224,6 +224,8 @@ type Sample { tissue: Tissue! """The state of this particular sample.""" bioState: BioState! + """For blocks, what is the highest section number already taken from this block?""" + blockHighestSection: Int } """A slot in a piece of labware, which may contain samples.""" @@ -236,10 +238,6 @@ type Slot { labwareId: Int! """The list of samples contained in this slot. May be empty.""" samples: [Sample!]! - """Is this slot a block of tissue? Blocks have different properties from sections.""" - block: Boolean! - """For blocks, what is the highest section number already taken from this block?""" - blockHighestSection: Int } type Labware { diff --git a/src/test/java/uk/ac/sanger/sccp/stan/EntityCreator.java b/src/test/java/uk/ac/sanger/sccp/stan/EntityCreator.java index 17ca82837..63c1401e8 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/EntityCreator.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/EntityCreator.java @@ -118,6 +118,14 @@ public Tissue createTissue(Donor donor, SpatialLocation sl, String externalName, } + public Sample createBlockSample(Tissue tissue) { + BioState bs = bioStateRepo.getByName("Tissue"); + if (tissue==null) { + tissue = createTissue(null, "EXT1"); + } + return sampleRepo.save(Sample.newBlock(null, tissue, bs, 0)); + } + public Sample createSample(Tissue tissue, Integer section) { BioState bs = bioStateRepo.getByName("Tissue"); return createSample(tissue, section, bs); @@ -134,17 +142,17 @@ public Sample createSample(Tissue tissue, Integer section, BioState bioState) { } public Labware createTube(String barcode) { - LabwareType lt = ltRepo.getByName("Tube"); - Labware lw = labwareRepo.save(new Labware(null, barcode, lt, null)); - Slot slot = slotRepo.save(new Slot(null, lw.getId(), new Address(1,1), new ArrayList<>(), null, null)); - lw.getSlots().add(slot); - return lw; + return createTube(barcode, null); } - public Labware createBlock(String barcode, Sample sample) { - LabwareType lt = ltRepo.getByName("Proviasette"); + public Labware createTube(String barcode, Sample sample) { + LabwareType lt = ltRepo.getByName("Tube"); Labware lw = labwareRepo.save(new Labware(null, barcode, lt, null)); - Slot slot = slotRepo.save(new Slot(null, lw.getId(), new Address(1,1), new ArrayList<>(List.of(sample)), sample.getId(), 0)); + List samples = new ArrayList<>(); + if (sample != null) { + samples.add(sample); + } + Slot slot = slotRepo.save(new Slot(null, lw.getId(), new Address(1,1), samples)); lw.getSlots().add(slot); return lw; } @@ -158,8 +166,7 @@ public Labware createLabware(String barcode, LabwareType lt, Sample... samples) List slots = Address.stream(lt.getNumRows(), lt.getNumColumns()) .map(ad -> { Sample sample = sampleIter.hasNext() ? sampleIter.next() : null; - return new Slot(null, lw.getId(), ad, (sample==null ? List.of() : List.of(sample)), - null, null); + return new Slot(null, lw.getId(), ad, (sample==null ? List.of() : List.of(sample))); }) .collect(Collectors.toList()); slotRepo.saveAll(slots); @@ -174,7 +181,7 @@ public Labware createLabware(String barcode, LabwareType lt, Sample[][] samples) .map(ad -> { Sample[] sams = sampleArrayIter.hasNext() ? sampleArrayIter.next() : null; List samList = (sams==null ? List.of() : Arrays.asList(sams)); - return new Slot(null, lw.getId(), ad, samList, null, null); + return new Slot(null, lw.getId(), ad, samList); }) .collect(Collectors.toList()); slotRepo.saveAll(slots); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/EntityFactory.java b/src/test/java/uk/ac/sanger/sccp/stan/EntityFactory.java index fcd23494d..6e8cc83b6 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/EntityFactory.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/EntityFactory.java @@ -154,8 +154,7 @@ public static Labware getTube() { if (tube==null) { int lwId = 100; int slotId = 1001; - Slot slot = new Slot(slotId, lwId, new Address(1,1), new ArrayList<>(List.of(getSample())), - null, null); + Slot slot = new Slot(slotId, lwId, new Address(1,1), new ArrayList<>(List.of(getSample()))); tube = new Labware(lwId, "STAN-00"+lwId, getTubeType(), new ArrayList<>(List.of(slot))); } return tube; @@ -177,7 +176,7 @@ public static Labware makeEmptyLabware(LabwareType lt, String barcode) { int lwId = ++idCounter; final int[] slotId = { 10*lwId }; List slots = Address.stream(lt.getNumRows(), lt.getNumColumns()) - .map(ad -> new Slot(++slotId[0], lwId, ad, new ArrayList<>(), null, null)) + .map(ad -> new Slot(++slotId[0], lwId, ad, new ArrayList<>())) .collect(toList()); if (barcode==null) { barcode = "STAN-"+lwId; @@ -199,12 +198,8 @@ public static Labware makeLabware(LabwareType lt, Sample... samples) { return lw; } - public static Labware makeBlock(Sample sample) { - Labware lw = makeLabware(getTubeType(), sample); - Slot slot = lw.getFirstSlot(); - slot.setBlockSampleId(sample.getId()); - slot.setBlockHighestSection(0); - return lw; + public static Labware makeTube(Sample sample) { + return makeLabware(getTubeType(), sample); } public static OperationType makeOperationType(String name, BioState newBioState, OperationTypeFlag... flags) { diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java index bab1ae13e..7dc05d79e 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java @@ -81,10 +81,10 @@ public void testClashes() throws Exception { tester.setUser(user); Tissue tissue1 = creator.createTissue(null, "EXT1"); Tissue tissue2 = creator.createTissue(tissue1.getDonor(), "EXT2"); - Sample sample1 = creator.createSample(tissue1, null); - Sample sample2 = creator.createSample(tissue2, null); - Labware lw1 = creator.createBlock("STAN-X", sample1); - Labware lw2 = creator.createBlock("STAN-Y", sample2); + Sample sample1 = creator.createBlockSample(tissue1); + Sample sample2 = creator.createBlockSample(tissue2); + Labware lw1 = creator.createTube("STAN-X", sample1); + Labware lw2 = creator.createTube("STAN-Y", sample2); when(mockRegService.register(any(), any())).thenReturn(RegisterResult.clashes( List.of(new RegisterClash(tissue1, List.of(lw1)), new RegisterClash(tissue2, List.of(lw2))) )); @@ -100,7 +100,7 @@ public void testClashes() throws Exception { .containsExactlyInAnyOrder("EXT1", "EXT2"); for (var clash : clashes) { String bc = clash.get("tissue").get("externalName").equals("EXT1") ? "STAN-X" : "STAN-Y"; - assertEquals(List.of(Map.of("barcode", bc,"labwareType", Map.of("name", "Proviasette"))), + assertEquals(List.of(Map.of("barcode", bc,"labwareType", Map.of("name", "Tube"))), clash.get("labware")); } } diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestOrientationQCMutation.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestOrientationQCMutation.java index 6e2bca018..1af304768 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestOrientationQCMutation.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestOrientationQCMutation.java @@ -42,8 +42,8 @@ public class TestOrientationQCMutation { @Transactional @Test public void testOrientationQC() throws Exception { - Sample sample = entityCreator.createSample(null, null); - Labware lw = entityCreator.createBlock("STAN-1", sample); + Sample sample = entityCreator.createBlockSample(null); + Labware lw = entityCreator.createTube("STAN-1", sample); Work work = entityCreator.createWork(null, null, null, null, null); OperationType opType = entityCreator.createOpType("Orientation QC", null, diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestPlanAndRecordSectionMutations.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestPlanAndRecordSectionMutations.java index f3877218a..239a6de5a 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestPlanAndRecordSectionMutations.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestPlanAndRecordSectionMutations.java @@ -50,12 +50,12 @@ public void testPlanAndRecordSection() throws Exception { entityCreator.createBioState("Fetal waste"); Sample[] blockSamples = { - entityCreator.createSample(entityCreator.createTissue(entityCreator.createDonor("DONOR1"), "TISSUE1"), null), - entityCreator.createSample(entityCreator.createTissue(entityCreator.createDonor("DONOR2"), "TISSUE2"), null), + entityCreator.createBlockSample(entityCreator.createTissue(entityCreator.createDonor("DONOR1"), "TISSUE1")), + entityCreator.createBlockSample(entityCreator.createTissue(entityCreator.createDonor("DONOR2"), "TISSUE2")), }; Labware[] sourceBlocks = { - entityCreator.createBlock("STAN-B70C", blockSamples[0]), - entityCreator.createBlock("STAN-B70D", blockSamples[1]), + entityCreator.createTube("STAN-B70C", blockSamples[0]), + entityCreator.createTube("STAN-B70D", blockSamples[1]), }; // Recording the plan @@ -272,8 +272,8 @@ private void testConfirm(Sample[] blockSamples, Labware[] sourceBlocks, String[] // Check that the source blocks' highest section numbers have been updated entityManager.refresh(sourceBlocks[0]); entityManager.refresh(sourceBlocks[1]); - assertEquals(14, sourceBlocks[0].getFirstSlot().getBlockHighestSection()); - assertEquals(17, sourceBlocks[1].getFirstSlot().getBlockHighestSection()); + assertEquals(14, sourceBlocks[0].getFirstSlot().getSamples().getFirst().getBlockHighestSection()); + assertEquals(17, sourceBlocks[1].getFirstSlot().getSamples().getFirst().getBlockHighestSection()); entityManager.flush(); entityManager.refresh(work); assertThat(work.getOperationIds()).hasSize(3); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterOriginalSamplesMutation.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterOriginalSamplesMutation.java index 8ebfd3eeb..129bc1ea0 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterOriginalSamplesMutation.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterOriginalSamplesMutation.java @@ -141,7 +141,7 @@ private Labware testBlockProcessing(String sourceBarcode, Work work) throws Exce } private void testSectioningBlock(Labware block, Work work) throws Exception { - final Integer blockSampleId = block.getFirstSlot().getBlockSampleId(); + final Integer blockSampleId = block.getFirstSlot().getSamples().getFirst().getId(); String planMutation = tester.readGraphQL("plan_simple.graphql") .replace("BARCODE0", block.getBarcode()) .replace("55555", String.valueOf(blockSampleId)); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestReleaseMutation.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestReleaseMutation.java index 4d19d3aab..d82cf68ff 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestReleaseMutation.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestReleaseMutation.java @@ -81,13 +81,12 @@ public void testRelease() throws Exception { Work work1 = entityCreator.createWork(null, null, null, null, null); Donor donor = entityCreator.createDonor("DONOR1"); Tissue tissue = entityCreator.createTissue(donor, "TISSUE1"); - Sample sample = entityCreator.createSample(tissue, null); + Sample sample = entityCreator.createBlockSample(tissue); + sample.setBlockHighestSection(6); Sample sample1 = entityCreator.createSample(tissue, 1); LabwareType lwtype = entityCreator.createLabwareType("lwtype4", 1, 4); - Labware block = entityCreator.createBlock("STAN-001", sample); + Labware block = entityCreator.createTube("STAN-001", sample); Slot blockSlot = block.getFirstSlot(); - blockSlot.setBlockSampleId(sample.getId()); - blockSlot.setBlockHighestSection(6); blockSlot = slotRepo.save(blockSlot); block.getSlots().set(0, blockSlot); Labware lw = entityCreator.createLabware("STAN-002", lwtype, sample, sample, null, sample1); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/repo/TestPlanActionRepo.java b/src/test/java/uk/ac/sanger/sccp/stan/repo/TestPlanActionRepo.java index 88362a8d4..f8e212635 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/repo/TestPlanActionRepo.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/repo/TestPlanActionRepo.java @@ -88,9 +88,9 @@ public void testFindMaxPlannedSection() { Labware lw = new Labware(null, "STAN-001A", any(labwareTypeRepo), null); labwareRepo.save(lw); - Slot slot1 = new Slot(null, lw.getId(), new Address(1,1), null, null, null); - Slot slot2 = new Slot(null, lw.getId(), new Address(1,2), null, null, null); - Slot slot3 = new Slot(null, lw.getId(), new Address(1,3), null, null, null); + Slot slot1 = new Slot(null, lw.getId(), new Address(1,1), null); + Slot slot2 = new Slot(null, lw.getId(), new Address(1,2), null); + Slot slot3 = new Slot(null, lw.getId(), new Address(1,3), null); slotRepo.save(slot1); slotRepo.save(slot2); slotRepo.save(slot3); @@ -106,8 +106,8 @@ public void testFindMaxPlannedSection() { @Test @Transactional public void testFindAllByDestinationLabwareId() { - Sample sample = entityCreator.createSample(entityCreator.createTissue(entityCreator.createDonor("DONOR1"), "TISSUE1"),null); - Labware sourceLabware = entityCreator.createBlock("STAN-000", sample); + Sample sample = entityCreator.createBlockSample(entityCreator.createTissue(entityCreator.createDonor("DONOR1"), "TISSUE1")); + Labware sourceLabware = entityCreator.createTube("STAN-000", sample); Slot sourceSlot = sourceLabware.getFirstSlot(); LabwareType lt = entityCreator.createLabwareType("2x2", 2, 2); Labware labware = entityCreator.createLabware("STAN-001", lt); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/repo/TestPlanOperationRepo.java b/src/test/java/uk/ac/sanger/sccp/stan/repo/TestPlanOperationRepo.java index 18e4d8e36..880f17194 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/repo/TestPlanOperationRepo.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/repo/TestPlanOperationRepo.java @@ -31,7 +31,8 @@ public class TestPlanOperationRepo { @Transactional public void testFindAllByDestinationIdIn() { Tissue tissue = entityCreator.createTissue(entityCreator.createDonor("DONOR1"), "TISSUE1"); - Labware block = entityCreator.createBlock("STAN-BLOCK", entityCreator.createSample(tissue, null)); + Sample sample = entityCreator.createBlockSample(tissue); + Labware block = entityCreator.createTube("STAN-BLOCK", sample); Slot source = block.getFirstSlot(); LabwareType lt = entityCreator.createLabwareType("1x2", 1, 2); Labware lw1 = entityCreator.createLabware("STAN-1", lt); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/repo/TestReleaseRepo.java b/src/test/java/uk/ac/sanger/sccp/stan/repo/TestReleaseRepo.java index ebc8feb6f..48678e833 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/repo/TestReleaseRepo.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/repo/TestReleaseRepo.java @@ -84,9 +84,9 @@ public void testSaveRelease() { } private Release[] createReleases() { - Sample sample = createSample(); - Labware lw1 = entityCreator.createBlock("STAN-01", sample); - Labware lw2 = entityCreator.createBlock("STAN-02", sample); + Sample sample = entityCreator.createBlockSample(null); + Labware lw1 = entityCreator.createTube("STAN-01", sample); + Labware lw2 = entityCreator.createTube("STAN-02", sample); User user = entityCreator.createUser("user1"); ReleaseDestination destination = entityCreator.createReleaseDestination("Venus"); ReleaseRecipient recipient = entityCreator.createReleaseRecipient("Mekon"); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java index 7a0c66259..e72822d5e 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java @@ -764,7 +764,6 @@ public void testCreateDestination(boolean prebarcoded) { assertSame(lw, service.createDestination(lt, sample, prebarcode)); assertThat(lw.getFirstSlot().getSamples()).containsExactly(sample); - assertTrue(lw.getFirstSlot().isBlock()); verify(mockLwService).create(lt, prebarcode, prebarcode); verify(mockSlotRepo).save(lw.getFirstSlot()); } diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/TestLabwareValidator.java b/src/test/java/uk/ac/sanger/sccp/stan/service/TestLabwareValidator.java index 5f837dfe6..65e4824ab 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/TestLabwareValidator.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/TestLabwareValidator.java @@ -268,10 +268,10 @@ public void testValidateSingleSample() { public void testValidateBlock() { final Runnable action = validator::validateBlock; testValidator(List.of(), action); - Sample sam1 = EntityFactory.getSample(); - Sample sam2 = new Sample(sam1.getId()+1, 800, sam1.getTissue(), sam1.getBioState()); - Labware goodLw = EntityFactory.makeBlock(sam1); - Labware badLw = EntityFactory.makeLabware(EntityFactory.getTubeType(), sam2); + Sample[] samples = EntityFactory.makeSamples(2); + samples[0].setBlockHighestSection(1); + Labware goodLw = EntityFactory.makeTube(samples[0]); + Labware badLw = EntityFactory.makeTube(samples[1]); testValidator(List.of(goodLw), action); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/TestParaffinProcessingService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/TestParaffinProcessingService.java index d08806966..2cce9332d 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/TestParaffinProcessingService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/TestParaffinProcessingService.java @@ -11,6 +11,7 @@ import uk.ac.sanger.sccp.stan.repo.*; import uk.ac.sanger.sccp.stan.request.OperationResult; import uk.ac.sanger.sccp.stan.request.ParaffinProcessingRequest; +import uk.ac.sanger.sccp.stan.service.ParaffinProcessingServiceImp.BlockChange; import uk.ac.sanger.sccp.stan.service.work.WorkService; import java.util.*; @@ -38,6 +39,8 @@ public class TestParaffinProcessingService { @Mock private TissueRepo mockTissueRepo; @Mock + private SampleRepo mockSampleRepo; + @Mock private SlotRepo mockSlotRepo; @Mock private WorkService mockWorkService; @@ -225,11 +228,12 @@ public void testRecord() { doReturn(ops).when(service).createOps(any(), any()); doNothing().when(service).recordComments(any(), any()); doNothing().when(service).updateMedium(any(), any()); - doNothing().when(service).createBlocks(any()); + List changes = List.of(new BlockChange(null, null, null)); + doReturn(changes).when(service).createBlocks(any()); assertEquals(new OperationResult(ops, labware), service.record(user, labware, work, comment, medium)); verify(service).updateMedium(labware, medium); - verify(service).createOps(user, labware); + verify(service).createOps(user, changes); verify(mockWorkService).link(work, ops); verify(service).recordComments(comment, ops); verify(service).createBlocks(labware); @@ -282,17 +286,22 @@ public void testCreateOps() { when(mockOpTypeRepo.getByName(opType.getName())).thenReturn(opType); User user = EntityFactory.getUser(); LabwareType lt = EntityFactory.getTubeType(); - Sample sample = EntityFactory.getSample(); + Sample[] samples = EntityFactory.makeSamples(4); List labware = IntStream.range(0, 2) - .mapToObj(i -> EntityFactory.makeLabware(lt, sample)) - .collect(toList()); + .mapToObj(i -> EntityFactory.makeLabware(lt, samples[i*2])) + .toList(); List ops = makeOps(2); - when(mockOpService.createOperationInPlace(any(), any(), any(), any(), any())).thenReturn(ops.get(0), ops.get(1)); - - assertThat(service.createOps(user, labware)).containsExactlyElementsOf(ops); + when(mockOpService.createOperation(any(), any(), any(), any())).thenReturn(ops.get(0), ops.get(1)); + List changes = IntStream.range(0, samples.length/2) + .mapToObj(i -> new BlockChange(labware.get(i).getFirstSlot(), samples[2*i], samples[2*i+1])) + .toList(); + assertThat(service.createOps(user, changes)).containsExactlyElementsOf(ops); - for (Labware lw : labware) { - verify(mockOpService).createOperationInPlace(opType, user, lw, null, null); + verify(mockOpService, times(labware.size())).createOperation(any(), any(), any(), any()); + for (int i = 0; i < labware.size(); ++i) { + Slot slot = labware.get(i).getFirstSlot(); + Action ac = new Action(null, null, slot, slot, samples[2*i+1], samples[2*i]); + verify(mockOpService).createOperation(opType, user, List.of(ac), null); } } @@ -304,32 +313,45 @@ public void testCreateBlocks(boolean anyChanged) { Sample[] samples = IntStream.rangeClosed(101,104) .mapToObj(i -> new Sample(i, null, tissue, bs)) .toArray(Sample[]::new); - LabwareType lt = EntityFactory.getTubeType(); - List labware; if (anyChanged) { - labware = IntStream.range(0, 4).mapToObj( - i -> (i < 2 ? EntityFactory.makeBlock(samples[i]) : EntityFactory.makeLabware(lt, samples[i])) - ).toList(); + samples[0].setBlockHighestSection(2); + samples[1].setBlockHighestSection(2); } else { - labware = IntStream.range(0,4).mapToObj( - i -> EntityFactory.makeBlock(samples[i]) - ).toList(); + for (Sample sam : samples) { + sam.setBlockHighestSection(2); + } } + List labware = Arrays.stream(samples).map(EntityFactory::makeTube).toList(); List changedSlots; + List changedSamples; if (anyChanged) { changedSlots = labware.subList(2,4).stream().map(Labware::getFirstSlot).toList(); + changedSamples = Stream.of(samples[2], samples[3]).map(sam -> Sample.newBlock(null, sam.getTissue(), sam.getBioState(), 0)).toList(); } else { - changedSlots = List.of(); + changedSlots = null; + changedSamples = null; } - service.createBlocks(labware); + List changes = service.createBlocks(labware); if (anyChanged) { verify(mockSlotRepo).saveAll(changedSlots); + verify(mockSampleRepo).saveAll(changedSamples); } else { verifyNoInteractions(mockSlotRepo); + verifyNoInteractions(mockSampleRepo); } for (Labware lw : labware) { assertTrue(lw.getFirstSlot().isBlock()); } + BlockChange[] expectedChanges = IntStream.range(0, samples.length) + .mapToObj(i -> new BlockChange(labware.get(i).getFirstSlot(), samples[i], samples[i])) + .toArray(BlockChange[]::new); + + if (anyChanged) { + for (int i = 2; i < 4; ++i) { + expectedChanges[i] = new BlockChange(labware.get(i).getFirstSlot(), samples[i], changedSamples.get(i-2)); + } + } + assertThat(changes).containsExactlyInAnyOrder(expectedChanges); } @Test diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/TestSampleService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/TestSampleService.java deleted file mode 100644 index 40cccf409..000000000 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/TestSampleService.java +++ /dev/null @@ -1,73 +0,0 @@ -package uk.ac.sanger.sccp.stan.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import uk.ac.sanger.sccp.stan.EntityFactory; -import uk.ac.sanger.sccp.stan.model.*; -import uk.ac.sanger.sccp.stan.repo.PlanActionRepo; -import uk.ac.sanger.sccp.stan.repo.SlotRepo; - -import java.util.*; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; - -/** - * Tests for {@link SampleService} - * @author dr6 - */ -public class TestSampleService { - private PlanActionRepo mockPlanActionRepo; - private SlotRepo mockSlotRepo; - - private SampleService sampleService; - - @BeforeEach - void setup() { - mockPlanActionRepo = mock(PlanActionRepo.class); - mockSlotRepo = mock(SlotRepo.class); - - sampleService = new SampleService(mockPlanActionRepo, mockSlotRepo); - } - - @ParameterizedTest - @MethodSource("nextSectionData") - public void testNextSection(Integer blockMaxSection, Integer planMaxSection, - int expectedNextSection) { - Sample sample = EntityFactory.getSample(); - int slotId = 100; - Slot slot = new Slot(slotId, 10, new Address(1,1), - List.of(sample), sample.getId(), blockMaxSection); - when(mockPlanActionRepo.findMaxPlannedSectionFromSlotId(slotId)) - .thenReturn(toOptional(planMaxSection)); - - assertEquals(sampleService.nextSection(slot), expectedNextSection); - verify(mockSlotRepo).save(slot); - assertEquals(slot.getBlockSampleId(), sample.getId()); - assertEquals(slot.getBlockHighestSection(), expectedNextSection); - } - - static Stream nextSectionData() { - return Arrays.stream(new Integer[][] { - {0, null, 1}, - {0, 0, 1}, - {4, null, 5}, - {4, 0, 5}, - {4, 2, 5}, - {0, null, 1}, - {0, 0, 1}, - {1, 2, 3}, - {0, 4, 5}, - {0, 4, 5}, - {2, 4, 5}, - {4, 4, 5}, - }).map(Arguments::of); - } - - private static OptionalInt toOptional(Integer num) { - return (num==null ? OptionalInt.empty() : OptionalInt.of(num)); - } -} diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/TestUnreleaseService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/TestUnreleaseService.java index 4bc3987c6..d638646ed 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/TestUnreleaseService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/TestUnreleaseService.java @@ -32,7 +32,7 @@ public class TestUnreleaseService { private LabwareValidatorFactory mockLabwareValidatorFactory; private LabwareRepo mockLwRepo; - private SlotRepo mockSlotRepo; + private SampleRepo mockSampleRepo; private OperationTypeRepo mockOpTypeRepo; private OperationService mockOpService; @@ -43,12 +43,12 @@ public class TestUnreleaseService { void setup() { mockLabwareValidatorFactory = mock(LabwareValidatorFactory.class); mockLwRepo = mock(LabwareRepo.class); - mockSlotRepo = mock(SlotRepo.class); + mockSampleRepo = mock(SampleRepo.class); mockOpTypeRepo = mock(OperationTypeRepo.class); mockOpService = mock(OperationService.class); mockWorkService = mock(WorkService.class); - service = spy(new UnreleaseServiceImp(mockLabwareValidatorFactory, mockLwRepo, mockSlotRepo, + service = spy(new UnreleaseServiceImp(mockLabwareValidatorFactory, mockLwRepo, mockSampleRepo, mockOpTypeRepo, mockOpService, mockWorkService)); } @@ -199,19 +199,18 @@ public void testValidateRequest() { @ParameterizedTest @CsvSource(value={ - "true, 2, 5,", - "true, 4, 3, 'For block STAN-1, cannot reduce the highest section number from 4 to 3.'", - "true,,2,", - "true,,-2,Cannot set the highest section to a negative number.", - "false,,1,Cannot set the highest section number from labware STAN-1 because it is not a block.", + "2, 5,", + "4, 3, 'For block STAN-1, cannot reduce the highest section number from 4 to 3.'", + "1,-2,Cannot set the highest section to a negative number.", + ",1,Cannot set the highest section number from labware STAN-1 because it is not a block.", }) - public void testHighestSectionProblem(boolean isBlock, Integer oldNum, int num, String expectedProblem) { - Sample sample = EntityFactory.getSample(); - Labware lw = (isBlock ? EntityFactory.makeBlock(sample) : EntityFactory.makeLabware(EntityFactory.getTubeType(), sample)); - lw.setBarcode("STAN-1"); - if (oldNum!=null || isBlock) { - lw.getFirstSlot().setBlockHighestSection(oldNum); + public void testHighestSectionProblem(Integer oldNum, int num, String expectedProblem) { + Sample sample = EntityFactory.makeSamples(1)[0]; + if (oldNum != null) { + sample.setBlockHighestSection(oldNum); } + Labware lw = EntityFactory.makeTube(sample); + lw.setBarcode("STAN-1"); assertEquals(expectedProblem, service.highestSectionProblem(lw, num)); } @@ -240,12 +239,12 @@ public void testPerform() { @Test public void testUpdateLabware() { - final List slotUpdates = new ArrayList<>(); + final List sampleUpdates = new ArrayList<>(); final List lwUpdates = new ArrayList<>(); - when(mockSlotRepo.saveAll(any())).then(invocation -> { - List slots = invocation.getArgument(0); - slotUpdates.addAll(slots); - return slots; + when(mockSampleRepo.saveAll(any())).then(invocation -> { + List samples = invocation.getArgument(0); + sampleUpdates.addAll(samples); + return samples; }); when(mockLwRepo.saveAll(any())).then(invocation -> { List lw = invocation.getArgument(0); @@ -253,19 +252,15 @@ public void testUpdateLabware() { return lw; }); - Sample sample = EntityFactory.getSample(); LabwareType lt = EntityFactory.getTubeType(); + Sample[] samples = EntityFactory.makeSamples(5); Labware[] labware = IntStream.range(0,5).mapToObj(i -> { - Labware lw = EntityFactory.makeLabware(lt, sample); + if (i > 0) { + samples[i].setBlockHighestSection(i); + } + Labware lw = EntityFactory.makeLabware(lt, samples[i]); lw.setBarcode("STAN-" + i); lw.setReleased(true); - if (i>0) { - final Slot slot = lw.getFirstSlot(); - slot.setBlockSampleId(sample.getId()); - if (i < 4) { - slot.setBlockHighestSection(i); - } - } return lw; }).toArray(Labware[]::new); @@ -288,10 +283,10 @@ public void testUpdateLabware() { } Integer[] expectedHighestSection = { null, 5, 10, 3, 12 }; for (int i = 0; i < labware.length; ++i) { - assertEquals(expectedHighestSection[i], labware[i].getFirstSlot().getBlockHighestSection()); + assertEquals(expectedHighestSection[i], labware[i].getFirstSlot().getSamples().getFirst().getBlockHighestSection()); } - Slot[] expectedSlotUpdates = IntStream.of(1,2,4).mapToObj(i -> labware[i].getFirstSlot()).toArray(Slot[]::new); - assertThat(slotUpdates).containsExactly(expectedSlotUpdates); + Sample[] expectedSampleUpdates = IntStream.of(1,2,4).mapToObj(i -> samples[i]).toArray(Sample[]::new); + assertThat(sampleUpdates).containsExactlyInAnyOrder(expectedSampleUpdates); } @Test diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/flag/TestFlagLookupService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/flag/TestFlagLookupService.java index b82203b6d..a90c4f101 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/flag/TestFlagLookupService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/flag/TestFlagLookupService.java @@ -234,7 +234,7 @@ void testLabwareFlagPriority_noflags() { SlotSample lwSs = new SlotSample(lw.getFirstSlot(), sample); Ancestry anc = mock(Ancestry.class); when(mockAncestoriser.findAncestry(any())).thenReturn(anc); - Labware otherLw = EntityFactory.makeBlock(sample); + Labware otherLw = EntityFactory.makeTube(sample); Set ancestorSS = Set.of(lwSs, new SlotSample(otherLw.getFirstSlot(), sample)); when(anc.keySet()).thenReturn(ancestorSS); when(mockFlagRepo.findAllByLabwareIdIn(any())).thenReturn(List.of()); @@ -254,7 +254,7 @@ void testLabwareFlagPriority(boolean relevant) { SlotSample lwSs = new SlotSample(lw.getFirstSlot(), sample); Ancestry anc = mock(Ancestry.class); when(mockAncestoriser.findAncestry(any())).thenReturn(anc); - Labware otherLw = EntityFactory.makeBlock(sample); + Labware otherLw = EntityFactory.makeTube(sample); Set ancestorSS = Set.of(lwSs, new SlotSample(otherLw.getFirstSlot(), sample)); when(anc.keySet()).thenReturn(ancestorSS); List flags = List.of( @@ -289,7 +289,7 @@ void testGetLabwareFlagged_multi() { SlotSample lw2Ss = new SlotSample(lw2.getFirstSlot(), sample); Ancestry anc = mock(Ancestry.class); when(mockAncestoriser.findAncestry(any())).thenReturn(anc); - Labware otherLw = EntityFactory.makeBlock(sample); + Labware otherLw = EntityFactory.makeTube(sample); SlotSample blockSs = new SlotSample(otherLw.getFirstSlot(), sample); Set ancSs = Set.of(lw1Ss, lw2Ss, blockSs); when(anc.keySet()).thenReturn(ancSs); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/history/TestHistoryService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/history/TestHistoryService.java index eeca62f6a..a7fc64307 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/history/TestHistoryService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/history/TestHistoryService.java @@ -1158,7 +1158,7 @@ public void testLoadLabwareFlags() { }) public void testResultDetail(PassFail res, Address address, String expected) { final int slotId = 7; - Slot slot = (address==null ? null : new Slot(slotId, 2, address, null, null, null)); + Slot slot = (address==null ? null : new Slot(slotId, 2, address, null)); Map slotIdMap = (slot==null ? Map.of() : Map.of(slotId, slot)); ResultOp result = new ResultOp(); result.setResult(res); @@ -1211,7 +1211,7 @@ public void testMeasurementDetail(String name, String value, Address address, St Integer slotId = null; if (address != null) { slotId = 70; - Slot slot = new Slot(slotId, 7, address, null, null, null); + Slot slot = new Slot(slotId, 7, address, null); slotIdMap = Map.of(slotId, slot); } Measurement measurement = new Measurement(6, name, value, 4, 5, slotId); @@ -1232,7 +1232,7 @@ public void testDoesCommentApply(OperationComment opCom, int sampleId, int labwa } static Stream doesCommentApplyData() { - Slot slot = new Slot(100, 10, new Address(1,1), null, null, null); + Slot slot = new Slot(100, 10, new Address(1,1), null); Comment com = new Comment(1, "Alabama", "Bananas"); return Stream.of( Arguments.of(new OperationComment(1, com, 1, null, null, null), 1, 10, null, true), @@ -1273,7 +1273,7 @@ static Stream doesMeasurementApplyData() { final int sampleId = 4; final int slotId = 10; final int lwId = 1; - Map slotIdMap = Map.of(slotId, new Slot(slotId, lwId, new Address(1,2), null, null, null)); + Map slotIdMap = Map.of(slotId, new Slot(slotId, lwId, new Address(1,2), null)); return Arrays.stream(new Object[][] { {new Measurement(1, "A", "1", null, 1, null), sampleId, lwId, slotIdMap, true}, {new Measurement(1, "A", "1", sampleId, 1, null), sampleId, lwId, slotIdMap, true}, diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/label/TestLabwareLabelDataService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/label/TestLabwareLabelDataService.java index 5ec24578b..e3d3a9c6f 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/label/TestLabwareLabelDataService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/label/TestLabwareLabelDataService.java @@ -112,7 +112,7 @@ public void testLabwareDataPlannedContents() { public void testSlotOrderForLabwareType(String ltName, boolean columnMajor) { LabwareType lt = new LabwareType(null, ltName, 2, 3, null, false); List slots = Address.stream(2, 3) - .map(ad -> new Slot(null, 100, ad, null, null, null)) + .map(ad -> new Slot(null, 100, ad, null)) .sorted(service.slotOrderForLabwareType(lt)) .toList(); if (columnMajor) { diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestAliquotService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestAliquotService.java index 04fac8098..5ba5e4722 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestAliquotService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestAliquotService.java @@ -271,9 +271,11 @@ static Stream validateRequestArgs() { OperationType analysisOp = EntityFactory.makeOperationType("Floop", null, OperationTypeFlag.ANALYSIS); OperationType sectionOp = EntityFactory.makeOperationType("Section", null, OperationTypeFlag.SOURCE_IS_BLOCK); - Sample sample = new Sample(50, null, EntityFactory.getTissue(), EntityFactory.getBioState()); - Labware block = EntityFactory.makeBlock(sample); - Labware section = EntityFactory.getTube(); + Sample[] samples = EntityFactory.makeSamples(2); + samples[0].setBlockHighestSection(0); + samples[1].setSection(1); + Labware block = EntityFactory.makeTube(samples[0]); + Labware section = EntityFactory.makeTube(samples[1]); return Arrays.stream(new Object[][]{ {3, aliquotOp, block}, diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestInPlaceOpService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestInPlaceOpService.java index 68a637ab8..3cca9953b 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestInPlaceOpService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestInPlaceOpService.java @@ -155,8 +155,8 @@ static Stream validateOpTypeArgs() { OperationType ot3 = EntityFactory.makeOperationType("Blodge", null, OperationTypeFlag.IN_PLACE, OperationTypeFlag.SOURCE_IS_BLOCK); LabwareType lt = EntityFactory.getTubeType(); Sample sam = EntityFactory.getSample(); - Labware block = EntityFactory.makeLabware(lt, sam); - block.getFirstSlot().setBlockSampleId(sam.getId()); + Sample blockSam = Sample.newBlock(sam.getId()+1, sam.getTissue(), sam.getBioState(), 1); + Labware block = EntityFactory.makeLabware(lt, blockSam); Labware tube = EntityFactory.makeLabware(lt, sam); return Arrays.stream(new Object[][] { {ot, tube, null}, @@ -164,9 +164,11 @@ static Stream validateOpTypeArgs() { {"", tube, "No operation type specified."}, {"Bananas", tube, "Unknown operation type: \"Bananas\""}, {ot2, tube, "Operation type Passage cannot be recorded in place."}, + {ot3, block, null}, {ot3, tube, "Operation type Blodge can only be recorded on a block."}, }).map(Arguments::of); } + @Test public void testMakeActions_nobs() { Sample sam1 = EntityFactory.getSample(); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmOperationService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmOperationService.java index d7a50b25d..f82688c7e 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmOperationService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmOperationService.java @@ -344,9 +344,9 @@ static Stream getOrCreateSampleArguments() { ); BioState rna = new BioState(2, "RNA"); Sample newSection = makeSample(544, 3, tissue1, bio); - Slot sourceSlot = new Slot(null, null, new Address(1,1), List.of(blockSample), null, null); - Slot emptySlot = new Slot(null, null, new Address(1,1), List.of(), null, null); - Slot populousSlot = new Slot(null, null, new Address(1,2), sections, null, null); + Slot sourceSlot = new Slot(null, null, new Address(1,1), List.of(blockSample)); + Slot emptySlot = new Slot(null, null, new Address(1,1), List.of()); + Slot populousSlot = new Slot(null, null, new Address(1,2), sections); return Stream.of( Arguments.of(new PlanAction(null, null, sourceSlot, emptySlot, blockSample, null, null, null), emptySlot, blockSample, false), diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionService.java index 42c56df6a..6dd287513 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionService.java @@ -23,6 +23,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; +import static java.util.stream.Collectors.toMap; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -515,11 +516,11 @@ public void testGetPlanActionMap() { .toArray(Sample[]::new); final Address A1 = new Address(1, 1); final Address A2 = new Address(1, 2); - Slot source = new Slot(10, 1, A1, null, null, null); + Slot source = new Slot(10, 1, A1, null); Slot[] slots = { - new Slot(100, 10, A1, Arrays.asList(samples), null, null), - new Slot(101, 10, A2, List.of(samples[0]), null, null), - new Slot(200, 20, A1, Arrays.asList(samples), null, null) + new Slot(100, 10, A1, Arrays.asList(samples)), + new Slot(101, 10, A2, List.of(samples[0])), + new Slot(200, 20, A1, Arrays.asList(samples)) }; PlanAction[] pas = { new PlanAction(51, 1, source, slots[0], samples[0]), @@ -549,11 +550,8 @@ public void testUpdateSourceBlocks() { final Tissue tissue = EntityFactory.getTissue(); for (int i = 0; i < sourceSamples.length; ++i) { final int sampleId = 50 + i; - sourceSamples[i] = new Sample(sampleId, null, tissue, bs); + sourceSamples[i] = Sample.newBlock(sampleId, tissue, bs, 10*i); sourceLabware[i] = EntityFactory.makeLabware(tubeType, sourceSamples[i]); - final Slot slot = sourceLabware[i].getFirstSlot(); - slot.setBlockSampleId(sampleId); - slot.setBlockHighestSection(10*i); } Sample[] sections = new Sample[4]; Labware[] destLabware = new Labware[2]; @@ -584,7 +582,7 @@ public void testUpdateSourceBlocks() { // Just to make sure that if source slots have competing instances, they still get updated correctly: for (int i = 0; i < srcs.length; ++i) { Slot src = srcs[i]; - srcs[i] = new Slot(src.getId(), src.getLabwareId(), src.getAddress(), src.getSamples(), src.getBlockSampleId(), src.getBlockHighestSection()); + srcs[i] = new Slot(src.getId(), src.getLabwareId(), src.getAddress(), src.getSamples()); } List op1Actions = List.of( @@ -593,23 +591,23 @@ public void testUpdateSourceBlocks() { ); ops[1].setActions(op1Actions); - final List updatedSources = new ArrayList<>(2); - when(mockSlotRepo.saveAll(any())).then(invocation -> { - Collection slots = invocation.getArgument(0); - updatedSources.addAll(slots); - - return slots; + final List updatedSamples = new ArrayList<>(2); + when(mockSampleRepo.saveAll(any())).then(invocation -> { + Collection samples = invocation.getArgument(0); + updatedSamples.addAll(samples); + return samples; }); service.updateSourceBlocks(Arrays.asList(ops)); - verify(mockSlotRepo).saveAll(any()); + verify(mockSampleRepo).saveAll(any()); - updatedSources.sort(Comparator.comparing(Slot::getId)); - final List expectedUpdatedSources = List.of( - new Slot(srcs[0].getId(), srcs[0].getLabwareId(), srcs[0].getAddress(), srcs[0].getSamples(), srcs[0].getBlockSampleId(), 21), - new Slot(srcs[1].getId(), srcs[1].getLabwareId(), srcs[1].getAddress(), srcs[1].getSamples(), srcs[1].getBlockSampleId(), 23) + updatedSamples.sort(Comparator.comparing(Sample::getId)); + Map srcSampleHighs = updatedSamples.stream() + .collect(toMap(Sample::getId, Sample::getBlockHighestSection)); + assertEquals( + Map.of(sourceSamples[0].getId(), sections[1].getSection(), sourceSamples[1].getId(), sections[3].getSection()), + srcSampleHighs ); - assertEquals(expectedUpdatedSources, updatedSources); } } diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionValidationService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionValidationService.java index 02acb8159..d21662f06 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionValidationService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionValidationService.java @@ -406,18 +406,16 @@ public void testValidateSections() { lw1.setBarcode("STAN-001"); Labware lw2 = EntityFactory.makeEmptyLabware(lt); lw2.setBarcode("STAN-002"); - Sample sampleA = EntityFactory.getSample(); + Sample sample0 = EntityFactory.getSample(); + Sample sampleA = Sample.newBlock(sample0.getId()+1, sample0.getTissue(), sample0.getBioState(), 12); Tissue tissueB = EntityFactory.makeTissue(EntityFactory.getDonor(), EntityFactory.getSpatialLocation()); Sample sampleB = new Sample(sampleA.getId()+1, null, tissueB, sampleA.getBioState()); LabwareType tubeType = EntityFactory.getTubeType(); Labware lwA = EntityFactory.makeLabware(tubeType, sampleA); final Slot slotA = lwA.getFirstSlot(); - slotA.setBlockSampleId(sampleA.getId()); - slotA.setBlockHighestSection(12); Labware lwB = EntityFactory.makeLabware(tubeType, sampleB); final Slot slotB = lwB.getFirstSlot(); - slotB.setBlockSampleId(sampleA.getId()); final Address A1 = new Address(1,1); final Address B3 = new Address(2,3); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/plan/TestPlanValidation.java b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/plan/TestPlanValidation.java index 525051969..1381476d5 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/plan/TestPlanValidation.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/plan/TestPlanValidation.java @@ -390,13 +390,11 @@ static Stream sourcesData() { Sample sample = EntityFactory.getSample(); int sectionSampleId = sample.getId(); int blockSampleId = sectionSampleId + 50; - Sample blockSample = new Sample(blockSampleId, null, sample.getTissue(), EntityFactory.getBioState()); + Sample blockSample = Sample.newBlock(blockSampleId, sample.getTissue(), EntityFactory.getBioState(), 0); LabwareType lt = EntityFactory.getTubeType(); Labware block = EntityFactory.makeEmptyLabware(lt); Slot blockSlot = block.getFirstSlot(); blockSlot.getSamples().add(blockSample); - blockSlot.setBlockSampleId(blockSampleId); - blockSlot.setBlockHighestSection(0); Labware nonBlock = EntityFactory.makeEmptyLabware(lt); nonBlock.getFirstSlot().getSamples().add(sample); @@ -534,12 +532,12 @@ static Stream hasDividedLayoutData() { .mapToObj(i -> EntityFactory.makeTissue(EntityFactory.getDonor(), EntityFactory.getSpatialLocation())) .toArray(Tissue[]::new); Sample[] sams = IntStream.range(0,3) - .mapToObj(i -> new Sample(10+i, null, tissues[i], EntityFactory.getBioState())) + .mapToObj(i -> Sample.newBlock(10+i, tissues[i], EntityFactory.getBioState(), 0)) .toArray(Sample[]::new); Labware[] blocks = Arrays.stream(sams) - .map(EntityFactory::makeBlock) + .map(EntityFactory::makeTube) .toArray(Labware[]::new); - Labware someOtherBlock = EntityFactory.makeBlock(sams[0]); + Labware someOtherBlock = EntityFactory.makeTube(sams[0]); UCMap sourceLwMap = UCMap.from(Labware::getBarcode, blocks); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java index a1e24819a..6feeee27b 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java @@ -384,9 +384,9 @@ public void testCreate() { }; BioState bioState = opType.getNewBioState(); - Sample[] samples = new Sample[]{ - new Sample(6000, null, tissues[0], bioState), - new Sample(6001, null, tissues[1], bioState), + Sample[] samples = { + Sample.newBlock(6000, tissues[0], bioState, 3), + Sample.newBlock(6001, tissues[1], bioState, 0), }; when(mockTissueRepo.save(any())).thenReturn(tissues[0], tissues[1]); @@ -423,13 +423,13 @@ public void testCreate() { cellClass, i==0 ? hmdmcs[i] : null, block.getSampleCollectionDate(), null)); - verify(mockSampleRepo).save(new Sample(null, null, tissues[i], bioState)); + verify(mockSampleRepo).save(Sample.newBlock(null, tissues[i], bioState, block.getHighestSection())); verify(mockLabwareService).create(lts[i]); Labware lw = lws[i]; verify(mockEntityManager).refresh(lw); Slot slot = lw.getFirstSlot(); - assertEquals(block.getHighestSection(), slot.getBlockHighestSection()); - assertEquals(samples[i].getId(), slot.getBlockSampleId()); + assertEquals(block.getHighestSection(), slot.getSamples().getFirst().getBlockHighestSection()); + assertEquals(samples[i].getId(), slot.getSamples().getFirst().getId()); verify(mockSlotRepo).save(slot); verify(mockOpService).createOperationInPlace(opType, user, slot, samples[i]); } @@ -504,7 +504,7 @@ public void testCreateProblems(Species species, Object hmdmcObj, String expected sl, donor, medium, fixative, null, hmdmc, null, null); BioState bioState = opType.getNewBioState(); - Sample sample = new Sample(6000, null, tissue, bioState); + Sample sample = Sample.newBlock(6000, tissue, bioState, 3); when(mockTissueRepo.save(any())).thenReturn(tissue); when(mockSampleRepo.save(any())).thenReturn(sample); @@ -532,12 +532,12 @@ public void testCreateProblems(Species species, Object hmdmcObj, String expected cellClass, hmdmc, null, null)); - verify(mockSampleRepo).save(new Sample(null, null, tissue, bioState)); + verify(mockSampleRepo).save(Sample.newBlock(null, tissue, bioState, 3)); verify(mockLabwareService).create(lt); verify(mockEntityManager).refresh(lw); Slot slot = lw.getFirstSlot(); - assertEquals(slot.getBlockHighestSection(), block.getHighestSection()); - assertEquals(slot.getBlockSampleId(), sample.getId()); + assertEquals(block.getHighestSection(), slot.getSamples().getFirst().getBlockHighestSection()); + assertEquals(sample.getId(), slot.getSamples().getFirst().getId()); verify(mockSlotRepo).save(slot); verify(mockOpService).createOperationInPlace(opType, user, slot, sample); verify(mockBioRiskRepo).recordBioRisk(sample, br, op.getId()); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/releasefile/TestReleaseFileService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/releasefile/TestReleaseFileService.java index 04a3a1764..fbb981efe 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/releasefile/TestReleaseFileService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/releasefile/TestReleaseFileService.java @@ -350,26 +350,12 @@ public void testLoadSnapshots() { @Test public void testLoadLastSection() { LabwareType lt = EntityFactory.getTubeType(); - Sample sampleA = EntityFactory.getSample(); - Tissue tissueB = EntityFactory.makeTissue(EntityFactory.getDonor(), EntityFactory.getSpatialLocation()); - Sample sampleB = new Sample(60, null, tissueB, EntityFactory.getBioState()); - Sample[] samples = { sampleA, sampleB, sampleA, sampleA, sampleB, sampleA }; - boolean[] isBlock = { true, true, true, true, true, false }; - Integer[] blockMaxSection = { 6, 6, 2, null, null, null }; - - Labware[] labware = IntStream.range(0, samples.length) - .mapToObj(i -> { - Sample sample = samples[i]; - Labware lw = EntityFactory.makeLabware(lt, sample); - if (isBlock[i]) { - Slot slot = lw.getFirstSlot(); - slot.setBlockSampleId(sample.getId()); - if (blockMaxSection[i]!=null) { - slot.setBlockHighestSection(blockMaxSection[i]); - } - } - return lw; - }) + Sample[] samples = EntityFactory.makeSamples(3); + samples[0].setBlockHighestSection(6); + samples[1].setBlockHighestSection(2); + + Labware[] labware = Arrays.stream(samples) + .map(sample -> EntityFactory.makeLabware(lt, sample)) .toArray(Labware[]::new); List entries = Arrays.stream(labware) @@ -378,7 +364,7 @@ public void testLoadLastSection() { service.loadLastSection(entries); - Integer[] expectedLastSection = {6, 6, 2, null, null, null}; + Integer[] expectedLastSection = {6, 2, null}; IntStream.range(0, expectedLastSection.length).forEach(i -> assertEquals(expectedLastSection[i], entries.get(i).getLastSection(), "element "+i) ); From 5158f9de65bd386488ba560bd0cb39e848c75f4c Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:01:58 +0000 Subject: [PATCH 02/13] x1404 support multiple samples in a block processing request Block processing can take samples from multi-sample source labware and can place multiple block-samples into destination labware. --- .../sccp/stan/request/TissueBlockRequest.java | 123 ++- .../service/BlockProcessingServiceImp.java | 538 +---------- .../sccp/stan/service/block/BlockData.java | 111 +++ .../stan/service/block/BlockLabwareData.java | 70 ++ .../sccp/stan/service/block/BlockMaker.java | 12 + .../stan/service/block/BlockMakerFactory.java | 52 ++ .../stan/service/block/BlockMakerImp.java | 217 +++++ .../stan/service/block/BlockValidator.java | 41 + .../service/block/BlockValidatorFactory.java | 56 ++ .../stan/service/block/BlockValidatorImp.java | 480 ++++++++++ .../uk/ac/sanger/sccp/utils/BasicUtils.java | 7 + src/main/resources/schema.graphqls | 18 +- .../TestRegisterOriginalSamplesMutation.java | 5 +- .../service/TestBlockProcessingService.java | 881 ++---------------- .../stan/service/block/TestBlockMaker.java | 330 +++++++ .../service/block/TestBlockValidator.java | 500 ++++++++++ .../resources/graphql/tissueblock.graphql | 10 +- 17 files changed, 2084 insertions(+), 1367 deletions(-) create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockData.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockLabwareData.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMaker.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerFactory.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerImp.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidator.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorFactory.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorImp.java create mode 100644 src/test/java/uk/ac/sanger/sccp/stan/service/block/TestBlockMaker.java create mode 100644 src/test/java/uk/ac/sanger/sccp/stan/service/block/TestBlockValidator.java diff --git a/src/main/java/uk/ac/sanger/sccp/stan/request/TissueBlockRequest.java b/src/main/java/uk/ac/sanger/sccp/stan/request/TissueBlockRequest.java index 83fe342e9..3f3781e4f 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/request/TissueBlockRequest.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/request/TissueBlockRequest.java @@ -1,10 +1,13 @@ package uk.ac.sanger.sccp.stan.request; +import uk.ac.sanger.sccp.stan.model.Address; import uk.ac.sanger.sccp.utils.BasicUtils; import java.util.List; import java.util.Objects; +import static uk.ac.sanger.sccp.utils.BasicUtils.nullToEmpty; + /** * A request to process original tissue into blocks. * @author dr6 @@ -84,34 +87,84 @@ public int hashCode() { } /** - * The input about a new block being created. + * The input about labware containing new blocks * @author dr6 */ public static class TissueBlockLabware { - private String sourceBarcode; private String labwareType; private String preBarcode; - private Integer commentId; - private String replicate; + private List contents = List.of(); public TissueBlockLabware() {} - public TissueBlockLabware(String sourceBarcode, String labwareType, String replicate) { - this(sourceBarcode, labwareType, replicate, null, null); + /** + * The labware type for the new labware. + */ + public String getLabwareType() { + return this.labwareType; } - public TissueBlockLabware(String sourceBarcode, String labwareType, String replicate, - String preBarcode, Integer commentId) { - this.sourceBarcode = sourceBarcode; + public void setLabwareType(String labwareType) { this.labwareType = labwareType; - this.replicate = replicate; - this.preBarcode = preBarcode; - this.commentId = commentId; } /** - * The original tissue barcode. + * The barcode of the new labware, if it is prebarcoded. */ + public String getPreBarcode() { + return this.preBarcode; + } + + public void setPreBarcode(String preBarcode) { + this.preBarcode = preBarcode; + } + + public List getContents() { + return this.contents; + } + + public void setContents(List contents) { + this.contents = nullToEmpty(contents); + } + + @Override + public String toString() { + return BasicUtils.describe("TissueBlockLabware") + .add("labwareType", labwareType) + .add("preBarcode", preBarcode) + .add("contents", contents) + .reprStringValues() + .omitNullValues() + .toString(); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + TissueBlockLabware that = (TissueBlockLabware) o; + return (Objects.equals(this.labwareType, that.labwareType) + && Objects.equals(this.preBarcode, that.preBarcode) + && Objects.equals(this.contents, that.contents)); + } + + @Override + public int hashCode() { + return Objects.hash(labwareType, preBarcode, contents); + } + } + + /** + * The content of a block-sample + */ + public static class TissueBlockContent { + private String sourceBarcode; + private Integer sourceSampleId; + private List
addresses = List.of(); + private Integer commentId; + private String replicate; + + public TissueBlockContent() {} + public String getSourceBarcode() { return this.sourceBarcode; } @@ -120,31 +173,22 @@ public void setSourceBarcode(String sourceBarcode) { this.sourceBarcode = sourceBarcode; } - /** - * The labware type for the new labware. - */ - public String getLabwareType() { - return this.labwareType; + public Integer getSourceSampleId() { + return this.sourceSampleId; } - public void setLabwareType(String labwareType) { - this.labwareType = labwareType; + public void setSourceSampleId(Integer sourceSampleId) { + this.sourceSampleId = sourceSampleId; } - /** - * The barcode of the new labware, if it is prebarcoded. - */ - public String getPreBarcode() { - return this.preBarcode; + public List
getAddresses() { + return this.addresses; } - public void setPreBarcode(String preBarcode) { - this.preBarcode = preBarcode; + public void setAddresses(List
addresses) { + this.addresses = nullToEmpty(addresses); } - /** - * The comment (if any) associated with this operation. - */ public Integer getCommentId() { return this.commentId; } @@ -153,9 +197,6 @@ public void setCommentId(Integer commentId) { this.commentId = commentId; } - /** - * The replicate number for the new block. - */ public String getReplicate() { return this.replicate; } @@ -166,25 +207,23 @@ public void setReplicate(String replicate) { @Override public String toString() { - return BasicUtils.describe("TissueBlockLabware") + return BasicUtils.describe(this) .add("sourceBarcode", sourceBarcode) - .add("labwareType", labwareType) - .add("preBarcode", preBarcode) + .add("sourceSampleId", sourceSampleId) + .add("addresses", addresses) .add("commentId", commentId) .add("replicate", replicate) .reprStringValues() - .omitNullValues() .toString(); } @Override public boolean equals(Object o) { - if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - TissueBlockLabware that = (TissueBlockLabware) o; + TissueBlockContent that = (TissueBlockContent) o; return (Objects.equals(this.sourceBarcode, that.sourceBarcode) - && Objects.equals(this.labwareType, that.labwareType) - && Objects.equals(this.preBarcode, that.preBarcode) + && Objects.equals(this.sourceSampleId, that.sourceSampleId) + && Objects.equals(this.addresses, that.addresses) && Objects.equals(this.commentId, that.commentId) && Objects.equals(this.replicate, that.replicate) ); @@ -192,7 +231,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(sourceBarcode, replicate); + return Objects.hash(sourceBarcode, sourceSampleId, addresses, commentId, replicate); } } } \ No newline at end of file diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/BlockProcessingServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/BlockProcessingServiceImp.java index 132f69e9d..bda611d00 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/BlockProcessingServiceImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/BlockProcessingServiceImp.java @@ -1,79 +1,33 @@ package uk.ac.sanger.sccp.stan.service; -import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import uk.ac.sanger.sccp.stan.Transactor; -import uk.ac.sanger.sccp.stan.model.*; -import uk.ac.sanger.sccp.stan.repo.*; +import uk.ac.sanger.sccp.stan.model.User; import uk.ac.sanger.sccp.stan.request.OperationResult; import uk.ac.sanger.sccp.stan.request.TissueBlockRequest; -import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockLabware; +import uk.ac.sanger.sccp.stan.service.block.*; import uk.ac.sanger.sccp.stan.service.store.StoreService; -import uk.ac.sanger.sccp.stan.service.work.WorkService; -import uk.ac.sanger.sccp.utils.*; - -import java.util.*; -import java.util.function.Function; import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static uk.ac.sanger.sccp.utils.BasicUtils.*; /** * @author dr6 */ @Service public class BlockProcessingServiceImp implements BlockProcessingService { - private final LabwareValidatorFactory lwValFactory; - private final Validator prebarcodeValidator; - private final Validator replicateValidator; - private final LabwareRepo lwRepo; - private final SlotRepo slotRepo; - private final OperationTypeRepo opTypeRepo; - private final OperationCommentRepo opCommentRepo; - private final LabwareTypeRepo ltRepo; - private final BioStateRepo bsRepo; - private final TissueRepo tissueRepo; - private final SampleRepo sampleRepo; - private final MediumRepo mediumRepo; - private final CommentValidationService commentValidationService; - private final OperationService opService; - private final LabwareService lwService; - private final BioRiskService bioRiskService; - private final WorkService workService; + + private final BlockValidatorFactory blockValidatorFactory; + private final BlockMakerFactory blockMakerFactory; private final StoreService storeService; private final Transactor transactor; @Autowired - public BlockProcessingServiceImp(LabwareValidatorFactory lwValFactory, - @Qualifier("tubePrebarcodeValidator") Validator prebarcodeValidator, - @Qualifier("replicateValidator") Validator replicateValidator, - LabwareRepo lwRepo, SlotRepo slotRepo, OperationTypeRepo opTypeRepo, - OperationCommentRepo opCommentRepo, LabwareTypeRepo ltRepo, - BioStateRepo bsRepo, TissueRepo tissueRepo, SampleRepo sampleRepo, MediumRepo mediumRepo, - CommentValidationService commentValidationService, OperationService opService, - LabwareService lwService, BioRiskService bioRiskService, WorkService workService, StoreService storeService, Transactor transactor) { - this.lwValFactory = lwValFactory; - this.prebarcodeValidator = prebarcodeValidator; - this.replicateValidator = replicateValidator; - this.lwRepo = lwRepo; - this.slotRepo = slotRepo; - this.opTypeRepo = opTypeRepo; - this.opCommentRepo = opCommentRepo; - this.ltRepo = ltRepo; - this.bsRepo = bsRepo; - this.tissueRepo = tissueRepo; - this.sampleRepo = sampleRepo; - this.mediumRepo = mediumRepo; - this.commentValidationService = commentValidationService; - this.opService = opService; - this.lwService = lwService; - this.bioRiskService = bioRiskService; - this.workService = workService; + public BlockProcessingServiceImp(BlockValidatorFactory blockValidatorFactory, BlockMakerFactory blockMakerFactory, + StoreService storeService, Transactor transactor) { + this.blockValidatorFactory = blockValidatorFactory; + this.blockMakerFactory = blockMakerFactory; this.storeService = storeService; this.transactor = transactor; } @@ -90,474 +44,12 @@ public OperationResult perform(User user, TissueBlockRequest request) throws Val public OperationResult performInsideTransaction(User user, TissueBlockRequest request) throws ValidationException { requireNonNull(user, "User is null."); requireNonNull(request, "Request is null."); - Collection problems = new LinkedHashSet<>(); - if (request.getLabware().isEmpty()) { - problems.add("No labware specified in request."); - throw new ValidationException("The request could not be validated.", problems); - } - UCMap sources = loadSources(problems, request); - Work work = workService.validateUsableWork(problems, request.getWorkNumber()); - UCMap lwTypes = loadEntities(problems, request, TissueBlockLabware::getLabwareType, - "Labware type", LabwareType::getName, true, ltRepo::findAllByNameIn); - checkPrebarcodes(problems, request, lwTypes); - Map comments = loadComments(problems, request); - checkReplicates(problems, request, sources); - checkDiscardBarcodes(problems, request); - Medium oct = null; - if (lwTypes.values().stream().anyMatch(lt -> lt.getName().equalsIgnoreCase(LabwareType.PROVIASETTE_NAME))) { - oct = mediumRepo.findByName("OCT").orElse(null); - if (oct==null) { - problems.add("OCT medium not found in database."); - } - } - if (!problems.isEmpty()) { - throw new ValidationException("The request could not be validated.", problems); - } - - List samples = createSamples(request, sources, oct); - List destinations = createDestinations(request, samples, lwTypes); - List ops = createOperations(request, user, sources, destinations, comments); - if (work!=null) { - workService.link(work, ops); - } - bioRiskService.copyOpSampleBioRisks(ops); - discardSources(request.getDiscardSourceBarcodes(), sources); - - return new OperationResult(ops, destinations); - } - - /** - * Loads the source labware for the request, and checks they are suitable - * @param problems receptacle for problems found - * @param request the request - * @return the loaded labware, mapped from barcodes - */ - public UCMap loadSources(Collection problems, TissueBlockRequest request) { - BioState bs = bsRepo.getByName("Original sample"); - Set barcodes = new HashSet<>(); - boolean anyMissing = false; - for (var block : request.getLabware()) { - String barcode = block.getSourceBarcode(); - if (nullOrEmpty(barcode)) { - anyMissing = true; - } else { - barcodes.add(barcode.toUpperCase()); - } - } - LabwareValidator val = lwValFactory.getValidator(); - if (anyMissing) { - problems.add("Source barcode missing."); - } - val.loadLabware(lwRepo, barcodes); - val.setSingleSample(true); - val.validateSources(); - val.validateBioState(bs); - problems.addAll(val.getErrors()); - - return UCMap.from(val.getLabware(), Labware::getBarcode); - } - - /** - * Checks that the prebarcodes look appropriate. - * Prebarcodes should be given for labware types that require it; and not given for labware types - * that do not require it. - * Prebarcodes should be of the expected format. - * Prebarcodes should be unique. - * @param problems receptacle for problems found - * @param request the request - * @param lwTypes the map to look up labware types - */ - public void checkPrebarcodes(Collection problems, TissueBlockRequest request, UCMap lwTypes) { - Set missingBarcodeForLwTypes = new LinkedHashSet<>(); - Set unexpectedBarcodeForLwTypes = new LinkedHashSet<>(); - Set prebarcodes = new HashSet<>(); - Set dupes = new LinkedHashSet<>(); - for (TissueBlockLabware block : request.getLabware()) { - LabwareType lt = lwTypes.get(block.getLabwareType()); - String barcode = block.getPreBarcode(); - if (nullOrEmpty(barcode)) { - if (lt!=null && lt.isPrebarcoded()) { - missingBarcodeForLwTypes.add(lt.getName()); - } - } else { - prebarcodeValidator.validate(barcode, problems::add); - if (lt!=null && !lt.isPrebarcoded()) { - unexpectedBarcodeForLwTypes.add(lt.getName()); - } - barcode = barcode.toUpperCase(); - if (!prebarcodes.add(barcode)) { - dupes.add(barcode); - } - } - } - if (!missingBarcodeForLwTypes.isEmpty()) { - problems.add(pluralise("A barcode is required for labware type{s} ", missingBarcodeForLwTypes.size()) + - missingBarcodeForLwTypes + "."); - } - if (!unexpectedBarcodeForLwTypes.isEmpty()) { - problems.add(pluralise("A barcode is not expected for labware type{s} ", unexpectedBarcodeForLwTypes.size()) + - unexpectedBarcodeForLwTypes + "."); - } - if (!dupes.isEmpty()) { - problems.add("Barcode specified multiple times: "+dupes); - } - if (!prebarcodes.isEmpty()) { - Collection existing = lwRepo.findByBarcodeIn(prebarcodes); - if (!existing.isEmpty()) { - List existingBarcodes = existing.stream() - .map(Labware::getBarcode) - .toList(); - problems.add("Barcode already in use: "+existingBarcodes); - } else { - existing = lwRepo.findByExternalBarcodeIn(prebarcodes); - if (!existing.isEmpty()) { - List existingBarcodes = existing.stream() - .map(Labware::getExternalBarcode) - .toList(); - problems.add("External barcode already in use: " + existingBarcodes); - } - } - } - } - - /** - * Validates and loads comments. Comments are optional, so null comment ids are ignored. - * @param problems receptacle for problems - * @param request the request - * @return a map of comments from their ids - */ - public Map loadComments(Collection problems, TissueBlockRequest request) { - Collection comments = commentValidationService.validateCommentIds(problems, - request.getLabware().stream() - .map(TissueBlockLabware::getCommentId) - .filter(Objects::nonNull) - ); - if (comments.isEmpty()) { - return Map.of(); - } - return comments.stream().collect(BasicUtils.inMap(Comment::getId)); - } - - /** - * Loads entities specified by a string field. - * @param problems receptacle for problems - * @param request the request - * @param fieldGetter the function to get the string from the block in the request - * @param fieldName the name of the field (for problem messages) - * @param entityFieldGetter the function to get the string from the entity - * @param required whether it is a problem for a string to be missing (null or empty) - * @param lookup function to look up matching entities (collectively) from a collection - * @return a map of the entities found from the strings values for the specified field - * @param the type of entity - */ - public UCMap loadEntities(Collection problems, TissueBlockRequest request, - Function fieldGetter, String fieldName, - Function entityFieldGetter, - boolean required, Function, ? extends Collection> lookup) { - boolean anyMissing = false; - Set fieldValues = new LinkedHashSet<>(); - for (var block : request.getLabware()) { - String value = fieldGetter.apply(block); - if (nullOrEmpty(value)) { - anyMissing = true; - } else { - fieldValues.add(value); - } - } - if (anyMissing && required) { - problems.add(fieldName+" missing."); - } - if (fieldValues.isEmpty()) { - return new UCMap<>(); - } - UCMap entities = UCMap.from(lookup.apply(fieldValues), entityFieldGetter); - List unknown = fieldValues.stream() - .filter(value -> entities.get(value)==null) - .filter(distinctUCSerial()) - .map(BasicUtils::repr) - .toList(); - if (!unknown.isEmpty()) { - problems.add(fieldName+" unknown: "+unknown); - } - return entities; - } - - /** - * Checks the formatting of the replicate field, if provided; otherwise, assigns the same replicate as the source. - * @param problems receptacle for problems - * @param request the request - * @param sources map to look up source labware from its barcode - */ - public void checkReplicates(Collection problems, TissueBlockRequest request, UCMap sources) { - boolean anyMissing = false; - boolean differentRep = false; - Set repKeys = new LinkedHashSet<>(); - Set repKeyDupes = new LinkedHashSet<>(); - for (var block : request.getLabware()) { - String sourceReplicateNumber = Optional.ofNullable(sources.get(block.getSourceBarcode())) - .flatMap(BlockProcessingServiceImp::getSample) - .map(sam -> sam.getTissue().getReplicate()) - .orElse(null); - if (nullOrEmpty(sourceReplicateNumber)) { - if (nullOrEmpty(block.getReplicate())) { - anyMissing = true; - } else if (replicateValidator.validate(block.getReplicate(), problems::add)) { - RepKey repKey = RepKey.from(sources.get(block.getSourceBarcode()), block.getReplicate()); - if (repKey != null && !repKeys.add(repKey)) { - repKeyDupes.add(repKey); - } - } - } else if (nullOrEmpty(block.getReplicate())) { - block.setReplicate(sourceReplicateNumber); - } else if (!block.getReplicate().equalsIgnoreCase(sourceReplicateNumber)) { - differentRep = true; - } - } - if (!repKeyDupes.isEmpty()) { - problems.add("Same replicate specified multiple times: "+repKeyDupes); - } - if (anyMissing) { - problems.add("Missing replicate for some blocks."); - } - if (differentRep) { - problems.add("Replicate numbers must match the source replicate number where present."); - } - List alreadyExistRepKeys = repKeys.stream() - .filter(rp -> !tissueRepo.findByDonorIdAndSpatialLocationIdAndReplicate( - rp.donorId(), rp.spatialLocationId(), rp.replicate()).isEmpty() - ).toList(); - if (!alreadyExistRepKeys.isEmpty()) { - problems.add("Replicate already exists in the database: "+alreadyExistRepKeys); - } - } - - /** - * Checks that the barcodes specified to discard are all source barcodes in the request. - * @param problems receptacle for problems - * @param request the request - */ - public void checkDiscardBarcodes(Collection problems, TissueBlockRequest request) { - if (request.getDiscardSourceBarcodes().isEmpty()) { - return; - } - Set sourceBarcodes = request.getLabware().stream() - .map(TissueBlockLabware::getSourceBarcode) - .filter(s -> !nullOrEmpty(s)) - .map(String::toUpperCase) - .collect(toSet()); - boolean anyNull = false; - Set missingBarcodes = new LinkedHashSet<>(); - for (String barcode : request.getDiscardSourceBarcodes()) { - if (nullOrEmpty(barcode)) { - anyNull = true; - } else if (!sourceBarcodes.contains(barcode.toUpperCase())) { - missingBarcodes.add(repr(barcode)); - } - } - if (anyNull) { - problems.add("A null or empty string was supplied as a barcode to discard."); - } - if (!missingBarcodes.isEmpty()) { - problems.add(pluralise("The given list of barcodes to discard includes {a |}barcode{s} " + - "that {is|are} not specified as {a |}source barcode{s} in this request: ", missingBarcodes.size()) - + missingBarcodes); - } - } - - /** - * Creates new samples, in a list parallel to the blocks specified in the request. - * @param request the request - * @param sources the source labware - * @param oct the oct medium for where it is required - * @return a list of samples, corresponding to the respective blocks in the request - */ - public List createSamples(TissueBlockRequest request, UCMap sources, Medium oct) { - final BioState bs = bsRepo.getByName("Tissue"); - return request.getLabware() - .stream() - .map(block -> createSample(block, sources.get(block.getSourceBarcode()), bs, - (block.getLabwareType().equalsIgnoreCase(LabwareType.PROVIASETTE_NAME)) ? oct : null)) - .collect(toList()); - } - - /** - * Creates a new derived tissue from the original; or returns the original if nothing needs changing. - * @param original the original tissue - * @param replicate the specified replicate number - * @param medium the specified medium - * @return a new or original tissue - */ - public Tissue getOrCreateTissue(Tissue original, String replicate, Medium medium) { - boolean changeReplicate = (!nullOrEmpty(replicate) && !replicate.equalsIgnoreCase(original.getReplicate())); - boolean changeMedium = (medium!=null && !medium.getId().equals(original.getMedium().getId())); - if (!changeReplicate && !changeMedium) { - return original; - } - Tissue tissue = original.derived(); - if (changeReplicate) { - tissue.setReplicate(replicate.toLowerCase()); - } - if (changeMedium) { - tissue.setMedium(medium); - } - return tissueRepo.save(tissue); - } - - /** - * Creates a new sample, from a new tissue if necessary. - * The new sample will represent a new block, so it will have the blockHighestSection field set to 0. - * @param block the specification of the block - * @param sourceLabware the source labware for the new block - * @param bs the bio state for the new block - * @param medium the medium for the new block, if specified - * @return the newly created sample - */ - public Sample createSample(TissueBlockLabware block, Labware sourceLabware, BioState bs, Medium medium) { - Tissue original = getSample(sourceLabware) - .map(Sample::getTissue) - .orElseThrow(); - Tissue tissue = getOrCreateTissue(original, block.getReplicate(), medium); - return sampleRepo.save(Sample.newBlock(null, tissue, bs, 0)); - } - - /** - * Creates destinations for the given request. - * @param request the request - * @param samples the samples for the respective blocks of the request - * @param lwTypes map to look up labware types by name - * @return a list of labware for respective blocks of the request - */ - public List createDestinations(TissueBlockRequest request, List samples, UCMap lwTypes) { - return Zip.of(request.getLabware().stream(), samples.stream()) - .map((tbl, sam) -> createDestination(lwTypes.get(tbl.getLabwareType()), sam, tbl.getPreBarcode())) - .toList(); - } - - /** - * Creates labware containing a sample. - * @param lwType the type of labware - * @param sample the sample the labware will contain in its first slot - * @param preBarcode the prebarcode (if any), or null - * @return the newly created labware, containing the sample - */ - public Labware createDestination(LabwareType lwType, Sample sample, String preBarcode) { - Labware lw = lwService.create(lwType, preBarcode, preBarcode); - Slot slot = lw.getFirstSlot(); - slot.addSample(sample); - slotRepo.save(slot); - return lw; - } - - /** - * Creates block processing operations. - * @param request the block processing request - * @param user the user responsible for the operations - * @param sources map to look up source labware by its barcode - * @param destinations the labware for the respective blocks of the request - * @param comments map to look up comments by their id - * @return the operations for the respective blocks in the request - */ - public List createOperations(TissueBlockRequest request, User user, - UCMap sources, List destinations, - Map comments) { - final OperationType opType = opTypeRepo.getByName("Block processing"); - final var destIter = destinations.iterator(); - return request.getLabware().stream() - .map(block -> createOperation(opType, user, sources.get(block.getSourceBarcode()), destIter.next(), - block.getCommentId()==null ? null : comments.get(block.getCommentId()))) - .collect(toList()); - } - - /** - * Creates an operation as specified - * @param opType the type of op - * @param user the user responsible for the op - * @param sourceLw the source labware (containing one sample in one slot) - * @param destLw the destination labware (containing one sample in its first slot) - * @param comment the comment to record (if any), or null - * @return the created operation - */ - public Operation createOperation(OperationType opType, User user, - Labware sourceLw, Labware destLw, Comment comment) { - Slot src = sourceLw.getSlots().stream() - .filter(slot -> !slot.getSamples().isEmpty()) - .findAny() - .orElseThrow(); - Slot dest = destLw.getFirstSlot(); - Sample srcSample = src.getSamples().getFirst(); - Sample dstSample = dest.getSamples().getFirst(); - Action action = new Action(null, null, src, dest, dstSample, srcSample); - Operation op = opService.createOperation(opType, user, List.of(action), null); - if (comment!=null) { - opCommentRepo.save(new OperationComment(null, comment, op.getId(), dstSample.getId(), dest.getId(), null)); - } - return op; - } - - /** - * Discards the indicated labware - * @param barcodes barcodes of labware to discard - * @param lwMap map to look up labware by its barcode - */ - public void discardSources(Collection barcodes, UCMap lwMap) { - if (!barcodes.isEmpty()) { - List labwareToSave = new ArrayList<>(barcodes.size()); - for (String bc : barcodes) { - Labware lw = lwMap.get(bc); - if (!lw.isDiscarded()) { - lw.setDiscarded(true); - labwareToSave.add(lw); - } - } - lwRepo.saveAll(labwareToSave); - } - } - - /** The unique fields associated with a block */ - record RepKey(Donor donor, SpatialLocation spatialLocation, String replicate) { - RepKey(Donor donor, SpatialLocation spatialLocation, String replicate) { - this.donor = donor; - this.spatialLocation = spatialLocation; - this.replicate = replicate.toLowerCase(); - } - - Integer donorId() { - return this.donor.getId(); - } - Integer spatialLocationId() { - return this.spatialLocation.getId(); - } - - static RepKey from(Labware sourceLabware, String replicate) { - if (sourceLabware==null) { - return null; - } - Tissue tissue = getSample(sourceLabware) - .map(Sample::getTissue) - .orElse(null); - if (tissue==null) { - return null; - } - return new RepKey(tissue.getDonor(), tissue.getSpatialLocation(), replicate); - } - - @NotNull - @Override - public String toString() { - return String.format("{Donor: %s, Tissue type: %s, Spatial location: %s, Replicate: %s}", - donor.getDonorName(), spatialLocation.getTissueType().getName(), spatialLocation.getCode(), - replicate); - } - } - /** - * Helper function to get the sample from a piece of labware. - * @param lw the piece of labware - * @return an optional sample contained in the labware - */ - public static Optional getSample(Labware lw) { - return lw.getSlots().stream() - .flatMap(slot -> slot.getSamples().stream()) - .findAny(); + BlockValidator val = blockValidatorFactory.createBlockValidator(request); + val.validate(); + val.raiseError(); + BlockMaker maker = blockMakerFactory.createBlockMaker(request, val.getLwData(), val.getMedium(), + val.getBioState(), val.getWork(), val.getOpType(), user); + return maker.record(); } } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockData.java b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockData.java new file mode 100644 index 000000000..d0d0abb76 --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockData.java @@ -0,0 +1,111 @@ +package uk.ac.sanger.sccp.stan.service.block; + +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockContent; +import uk.ac.sanger.sccp.utils.BasicUtils; + +import java.util.List; +import java.util.Objects; + +/** + * @author dr6 + */ +public class BlockData { + private TissueBlockContent requestContent; + private Labware sourceLabware; + private Sample sourceSample; + private Sample sample; + private List sourceSlots; + private List destSlots; + private Comment comment; + + public BlockData(TissueBlockContent requestContent) { + this.requestContent = requestContent; + } + + public TissueBlockContent getRequestContent() { + return this.requestContent; + } + + public void setRequestContent(TissueBlockContent requestContent) { + this.requestContent = requestContent; + } + + public Labware getSourceLabware() { + return this.sourceLabware; + } + + public void setSourceLabware(Labware sourceLabware) { + this.sourceLabware = sourceLabware; + } + + public Sample getSourceSample() { + return this.sourceSample; + } + + public void setSourceSample(Sample sourceSample) { + this.sourceSample = sourceSample; + } + + public Sample getSample() { + return this.sample; + } + + public void setSample(Sample sample) { + this.sample = sample; + } + + public List getSourceSlots() { + return this.sourceSlots; + } + + public void setSourceSlots(List sourceSlots) { + this.sourceSlots = sourceSlots; + } + + public List getDestSlots() { + return this.destSlots; + } + + public void setDestSlots(List destSlots) { + this.destSlots = destSlots; + } + + public Comment getComment() { + return this.comment; + } + + public void setComment(Comment comment) { + this.comment = comment; + } + + @Override + public String toString() { + return BasicUtils.describe(this) + .add("requestContent", requestContent) + .add("sourceSample", sourceSample) + .add("sample", sample) + .add("sourceSlots", sourceSlots) + .add("destSlots", destSlots) + .add("comment", comment) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + BlockData that = (BlockData) o; + return (Objects.equals(this.requestContent, that.requestContent) + && Objects.equals(this.sourceSample, that.sourceSample) + && Objects.equals(this.sample, that.sample) + && Objects.equals(this.sourceSlots, that.sourceSlots) + && Objects.equals(this.destSlots, that.destSlots) + && Objects.equals(this.comment, that.comment) + ); + } + + @Override + public int hashCode() { + return requestContent.hashCode(); + } +} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockLabwareData.java b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockLabwareData.java new file mode 100644 index 000000000..8be4e82a0 --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockLabwareData.java @@ -0,0 +1,70 @@ +package uk.ac.sanger.sccp.stan.service.block; + +import uk.ac.sanger.sccp.stan.model.Labware; +import uk.ac.sanger.sccp.stan.model.LabwareType; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockLabware; + +import java.util.List; +import java.util.Objects; + +/** + * @author dr6 + */ +public class BlockLabwareData { + private TissueBlockLabware requestLabware; + private LabwareType lwType; + private Labware labware; + private List blocks; + + public BlockLabwareData(TissueBlockLabware requestLabware) { + this.requestLabware = requestLabware; + this.blocks = requestLabware.getContents().stream().map(BlockData::new).toList(); + } + + public TissueBlockLabware getRequestLabware() { + return this.requestLabware; + } + + public void setRequestLabware(TissueBlockLabware requestLabware) { + this.requestLabware = requestLabware; + } + + public LabwareType getLwType() { + return this.lwType; + } + + public void setLwType(LabwareType lwType) { + this.lwType = lwType; + } + + public Labware getLabware() { + return this.labware; + } + + public void setLabware(Labware labware) { + this.labware = labware; + } + + public List getBlocks() { + return this.blocks; + } + + public void setBlocks(List blocks) { + this.blocks = blocks; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + BlockLabwareData that = (BlockLabwareData) o; + return (Objects.equals(this.requestLabware, that.requestLabware) + && Objects.equals(this.lwType, that.lwType) + && Objects.equals(this.labware, that.labware) + && Objects.equals(this.blocks, that.blocks)); + } + + @Override + public int hashCode() { + return requestLabware.hashCode(); + } +} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMaker.java b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMaker.java new file mode 100644 index 000000000..76f8157fe --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMaker.java @@ -0,0 +1,12 @@ +package uk.ac.sanger.sccp.stan.service.block; + +import uk.ac.sanger.sccp.stan.request.OperationResult; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest; + +/** + * Utility for executing a {@link TissueBlockRequest} + */ +public interface BlockMaker { + /** Executes the request */ + OperationResult record(); +} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerFactory.java b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerFactory.java new file mode 100644 index 000000000..82785e717 --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerFactory.java @@ -0,0 +1,52 @@ +package uk.ac.sanger.sccp.stan.service.block; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.repo.*; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest; +import uk.ac.sanger.sccp.stan.service.*; +import uk.ac.sanger.sccp.stan.service.work.WorkService; + +import java.util.List; + + +/** + * Component for creating block makers. + * @author dr6 + */ +@Component +public class BlockMakerFactory { + private final TissueRepo tissueRepo; + private final SampleRepo sampleRepo; + private final SlotRepo slotRepo; + private final LabwareRepo lwRepo; + private final OperationCommentRepo opcomRepo; + private final LabwareService lwService; + private final OperationService opService; + private final WorkService workService; + private final BioRiskService bioRiskService; + + @Autowired + public BlockMakerFactory(TissueRepo tissueRepo, SampleRepo sampleRepo, SlotRepo slotRepo, LabwareRepo lwRepo, + OperationCommentRepo opcomRepo, LabwareService lwService, OperationService opService, + WorkService workService, BioRiskService bioRiskService) { + this.tissueRepo = tissueRepo; + this.sampleRepo = sampleRepo; + this.slotRepo = slotRepo; + this.lwRepo = lwRepo; + this.opcomRepo = opcomRepo; + this.lwService = lwService; + this.opService = opService; + this.workService = workService; + this.bioRiskService = bioRiskService; + } + + /** Creates a block maker for the given request with the given data. */ + public BlockMaker createBlockMaker(TissueBlockRequest request, List lwData, + Medium medium, BioState bioState, Work work, OperationType opType, User user) { + return new BlockMakerImp(tissueRepo, sampleRepo, slotRepo, lwRepo, opcomRepo, + lwService, opService, workService, bioRiskService, + request, lwData, medium, bioState, work, opType, user); + } +} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerImp.java new file mode 100644 index 000000000..e83efc4bc --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerImp.java @@ -0,0 +1,217 @@ +package uk.ac.sanger.sccp.stan.service.block; + +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.repo.*; +import uk.ac.sanger.sccp.stan.request.OperationResult; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest; +import uk.ac.sanger.sccp.stan.service.*; +import uk.ac.sanger.sccp.stan.service.work.WorkService; + +import java.util.*; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toSet; +import static uk.ac.sanger.sccp.utils.BasicUtils.iter; +import static uk.ac.sanger.sccp.utils.BasicUtils.nullOrEmpty; + +/** + * @author dr6 + */ +public class BlockMakerImp implements BlockMaker { + private final TissueRepo tissueRepo; + private final SampleRepo sampleRepo; + private final SlotRepo slotRepo; + private final LabwareRepo lwRepo; + private final OperationCommentRepo opcomRepo; + private final LabwareService lwService; + private final OperationService opService; + private final WorkService workService; + private final BioRiskService bioRiskService; + + private final TissueBlockRequest request; + private final List lwData; + private final Medium medium; + private final BioState bioState; + private final Work work; + private final OperationType opType; + private final User user; + + public BlockMakerImp(TissueRepo tissueRepo, SampleRepo sampleRepo, SlotRepo slotRepo, LabwareRepo lwRepo, + OperationCommentRepo opcomRepo, + LabwareService lwService, OperationService opService, WorkService workService, + BioRiskService bioRiskService, + TissueBlockRequest request, List lwData, + Medium medium, BioState bioState, Work work, OperationType opType, User user) { + this.tissueRepo = tissueRepo; + this.sampleRepo = sampleRepo; + this.slotRepo = slotRepo; + this.lwRepo = lwRepo; + this.opcomRepo = opcomRepo; + this.lwService = lwService; + this.opService = opService; + this.workService = workService; + this.bioRiskService = bioRiskService; + this.request = request; + this.lwData = lwData; + this.medium = medium; + this.bioState = bioState; + this.work = work; + this.opType = opType; + this.user = user; + } + + @Override + public OperationResult record() { + List labware = createLabware(); + createSamples(); + findSlots(); + fillLabware(); + List ops = createOperations(); + if (work != null) { + workService.link(work, ops); + } + bioRiskService.copyOpSampleBioRisks(ops); + discardSources(); + return new OperationResult(ops, labware); + } + + /** Create the destination labware, initially empty */ + public List createLabware() { + List labware = new ArrayList<>(lwData.size()); + for (BlockLabwareData lwd : lwData) { + String prebarcode = lwd.getRequestLabware().getPreBarcode(); + Labware lw = lwService.create(lwd.getLwType(), prebarcode, prebarcode); + labware.add(lw); + lwd.setLabware(lw); + } + return labware; + } + + /** Either return the tissue or create a new tissue matching the given fields */ + public Tissue getOrCreateTissue(Tissue original, String replicate, Medium medium) { + boolean changeReplicate = (!nullOrEmpty(replicate) && !replicate.equalsIgnoreCase(original.getReplicate())); + boolean changeMedium = (medium!=null && !medium.getId().equals(original.getMedium().getId())); + if (!changeReplicate && !changeMedium) { + return original; + } + Tissue tissue = original.derived(); + if (changeReplicate) { + tissue.setReplicate(replicate.toLowerCase()); + } + if (changeMedium) { + tissue.setMedium(medium); + } + return tissueRepo.save(tissue); + } + + /** Creates the samples for the requested blocks */ + public void createSamples() { + for (BlockData bd : iter(blockDataStream())) { + Tissue original = bd.getSourceSample().getTissue(); + Tissue tissue = getOrCreateTissue(original, bd.getRequestContent().getReplicate(), medium); + Sample sample = sampleRepo.save(Sample.newBlock(null, tissue, bioState, 0)); + bd.setSample(sample); + } + } + + /** + * Finds the source and destination slots for each block + */ + public void findSlots() { + for (BlockLabwareData lwd : lwData) { + Labware destLabware = lwd.getLabware(); + for (BlockData bd : lwd.getBlocks()) { + Set
destAddresses = new HashSet<>(bd.getRequestContent().getAddresses()); + List destSlots = destLabware.getSlots().stream() + .filter(slot -> destAddresses.contains(slot.getAddress())) + .toList(); + bd.setDestSlots(destSlots); + Integer sampleId = bd.getSourceSample().getId(); + List sourceSlots = bd.getSourceLabware().getSlots().stream() + .filter(slot -> slot.getSamples().stream().anyMatch(sam -> sam.getId().equals(sampleId))) + .toList(); + bd.setSourceSlots(sourceSlots); + } + } + } + + /** Creates the required operations and records the requested comments */ + public List createOperations() { + List ops = new ArrayList<>(lwData.size()); + for (BlockLabwareData lwd : lwData) { + Operation op = createOperation(lwd); + recordComments(lwd, op.getId()); + ops.add(op); + } + return ops; + } + + /** + * Creates an operation for the specified destination labware + * with an action for each combination of source/destination slot + */ + public Operation createOperation(BlockLabwareData lwd) { + List actions = new ArrayList<>(); + for (BlockData bd : lwd.getBlocks()) { + Sample sample = bd.getSample(); + Sample sourceSample = bd.getSourceSample(); + for (Slot destSlot : bd.getDestSlots()) { + for (Slot sourceSlot : bd.getSourceSlots()) { + actions.add(new Action(null, null, sourceSlot, destSlot, sample, sourceSample)); + } + } + } + return opService.createOperation(opType, user, actions, null); + } + + /** Records the requested comments against the specified operation */ + public void recordComments(BlockLabwareData lwd, Integer opId) { + List opcoms = new ArrayList<>(); + for (BlockData bd : lwd.getBlocks()) { + Comment com = bd.getComment(); + if (com == null) { + continue; + } + Integer sampleId = bd.getSample().getId(); + for (Slot destSlot : bd.getDestSlots()) { + OperationComment opcom = new OperationComment(null, com, opId, sampleId, destSlot.getId(), null); + opcoms.add(opcom); + } + } + if (!opcoms.isEmpty()) { + opcomRepo.saveAll(opcoms); + } + } + + /** Puts the new samples into the slots and saves the updated slots */ + public void fillLabware() { + Set slotsToUpdate = new HashSet<>(); + for (BlockData bd : iter(blockDataStream())) { + for (Slot slot : bd.getDestSlots()) { + slot.addSample(bd.getSample()); + slotsToUpdate.add(slot); + } + } + slotRepo.saveAll(slotsToUpdate); + } + + /** Marks any sources discarded if the request specifies that we do so */ + public void discardSources() { + if (!nullOrEmpty(request.getDiscardSourceBarcodes())) { + Set discardBarcodes = request.getDiscardSourceBarcodes().stream() + .map(String::toUpperCase) + .collect(toSet()); + Set lwToDiscard = blockDataStream().map(BlockData::getSourceLabware) + .filter(lw -> !lw.isDiscarded() && discardBarcodes.contains(lw.getBarcode().toUpperCase())) + .collect(toSet()); + lwToDiscard.forEach(lw -> lw.setDiscarded(true)); + lwRepo.saveAll(lwToDiscard); + } + } + + /** Stream of block data from the request */ + Stream blockDataStream() { + return lwData.stream() + .flatMap(lw -> lw.getBlocks().stream()); + } +} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidator.java b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidator.java new file mode 100644 index 000000000..09a2daa84 --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidator.java @@ -0,0 +1,41 @@ +package uk.ac.sanger.sccp.stan.service.block; + +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest; +import uk.ac.sanger.sccp.stan.service.ValidationException; + +import java.util.Collection; +import java.util.List; + +/** Utility for loading data and validating a {@link TissueBlockRequest}. */ +public interface BlockValidator { + /** + * Validates the request. + * Loads the data for the request into various fields. + */ + void validate(); + + /** Gets the collated data for the request. */ + List getLwData(); + + /** Gets the work (if any) indicated in the request. */ + Work getWork(); + + /** Gets the appropriate bio state for the request. */ + BioState getBioState(); + + /** Gets the appropriate medium for the request. */ + Medium getMedium(); + + /** Gets the appropriate operation type for the request. */ + OperationType getOpType(); + + /** Gets any problems found. */ + Collection getProblems(); + + /** + * Throws a ValidationException if there are any problems. + * @exception ValidationException if there are any problems + */ + void raiseError(); +} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorFactory.java b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorFactory.java new file mode 100644 index 000000000..76cd5b8e1 --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorFactory.java @@ -0,0 +1,56 @@ +package uk.ac.sanger.sccp.stan.service.block; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import uk.ac.sanger.sccp.stan.repo.*; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest; +import uk.ac.sanger.sccp.stan.service.*; +import uk.ac.sanger.sccp.stan.service.work.WorkService; + +/** + * Component for creating block request validators + * @author dr6 + */ +@Component +public class BlockValidatorFactory { + private final LabwareValidatorFactory lwValFactory; + private final Validator prebarcodeValidator; + private final Validator replicateValidator; + + private final LabwareRepo lwRepo; + private final OperationTypeRepo opTypeRepo; + private final LabwareTypeRepo ltRepo; + private final BioStateRepo bsRepo; + private final TissueRepo tissueRepo; + private final MediumRepo mediumRepo; + private final CommentValidationService commentValidationService; + private final WorkService workService; + + @Autowired + public BlockValidatorFactory(LabwareValidatorFactory lwValFactory, + @Qualifier("tubePrebarcodeValidator") Validator prebarcodeValidator, + @Qualifier("replicateValidator") Validator replicateValidator, + LabwareRepo lwRepo, OperationTypeRepo opTypeRepo, + LabwareTypeRepo ltRepo, BioStateRepo bsRepo, TissueRepo tissueRepo, MediumRepo mediumRepo, + CommentValidationService commentValidationService, WorkService workService) { + this.lwValFactory = lwValFactory; + this.prebarcodeValidator = prebarcodeValidator; + this.replicateValidator = replicateValidator; + this.lwRepo = lwRepo; + this.opTypeRepo = opTypeRepo; + this.ltRepo = ltRepo; + this.bsRepo = bsRepo; + this.tissueRepo = tissueRepo; + this.mediumRepo = mediumRepo; + this.commentValidationService = commentValidationService; + this.workService = workService; + } + + /** Creates a validator for the given block request. */ + public BlockValidator createBlockValidator(TissueBlockRequest request) { + return new BlockValidatorImp(lwValFactory, prebarcodeValidator, replicateValidator, + lwRepo, opTypeRepo, ltRepo, bsRepo, tissueRepo, mediumRepo, + commentValidationService, workService, request); + } +} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorImp.java new file mode 100644 index 000000000..523939b42 --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorImp.java @@ -0,0 +1,480 @@ +package uk.ac.sanger.sccp.stan.service.block; + +import org.jetbrains.annotations.NotNull; +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.repo.*; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockContent; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockLabware; +import uk.ac.sanger.sccp.stan.service.*; +import uk.ac.sanger.sccp.stan.service.work.WorkService; +import uk.ac.sanger.sccp.utils.UCMap; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toSet; +import static uk.ac.sanger.sccp.utils.BasicUtils.*; + +/** + * Helper to validate a block request. + * @author dr6 + */ +public class BlockValidatorImp implements BlockValidator { + private final LabwareValidatorFactory lwValFactory; + private final Validator prebarcodeValidator; + private final Validator replicateValidator; + private final LabwareRepo lwRepo; + private final OperationTypeRepo opTypeRepo; + private final LabwareTypeRepo ltRepo; + private final BioStateRepo bsRepo; + private final TissueRepo tissueRepo; + private final MediumRepo mediumRepo; + private final CommentValidationService commentValidationService; + private final WorkService workService; + + private final TissueBlockRequest request; + private List lwData; + private Work work; + private BioState bioState; + private Medium medium; + private OperationType opType; + + private Collection problems; + + public BlockValidatorImp(LabwareValidatorFactory lwValFactory, + Validator prebarcodeValidator, Validator replicateValidator, + LabwareRepo lwRepo, OperationTypeRepo opTypeRepo, LabwareTypeRepo ltRepo, + BioStateRepo bsRepo, TissueRepo tissueRepo, MediumRepo mediumRepo, + CommentValidationService commentValidationService, WorkService workService, + TissueBlockRequest request) { + this.lwValFactory = lwValFactory; + this.prebarcodeValidator = prebarcodeValidator; + this.replicateValidator = replicateValidator; + this.lwRepo = lwRepo; + this.opTypeRepo = opTypeRepo; + this.ltRepo = ltRepo; + this.bsRepo = bsRepo; + this.tissueRepo = tissueRepo; + this.mediumRepo = mediumRepo; + this.commentValidationService = commentValidationService; + this.workService = workService; + this.request = request; + } + + /** Loads an entity from the database or returns null and records a problem if it is not found. */ + E loadFromOpt(String typeName, Function> loader, String name) { + Optional opt = loader.apply(name); + if (opt.isPresent()) { + return opt.get(); + } + problems.add(String.format("%s %s not found in database.", typeName, repr(name))); + return null; + } + + @Override + public void validate() { + setProblems(new LinkedHashSet<>()); + if (nullOrEmpty(request.getLabware())) { + problems.add("No labware specified."); + return; + } + setLwData(request.getLabware().stream().map(BlockLabwareData::new).toList()); + loadEntities(); + checkDestAddresses(); + checkPrebarcodes(); + checkReplicates(); + checkDiscardBarcodes(); + } + + /** + * Loads the entities referenced by the request. + */ + public void loadEntities() { + setWork(workService.validateUsableWork(problems, request.getWorkNumber())); + setBioState(loadFromOpt("Bio state", bsRepo::findByName, "Original sample")); + setMedium(loadFromOpt("Medium", mediumRepo::findByName, "OCT")); + setOpType(loadFromOpt("Operation type", opTypeRepo::findByName, "Block processing")); + loadSources(); + loadSourceSamples(); + loadLabwareTypes(); + loadComments(); + } + + /** + * Loads the source labware into a map. + */ + public void loadSources() { + Set barcodes = new HashSet<>(); + boolean anyMissing = false; + for (var block : iter(requestContents())) { + String barcode = block.getSourceBarcode(); + if (nullOrEmpty(barcode)) { + anyMissing = true; + } else { + barcodes.add(barcode.toUpperCase()); + } + } + LabwareValidator val = lwValFactory.getValidator(); + if (anyMissing) { + problems.add("Source barcode missing."); + } + val.loadLabware(lwRepo, barcodes); + val.validateSources(); + if (bioState!=null) { + val.validateBioState(bioState); + } + problems.addAll(val.getErrors()); + UCMap sourceLabware = UCMap.from(val.getLabware(), Labware::getBarcode); + for (BlockData bd : iter(blockDatas())) { + bd.setSourceLabware(sourceLabware.get(bd.getRequestContent().getSourceBarcode())); + } + } + + /** Loads the source samples into a map. */ + public void loadSourceSamples() { + boolean anyMissing = false; + for (var lwd : lwData) { + for (var bd : lwd.getBlocks()) { + Integer sourceSampleId = bd.getRequestContent().getSourceSampleId(); + if (sourceSampleId==null) { + anyMissing = true; + continue; + } + + Labware lw = bd.getSourceLabware(); + if (lw==null) { + continue; + } + Sample sample = lw.getSlots().stream().flatMap(slot -> slot.getSamples().stream()) + .filter(sam -> sam.getId().equals(sourceSampleId)) + .findFirst() + .orElse(null); + if (sample==null) { + problems.add(String.format("Sample id %s not present in labware %s.", sourceSampleId, lw.getBarcode())); + } else { + bd.setSourceSample(sample); + } + } + } + if (anyMissing) { + problems.add("Source sample ID missing from block request."); + } + } + + /** Loads the labware types into the data objects. */ + public void loadLabwareTypes() { + Set lwTypeNames = lwData.stream() + .map(ld -> ld.getRequestLabware().getLabwareType()) + .filter(s -> !nullOrEmpty(s)) + .map(String::toUpperCase) + .collect(toSet()); + UCMap lwTypes = UCMap.from(ltRepo.findAllByNameIn(lwTypeNames), LabwareType::getName); + boolean anyMissing = false; + Set unknown = new LinkedHashSet<>(); + for (BlockLabwareData ld : lwData) { + TissueBlockLabware rlw = ld.getRequestLabware(); + if (nullOrEmpty(rlw.getLabwareType())) { + anyMissing = true; + } else { + LabwareType lt = lwTypes.get(rlw.getLabwareType()); + if (lt==null) { + unknown.add(repr(rlw.getLabwareType())); + } else { + ld.setLwType(lt); + } + } + } + if (anyMissing) { + problems.add("Missing labware type."); + } + if (!unknown.isEmpty()) { + problems.add("Unknown labware types: "+unknown); + } + } + + /** Loads the referenced comment into the data objects. */ + public void loadComments() { + Stream commentIdStream = requestContents() + .map(TissueBlockContent::getCommentId) + .filter(Objects::nonNull); + Map commentMap = commentValidationService.validateCommentIds(problems, commentIdStream).stream() + .collect(inMap(Comment::getId)); + for (BlockData bd : iter(lwData.stream().flatMap(ld -> ld.getBlocks().stream()))) { + bd.setComment(commentMap.get(bd.getRequestContent().getCommentId())); + } + } + + /** Checks that the destination addresses are valid. */ + public void checkDestAddresses() { + boolean anyMissing = false; + for (var lwd : lwData) { + LabwareType lt = lwd.getLwType(); + for (var bl : lwd.getBlocks()) { + List
addresses = bl.getRequestContent().getAddresses(); + if (nullOrEmpty(addresses)) { + anyMissing = true; + } else { + for (Address addr : addresses) { + if (lt != null && lt.indexOf(addr) < 0) { + problems.add(String.format("Slot address %s not valid in labware type %s.", addr, lt.getName())); + } + } + } + } + } + if (anyMissing) { + problems.add("Destination slot addresses missing from block request."); + } + } + + /** + * Checks that the prebarcodes look appropriate. + * Prebarcodes should be given for labware types that require it; and not given for labware types + * that do not require it. + * Prebarcodes should be of the expected format. + * Prebarcodes should be unique. + */ + public void checkPrebarcodes() { + Set missingBarcodeForLwTypes = new LinkedHashSet<>(); + Set unexpectedBarcodeForLwTypes = new LinkedHashSet<>(); + Set prebarcodes = new HashSet<>(); + Set dupes = new LinkedHashSet<>(); + for (var lwd : lwData) { + LabwareType lt = lwd.getLwType(); + String barcode = lwd.getRequestLabware().getPreBarcode(); + if (nullOrEmpty(barcode)) { + if (lt != null && lt.isPrebarcoded()) { + missingBarcodeForLwTypes.add(lt.getName()); + } + } else { + prebarcodeValidator.validate(barcode, problems::add); + if (lt != null && !lt.isPrebarcoded()) { + unexpectedBarcodeForLwTypes.add(lt.getName()); + } + barcode = barcode.toUpperCase(); + if (!prebarcodes.add(barcode)) { + dupes.add(barcode); + } + } + } + + if (!missingBarcodeForLwTypes.isEmpty()) { + problems.add(pluralise("A barcode is required for labware type{s} ", missingBarcodeForLwTypes.size()) + + missingBarcodeForLwTypes + "."); + } + if (!unexpectedBarcodeForLwTypes.isEmpty()) { + problems.add(pluralise("A barcode is not expected for labware type{s} ", unexpectedBarcodeForLwTypes.size()) + + unexpectedBarcodeForLwTypes + "."); + } + if (!dupes.isEmpty()) { + problems.add("Barcode specified multiple times: "+dupes); + } + + if (!prebarcodes.isEmpty()) { + Collection existing = lwRepo.findByBarcodeIn(prebarcodes); + if (!existing.isEmpty()) { + List existingBarcodes = existing.stream() + .map(Labware::getBarcode) + .toList(); + problems.add("Barcode already in use: "+existingBarcodes); + } else { + existing = lwRepo.findByExternalBarcodeIn(prebarcodes); + if (!existing.isEmpty()) { + List existingBarcodes = existing.stream() + .map(Labware::getExternalBarcode) + .toList(); + problems.add("External barcode already in use: " + existingBarcodes); + } + } + } + + } + + /** + * Checks the formatting of the replicate field, if provided; otherwise, assigns the same replicate as the source. + */ + public void checkReplicates() { + boolean anyMissing = false; + boolean differentRep = false; + Set repKeys = new LinkedHashSet<>(); + Set repKeyDupes = new LinkedHashSet<>(); + for (var bl : iter(blockDatas())) { + String newRep = bl.getRequestContent().getReplicate(); + Sample sourceSample = bl.getSourceSample(); + String sourceRep = Optional.ofNullable(sourceSample) + .map(Sample::getTissue) + .map(Tissue::getReplicate) + .orElse(null); + if (nullOrEmpty(sourceRep)) { + if (nullOrEmpty(newRep)) { + anyMissing = true; + } else if (replicateValidator.validate(newRep, problems::add)) { + RepKey repKey = RepKey.from(sourceSample, bl.getRequestContent().getReplicate()); + if (repKey != null && !repKeys.add(repKey)) { + repKeyDupes.add(repKey); + } + } + } else if (nullOrEmpty(newRep)) { + bl.getRequestContent().setReplicate(sourceRep); + } else if (!newRep.equalsIgnoreCase(sourceRep)) { + differentRep = true; + } + } + + if (!repKeyDupes.isEmpty()) { + problems.add("Same replicate specified multiple times: " + repKeyDupes); + } + if (anyMissing) { + problems.add("Missing replicate for some blocks."); + } + if (differentRep) { + problems.add("Replicate numbers must match the source replicate number where present."); + } + List alreadyExistRepKeys = repKeys.stream() + .filter(rp -> !tissueRepo.findByDonorIdAndSpatialLocationIdAndReplicate( + rp.donorId(), rp.spatialLocationId(), rp.replicate()).isEmpty() + ).toList(); + if (!alreadyExistRepKeys.isEmpty()) { + problems.add("Replicate already exists in the database: " + alreadyExistRepKeys); + } + } + + /** Checks that the discard barcodes are source barcodes in the request. */ + public void checkDiscardBarcodes() { + List discardBarcodes = request.getDiscardSourceBarcodes(); + if (discardBarcodes.isEmpty()) { + return; + } + Set sourceBarcodes = requestContents() + .map(TissueBlockContent::getSourceBarcode) + .filter(s -> !nullOrEmpty(s)) + .map(String::toUpperCase) + .collect(toSet()); + boolean anyNull = false; + Set missingBarcodes = new LinkedHashSet<>(); + for (String barcode : discardBarcodes) { + if (nullOrEmpty(barcode)) { + anyNull = true; + } else if (!sourceBarcodes.contains(barcode.toUpperCase())) { + missingBarcodes.add(repr(barcode)); + } + } + if (anyNull) { + problems.add("A null or empty string was supplied as a barcode to discard."); + } + if (!missingBarcodes.isEmpty()) { + problems.add(pluralise("The given list of barcodes to discard includes {a |}barcode{s} " + + "that {is|are} not specified as {a |}source barcode{s} in this request: ", missingBarcodes.size()) + + missingBarcodes); + } + } + + public void setLwData(List lwData) { + this.lwData = lwData; + } + + @Override + public List getLwData() { + return this.lwData; + } + + public void setWork(Work work) { + this.work = work; + } + + @Override + public Work getWork() { + return this.work; + } + + public void setBioState(BioState bioState) { + this.bioState = bioState; + } + + @Override + public BioState getBioState() { + return this.bioState; + } + + public void setMedium(Medium medium) { + this.medium = medium; + } + + @Override + public Medium getMedium() { + return this.medium; + } + + public void setOpType(OperationType opType) { + this.opType = opType; + } + + @Override + public OperationType getOpType() { + return this.opType; + } + + public void setProblems(Collection problems) { + this.problems = problems; + } + + @Override + public Collection getProblems() { + return this.problems; + } + + @Override + public void raiseError() { + if (!problems.isEmpty()) { + throw new ValidationException(problems); + } + } + + /** A stream of the contents in the request. */ + Stream requestContents() { + return request.getLabware().stream().flatMap(rlw -> rlw.getContents().stream()); + } + + /** A stream of the block data for the request */ + Stream blockDatas() { + return lwData.stream().flatMap(lwd -> lwd.getBlocks().stream()); + } + + + /** The unique fields associated with a block */ + record RepKey(Donor donor, SpatialLocation spatialLocation, String replicate) { + RepKey(Donor donor, SpatialLocation spatialLocation, String replicate) { + this.donor = donor; + this.spatialLocation = spatialLocation; + this.replicate = replicate.toLowerCase(); + } + + Integer donorId() { + return this.donor.getId(); + } + Integer spatialLocationId() { + return this.spatialLocation.getId(); + } + + static RepKey from(Sample sample, String replicate) { + if (sample==null) { + return null; + } + Tissue tissue = sample.getTissue(); + if (tissue==null) { + return null; + } + return new RepKey(tissue.getDonor(), tissue.getSpatialLocation(), replicate); + } + + @NotNull + @Override + public String toString() { + return String.format("{Donor: %s, Tissue type: %s, Spatial location: %s, Replicate: %s}", + donor.getDonorName(), spatialLocation.getTissueType().getName(), spatialLocation.getCode(), + replicate); + } + } +} diff --git a/src/main/java/uk/ac/sanger/sccp/utils/BasicUtils.java b/src/main/java/uk/ac/sanger/sccp/utils/BasicUtils.java index a95b21340..2e36e52b5 100644 --- a/src/main/java/uk/ac/sanger/sccp/utils/BasicUtils.java +++ b/src/main/java/uk/ac/sanger/sccp/utils/BasicUtils.java @@ -621,6 +621,13 @@ public static Stream stream(Iterable iterable) { return StreamSupport.stream(iterable.spliterator(), false); } + /** + * Returns an iterable that emits the iterator from the given stream. + */ + public static Iterable iter(Stream stream) { + return stream::iterator; + } + /** * Does the given iterable contain any duplicates? * Duplication is checked using a hashset. diff --git a/src/main/resources/schema.graphqls b/src/main/resources/schema.graphqls index e1c56f7f2..acecb5c4d 100644 --- a/src/main/resources/schema.graphqls +++ b/src/main/resources/schema.graphqls @@ -1810,16 +1810,26 @@ input TissueBlockRequest { """The input about a new block being created.""" input TissueBlockLabware { - """The original tissue barcode.""" - sourceBarcode: String! """The labware type for the new labware.""" labwareType: String! """The barcode of the new labware, if it is prebarcoded.""" preBarcode: String - """The comment (if any) associated with this operation.""" + """The contents (new samples) for the new labware.""" + contents: [TissueBlockContent!]! +} + +"""Info describing a new block in a tissue block request.""" +input TissueBlockContent { + """The barcode of the labware containing the source sample.""" + sourceBarcode: String! + """The id of the source sample.""" + sourceSampleId: Int! + """The addresses to place the sample in the new labware.""" + addresses: [Address!]! + """The id of a comment to record on the new block, if any.""" commentId: Int """The replicate number for the new block.""" - replicate: String! + replicate: String } """A request to transfer original sample into pots.""" diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterOriginalSamplesMutation.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterOriginalSamplesMutation.java index 129bc1ea0..262d0c54a 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterOriginalSamplesMutation.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterOriginalSamplesMutation.java @@ -112,7 +112,7 @@ public void testRegisterOriginalSamples(boolean hasExternalName) throws Exceptio assertThat(bioRiskRepo.loadBioRiskForSampleId(sample.getId())).contains(risk1); Work work = entityCreator.createWork(null, null, null, null, null); - Labware block = testBlockProcessing(barcode, work); + Labware block = testBlockProcessing(barcode, work, sample.getId()); testSectioningBlock(block, work); lw.setDiscarded(false); lwRepo.save(lw); @@ -121,10 +121,11 @@ public void testRegisterOriginalSamples(boolean hasExternalName) throws Exceptio } - private Labware testBlockProcessing(String sourceBarcode, Work work) throws Exception { + private Labware testBlockProcessing(String sourceBarcode, Work work, Integer sourceSampleId) throws Exception { OperationType opType = entityCreator.createOpType("Block processing", null); String mutation = tester.readGraphQL("tissueblock.graphql") .replace("WORKNUMBER", work.getWorkNumber()) + .replace("999", String.valueOf(sourceSampleId)) .replace("BARCODE", sourceBarcode); Object result = tester.post(mutation); String destBarcode = chainGet(result, "data", "performTissueBlock", "labware", 0, "barcode"); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java index e72822d5e..a65f2df93 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java @@ -1,90 +1,47 @@ package uk.ac.sanger.sccp.stan.service; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.*; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.*; import uk.ac.sanger.sccp.stan.*; import uk.ac.sanger.sccp.stan.Matchers; import uk.ac.sanger.sccp.stan.model.*; -import uk.ac.sanger.sccp.stan.repo.*; import uk.ac.sanger.sccp.stan.request.OperationResult; import uk.ac.sanger.sccp.stan.request.TissueBlockRequest; -import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockLabware; +import uk.ac.sanger.sccp.stan.service.block.*; import uk.ac.sanger.sccp.stan.service.store.StoreService; -import uk.ac.sanger.sccp.stan.service.work.WorkService; -import uk.ac.sanger.sccp.utils.UCMap; -import java.util.*; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.IntStream; -import java.util.stream.Stream; +import java.util.List; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; -import static uk.ac.sanger.sccp.stan.Matchers.*; -import static uk.ac.sanger.sccp.utils.BasicUtils.coalesce; /** * Tests {@link BlockProcessingServiceImp} */ public class TestBlockProcessingService { @Mock - private LabwareValidatorFactory mockLwValFactory; + BlockValidatorFactory mockBlockValFactory; @Mock - private Validator mockPrebarcodeValidator; + BlockMakerFactory mockBlockMakerFactory; @Mock - private Validator mockReplicateValidator; + StoreService mockStoreService; @Mock - private LabwareRepo mockLwRepo; - @Mock - private SlotRepo mockSlotRepo; - @Mock - private OperationTypeRepo mockOpTypeRepo; - @Mock - private OperationCommentRepo mockOpCommentRepo; - @Mock - private LabwareTypeRepo mockLtRepo; - @Mock - private BioStateRepo mockBsRepo; - @Mock - private TissueRepo mockTissueRepo; - @Mock - private SampleRepo mockSampleRepo; - @Mock - private MediumRepo mockMediumRepo; - @Mock - private CommentValidationService mockCommentValidationService; - @Mock - private OperationService mockOpService; - @Mock - private LabwareService mockLwService; - @Mock - private BioRiskService mockBioRiskService; - @Mock - private WorkService mockWorkService; - @Mock - private StoreService mockStoreService; - @Mock - private Transactor mockTransactor; - - private BlockProcessingServiceImp service; + Transactor mockTransactor; + @InjectMocks + BlockProcessingServiceImp service; private AutoCloseable mocking; @BeforeEach void setup() { mocking = MockitoAnnotations.openMocks(this); - service = spy(new BlockProcessingServiceImp(mockLwValFactory, mockPrebarcodeValidator, mockReplicateValidator, - mockLwRepo, mockSlotRepo, mockOpTypeRepo, mockOpCommentRepo, mockLtRepo, - mockBsRepo, mockTissueRepo, mockSampleRepo, mockMediumRepo, - mockCommentValidationService, mockOpService, mockLwService, mockBioRiskService, mockWorkService, - mockStoreService, - mockTransactor)); + service = spy(service); } @AfterEach @@ -93,761 +50,97 @@ void cleanup() throws Exception { } @ParameterizedTest - @CsvSource({"true,true", "true,false", "false,true"}) - public void testPerform(boolean succeeds, boolean discard) { + @CsvSource({ + "true,true", + "true,false", + "false,true", + }) + void testPerform(boolean success, boolean discard) { User user = EntityFactory.getUser(); - TissueBlockRequest request = new TissueBlockRequest(List.of()); - if (discard) { - request.setDiscardSourceBarcodes(List.of("STAN-A1")); - } + TissueBlockRequest request = new TissueBlockRequest(); + List discardBarcodes = discard ? List.of("STAN-1") : List.of(); + request.setDiscardSourceBarcodes(discardBarcodes); Matchers.mockTransactor(mockTransactor); OperationResult opres; - if (succeeds) { - opres = new OperationResult(List.of(), List.of()); + List problems; + if (success) { + opres = new OperationResult(List.of(new Operation()), List.of()); + problems = null; doReturn(opres).when(service).performInsideTransaction(any(), any()); } else { opres = null; - doThrow(ValidationException.class).when(service).performInsideTransaction(any(), any()); + problems = List.of("Bad"); + doThrow(new ValidationException(problems)).when(service).performInsideTransaction(any(), any()); } - - if (succeeds) { - assertSame(opres, service.perform(user, request)); + if (problems != null) { + Matchers.assertValidationException(() -> service.perform(user, request), problems); } else { - assertThrows(ValidationException.class, () -> service.perform(user, request)); + assertSame(opres, service.perform(user, request)); } - verify(mockTransactor).transact(any(), any()); verify(service).performInsideTransaction(user, request); - if (succeeds && discard) { - verify(mockStoreService).discardStorage(user, request.getDiscardSourceBarcodes()); + verify(mockTransactor).transact(eq("Block processing"), any()); + if (success && discard) { + verify(mockStoreService).discardStorage(user, discardBarcodes); } else { verifyNoInteractions(mockStoreService); } } - @Test - public void testPerformInTransaction_noLabware() { - User user = EntityFactory.getUser(); - TissueBlockRequest request = new TissueBlockRequest(List.of()); - assertValidationException(() -> service.performInsideTransaction(user, request), - "The request could not be validated.", - "No labware specified in request."); - verifyNoInteractions(mockWorkService); - verify(service, never()).loadSources(any(), any()); - verify(service, never()).loadEntities(any(), any(), any(), any(), any(), anyBoolean(), any()); - verify(service, never()).checkPrebarcodes(any(), any(), any()); - verifyNoCreation(); - } - - @Test - public void testPerformInsideTransaction_invalid() { - User user = EntityFactory.getUser(); - TissueBlockLabware block = new TissueBlockLabware("STAN-1", "lt", "1a"); - TissueBlockRequest request = new TissueBlockRequest(List.of(block), "SGP5", List.of("STAN-1")); - UCMap sources = UCMap.from(Labware::getBarcode, EntityFactory.getTube()); - LabwareType lt = EntityFactory.makeLabwareType(1,1, "lt"); - UCMap lwTypes = UCMap.from(LabwareType::getName, lt); - Work work = new Work(5, "SGP5", null, null, null, null, null, null); - Map commentMap = Map.of(50, new Comment(50, "Interesting", "science")); - - final String problem = "Bad sources."; - - stubValidation(sources, lwTypes, work, commentMap, problem); - - assertValidationException(() -> service.performInsideTransaction(user, request), - "The request could not be validated.", problem); - - verifyValidation(request, sources, lwTypes); - } - @ParameterizedTest - @ValueSource(booleans={false,true}) - public void testPerformInTransaction(boolean simple) { - String ltName = simple ? "lt" : "proviasette"; - LabwareType lt = EntityFactory.makeLabwareType(1,1, ltName); - Work work = (simple ? null : new Work(5, "SGP5", null, null, null, null, null, null)); - Comment comment = (simple ? null : new Comment(50, "Interesting", "science")); - TissueBlockLabware block = new TissueBlockLabware("STAN-1", ltName, "1a"); - TissueBlockRequest request = new TissueBlockRequest(List.of(block)); - Medium medium = null; - if (!simple) { - block.setCommentId(comment.getId()); - block.setPreBarcode("PREBC1"); - request.setWorkNumber(work.getWorkNumber()); - request.setDiscardSourceBarcodes(List.of("STAN-1")); - medium = new Medium(50, "OCT"); - when(mockMediumRepo.findByName("OCT")).thenReturn(Optional.of(medium)); - } - UCMap sources = UCMap.from(Labware::getBarcode, EntityFactory.getTube()); - UCMap ltMap = UCMap.from(LabwareType::getName, lt); - Map commentMap = Map.of(50, new Comment(50, "Hello", "stuff")); - - stubValidation(sources, ltMap, work, commentMap, null); - - List samples = List.of(EntityFactory.getSample()); - List dests = List.of(EntityFactory.makeLabware(lt, samples.getFirst())); - List ops = List.of(new Operation()); - doReturn(samples).when(service).createSamples(any(), any(), any()); - doReturn(dests).when(service).createDestinations(any(), any(), any()); - doReturn(ops).when(service).createOperations(any(), any(), any(), any(), any()); - doNothing().when(service).discardSources(any(), any()); - + @ValueSource(booleans={false, true}) + void testPerformInsideTransaction(boolean success) { User user = EntityFactory.getUser(); - assertEquals(new OperationResult(ops, dests), service.performInsideTransaction(user, request)); - if (!simple) { - verify(mockMediumRepo).findByName("OCT"); - } else { - verifyNoInteractions(mockMediumRepo); - } - - verifyValidation(request, sources, ltMap); - verifyCreation(request, user, medium, sources, dests, commentMap, work, samples, ltMap, ops); - } - - private void stubValidation(UCMap sources, UCMap lwTypes, Work work, - Map commentMap, String problem) { - if (problem!=null) { - doAnswer(Matchers.addProblem(problem, sources)).when(service).loadSources(any(), any()); - } else { - doReturn(sources).when(service).loadSources(any(), any()); - } - doReturn(work).when(mockWorkService).validateUsableWork(any(), any()); - doReturn(lwTypes).when(service).loadEntities(any(), any(), any(), eq("Labware type"), any(), anyBoolean(), any()); - doNothing().when(service).checkPrebarcodes(any(), any(), any()); - doReturn(commentMap).when(service).loadComments(any(), any()); - doNothing().when(service).checkReplicates(any(), any(), any()); - doNothing().when(service).checkDiscardBarcodes(any(), any()); - } - - private void verifyValidation(TissueBlockRequest request, UCMap sources, UCMap lwTypes) { - //noinspection unchecked - ArgumentCaptor> problemsCaptor = ArgumentCaptor.forClass(Set.class); - LabwareType lt = EntityFactory.getTubeType(); - final List ltList = List.of(lt); - TissueBlockLabware block = request.getLabware().getFirst(); - - verify(service).loadSources(problemsCaptor.capture(), same(request)); - final Set problems = problemsCaptor.getValue(); - verify(mockWorkService).validateUsableWork(same(problems), eq(request.getWorkNumber())); - when(mockLtRepo.findAllByNameIn(any())).thenReturn(ltList); - verify(service).loadEntities(same(problems), same(request), - functionLike(TissueBlockLabware::getLabwareType, block), eq("Labware type"), - functionLike(LabwareType::getName, lt), eq(true), - functionGiving(null, ltList)); - verify(service).checkPrebarcodes(same(problems), same(request), same(lwTypes)); - verify(service).loadComments(same(problems), same(request)); - verify(service).checkReplicates(same(problems), same(request), same(sources)); - verify(service).checkDiscardBarcodes(same(problems), same(request)); - } - - private void verifyNoCreation() { - verify(service, never()).createSamples(any(), any(), any()); - verify(service, never()).createDestinations(any(), any(), any()); - verify(service, never()).createOperations(any(), any(), any(), any(), any()); - verifyNoInteractions(mockBioRiskService); - verify(mockWorkService, never()).link(any(Work.class), any()); - verify(service, never()).discardSources(any(), any()); - } - - private void verifyCreation(TissueBlockRequest request, User user, Medium medium, UCMap sources, List dests, - Map commentMap, Work work, - List samples, UCMap ltMap, List ops) { - verify(service).createSamples(same(request), same(sources), same(medium)); - verify(service).createDestinations(same(request), same(samples), same(ltMap)); - verify(service).createOperations(same(request), same(user), same(sources), same(dests), same(commentMap)); - verify(mockBioRiskService).copyOpSampleBioRisks(same(ops)); - if (work!=null) { - verify(mockWorkService).link(same(work), same(ops)); - } else { - verify(mockWorkService, never()).link(any(Work.class), any()); - } - verify(service).discardSources(any(), any()); - } - - @ParameterizedTest - @MethodSource("loadSourcesArgs") - public void testLoadSources(Collection barcodes, List labware, BioState bs, - Collection valErrors, - Collection expectedProblems) { - TissueBlockRequest request = new TissueBlockRequest( - barcodes.stream() - .map(bc -> { - TissueBlockLabware block = new TissueBlockLabware(); - block.setSourceBarcode(bc); - return block; - }) - .collect(toList()) - ); - when(mockBsRepo.getByName("Original sample")).thenReturn(bs); - LabwareValidator val = mock(LabwareValidator.class); - when(mockLwValFactory.getValidator()).thenReturn(val); - when(val.getErrors()).thenReturn(valErrors); - when(val.loadLabware(any(), any())).thenReturn(labware); - when(val.getLabware()).thenReturn(labware); - - final List problems = new ArrayList<>(expectedProblems.size()); - UCMap lwMap = service.loadSources(problems, request); - - assertThat(lwMap).hasSize(labware.size()); - labware.forEach(lw -> assertSame(lw, lwMap.get(lw.getBarcode()))); - - Set nonNullBarcodes = barcodes.stream() - .filter(bc -> bc!=null && !bc.isEmpty()) - .collect(toSet()); - - verify(val).loadLabware(mockLwRepo, nonNullBarcodes); - verify(val).setSingleSample(true); - verify(val).validateSources(); - verify(val).validateBioState(bs); - - assertThat(problems).containsExactlyInAnyOrderElementsOf(expectedProblems); - } - - static Stream loadSourcesArgs() { - LabwareType lt = EntityFactory.getTubeType(); - Labware lw1 = EntityFactory.makeLabware(lt); - lw1.setBarcode("STAN-1"); - Labware lw2 = EntityFactory.makeLabware(lt); - lw2.setBarcode("STAN-2"); - BioState bs = EntityFactory.getBioState(); - - return Arrays.stream(new Object[][]{ - {List.of("STAN-404", "STAN-405"), List.of(), bs, List.of("Unknown barcodes: STAN-404, STAN-405"), - List.of("Unknown barcodes: STAN-404, STAN-405")}, - {List.of("STAN-1", "STAN-2", ""), List.of(lw1, lw2), bs, List.of(), - List.of("Source barcode missing.")}, - {Arrays.asList("STAN-404", null, "STAN-1"), List.of(lw1), bs, List.of("Bad barcode: STAN-404"), - List.of("Bad barcode: STAN-404", "Source barcode missing.")}, - }).map(Arguments::of); - } - - @ParameterizedTest - @MethodSource("checkPrebarcodesArgs") - public void testCheckPrebarcodes(List prebarcodes, List lwTypeNames, UCMap ltMap, - List existingLabware, boolean existingFromExternal, - Collection expectedProblems) { - when(mockPrebarcodeValidator.validate(any(), any())).then(invocation -> { - String barcode = invocation.getArgument(0); - Consumer problemConsumer = invocation.getArgument(1); - if (barcode!=null && barcode.indexOf('!') < 0) { - return true; - } - problemConsumer.accept("Bad barcode: "+barcode); - return false; - }); - when(mockLwRepo.findByBarcodeIn(any())).thenReturn(existingFromExternal ? List.of() : existingLabware); - when(mockLwRepo.findByExternalBarcodeIn(any())).thenReturn(existingFromExternal ? existingLabware : List.of()); - - final List problems = new ArrayList<>(expectedProblems.size()); - final Iterator lwTypeNameIter = lwTypeNames.iterator(); - TissueBlockRequest request = new TissueBlockRequest( - prebarcodes.stream() - .map(bc -> { - TissueBlockLabware block = new TissueBlockLabware(); - block.setPreBarcode(bc); - block.setLabwareType(lwTypeNameIter.next()); - return block; - }) - .collect(toList()) - ); - service.checkPrebarcodes(problems, request, ltMap); - - assertThat(problems).containsExactlyInAnyOrderElementsOf(expectedProblems); - Set prebarcodeSet = prebarcodes.stream() - .filter(bc -> bc!=null && !bc.isEmpty()) - .peek(bc -> verify(mockPrebarcodeValidator).validate(eq(bc), any())) - .map(String::toUpperCase) - .collect(toSet()); - if (!prebarcodeSet.isEmpty()) { - verify(mockLwRepo).findByBarcodeIn(prebarcodeSet); - if (existingFromExternal) { - verify(mockLwRepo).findByExternalBarcodeIn(prebarcodeSet); - } - } - } - - static Stream checkPrebarcodesArgs() { - LabwareType pretube = EntityFactory.makeLabwareType(1,1, "Pretube"); - pretube.setPrebarcoded(true); - UCMap ltMap = UCMap.from(LabwareType::getName, EntityFactory.getTubeType(), pretube); - Labware existingLw = EntityFactory.makeEmptyLabware(pretube); - existingLw.setBarcode("EXISTING"); - existingLw.setExternalBarcode("EXISTING"); - List existingLwList = List.of(existingLw); - return Arrays.stream(new Object[][]{ - {Arrays.asList("ALPHA", null, "BETA", null), List.of("Pretube", "Tube", "Pretube", "Tube"), - ltMap, List.of(), false, List.of()}, - {Arrays.asList("ALPHA", "BETA"), List.of("Tube", "tube"), ltMap, List.of(), false, - List.of("A barcode is not expected for labware type [Tube].")}, - {Arrays.asList(null, null), List.of("pretube", "pretube"), ltMap, List.of(), false, - List.of("A barcode is required for labware type [Pretube].")}, - {Arrays.asList(null, "ALPHA"), List.of("", "foo"), ltMap, List.of(), false, List.of()}, - {List.of("ALPHA", "Beta", "Alpha"), List.of("Pretube", "Pretube", "Pretube"), ltMap, List.of(), false, - List.of("Barcode specified multiple times: [ALPHA]")}, - {Arrays.asList("Existing", "Alpha", null), List.of("Pretube", "Pretube", "Tube"), ltMap, - existingLwList, false, List.of("Barcode already in use: [EXISTING]")}, - {Arrays.asList("Existing", "Alpha", null), List.of("Pretube", "Pretube", "Tube"), ltMap, - existingLwList, true, List.of("External barcode already in use: [EXISTING]")}, - {Arrays.asList(null, "Alpha", "Beta!"), List.of("Tube", "Pretube", "Pretube"), ltMap, - List.of(), false, List.of("Bad barcode: Beta!")}, - {List.of("", "Alpha", "ALPHA", "gamma", "Beta!", "existing"), - List.of("Pretube", "Pretube", "Pretube", "Tube", "Pretube", "Pretube"), - ltMap, existingLwList, false, - List.of("A barcode is not expected for labware type [Tube].", - "A barcode is required for labware type [Pretube].", - "Barcode specified multiple times: [ALPHA]", - "Bad barcode: Beta!", - "Barcode already in use: [EXISTING]")}, - - }).map(Arguments::of); - } - - @ParameterizedTest - @MethodSource("loadCommentsArgs") - public void testLoadComments(List ids, List comments, List errors) { - final List receivedCommentIds = new ArrayList<>(ids.size()); - when(mockCommentValidationService.validateCommentIds(any(), any())) - .then(invocation -> { - Stream commentIds = invocation.getArgument(1); - commentIds.forEach(receivedCommentIds::add); - if (!errors.isEmpty()) { - Collection problems = invocation.getArgument(0); - problems.addAll(errors); - } - return comments; - }); - List problems = new ArrayList<>(errors.size()); - TissueBlockRequest request = new TissueBlockRequest( - ids.stream() - .map(id -> { - TissueBlockLabware block = new TissueBlockLabware(); - block.setCommentId(id); - return block; - }) - .collect(toList()) - ); - Map map = service.loadComments(problems, request); - assertThat(map.values()).containsExactlyInAnyOrderElementsOf(comments); - for (Comment comment : comments) { - assertSame(comment, map.get(comment.getId())); - } - assertThat(problems).containsExactlyElementsOf(errors); - verify(mockCommentValidationService).validateCommentIds(any(), any()); - List expectedCommentIds = ids.stream() - .filter(Objects::nonNull) - .collect(toList()); - assertThat(receivedCommentIds).containsExactlyInAnyOrderElementsOf(expectedCommentIds); - } - - static Stream loadCommentsArgs() { - Comment com1 = new Comment(1, "Banana", "Fruit"); - Comment com2 = new Comment(2, "Whale", "Insect"); - List comments = List.of(com1, com2); - return Arrays.stream(new Object[][] { - {List.of(), List.of(), List.of()}, - {Arrays.asList(null, null), List.of(), List.of()}, - {Arrays.asList(null, 1, 2, 1, null), comments, List.of()}, - {Arrays.asList(null, 1, 2, 1, 5), comments, List.of("Bad comment id: 5")}, - }).map(Arguments::of); - } - - @ParameterizedTest - @MethodSource("loadEntitiesArgs") - public void testLoadEntities(TissueBlockRequest request, Function fieldGetter, - String fieldName, Function entityFieldGetter, boolean required, - Collection entities, Collection expectedProblems) { - final Set lookedUp = new HashSet<>(); - Function, Collection> lookupFunction = strings -> { - lookedUp.addAll(strings); - return entities; - }; - final List problems = new ArrayList<>(expectedProblems.size()); - var result = service.loadEntities(problems, request, fieldGetter, fieldName, entityFieldGetter, - required, lookupFunction); - assertThat(result.values()).containsExactlyInAnyOrderElementsOf(entities); - for (var entity : entities) { - assertSame(entity, result.get(entityFieldGetter.apply(entity))); - } - assertThat(problems).containsExactlyInAnyOrderElementsOf(expectedProblems); - Set expectedFieldValues = request.getLabware().stream() - .map(fieldGetter) - .filter(s -> s!=null && !s.isEmpty()) - .collect(toSet()); - assertEquals(lookedUp, expectedFieldValues); - } - - static Stream loadEntitiesArgs() { - LabwareType lt1 = EntityFactory.makeLabwareType(1,1, "lt1"); - LabwareType lt2 = EntityFactory.makeLabwareType(1,1, "lt2"); - List lwTypes = List.of(lt1, lt2); - Function fieldGetter = TissueBlockLabware::getLabwareType; - Function entityFieldGetter = LabwareType::getName; - final String fieldName = "Labware type"; - return Arrays.stream(new Object[][]{ - {lwTypesToRequest("lt1", "lt2", "LT1"), fieldGetter, fieldName, entityFieldGetter, - true, lwTypes, List.of()}, - {lwTypesToRequest("lt1", "lt2", "", null), fieldGetter, fieldName, entityFieldGetter, - false, lwTypes, List.of()}, - {lwTypesToRequest("lt1", "lt2", ""), fieldGetter, fieldName, entityFieldGetter, - true, lwTypes, List.of("Labware type missing.")}, - {lwTypesToRequest(""), fieldGetter, fieldName, entityFieldGetter, - false, List.of(), List.of()}, - {lwTypesToRequest("lt1", "lt2", null), fieldGetter, fieldName, entityFieldGetter, - true, lwTypes, List.of("Labware type missing.")}, - {lwTypesToRequest("LT1", "LT4", "LT5", "lt5"), fieldGetter, fieldName, entityFieldGetter, - true, List.of(lt1), List.of("Labware type unknown: [\"LT4\", \"LT5\"]")}, - {lwTypesToRequest("LT1", "LT4", null), fieldGetter, fieldName, entityFieldGetter, - true, List.of(lt1), List.of("Labware type unknown: [\"LT4\"]", "Labware type missing.")}, - }).map(Arguments::of); - } - - private static TissueBlockRequest lwTypesToRequest(String... labwareTypeNames) { - return new TissueBlockRequest(Arrays.stream(labwareTypeNames) - .map(ltName -> { - TissueBlockLabware block = new TissueBlockLabware(); - block.setLabwareType(ltName); - return block; - }).collect(toList())); - } - - @ParameterizedTest - @MethodSource("checkReplicatesArgs") - public void testCheckReplicates(TissueBlockRequest request, UCMap sources, - Collection existingTissues, Collection expectedProblems) { - when(mockReplicateValidator.validate(any(), any())).then(invocation -> { - String string = invocation.getArgument(0); - if (string.indexOf('!') < 0) { - return true; - } - Consumer addProblem = invocation.getArgument(1); - addProblem.accept("Bad rep: "+string); - return false; - }); - when(mockTissueRepo.findByDonorIdAndSpatialLocationIdAndReplicate(anyInt(), anyInt(), any())).then(invocation -> { - int donorId = invocation.getArgument(0); - int slId = invocation.getArgument(1); - String rep = invocation.getArgument(2); - return existingTissues.stream() - .filter(t -> t.getDonor().getId()==donorId && t.getSpatialLocation().getId()==slId && rep.equalsIgnoreCase(t.getReplicate())) - .collect(toList()); - }); - final List problems = new ArrayList<>(expectedProblems.size()); - service.checkReplicates(problems, request, sources); - assertThat(problems).containsExactlyInAnyOrderElementsOf(expectedProblems); - } - - static Stream checkReplicatesArgs() { - Donor d1 = new Donor(1, "DONOR1", null, null); - Donor d2 = new Donor(2, "DONOR2", null, null); - TissueType tt = new TissueType(1, "Arm", "ARM"); - SpatialLocation sl1 = new SpatialLocation(101, "SL1", 1, tt); - SpatialLocation sl2 = new SpatialLocation(102, "SL2", 2, tt); - Tissue t1A = makeTissue(1, "t1A", null, sl1, d1); - Tissue t1B = makeTissue(2, "t1B", "1b", sl1, d1); - Tissue t1C = makeTissue(3, "t1C", "1c", sl2, d1); - Tissue t2A = makeTissue(4, "t2A", "2a", sl1, d2); - Tissue t2B = makeTissue(5, "t2B", "2b", sl2, d2); - final List tissues = List.of(t1A, t1B, t1C, t2A, t2B); - // Cases: - LabwareType lt = EntityFactory.getTubeType(); - BioState bs = EntityFactory.getBioState(); - Labware lw1 = EntityFactory.makeLabware(lt, new Sample(1, null, t1A, bs)); - lw1.setBarcode("STAN-1"); - Labware lw2 = EntityFactory.makeLabware(lt, new Sample(2, null, t2A, bs)); - lw2.setBarcode("STAN-2"); - UCMap sources = UCMap.from(Labware::getBarcode, lw1, lw2); - - return Arrays.stream(new Object[][] { - {requestForReplicates("STAN-1", "5", "STAN-1", "6", "STAN-1", "1c", "STAN-2", "2a")}, - {requestForReplicates("STAN-1", "5", "STAN-1", "5", "STAN-2", "6", "Stan-2", "6", "STAN-2", "7"), - List.of("Same replicate specified multiple times: [{Donor: DONOR1, Tissue type: Arm, Spatial location: 1, Replicate: 5}]", - "Replicate numbers must match the source replicate number where present.")}, - {requestForReplicates("STAN-1", null), "Missing replicate for some blocks."}, - {requestForReplicates("STAN-1", ""), "Missing replicate for some blocks."}, - {requestForReplicates("STAN-1", "5", "Stan-1", "1b"), "Replicate already exists in the database: " + - "[{Donor: DONOR1, Tissue type: Arm, Spatial location: 1, Replicate: 1b}]"}, - {requestForReplicates(null, "1b", "STAN-2", "5F", "Stan-2", "5A", "Stan-1", null, - "STAN-1", "1F", "STAN-1", "1f", "STAN-1", "1b"), - List.of("Same replicate specified multiple times: [{Donor: DONOR1, Tissue type: Arm, Spatial location: 1, Replicate: 1f}]", - "Missing replicate for some blocks.", - "Replicate numbers must match the source replicate number where present.", - "Replicate already exists in the database: [{Donor: DONOR1, Tissue type: Arm, Spatial location: 1, Replicate: 1b}]")}, - }).map(arr -> { - List problems; - if (arr.length < 2) { - problems = List.of(); - } else if (arr[1] instanceof String) { - problems = List.of(arr[1]); - } else { - problems = (List) arr[1]; - } - return Arguments.of(arr[0], sources, tissues, problems); - }); - } - - static Tissue makeTissue(Integer id, String name, String replicate, SpatialLocation sl, Donor d) { - return new Tissue(id, name, replicate, sl, d, null, null, null, null, null, null); - } - - static TissueBlockRequest requestForReplicates(String... barcodesAndReplicates) { - assert barcodesAndReplicates.length % 2 == 0; - List blocks = new ArrayList<>(barcodesAndReplicates.length/2); - for (int i = 0; i < barcodesAndReplicates.length; i += 2) { - TissueBlockLabware block = new TissueBlockLabware(); - block.setSourceBarcode(barcodesAndReplicates[i]); - block.setReplicate(barcodesAndReplicates[i+1]); - blocks.add(block); - } - return new TissueBlockRequest(blocks); - } - - @ParameterizedTest - @CsvSource(value = { - ";;", - "; Stan-1/Stan-2;", - "STAN-1; Stan-1/Stan-2/STAN-2;", - "/Stan-1;Stan-1;A null or empty string was supplied as a barcode to discard.", - "Stan-1/Stan-4/Stan-5; STAN-1/STAN-1/STAN-2; The given list of barcodes to discard includes barcodes " + - "that are not specified as source barcodes in this request: [\"Stan-4\", \"Stan-5\"]", - "Stan-1//Stan-5; STAN-1/STAN-2; The given list of barcodes to discard includes a barcode that is not " + - "specified as a source barcode in this request: [\"Stan-5\"]/A null or empty string was " + - "supplied as a barcode to discard.", - }, delimiter=';') - public void testCheckDiscardBarcodes(String discardBarcodesJoined, String sourceBarcodesJoined, String expectedProblemsJoined) { - String[] discardBarcodes = (discardBarcodesJoined==null ? new String[0] : discardBarcodesJoined.split("/")); - String[] sourceBarcodes = (sourceBarcodesJoined==null ? new String[0] : sourceBarcodesJoined.split("/")); - String[] expectedProblems = (expectedProblemsJoined==null ? new String[0] : expectedProblemsJoined.split("/")); - - List blocks = Arrays.stream(sourceBarcodes) - .map(bc -> { - TissueBlockLabware block = new TissueBlockLabware(); - block.setSourceBarcode(bc); - return block; - }).collect(toList()); - TissueBlockRequest request = new TissueBlockRequest(blocks, null, Arrays.asList(discardBarcodes)); - - final List problems = new ArrayList<>(expectedProblems.length); - service.checkDiscardBarcodes(problems, request); - assertThat(problems).containsExactlyInAnyOrder(expectedProblems); - } - - @Test - public void testCreateSamples() { - BioState bs = EntityFactory.getBioState(); - when(mockBsRepo.getByName("Tissue")).thenReturn(bs); - LabwareType lt = EntityFactory.getTubeType(); - Labware lw1 = EntityFactory.makeEmptyLabware(lt); - lw1.setBarcode("STAN-1"); - Labware lw2 = EntityFactory.makeEmptyLabware(lt); - lw2.setBarcode("STAN-2"); - Medium oct = new Medium(50, "OCT"); - - TissueBlockLabware block1 = new TissueBlockLabware("STAN-1", "lt", "1a"); - TissueBlockLabware block2 = new TissueBlockLabware("STAN-2", "proviasette", "2b"); - TissueBlockRequest request = new TissueBlockRequest(List.of(block1, block2)); - - Sample sam1 = EntityFactory.getSample(); - Sample sam2 = new Sample(10, 20, sam1.getTissue(), bs); - - doReturn(sam1, sam2).when(service).createSample(any(), any(), any(), any()); - - List samples = service.createSamples(request, UCMap.from(Labware::getBarcode, lw1, lw2), oct); - - assertThat(samples).containsExactly(sam1, sam2); - verify(service, times(2)).createSample(any(), any(), any(), any()); - verify(service).createSample(block1, lw1, bs, null); - verify(service).createSample(block2, lw2, bs, oct); - } - - @ParameterizedTest - @CsvSource({ - ",,None,,false", - ",,OCT,OCT,false", - "1a,1a,OCT,OCT,false", - ",1a,None,,true", - "1a,1a,None,OCT,true", - "1a,1b,None,OCT,true", - }) - public void testGetOrCreateTissue(String oldRep, String newRep, String oldMedium, String newMedium, boolean save) { - if (save) { - when(mockTissueRepo.save(any())).thenAnswer(invocation -> { - Tissue tis = invocation.getArgument(0); - tis.setId(50); - return tis; - }); - } - Medium med0 = new Medium(100, oldMedium); - Medium med1; - if (newMedium==null) { - med1 = null; - } else if (newMedium.equalsIgnoreCase(oldMedium)) { - med1 = med0; + TissueBlockRequest request = new TissueBlockRequest(); + BlockValidator val = mock(BlockValidator.class); + when(mockBlockValFactory.createBlockValidator(any())).thenReturn(val); + BlockMaker maker = mock(BlockMaker.class); + when(mockBlockMakerFactory.createBlockMaker(any(), any(), any(), any(), any(), any(), any())) + .thenReturn(maker); + OperationResult opres; + List problems; + Medium medium; + Work work; + OperationType opType; + BioState bioState; + List lwData; + if (success) { + opres = new OperationResult(List.of(new Operation()), List.of()); + problems = null; + medium = EntityFactory.getMedium(); + work = EntityFactory.makeWork("SGP-1"); + opType = EntityFactory.makeOperationType("Block processing", null); + bioState = EntityFactory.getBioState(); + lwData = singletonList(null); + when(val.getMedium()).thenReturn(medium); + when(val.getWork()).thenReturn(work); + when(val.getOpType()).thenReturn(opType); + when(val.getBioState()).thenReturn(bioState); + when(val.getLwData()).thenReturn(lwData); + when(maker.record()).thenReturn(opres); } else { - med1 = new Medium(101, newMedium); - } - Donor donor = EntityFactory.getDonor(); - SpatialLocation sl = EntityFactory.getSpatialLocation(); - Tissue tissue0 = EntityFactory.makeTissue(donor, sl); - tissue0.setMedium(med0); - tissue0.setReplicate(oldRep); - Tissue tissue1 = service.getOrCreateTissue(tissue0, newRep, med1); - if (!save) { - verifyNoInteractions(mockTissueRepo); - assertSame(tissue0, tissue1); + opres = null; + medium = null; + work = null; + opType = null; + bioState = null; + lwData = null; + problems = List.of("Bad"); + doThrow(new ValidationException(problems)).when(val).raiseError(); + } + if (success) { + assertSame(opres, service.performInsideTransaction(user, request)); } else { - verify(mockTissueRepo).save(tissue1); - assertNotNull(tissue1.getId()); - } - assertSame(coalesce(med1, med0), tissue1.getMedium()); - assertEquals(newRep, tissue1.getReplicate()); - } - - @Test - public void testCreateSample() { - Tissue originalTissue = EntityFactory.getTissue(); - BioState bs1 = new BioState(1, "Banana"); - BioState bs2 = new BioState(2, "Custard"); - Sample originalSample = new Sample(5, null, originalTissue, bs1); - Tissue newTissue = originalTissue.derived(); - Labware lw = EntityFactory.makeLabware(EntityFactory.getTubeType(), originalSample); - when(mockSampleRepo.save(any())).then(invocation -> { - Sample sample = invocation.getArgument(0); - assertNull(sample.getId()); - sample.setId(600); - return sample; - }); - Medium medium = new Medium(100, "Oct"); - String repl = "1a"; - doReturn(newTissue).when(service).getOrCreateTissue(any(), any(), any()); - TissueBlockLabware block = new TissueBlockLabware(); - block.setReplicate(repl); - Sample sample = service.createSample(block, lw, bs2, medium); - verify(mockSampleRepo).save(any()); - verify(service).getOrCreateTissue(originalTissue, repl, medium); - assertEquals(600, sample.getId()); - assertSame(bs2, sample.getBioState()); - assertSame(newTissue, sample.getTissue()); - } - - @Test - public void testCreateDestinations() { - Sample sam1 = EntityFactory.getSample(); - Sample sam2 = new Sample(sam1.getId()+1, null, sam1.getTissue(), sam1.getBioState()); - LabwareType lt1 = EntityFactory.makeLabwareType(1,1, "lt1"); - LabwareType lt2 = EntityFactory.makeLabwareType(1,1, "lt2"); - Labware lw1 = EntityFactory.makeLabware(lt1, sam1); - Labware lw2 = EntityFactory.makeLabware(lt2, sam2); - doReturn(lw1, lw2).when(service).createDestination(any(), any(), any()); - TissueBlockLabware block1 = new TissueBlockLabware(); - block1.setLabwareType("lt1"); - block1.setPreBarcode("ABC123"); - TissueBlockLabware block2 = new TissueBlockLabware(); - block2.setLabwareType("lt2"); - - TissueBlockRequest request = new TissueBlockRequest(List.of(block1, block2)); - - List labware = service.createDestinations(request, List.of(sam1, sam2), UCMap.from(LabwareType::getName, lt1, lt2)); - - assertThat(labware).containsExactly(lw1, lw2); - verify(service, times(2)).createDestination(any(), any(), any()); - verify(service).createDestination(lt1, sam1, "ABC123"); - verify(service).createDestination(lt2, sam2, null); - } - - @ParameterizedTest - @ValueSource(booleans={false,true}) - public void testCreateDestination(boolean prebarcoded) { - LabwareType lt = EntityFactory.getTubeType(); - Sample sample = EntityFactory.getSample(); - String prebarcode = prebarcoded ? "ABC123" : null; - Labware lw = EntityFactory.makeEmptyLabware(lt); - when(mockLwService.create(any(), any(), any())).thenReturn(lw); - - assertSame(lw, service.createDestination(lt, sample, prebarcode)); - assertThat(lw.getFirstSlot().getSamples()).containsExactly(sample); - verify(mockLwService).create(lt, prebarcode, prebarcode); - verify(mockSlotRepo).save(lw.getFirstSlot()); - } - - @Test - public void testCreateOperations() { - Operation op1 = new Operation(); - op1.setId(101); - Operation op2 = new Operation(); - op2.setId(102); - OperationType opType = EntityFactory.makeOperationType("Block processing", null); - when(mockOpTypeRepo.getByName("Block processing")).thenReturn(opType); - User user = EntityFactory.getUser(); - LabwareType lt = EntityFactory.getTubeType(); - Labware[] labware = IntStream.range(1, 5) - .mapToObj(i -> { - Labware lw = EntityFactory.makeEmptyLabware(lt); - lw.setId(i); - lw.setBarcode("STAN-"+i); - return lw; - }).toArray(Labware[]::new); - Comment comment = new Comment(5, "Nice one", "sarcasm"); - Map commentMap = new HashMap<>(1); - commentMap.put(comment.getId(), comment); - - TissueBlockLabware block1 = new TissueBlockLabware(); - block1.setSourceBarcode("STAN-1"); - block1.setCommentId(comment.getId()); - TissueBlockLabware block2 = new TissueBlockLabware(); - block2.setSourceBarcode("STAN-2"); - TissueBlockRequest request = new TissueBlockRequest(List.of(block1, block2)); - - doReturn(op1, op2).when(service).createOperation(any(), any(), any(), any(), any()); - - assertThat(service.createOperations(request, user, UCMap.from(Labware::getBarcode, labware[0], labware[1]), - List.of(labware[2], labware[3]), commentMap)).containsExactly(op1, op2); - - verify(service, times(2)).createOperation(any(), any(), any(), any(), any()); - verify(service).createOperation(opType, user, labware[0], labware[2], comment); - verify(service).createOperation(opType, user, labware[1], labware[3], null); - } - - @ParameterizedTest - @ValueSource(booleans={false,true}) - public void testCreateOperation(boolean hasComment) { - Comment comment = (hasComment ? new Comment(5, "Yeah right", "sarcasm") : null); - User user = EntityFactory.getUser(); - OperationType opType = EntityFactory.makeOperationType("Block processing", null); - - final Sample sam0 = EntityFactory.getSample(); - final Sample sam1 = new Sample(sam0.getId()+1, null, sam0.getTissue(), sam0.getBioState()); - Labware lw0 = EntityFactory.makeLabware(EntityFactory.getTubeType(), sam0); - Labware lw1 = EntityFactory.makeLabware(EntityFactory.getTubeType(), sam1); - - Operation op = new Operation(); - op.setId(500); - when(mockOpService.createOperation(any(), any(), any(), any())).thenReturn(op); - - assertSame(op, service.createOperation(opType, user, lw0, lw1, comment)); - verify(mockOpService).createOperation(opType, user, List.of( - new Action(null, null, lw0.getFirstSlot(), lw1.getFirstSlot(), sam1, sam0)), - null); - if (comment==null) { - verifyNoInteractions(mockOpCommentRepo); + Matchers.assertValidationException(() -> service.performInsideTransaction(user, request), problems); + } + InOrder inOrder = inOrder(mockBlockValFactory, mockBlockMakerFactory, val, maker); + inOrder.verify(mockBlockValFactory).createBlockValidator(request); + inOrder.verify(val).validate(); + inOrder.verify(val).raiseError(); + if (success) { + inOrder.verify(mockBlockMakerFactory).createBlockMaker(request, lwData, medium, bioState, work, opType, user); + inOrder.verify(maker).record(); } else { - verify(mockOpCommentRepo).save(new OperationComment(null, comment, 500, sam1.getId(), lw1.getFirstSlot().getId(), null)); + inOrder.verifyNoMoreInteractions(); } } - - @Test - public void testDiscardSources_none() { - service.discardSources(List.of(), new UCMap<>()); - } - - @Test - public void testDiscardSources() { - LabwareType lt = EntityFactory.getTubeType(); - Labware lw1 = EntityFactory.makeEmptyLabware(lt); - Labware lw2 = EntityFactory.makeEmptyLabware(lt); - Labware lw3 = EntityFactory.makeEmptyLabware(lt); - service.discardSources(List.of(lw1.getBarcode(), lw2.getBarcode()), UCMap.from(Labware::getBarcode, lw1, lw2, lw3)); - verify(mockLwRepo).saveAll(List.of(lw1, lw2)); - assertTrue(lw1.isDiscarded()); - assertTrue(lw2.isDiscarded()); - assertFalse(lw3.isDiscarded()); - } } \ No newline at end of file diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/block/TestBlockMaker.java b/src/test/java/uk/ac/sanger/sccp/stan/service/block/TestBlockMaker.java new file mode 100644 index 000000000..ba4d2bd51 --- /dev/null +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/block/TestBlockMaker.java @@ -0,0 +1,330 @@ +package uk.ac.sanger.sccp.stan.service.block; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.*; +import uk.ac.sanger.sccp.stan.EntityFactory; +import uk.ac.sanger.sccp.stan.Matchers; +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.repo.*; +import uk.ac.sanger.sccp.stan.request.OperationResult; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockContent; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockLabware; +import uk.ac.sanger.sccp.stan.service.*; +import uk.ac.sanger.sccp.stan.service.work.WorkService; +import uk.ac.sanger.sccp.utils.Zip; + +import java.util.*; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** Test {@link BlockMakerImp} */ +class TestBlockMaker { + @Mock + TissueRepo mockTissueRepo; + @Mock + SampleRepo mockSampleRepo; + @Mock + SlotRepo mockSlotRepo; + @Mock + LabwareRepo mockLwRepo; + @Mock + OperationCommentRepo mockOpcomRepo; + @Mock + LabwareService mockLwService; + @Mock + OperationService mockOpService; + @Mock + WorkService mockWorkService; + @Mock + BioRiskService mockBioRiskService; + + private AutoCloseable mocking; + + @BeforeEach + void setup() { + mocking = MockitoAnnotations.openMocks(this); + } + + @AfterEach + void cleanup() throws Exception { + mocking.close(); + } + + private BlockMakerImp makeBlockMaker(TissueBlockRequest request, List blds, + Medium medium, BioState bs, Work work, OperationType opType, User user) { + return new BlockMakerImp(mockTissueRepo, mockSampleRepo, mockSlotRepo, mockLwRepo, mockOpcomRepo, + mockLwService, mockOpService, mockWorkService, mockBioRiskService, + request, blds, medium, bs, work, opType, user); + } + + @Test + void testRecord() { + Work work = EntityFactory.makeWork("SGP1"); + BlockMakerImp maker = spy(makeBlockMaker(null, null, null, null, work, null, null)); + List lws = List.of(EntityFactory.getTube()); + Operation op = new Operation(); + op.setId(100); + List ops = List.of(op); + doReturn(lws).when(maker).createLabware(); + doNothing().when(maker).createSamples(); + doNothing().when(maker).findSlots(); + doNothing().when(maker).fillLabware(); + doNothing().when(maker).discardSources(); + doReturn(ops).when(maker).createOperations(); + OperationResult opres = maker.record(); + + verify(maker).createLabware(); + verify(maker).createSamples(); + verify(maker).findSlots(); + verify(maker).fillLabware(); + verify(maker).createOperations(); + verify(mockWorkService).link(work, ops); + verify(mockBioRiskService).copyOpSampleBioRisks(ops); + verify(maker).discardSources(); + + assertEquals(lws, opres.getLabware()); + assertEquals(ops, opres.getOperations()); + } + + @Test + void testCreateLabware() { + LabwareType[] lts = { EntityFactory.getTubeType(), + EntityFactory.makeLabwareType(1, 1)}; + List lws = Arrays.stream(lts).map(EntityFactory::makeEmptyLabware).toList(); + List tbls = List.of(new TissueBlockLabware(), new TissueBlockLabware()); + final String prebarcode = "PREB1"; + tbls.get(0).setPreBarcode(prebarcode); + List lds = tbls.stream().map(BlockLabwareData::new).toList(); + Zip.of(Arrays.stream(lts), lds.stream()).forEach((lt, ld) -> ld.setLwType(lt)); + Zip.of(Arrays.stream(lts), lws.stream()).forEach((lt, lw) -> when(mockLwService.create(same(lt), any(), any())).thenReturn(lw)); + BlockMakerImp maker = makeBlockMaker(new TissueBlockRequest(tbls), lds, null, null, null, null, null); + List result = maker.createLabware(); + verify(mockLwService).create(lts[0], prebarcode, prebarcode); + verify(mockLwService).create(lts[1], null, null); + assertEquals(lws, result); + } + + @ParameterizedTest + @CsvSource({ + "rep1,rep1, MED,MED, false", + ",rep1, MED,MED, true", + "rep1,rep1, WAT,MED, true", + }) + void testGetOrCreateTissue(String oldRep, String newRep, String oldMedium, String newMedium, boolean expectNew) { + Medium oldMed = new Medium(1, oldMedium); + Medium newMed = oldMedium.equalsIgnoreCase(newMedium) ? oldMed : new Medium(2, newMedium); + Donor donor = EntityFactory.getDonor(); + SpatialLocation sl = EntityFactory.getSpatialLocation(); + Tissue oldTissue = EntityFactory.makeTissue(donor, sl); + oldTissue.setReplicate(oldRep); + oldTissue.setMedium(oldMed); + when(mockTissueRepo.save(any())).then(Matchers.returnArgument()); + + BlockMakerImp maker = makeBlockMaker(null, null, null, null, null, null, null); + Tissue newTissue = maker.getOrCreateTissue(oldTissue, newRep, newMed); + if (expectNew) { + verify(mockTissueRepo).save(newTissue); + assertNotEquals(oldTissue, newTissue); + assertNull(newTissue.getId()); + assertEquals(newRep, newTissue.getReplicate()); + assertEquals(newMed, newTissue.getMedium()); + } else { + verifyNoInteractions(mockTissueRepo); + assertSame(oldTissue, newTissue); + } + } + + @Test + void testCreateSamples() { + BioState bs = EntityFactory.getBioState(); + Medium medium = EntityFactory.getMedium(); + Sample[] sourceSamples = EntityFactory.makeSamples(2); + Tissue altTissue = EntityFactory.makeTissue(EntityFactory.getDonor(), EntityFactory.getSpatialLocation()); + String[] reps = {"rep1", "rep2"}; + List bds = IntStream.range(0, 2).mapToObj(i -> new BlockData(new TissueBlockContent())).toList(); + for (int i = 0; i < sourceSamples.length; ++i) { + bds.get(i).setSourceSample(sourceSamples[i]); + bds.get(i).getRequestContent().setReplicate(reps[i]); + } + List bls = List.of(new BlockLabwareData(new TissueBlockLabware())); + bls.getFirst().setBlocks(bds); + BlockMakerImp maker = spy(makeBlockMaker(null, bls, medium, bs, null, null, null)); + doAnswer(Matchers.returnArgument()).when(maker).getOrCreateTissue(any(), eq(reps[0]), any()); + doReturn(altTissue).when(maker).getOrCreateTissue(any(), eq(reps[1]), any()); + when(mockSampleRepo.save(any())).then(Matchers.returnArgument()); + + maker.createSamples(); + Sample[] newSamples = bds.stream().map(BlockData::getSample).toArray(Sample[]::new); + for (int i = 0; i < newSamples.length; i++) { + verify(maker).getOrCreateTissue(same(sourceSamples[i].getTissue()), eq(reps[i]), same(medium)); + Sample sam = newSamples[i]; + assertNull(sam.getId()); + verify(mockSampleRepo).save(sam); + assertSame(i==0 ? sourceSamples[0].getTissue() : altTissue, sam.getTissue()); + assertEquals(0, sam.getBlockHighestSection()); + assertSame(bs, sam.getBioState()); + assertSame(medium, sam.getTissue().getMedium()); + } + } + + @Test + void testFindSlots() { + LabwareType lt = EntityFactory.makeLabwareType(1, 3); + Sample[] samples = EntityFactory.makeSamples(2); + Labware sourceLw = EntityFactory.makeEmptyLabware(lt); + List sourceSlots = sourceLw.getSlots(); + sourceSlots.get(0).addSample(samples[0]); + sourceSlots.get(1).addSample(samples[1]); + sourceSlots.get(2).addSample(samples[1]); + sourceSlots.get(2).addSample(samples[0]); + Labware destLw = EntityFactory.makeEmptyLabware(lt); + TissueBlockContent con = new TissueBlockContent(); + Address A1 = new Address(1,1), A2 = new Address(1,2); + con.setAddresses(List.of(A1, A2)); + BlockData bd = new BlockData(con); + bd.setSourceLabware(sourceLw); + bd.setSourceSample(samples[0]); + BlockLabwareData bl = new BlockLabwareData(new TissueBlockLabware()); + bl.setLabware(destLw); + bl.setBlocks(List.of(bd)); + BlockMakerImp maker = makeBlockMaker(null, List.of(bl), null, null, null, null, null); + maker.findSlots(); + assertThat(bd.getSourceSlots()).containsExactlyInAnyOrder(sourceSlots.get(0), sourceSlots.get(2)); + assertThat(bd.getDestSlots()).containsExactlyInAnyOrder(destLw.getSlot(A1), destLw.getSlot(A2)); + } + + @Test + void testCreateOperations() { + List bls = IntStream.range(0,2).mapToObj(i -> new BlockLabwareData(new TissueBlockLabware())).toList(); + Operation[] ops = IntStream.range(10,12).mapToObj(i -> { + Operation op = new Operation(); + op.setId(i); + return op; + }).toArray(Operation[]::new); + BlockMakerImp maker = spy(makeBlockMaker(null, bls, null, null, null, null, null)); + doReturn(ops[0], ops[1]).when(maker).createOperation(any()); + doNothing().when(maker).recordComments(any(), any()); + assertThat(maker.createOperations()).containsExactly(ops); + bls.forEach(bl -> verify(maker).createOperation(same(bl))); + Zip.of(bls.stream(), Arrays.stream(ops)).forEach((bl, op) -> verify(maker).recordComments(bl, op.getId())); + } + + @Test + void testCreateOperation() { + OperationType opType = EntityFactory.makeOperationType("opname", null); + User user = EntityFactory.getUser(); + final Address A1 = new Address(1,1), A2 = new Address(1,2); + Sample[] samples = EntityFactory.makeSamples(4); + LabwareType lt = EntityFactory.makeLabwareType(1,2); + Labware[] lws = IntStream.range(0,3).mapToObj(i -> EntityFactory.makeEmptyLabware(lt)).toArray(Labware[]::new); + BlockData bd1 = new BlockData(new TissueBlockContent()); + bd1.setSourceSample(samples[0]); + bd1.setSourceSlots(List.of(lws[0].getSlot(A1), lws[0].getSlot(A2))); + bd1.setDestSlots(List.of(lws[2].getSlot(A1))); + bd1.setSample(samples[2]); + BlockData bd2 = new BlockData(new TissueBlockContent()); + bd2.setSourceSample(samples[1]); + bd2.setSample(samples[3]); + bd2.setSourceSlots(List.of(lws[1].getSlot(A1))); + bd2.setDestSlots(List.of(lws[2].getSlot(A1), lws[2].getSlot(A2))); + BlockLabwareData ld = new BlockLabwareData(new TissueBlockLabware()); + ld.setBlocks(List.of(bd1, bd2)); + + Operation op = new Operation(); + op.setId(100); + when(mockOpService.createOperation(any(), any(), any(), any())).thenReturn(op); + BlockMakerImp maker = makeBlockMaker(null, List.of(ld), null, null, null, opType, user); + assertThat(maker.createOperations()).containsExactly(op); + ArgumentCaptor> acCaptor = Matchers.genericCaptor(List.class); + verify(mockOpService).createOperation(same(opType), same(user), acCaptor.capture(), isNull()); + List actions = acCaptor.getValue(); + assertThat(actions).containsExactlyInAnyOrder( + new Action(null, null, lws[0].getSlot(A1), lws[2].getSlot(A1), samples[2], samples[0]), + new Action(null, null, lws[0].getSlot(A2), lws[2].getSlot(A1), samples[2], samples[0]), + new Action(null, null, lws[1].getSlot(A1), lws[2].getSlot(A1), samples[3], samples[1]), + new Action(null, null, lws[1].getSlot(A1), lws[2].getSlot(A2), samples[3], samples[1]) + ); + } + + @Test + void testRecordComments() { + final Integer opId = 100; + + Labware lw = EntityFactory.makeEmptyLabware(EntityFactory.makeLabwareType(1, 2)); + BlockData bd = new BlockData(new TissueBlockContent()); + Comment com = new Comment(1, "com1", "cat"); + bd.setComment(com); + Sample sam = EntityFactory.getSample(); + bd.setSample(sam); + bd.setDestSlots(lw.getSlots()); + BlockData bd0 = new BlockData(new TissueBlockContent()); + bd0.setComment(null); + BlockLabwareData ld = new BlockLabwareData(new TissueBlockLabware()); + ld.setBlocks(List.of(bd0, bd)); + + BlockMakerImp maker = makeBlockMaker(null, List.of(ld), null, null, null, null, null); + maker.recordComments(ld, opId); + ArgumentCaptor> comCaptor = Matchers.genericCaptor(List.class); + verify(mockOpcomRepo).saveAll(comCaptor.capture()); + List opcoms = comCaptor.getValue(); + assertThat(opcoms).hasSize(2); + for (OperationComment opcom : opcoms) { + assertNull(opcom.getId()); + assertSame(com, opcom.getComment()); + assertEquals(opId, opcom.getOperationId()); + assertSame(sam.getId(), opcom.getSampleId()); + assertNull(opcom.getLabwareId()); + } + Integer[] slotIds = lw.getSlots().stream().map(Slot::getId).toArray(Integer[]::new); + assertThat(opcoms.stream().map(OperationComment::getSlotId)).containsExactlyInAnyOrder(slotIds); + } + + @Test + void testFillLabware() { + final Address A1 = new Address(1,1), A2 = new Address(1,2), A3 = new Address(1,3); + LabwareType lt = EntityFactory.makeLabwareType(1,4); + Labware lw = EntityFactory.makeEmptyLabware(lt); + Sample[] samples = EntityFactory.makeSamples(2); + List bds = IntStream.range(0,2).mapToObj(i -> { + BlockData bd = new BlockData(new TissueBlockContent()); + bd.setSample(samples[i]); + bd.setDestSlots(i==0 ? List.of(lw.getSlot(A1)) : List.of(lw.getSlot(A2), lw.getSlot(A3))); + return bd; + }).toList(); + BlockLabwareData bl = new BlockLabwareData(new TissueBlockLabware()); + bl.setBlocks(bds); + BlockMakerImp maker = makeBlockMaker(null, List.of(bl), null, null, null, null, null); + maker.fillLabware(); + assertThat(lw.getSlot(A1).getSamples()).containsExactly(samples[0]); + assertThat(lw.getSlot(A2).getSamples()).containsExactly(samples[1]); + assertThat(lw.getSlot(A3).getSamples()).containsExactly(samples[1]); + verify(mockSlotRepo).saveAll(Set.of(lw.getSlot(A1), lw.getSlot(A2), lw.getSlot(A3))); + } + + @Test + void testDiscardSources() { + TissueBlockRequest request = new TissueBlockRequest(); + LabwareType lt = EntityFactory.getTubeType(); + List lws = IntStream.range(0,4).mapToObj(i -> EntityFactory.makeEmptyLabware(lt)).toList(); + List bds = lws.stream().map(lw -> { + BlockData bd = new BlockData(new TissueBlockContent()); + bd.setSourceLabware(lw); + return bd; + }).toList(); + BlockLabwareData ld = new BlockLabwareData(new TissueBlockLabware()); + ld.setBlocks(bds); + lws.get(1).setDiscarded(true); + request.setDiscardSourceBarcodes(lws.subList(0,3).stream().map(Labware::getBarcode).toList()); + BlockMakerImp maker = makeBlockMaker(request, List.of(ld), null, null, null, null, null); + maker.discardSources(); + + Zip.enumerate(lws.stream()).forEach((i, lw) -> assertEquals(i != 3, lw.isDiscarded(), ""+i)); + verify(mockLwRepo).saveAll(Set.of(lws.get(0), lws.get(2))); + } +} \ No newline at end of file diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/block/TestBlockValidator.java b/src/test/java/uk/ac/sanger/sccp/stan/service/block/TestBlockValidator.java new file mode 100644 index 000000000..6af2f10a5 --- /dev/null +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/block/TestBlockValidator.java @@ -0,0 +1,500 @@ +package uk.ac.sanger.sccp.stan.service.block; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.*; +import org.mockito.stubbing.Answer; +import uk.ac.sanger.sccp.stan.EntityFactory; +import uk.ac.sanger.sccp.stan.Matchers; +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.repo.*; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockContent; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockLabware; +import uk.ac.sanger.sccp.stan.service.*; +import uk.ac.sanger.sccp.stan.service.work.WorkService; +import uk.ac.sanger.sccp.utils.Zip; + +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.*; +import static uk.ac.sanger.sccp.stan.Matchers.*; + +/** Tests {@link BlockValidatorImp} */ +class TestBlockValidator { + @Mock + LabwareValidatorFactory mockLwValFactory; + @Mock + Validator mockPrebarcodeValidator; + @Mock + Validator mockReplicateValidator; + @Mock + LabwareRepo mockLwRepo; + @Mock + OperationTypeRepo mockOpTypeRepo; + @Mock + LabwareTypeRepo mockLtRepo; + @Mock + BioStateRepo mockBsRepo; + @Mock + TissueRepo mockTissueRepo; + @Mock + MediumRepo mockMediumRepo; + @Mock + CommentValidationService mockCommentValidationService; + @Mock + WorkService mockWorkService; + + private AutoCloseable mocking; + + @BeforeEach + void setup() { + mocking = MockitoAnnotations.openMocks(this); + } + + @AfterEach + void cleanup() throws Exception { + mocking.close(); + } + + BlockValidatorImp makeVal(TissueBlockRequest request) { + return new BlockValidatorImp(mockLwValFactory, mockPrebarcodeValidator, mockReplicateValidator, + mockLwRepo, mockOpTypeRepo, mockLtRepo, mockBsRepo, mockTissueRepo, mockMediumRepo, + mockCommentValidationService, mockWorkService, + request); + } + + @Test + void testValidate_noLabware() { + TissueBlockRequest request = new TissueBlockRequest(); + BlockValidatorImp val = makeVal(request); + val.validate(); + assertProblem(val.getProblems(), "No labware specified."); + } + + @Test + void testValidate() { + TissueBlockRequest request = new TissueBlockRequest(); + TissueBlockLabware rlw = new TissueBlockLabware(); + TissueBlockContent content = new TissueBlockContent(); + content.setAddresses(List.of(new Address(1,1))); + rlw.setContents(List.of(content)); + request.setLabware(List.of(rlw)); + + BlockValidatorImp val = spy(makeVal(request)); + doNothing().when(val).loadEntities(); + doNothing().when(val).checkDestAddresses(); + doNothing().when(val).checkPrebarcodes(); + doNothing().when(val).checkReplicates(); + doAnswer(addProblem(val, "Bad barcode")).when(val).checkDiscardBarcodes(); + + val.validate(); + + InOrder inOrder = inOrder(val); + inOrder.verify(val).loadEntities(); + inOrder.verify(val).checkDestAddresses(); + inOrder.verify(val).checkPrebarcodes(); + inOrder.verify(val).checkReplicates(); + inOrder.verify(val).checkDiscardBarcodes(); + + assertThat(val.getLwData()).containsExactly(new BlockLabwareData(request.getLabware().getFirst())); + assertThat(val.getProblems()).containsExactly("Bad barcode"); + } + + static Answer addProblem(BlockValidatorImp val, String problem, X returnValue) { + return invocation -> { + val.getProblems().add(problem); + return returnValue; + }; + } + + static Answer addProblem(BlockValidatorImp val, String problem) { + return addProblem(val, problem, null); + } + + @Test + void testLoadEntities() { + Work work = EntityFactory.makeWork("SGP1"); + OperationType opType = EntityFactory.makeOperationType("Block processing", null); + BioState bs = EntityFactory.getBioState(); + Medium medium = EntityFactory.getMedium(); + when(mockWorkService.validateUsableWork(any(), any())).thenReturn(work); + when(mockBsRepo.findByName(any())).thenReturn(Optional.of(bs)); + when(mockOpTypeRepo.findByName(any())).thenReturn(Optional.of(opType)); + when(mockMediumRepo.findByName(any())).thenReturn(Optional.of(medium)); + BlockValidatorImp val = spy(makeVal(new TissueBlockRequest())); + doNothing().when(val).loadSources(); + doNothing().when(val).loadSourceSamples(); + doNothing().when(val).loadLabwareTypes(); + doNothing().when(val).loadComments(); + + val.loadEntities(); + verify(val).loadSources(); + verify(val).loadSourceSamples(); + verify(val).loadLabwareTypes(); + verify(val).loadComments(); + assertSame(work, val.getWork()); + assertSame(opType, val.getOpType()); + assertSame(bs, val.getBioState()); + assertSame(medium, val.getMedium()); + } + + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testLoadSources(boolean ok) { + LabwareValidator lwVal = mock(LabwareValidator.class); + BioState bs = EntityFactory.getBioState(); + when(mockLwValFactory.getValidator()).thenReturn(lwVal); + LabwareType lt = EntityFactory.getTubeType(); + Labware[] lws = IntStream.range(0,2).mapToObj(i -> EntityFactory.makeEmptyLabware(lt)).toArray(Labware[]::new); + List cons = Stream.of(ok ? lws[0].getBarcode().toLowerCase() : null, lws[0].getBarcode(), lws[1].getBarcode()) + .map(s -> { + TissueBlockContent c = new TissueBlockContent(); + c.setSourceBarcode(s); + return c; + }).toList(); + List tbls = cons.stream().map(con -> { + TissueBlockLabware tbl = new TissueBlockLabware(); + tbl.setContents(List.of(con)); + return tbl; + }).toList(); + TissueBlockRequest request = new TissueBlockRequest(); + request.setLabware(tbls); + when(lwVal.getLabware()).thenReturn(Arrays.asList(lws)); + if (!ok) { + when(lwVal.getErrors()).thenReturn(List.of("Bad labware")); + } + + BlockValidatorImp val = makeVal(request); + List lds = tbls.stream().map(BlockLabwareData::new).toList(); + val.setLwData(lds); + val.setProblems(new LinkedHashSet<>()); + val.setBioState(bs); + val.loadSources(); + assertSame(ok ? lws[0] : null, lds.get(0).getBlocks().getFirst().getSourceLabware()); + assertSame(lws[0], lds.get(1).getBlocks().getFirst().getSourceLabware()); + assertSame(lws[1], lds.get(2).getBlocks().getFirst().getSourceLabware()); + if (ok) { + assertThat(val.getProblems()).isEmpty(); + } else { + assertThat(val.getProblems()).containsExactlyInAnyOrder("Bad labware", "Source barcode missing."); + } + + verify(lwVal).loadLabware(mockLwRepo, Set.of(lws[0].getBarcode(), lws[1].getBarcode())); + verify(lwVal, never()).setSingleSample(true); + verify(lwVal).validateSources(); + verify(lwVal).validateBioState(bs); + } + + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testLoadSourceSamples(boolean ok) { + Sample[] samples = EntityFactory.makeSamples(2); + LabwareType lt = EntityFactory.makeLabwareType(1,2); + Labware lw1 = EntityFactory.makeLabware(lt, samples); + List cons = List.of(new TissueBlockContent(), new TissueBlockContent()); + Integer badSampleId; + if (ok) { + badSampleId = null; + Zip.of(cons.stream(), Arrays.stream(samples)).forEach((con, sam) -> con.setSourceSampleId(sam.getId())); + } else { + badSampleId = samples[1].getId()+1; + cons.get(0).setSourceSampleId(badSampleId); + } + TissueBlockLabware tbl = new TissueBlockLabware(); + tbl.setContents(cons); + TissueBlockRequest request = new TissueBlockRequest(); + request.setLabware(List.of(tbl)); + BlockLabwareData ld = new BlockLabwareData(tbl); + ld.getBlocks().forEach(bl -> bl.setSourceLabware(lw1)); + BlockValidatorImp val = makeVal(request); + val.setLwData(List.of(ld)); + val.setProblems(new LinkedHashSet<>()); + + val.loadSourceSamples(); + assertThat(val.getLwData()).containsExactly(ld); + if (ok) { + Zip.of(ld.getBlocks().stream(), Arrays.stream(samples)).forEach((bl, sam) -> assertSame(sam, bl.getSourceSample())); + assertThat(val.getProblems()).isEmpty(); + } else { + ld.getBlocks().forEach(bl -> assertNull(bl.getSourceSample())); + assertThat(val.getProblems()).containsExactlyInAnyOrder("Source sample ID missing from block request.", + "Sample id "+badSampleId+" not present in labware "+lw1.getBarcode()+"."); + } + } + + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testLoadLabwareTypes(boolean ok) { + LabwareType lt1 = EntityFactory.makeLabwareType(1,1,"Alpha"); + LabwareType lt2 = EntityFactory.makeLabwareType(1,1, "Beta"); + List tbls = Stream.of("Alpha", "alpha", "beta", "BETA") + .map(ln -> { + TissueBlockLabware tbl = new TissueBlockLabware(); + tbl.setLabwareType(ln); + return tbl; + }).toList(); + if (!ok) { + tbls.get(1).setLabwareType("Gamma"); + tbls.get(3).setLabwareType(null); + } + List bls = tbls.stream().map(BlockLabwareData::new).toList(); + when(mockLtRepo.findAllByNameIn(any())).thenReturn(List.of(lt1, lt2)); + TissueBlockRequest request = new TissueBlockRequest(tbls); + BlockValidatorImp val = makeVal(request); + val.setProblems(new LinkedHashSet<>()); + val.setLwData(bls); + val.loadLabwareTypes(); + assertThat(val.getLwData()).containsExactlyElementsOf(bls); + assertSame(lt1, bls.get(0).getLwType()); + assertSame(lt2, bls.get(2).getLwType()); + if (ok) { + verify(mockLtRepo).findAllByNameIn(Set.of("ALPHA", "BETA")); + assertSame(lt1, bls.get(1).getLwType()); + assertSame(lt2, bls.get(3).getLwType()); + assertThat(val.getProblems()).isEmpty(); + } else { + verify(mockLtRepo).findAllByNameIn(Set.of("ALPHA", "BETA", "GAMMA")); + assertNull(bls.get(1).getLwType()); + assertNull(bls.get(3).getLwType()); + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "Missing labware type.", + "Unknown labware types: [\"Gamma\"]"); + } + } + + @Test + void testLoadComments() { + List comments = List.of(new Comment(1, "com1", "cat"), new Comment(2, "com2", "cat")); + List cons = comments.stream().map(comment -> { + TissueBlockContent con = new TissueBlockContent(); + con.setCommentId(comment.getId()); + return con; + }).toList(); + TissueBlockLabware tbl = new TissueBlockLabware(); + tbl.setContents(cons); + BlockLabwareData bld = new BlockLabwareData(tbl); + TissueBlockRequest request = new TissueBlockRequest(List.of(tbl)); + when(mockCommentValidationService.validateCommentIds(any(), any())).thenReturn(comments); + + BlockValidatorImp val = makeVal(request); + val.setLwData(List.of(bld)); + List problems = new ArrayList<>(); + val.setProblems(problems); + val.loadComments(); + ArgumentCaptor> commentIdStreamCaptor = Matchers.streamCaptor(); + verify(mockCommentValidationService).validateCommentIds(same(problems), commentIdStreamCaptor.capture()); + assertThat(commentIdStreamCaptor.getValue()).containsExactly(1,2); + assertThat(problems).isEmpty(); + Zip.of(comments.stream(), bld.getBlocks().stream()) + .forEach((com, bl) -> assertSame(com, bl.getComment())); + } + + private static TissueBlockContent contentForAddresses(Address... addresses) { + TissueBlockContent con = new TissueBlockContent(); + con.setAddresses(Arrays.asList(addresses)); + return con; + } + + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testCheckDestAddresses(boolean ok) { + LabwareType lt1 = EntityFactory.getTubeType(); + LabwareType lt2 = EntityFactory.makeLabwareType(1, 2, "lt2"); + Address A1 = new Address(1, 1), A2 = new Address(1,2); + TissueBlockLabware tbl1 = new TissueBlockLabware(); + TissueBlockLabware tbl2 = new TissueBlockLabware(); + tbl1.setLabwareType(lt1.getName()); + tbl2.setLabwareType(lt2.getName()); + if (ok) { + tbl1.setContents(List.of(contentForAddresses(A1))); + tbl2.setContents(List.of(contentForAddresses(A1, A2))); + } else { + tbl1.setContents(List.of(contentForAddresses(A1), contentForAddresses(A1, A2))); + tbl2.setContents(List.of(contentForAddresses(A1), new TissueBlockContent())); + } + TissueBlockRequest request = new TissueBlockRequest(List.of(tbl1, tbl2)); + + List blds = request.getLabware().stream().map(BlockLabwareData::new).toList(); + blds.get(0).setLwType(lt1); + blds.get(1).setLwType(lt2); + BlockValidatorImp val = makeVal(request); + val.setLwData(blds); + val.setProblems(new LinkedHashSet<>()); + val.checkDestAddresses(); + if (ok) { + assertThat(val.getProblems()).isEmpty(); + } else { + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "Slot address A2 not valid in labware type "+lt1.getName()+".", + "Destination slot addresses missing from block request." + ); + } + } + + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testCheckPrebarcodes(boolean ok) { + LabwareType lt1 = EntityFactory.makeLabwareType(1,1, "lt1"); + LabwareType lt2 = EntityFactory.makeLabwareType(1,1, "lt2"); + lt1.setPrebarcoded(false); + lt2.setPrebarcoded(true); + List tbls = IntStream.range(0,5) + .mapToObj(i -> new TissueBlockLabware()) + .toList(); + if (ok) { + IntStream.range(1, tbls.size()).forEach(i -> tbls.get(i).setPreBarcode("preb"+i)); + } else { + tbls.get(0).setPreBarcode("preb1"); + IntStream.range(2, tbls.size()).forEach(i -> tbls.get(i).setPreBarcode("preb"+i)); + tbls.get(2).setPreBarcode("wut!"); + tbls.get(4).setPreBarcode("preb3"); + } + TissueBlockRequest request = new TissueBlockRequest(tbls); + + List blds = request.getLabware().stream().map(BlockLabwareData::new).toList(); + BlockValidatorImp val = makeVal(request); + Zip.enumerate(blds.stream()).forEach((i, bld) -> bld.setLwType(i==0 ? lt1 : lt2)); + val.setLwData(blds); + val.setProblems(new LinkedHashSet<>()); + if (!ok) { + when(mockPrebarcodeValidator.validate(any(), any())).then(invocation -> { + Consumer problemConsumer = invocation.getArgument(1); + String barcode = invocation.getArgument(0); + if (barcode.indexOf('!')>=0) { + problemConsumer.accept("Bad barcode: " + barcode); + return false; + } + return true; + }); + Labware lw1 = EntityFactory.makeEmptyLabware(lt1); + lw1.setBarcode("preb3"); + when(mockLwRepo.findByBarcodeIn(any())).thenReturn(List.of(lw1)); + } + val.checkPrebarcodes(); + if (ok) { + assertThat(val.getProblems()).isEmpty(); + } else { + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "A barcode is not expected for labware type ["+lt1.getName()+"].", + "A barcode is required for labware type ["+lt2.getName()+"].", + "Bad barcode: wut!", + "Barcode already in use: [preb3]", + "Barcode specified multiple times: [PREB3]" + ); + } + verify(mockPrebarcodeValidator, times(4)).validate(any(), any()); + verify(mockLwRepo).findByBarcodeIn(ok ? Set.of("PREB1", "PREB2", "PREB3", "PREB4") + : Set.of("WUT!", "PREB1", "PREB3")); + } + + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testCheckReplicates(boolean ok) { + Donor donor = EntityFactory.getDonor(); + SpatialLocation sl = EntityFactory.getSpatialLocation(); + BioState bs = EntityFactory.getBioState(); + Tissue tis1 = EntityFactory.makeTissue(donor, sl); + tis1.setReplicate("REP"); + Tissue tis2 = EntityFactory.makeTissue(donor, sl); + tis2.setReplicate(null); + Sample sam1 = new Sample(1, null, tis1, bs); + Sample sam2 = new Sample(2, null, tis2, bs); + List bds; + if (ok) { + bds = Zip.of(Stream.of(sam1, sam2), Stream.of("REP", "REP1")) + .map((sam, rep) -> { + BlockData bd = new BlockData(new TissueBlockContent()); + bd.setSourceSample(sam); + bd.getRequestContent().setReplicate(rep); + return bd; + }).toList(); + when(mockReplicateValidator.validate(any(), any())).thenReturn(true); + } else { + bds = Zip.of(Stream.of(sam1, sam2, sam2, sam2, sam2, sam2), + Stream.of("REPX", null, "REP1", "REP1", "REP!", "REP2")) + .map((sam, rep) -> { + BlockData bd = new BlockData(new TissueBlockContent()); + bd.setSourceSample(sam); + bd.getRequestContent().setReplicate(rep); + return bd; + }).toList(); + when(mockReplicateValidator.validate(any(), any())).thenAnswer(invocation -> { + Consumer problemConsumer = invocation.getArgument(1); + String rep = invocation.getArgument(0); + if (rep.indexOf('!') >= 0) { + problemConsumer.accept("Bad replicate: " + rep); + return false; + } + return true; + }); + when(mockTissueRepo.findByDonorIdAndSpatialLocationIdAndReplicate(anyInt(), anyInt(), eqCi("REP2"))) + .thenReturn(List.of(tis2)); + } + BlockValidatorImp val = makeVal(new TissueBlockRequest()); + BlockLabwareData bld = new BlockLabwareData(new TissueBlockLabware()); + bld.setBlocks(bds); + val.setLwData(List.of(bld)); + val.setProblems(new LinkedHashSet<>()); + val.checkReplicates(); + if (ok) { + assertThat(val.getProblems()).isEmpty(); + } else { + String repdesc = String.format("{Donor: %s, Tissue type: %s, Spatial location: %s, Replicate: %%s}", + donor.getDonorName(), tis1.getTissueType().getName(), sl.getCode()); + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "Bad replicate: REP!", + "Missing replicate for some blocks.", + "Replicate numbers must match the source replicate number where present.", + "Same replicate specified multiple times: ["+String.format(repdesc, "rep1")+"]", + "Replicate already exists in the database: ["+String.format(repdesc, "rep2")+"]" + ); + } + } + + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testCheckDiscardBarcodes(boolean ok) { + List cons = Stream.of("STAN-1", "STAN-2") + .map(bc -> { + TissueBlockContent con = new TissueBlockContent(); + con.setSourceBarcode(bc); + return con; + }).toList(); + TissueBlockLabware tbl = new TissueBlockLabware(); + tbl.setContents(cons); + TissueBlockRequest request = new TissueBlockRequest(List.of(tbl)); + request.setDiscardSourceBarcodes(ok ? List.of("STAN-1") : List.of("STAN-1", "STAN-3", "")); + BlockValidatorImp val = makeVal(request); + val.setProblems(new LinkedHashSet<>()); + val.checkDiscardBarcodes(); + if (ok) { + assertThat(val.getProblems()).isEmpty(); + } else { + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "A null or empty string was supplied as a barcode to discard.", + "The given list of barcodes to discard includes a barcode that is not specified as a source barcode in this request: [\"STAN-3\"]" + ); + } + } + + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testRaiseError(boolean ok) { + BlockValidatorImp val = makeVal(new TissueBlockRequest()); + List problems = ok ? List.of() : List.of("Problem 1", "Problem 2"); + val.setProblems(problems); + if (ok) { + val.raiseError(); + } else { + assertValidationException(val::raiseError, problems); + } + } +} \ No newline at end of file diff --git a/src/test/resources/graphql/tissueblock.graphql b/src/test/resources/graphql/tissueblock.graphql index 893eb0616..fa43aa702 100644 --- a/src/test/resources/graphql/tissueblock.graphql +++ b/src/test/resources/graphql/tissueblock.graphql @@ -3,9 +3,15 @@ mutation { discardSourceBarcodes: ["BARCODE"] workNumber: "WORKNUMBER" labware: [{ - sourceBarcode: "BARCODE" labwareType: "Tube" - replicate: "5c" + contents: [ + { + sourceBarcode: "BARCODE" + replicate: "5c" + addresses: ["A1"] + sourceSampleId: 999 + } + ] }] }) { labware { From d88701ca02fbd70ddbafc1a6b05bb57df6825058 Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:21:00 +0000 Subject: [PATCH 03/13] x1404 code review --- .../java/uk/ac/sanger/sccp/stan/request/TissueBlockRequest.java | 2 ++ .../uk/ac/sanger/sccp/stan/service/block/BlockMakerImp.java | 1 + .../uk/ac/sanger/sccp/stan/service/block/BlockValidatorImp.java | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/uk/ac/sanger/sccp/stan/request/TissueBlockRequest.java b/src/main/java/uk/ac/sanger/sccp/stan/request/TissueBlockRequest.java index 3f3781e4f..9b9666939 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/request/TissueBlockRequest.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/request/TissueBlockRequest.java @@ -140,6 +140,7 @@ public String toString() { @Override public boolean equals(Object o) { + if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TissueBlockLabware that = (TissueBlockLabware) o; return (Objects.equals(this.labwareType, that.labwareType) @@ -219,6 +220,7 @@ public String toString() { @Override public boolean equals(Object o) { + if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TissueBlockContent that = (TissueBlockContent) o; return (Objects.equals(this.sourceBarcode, that.sourceBarcode) diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerImp.java index e83efc4bc..c5ae18b39 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockMakerImp.java @@ -80,6 +80,7 @@ public List createLabware() { List labware = new ArrayList<>(lwData.size()); for (BlockLabwareData lwd : lwData) { String prebarcode = lwd.getRequestLabware().getPreBarcode(); + // Uses the prebarcode for the labware external barcode and the de facto barcode Labware lw = lwService.create(lwd.getLwType(), prebarcode, prebarcode); labware.add(lw); lwd.setLabware(lw); diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorImp.java index 523939b42..f70ecfb1f 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/block/BlockValidatorImp.java @@ -116,10 +116,10 @@ public void loadSources() { barcodes.add(barcode.toUpperCase()); } } - LabwareValidator val = lwValFactory.getValidator(); if (anyMissing) { problems.add("Source barcode missing."); } + LabwareValidator val = lwValFactory.getValidator(); val.loadLabware(lwRepo, barcodes); val.validateSources(); if (bioState!=null) { From 6b15d5a00bb4e08a83a03efc4e747b9d5b0cb05d Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:49:42 +0000 Subject: [PATCH 04/13] x1403 in progress --- .../register/BlockRegisterLabware.java | 94 ++++++++ .../register/BlockRegisterRequest.java | 208 +++-------------- .../register/BlockRegisterRequest_old.java | 211 ++++++++++++++++++ .../request/register/BlockRegisterSample.java | 193 ++++++++++++++++ .../request/register/RegisterRequest.java | 10 +- .../register/FileRegisterServiceImp.java | 4 +- .../register/RegisterClashChecker.java | 2 +- .../service/register/RegisterServiceImp.java | 8 +- .../register/RegisterValidationImp.java | 38 ++-- .../service/register/TissueFieldChecker.java | 32 +-- .../filereader/BlockRegisterFileReader.java | 10 +- .../BlockRegisterFileReaderImp.java | 77 +------ .../BlockRegisterFileReaderImp_old.java | 120 ++++++++++ src/main/resources/schema.graphqls | 36 ++- .../TestFileBlockRegister.java | 2 +- .../register/TestFileRegisterService.java | 8 +- .../register/TestRegisterClashChecker.java | 6 +- .../service/register/TestRegisterService.java | 42 ++-- .../register/TestRegisterValidation.java | 58 ++--- .../register/TestTissueFieldChecker.java | 8 +- .../TestBlockRegisterFileReader.java | 22 +- 21 files changed, 811 insertions(+), 378 deletions(-) create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterLabware.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest_old.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterSample.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp_old.java diff --git a/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterLabware.java b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterLabware.java new file mode 100644 index 000000000..768935f24 --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterLabware.java @@ -0,0 +1,94 @@ +package uk.ac.sanger.sccp.stan.request.register; + +import java.util.List; +import java.util.Objects; + +import static uk.ac.sanger.sccp.utils.BasicUtils.describe; +import static uk.ac.sanger.sccp.utils.BasicUtils.nullToEmpty; + +/** + * A request to register in a piece of labware containing one or more block-samples. + * @author dr6 + */ +public class BlockRegisterLabware { + private String labwareType; + private String medium; + private String fixative; + private String externalBarcode; + private List samples = List.of(); + + /** The name of the type of labware containing the block. */ + public String getLabwareType() { + return this.labwareType; + } + + public void setLabwareType(String labwareType) { + this.labwareType = labwareType; + } + + /** The medium used for the tissue. */ + public String getMedium() { + return this.medium; + } + + public void setMedium(String medium) { + this.medium = medium; + } + + /** The fixative used for the tissue. */ + public String getFixative() { + return this.fixative; + } + + public void setFixative(String fixative) { + this.fixative = fixative; + } + + /** The external barcode of the labware. */ + public String getExternalBarcode() { + return this.externalBarcode; + } + + public void setExternalBarcode(String externalBarcode) { + this.externalBarcode = externalBarcode; + } + + /** The samples in this block. */ + public List getSamples() { + return this.samples; + } + + public void setSamples(List samples) { + this.samples = nullToEmpty(samples); + } + + @Override + public String toString() { + return describe(this) + .add("labwareType", labwareType) + .add("medium", medium) + .add("fixative", fixative) + .add("externalBarcode", externalBarcode) + .add("samples", samples) + .reprStringValues() + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || o.getClass() != this.getClass()) return false; + BlockRegisterLabware that = (BlockRegisterLabware) o; + return (Objects.equals(this.labwareType, that.labwareType) + && Objects.equals(this.medium, that.medium) + && Objects.equals(this.fixative, that.fixative) + && Objects.equals(this.externalBarcode, that.externalBarcode) + && Objects.equals(this.samples, that.samples) + ); + } + + @Override + public int hashCode() { + return Objects.hash(labwareType, medium, fixative, externalBarcode, samples); + } +} \ No newline at end of file diff --git a/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest.java b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest.java index fe0bfeb0a..52a69c9c2 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest.java @@ -1,211 +1,65 @@ package uk.ac.sanger.sccp.stan.request.register; -import uk.ac.sanger.sccp.stan.model.LifeStage; - -import java.time.LocalDate; +import java.util.List; import java.util.Objects; import static uk.ac.sanger.sccp.utils.BasicUtils.describe; +import static uk.ac.sanger.sccp.utils.BasicUtils.nullToEmpty; /** - * The information required to register a block. + * A request to register new blocks of tissue. * @author dr6 */ public class BlockRegisterRequest { - private String donorIdentifier; - private LifeStage lifeStage; - private String hmdmc; - private String tissueType; - private int spatialLocation; - private String replicateNumber; - private String externalIdentifier; - private int highestSection; - private String labwareType; - private String medium; - private String fixative; - private String species; - private boolean existingTissue; - private LocalDate sampleCollectionDate; - private String bioRiskCode; - private String cellClass; - - public String getDonorIdentifier() { - return this.donorIdentifier; - } - - public void setDonorIdentifier(String donorIdentifier) { - this.donorIdentifier = donorIdentifier; - } - - public LifeStage getLifeStage() { - return this.lifeStage; - } - - public void setLifeStage(LifeStage lifeStage) { - this.lifeStage = lifeStage; - } - - public String getHmdmc() { - return this.hmdmc; - } - - public void setHmdmc(String hmdmc) { - this.hmdmc = hmdmc; - } - - public String getTissueType() { - return this.tissueType; - } - - public void setTissueType(String tissueType) { - this.tissueType = tissueType; - } - - public int getSpatialLocation() { - return this.spatialLocation; - } - - public void setSpatialLocation(int spatialLocation) { - this.spatialLocation = spatialLocation; - } - - public String getReplicateNumber() { - return this.replicateNumber; - } - - public void setReplicateNumber(String replicateNumber) { - this.replicateNumber = replicateNumber; - } - - public String getExternalIdentifier() { - return this.externalIdentifier; - } - - public void setExternalIdentifier(String externalIdentifier) { - this.externalIdentifier = externalIdentifier; - } - - public int getHighestSection() { - return this.highestSection; - } - - public void setHighestSection(int highestSection) { - this.highestSection = highestSection; - } - - public String getLabwareType() { - return this.labwareType; - } - - public void setLabwareType(String labwareType) { - this.labwareType = labwareType; - } - - public String getMedium() { - return this.medium; - } - - public void setMedium(String medium) { - this.medium = medium; - } - - public String getFixative() { - return this.fixative; - } - - public void setFixative(String fixative) { - this.fixative = fixative; - } - - public String getSpecies() { - return this.species; - } - - public void setSpecies(String species) { - this.species = species; - } - - public boolean isExistingTissue() { - return this.existingTissue; - } + private List workNumbers = List.of(); + private List labware = List.of(); - public void setExistingTissue(boolean existingTissue) { - this.existingTissue = existingTissue; - } + public BlockRegisterRequest() {} // required - public LocalDate getSampleCollectionDate() { - return this.sampleCollectionDate; + public BlockRegisterRequest(List workNumbers, List labware) { + setWorkNumbers(workNumbers); + setLabware(labware); } - public void setSampleCollectionDate(LocalDate sampleCollectionDate) { - this.sampleCollectionDate = sampleCollectionDate; + /** The work numbers for the request. */ + public List getWorkNumbers() { + return this.workNumbers; } - public String getBioRiskCode() { - return this.bioRiskCode; + public void setWorkNumbers(List workNumbers) { + this.workNumbers = nullToEmpty(workNumbers); } - public void setBioRiskCode(String bioRiskCode) { - this.bioRiskCode = bioRiskCode; + /** The labware to register. */ + public List getLabware() { + return this.labware; } - public String getCellClass() { - return this.cellClass; + public void setLabware(List labware) { + this.labware = nullToEmpty(labware); } - public void setCellClass(String cellClass) { - this.cellClass = cellClass; + @Override + public String toString() { + return describe(this) + .add("workNumbers", workNumbers) + .add("labware", labware) + .reprStringValues() + .toString(); } @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (o == null || o.getClass() != this.getClass()) return false; BlockRegisterRequest that = (BlockRegisterRequest) o; - return (this.spatialLocation == that.spatialLocation - && Objects.equals(this.replicateNumber, that.replicateNumber) - && this.highestSection == that.highestSection - && this.existingTissue == that.existingTissue - && Objects.equals(this.donorIdentifier, that.donorIdentifier) - && this.lifeStage == that.lifeStage - && Objects.equals(this.hmdmc, that.hmdmc) - && Objects.equals(this.tissueType, that.tissueType) - && Objects.equals(this.externalIdentifier, that.externalIdentifier) - && Objects.equals(this.labwareType, that.labwareType) - && Objects.equals(this.medium, that.medium) - && Objects.equals(this.fixative, that.fixative) - && Objects.equals(this.species, that.species) - && Objects.equals(this.sampleCollectionDate, that.sampleCollectionDate) - && Objects.equals(this.bioRiskCode, that.bioRiskCode) - && Objects.equals(this.cellClass, that.cellClass) + return (Objects.equals(this.workNumbers, that.workNumbers) + && Objects.equals(this.labware, that.labware) ); } @Override public int hashCode() { - return (externalIdentifier!=null ? externalIdentifier.hashCode() : 0); - } - - @Override - public String toString() { - return describe(this) - .add("donorIdentifier", donorIdentifier) - .add("lifeStage", lifeStage) - .add("hmdmc", hmdmc) - .add("tissueType", tissueType) - .add("spatialLocation", spatialLocation) - .add("replicateNumber", replicateNumber) - .add("externalIdentifier", externalIdentifier) - .add("highestSection", highestSection) - .add("labwareType", labwareType) - .add("medium", medium) - .add("fixative", fixative) - .add("species", species) - .add("existingTissue", existingTissue) - .add("sampleCollectionDate", sampleCollectionDate) - .add("bioRiskCode", bioRiskCode) - .add("cellClass", cellClass) - .reprStringValues() - .toString(); + return Objects.hash(workNumbers, labware); } -} +} \ No newline at end of file diff --git a/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest_old.java b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest_old.java new file mode 100644 index 000000000..11c13acdf --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest_old.java @@ -0,0 +1,211 @@ +package uk.ac.sanger.sccp.stan.request.register; + +import uk.ac.sanger.sccp.stan.model.LifeStage; + +import java.time.LocalDate; +import java.util.Objects; + +import static uk.ac.sanger.sccp.utils.BasicUtils.describe; + +/** + * The information required to register a block. + * @author dr6 + */ +public class BlockRegisterRequest_old { + private String donorIdentifier; + private LifeStage lifeStage; + private String hmdmc; + private String tissueType; + private int spatialLocation; + private String replicateNumber; + private String externalIdentifier; + private int highestSection; + private String labwareType; + private String medium; + private String fixative; + private String species; + private boolean existingTissue; + private LocalDate sampleCollectionDate; + private String bioRiskCode; + private String cellClass; + + public String getDonorIdentifier() { + return this.donorIdentifier; + } + + public void setDonorIdentifier(String donorIdentifier) { + this.donorIdentifier = donorIdentifier; + } + + public LifeStage getLifeStage() { + return this.lifeStage; + } + + public void setLifeStage(LifeStage lifeStage) { + this.lifeStage = lifeStage; + } + + public String getHmdmc() { + return this.hmdmc; + } + + public void setHmdmc(String hmdmc) { + this.hmdmc = hmdmc; + } + + public String getTissueType() { + return this.tissueType; + } + + public void setTissueType(String tissueType) { + this.tissueType = tissueType; + } + + public int getSpatialLocation() { + return this.spatialLocation; + } + + public void setSpatialLocation(int spatialLocation) { + this.spatialLocation = spatialLocation; + } + + public String getReplicateNumber() { + return this.replicateNumber; + } + + public void setReplicateNumber(String replicateNumber) { + this.replicateNumber = replicateNumber; + } + + public String getExternalIdentifier() { + return this.externalIdentifier; + } + + public void setExternalIdentifier(String externalIdentifier) { + this.externalIdentifier = externalIdentifier; + } + + public int getHighestSection() { + return this.highestSection; + } + + public void setHighestSection(int highestSection) { + this.highestSection = highestSection; + } + + public String getLabwareType() { + return this.labwareType; + } + + public void setLabwareType(String labwareType) { + this.labwareType = labwareType; + } + + public String getMedium() { + return this.medium; + } + + public void setMedium(String medium) { + this.medium = medium; + } + + public String getFixative() { + return this.fixative; + } + + public void setFixative(String fixative) { + this.fixative = fixative; + } + + public String getSpecies() { + return this.species; + } + + public void setSpecies(String species) { + this.species = species; + } + + public boolean isExistingTissue() { + return this.existingTissue; + } + + public void setExistingTissue(boolean existingTissue) { + this.existingTissue = existingTissue; + } + + public LocalDate getSampleCollectionDate() { + return this.sampleCollectionDate; + } + + public void setSampleCollectionDate(LocalDate sampleCollectionDate) { + this.sampleCollectionDate = sampleCollectionDate; + } + + public String getBioRiskCode() { + return this.bioRiskCode; + } + + public void setBioRiskCode(String bioRiskCode) { + this.bioRiskCode = bioRiskCode; + } + + public String getCellClass() { + return this.cellClass; + } + + public void setCellClass(String cellClass) { + this.cellClass = cellClass; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BlockRegisterRequest_old that = (BlockRegisterRequest_old) o; + return (this.spatialLocation == that.spatialLocation + && Objects.equals(this.replicateNumber, that.replicateNumber) + && this.highestSection == that.highestSection + && this.existingTissue == that.existingTissue + && Objects.equals(this.donorIdentifier, that.donorIdentifier) + && this.lifeStage == that.lifeStage + && Objects.equals(this.hmdmc, that.hmdmc) + && Objects.equals(this.tissueType, that.tissueType) + && Objects.equals(this.externalIdentifier, that.externalIdentifier) + && Objects.equals(this.labwareType, that.labwareType) + && Objects.equals(this.medium, that.medium) + && Objects.equals(this.fixative, that.fixative) + && Objects.equals(this.species, that.species) + && Objects.equals(this.sampleCollectionDate, that.sampleCollectionDate) + && Objects.equals(this.bioRiskCode, that.bioRiskCode) + && Objects.equals(this.cellClass, that.cellClass) + ); + } + + @Override + public int hashCode() { + return (externalIdentifier!=null ? externalIdentifier.hashCode() : 0); + } + + @Override + public String toString() { + return describe(this) + .add("donorIdentifier", donorIdentifier) + .add("lifeStage", lifeStage) + .add("hmdmc", hmdmc) + .add("tissueType", tissueType) + .add("spatialLocation", spatialLocation) + .add("replicateNumber", replicateNumber) + .add("externalIdentifier", externalIdentifier) + .add("highestSection", highestSection) + .add("labwareType", labwareType) + .add("medium", medium) + .add("fixative", fixative) + .add("species", species) + .add("existingTissue", existingTissue) + .add("sampleCollectionDate", sampleCollectionDate) + .add("bioRiskCode", bioRiskCode) + .add("cellClass", cellClass) + .reprStringValues() + .toString(); + } +} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterSample.java b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterSample.java new file mode 100644 index 000000000..8a69e3cf5 --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterSample.java @@ -0,0 +1,193 @@ +package uk.ac.sanger.sccp.stan.request.register; + +import uk.ac.sanger.sccp.stan.model.LifeStage; + +import java.time.LocalDate; +import java.util.Objects; + +import static uk.ac.sanger.sccp.utils.BasicUtils.describe; + +/** + * A sample inside a block being registered. + * @author dr6 + */ +public class BlockRegisterSample { + private String donorIdentifier; + private LifeStage lifeStage; + private String hmdmc; + private String tissueType; + private Integer spatialLocation; + private String replicateNumber; + private String externalIdentifier; + private Integer highestSection; + private String species; + private String cellClass; + private boolean existingTissue; + private LocalDate sampleCollectionDate; + private String bioRiskCode; + + /** The string to use as the donor name. */ + public String getDonorIdentifier() { + return this.donorIdentifier; + } + + public void setDonorIdentifier(String donorIdentifier) { + this.donorIdentifier = donorIdentifier; + } + + /** The life stage of the donor. */ + public LifeStage getLifeStage() { + return this.lifeStage; + } + + public void setLifeStage(LifeStage lifeStage) { + this.lifeStage = lifeStage; + } + + /** The HMDMC to use for the tissue. */ + public String getHmdmc() { + return this.hmdmc; + } + + public void setHmdmc(String hmdmc) { + this.hmdmc = hmdmc; + } + + /** The name of the tissue type (the organ from which the tissue is taken). */ + public String getTissueType() { + return this.tissueType; + } + + public void setTissueType(String tissueType) { + this.tissueType = tissueType; + } + + /** The code for the spatial location from which the tissue is taken. */ + public Integer getSpatialLocation() { + return this.spatialLocation; + } + + public void setSpatialLocation(Integer spatialLocation) { + this.spatialLocation = spatialLocation; + } + + /** The string to use for the replicate number of the tissue. */ + public String getReplicateNumber() { + return this.replicateNumber; + } + + public void setReplicateNumber(String replicateNumber) { + this.replicateNumber = replicateNumber; + } + + /** The external identifier used to identify the tissue. */ + public String getExternalIdentifier() { + return this.externalIdentifier; + } + + public void setExternalIdentifier(String externalIdentifier) { + this.externalIdentifier = externalIdentifier; + } + + /** The highest section already taken from the tissue block. */ + public Integer getHighestSection() { + return this.highestSection; + } + + public void setHighestSection(Integer highestSection) { + this.highestSection = highestSection; + } + + /** The species of the donor. */ + public String getSpecies() { + return this.species; + } + + public void setSpecies(String species) { + this.species = species; + } + + /** The cellular classification of the tissue. */ + public String getCellClass() { + return this.cellClass; + } + + public void setCellClass(String cellClass) { + this.cellClass = cellClass; + } + + /** Is this a new block of tissue already in the application's database? */ + public boolean isExistingTissue() { + return this.existingTissue; + } + + public void setExistingTissue(boolean existingTissue) { + this.existingTissue = existingTissue; + } + + /** The date the original sample was collected, if known. */ + public LocalDate getSampleCollectionDate() { + return this.sampleCollectionDate; + } + + public void setSampleCollectionDate(LocalDate sampleCollectionDate) { + this.sampleCollectionDate = sampleCollectionDate; + } + + /** The biological risk number for this block. */ + public String getBioRiskCode() { + return this.bioRiskCode; + } + + public void setBioRiskCode(String bioRiskCode) { + this.bioRiskCode = bioRiskCode; + } + + @Override + public String toString() { + return describe(this) + .add("donorIdentifier", donorIdentifier) + .add("lifeStage", lifeStage) + .add("hmdmc", hmdmc) + .add("tissueType", tissueType) + .add("spatialLocation", spatialLocation) + .add("replicateNumber", replicateNumber) + .add("externalIdentifier", externalIdentifier) + .add("highestSection", highestSection) + .add("species", species) + .add("cellClass", cellClass) + .add("existingTissue", existingTissue) + .add("sampleCollectionDate", sampleCollectionDate) + .add("bioRiskCode", bioRiskCode) + .reprStringValues() + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || o.getClass() != this.getClass()) return false; + BlockRegisterSample that = (BlockRegisterSample) o; + return (this.existingTissue == that.existingTissue + && Objects.equals(this.donorIdentifier, that.donorIdentifier) + && Objects.equals(this.lifeStage, that.lifeStage) + && Objects.equals(this.hmdmc, that.hmdmc) + && Objects.equals(this.tissueType, that.tissueType) + && Objects.equals(this.spatialLocation, that.spatialLocation) + && Objects.equals(this.replicateNumber, that.replicateNumber) + && Objects.equals(this.externalIdentifier, that.externalIdentifier) + && Objects.equals(this.highestSection, that.highestSection) + && Objects.equals(this.species, that.species) + && Objects.equals(this.cellClass, that.cellClass) + && Objects.equals(this.sampleCollectionDate, that.sampleCollectionDate) + && Objects.equals(this.bioRiskCode, that.bioRiskCode) + ); + } + + @Override + public int hashCode() { + return Objects.hash(donorIdentifier, lifeStage, hmdmc, tissueType, spatialLocation, replicateNumber, + externalIdentifier, highestSection, species, cellClass, existingTissue, sampleCollectionDate, + bioRiskCode); + } +} \ No newline at end of file diff --git a/src/main/java/uk/ac/sanger/sccp/stan/request/register/RegisterRequest.java b/src/main/java/uk/ac/sanger/sccp/stan/request/register/RegisterRequest.java index 635857836..8ceaa8690 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/request/register/RegisterRequest.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/request/register/RegisterRequest.java @@ -9,27 +9,27 @@ * @author dr6 */ public class RegisterRequest { - private List blocks; + private List blocks; private List workNumbers; public RegisterRequest() { this(null, null); } - public RegisterRequest(List blocks) { + public RegisterRequest(List blocks) { this(blocks, null); } - public RegisterRequest(List blocks, List workNumbers) { + public RegisterRequest(List blocks, List workNumbers) { setBlocks(blocks); setWorkNumbers(workNumbers); } - public List getBlocks() { + public List getBlocks() { return this.blocks; } - public void setBlocks(List blocks) { + public void setBlocks(List blocks) { this.blocks = (blocks==null ? List.of() : blocks); } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/FileRegisterServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/FileRegisterServiceImp.java index 9dd34f9b3..d76950967 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/FileRegisterServiceImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/FileRegisterServiceImp.java @@ -94,7 +94,7 @@ public void updateWithExisting(RegisterRequest request, List existingExt .filter(Objects::nonNull) .map(String::toUpperCase) .collect(toSet()); - for (BlockRegisterRequest block : request.getBlocks()) { + for (BlockRegisterRequest_old block : request.getBlocks()) { if (block != null && !nullOrEmpty(block.getExternalIdentifier()) && externalNamesUC.contains(block.getExternalIdentifier().toUpperCase())) { block.setExistingTissue(true); @@ -115,7 +115,7 @@ public void updateToRemove(RegisterRequest request, List externalNames) .filter(Objects::nonNull) .map(String::toUpperCase) .collect(toSet()); - List blocks = request.getBlocks().stream() + List blocks = request.getBlocks().stream() .filter(block -> block==null || block.getExternalIdentifier()==null || !ignoreUC.contains(block.getExternalIdentifier().toUpperCase())) .toList(); request.setBlocks(blocks); diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java index 97551fe9b..3a9bc7476 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java @@ -36,7 +36,7 @@ public RegisterClashChecker(TissueRepo tissueRepo, SampleRepo sampleRepo, Operat public List findClashes(RegisterRequest request) { Set externalNames = request.getBlocks().stream() .filter(br -> !br.isExistingTissue()) - .map(BlockRegisterRequest::getExternalIdentifier) + .map(BlockRegisterRequest_old::getExternalIdentifier) .collect(toSet()); if (externalNames.isEmpty()) { return List.of(); diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java index a2c3992f9..cf100683c 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java @@ -69,7 +69,7 @@ public RegisterResult register(User user, RegisterRequest request) { public Map createDonors(RegisterRequest request, RegisterValidation validation) { Map donors = new HashMap<>(); - for (BlockRegisterRequest block : request.getBlocks()) { + for (BlockRegisterRequest_old block : request.getBlocks()) { String donorName = block.getDonorIdentifier().toUpperCase(); if (!donors.containsKey(donorName)) { Donor donor = validation.getDonor(donorName); @@ -85,7 +85,7 @@ public Map createDonors(RegisterRequest request, RegisterValidati public Map createTissues(RegisterRequest request, RegisterValidation validation) { Map donors = createDonors(request, validation); Map tissueMap = new HashMap<>(request.getBlocks().size()); - for (BlockRegisterRequest block : request.getBlocks()) { + for (BlockRegisterRequest_old block : request.getBlocks()) { final String tissueKey = block.getExternalIdentifier().toUpperCase(); Tissue existingTissue = validation.getTissue(tissueKey); if (tissueMap.get(tissueKey)!=null) { @@ -130,7 +130,7 @@ public Map createTissues(RegisterRequest request, RegisterValida */ public void updateExistingTissues(RegisterRequest request, RegisterValidation validation) { List toUpdate = new ArrayList<>(); - for (BlockRegisterRequest brr : request.getBlocks()) { + for (BlockRegisterRequest_old brr : request.getBlocks()) { if (brr.isExistingTissue() && brr.getSampleCollectionDate()!=null) { Tissue tissue = validation.getTissue(brr.getExternalIdentifier()); if (tissue!=null && tissue.getCollectionDate()==null) { @@ -153,7 +153,7 @@ public RegisterResult create(RegisterRequest request, User user, RegisterValidat List ops = new ArrayList<>(request.getBlocks().size()); - for (BlockRegisterRequest block : request.getBlocks()) { + for (BlockRegisterRequest_old block : request.getBlocks()) { Tissue tissue = tissues.get(block.getExternalIdentifier().toUpperCase()); Sample sample = sampleRepo.save(Sample.newBlock(null, tissue, bioState, block.getHighestSection())); LabwareType labwareType = validation.getLabwareType(block.getLabwareType()); diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationImp.java index 906424ab7..cbcf773bc 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationImp.java @@ -2,7 +2,7 @@ import uk.ac.sanger.sccp.stan.model.*; import uk.ac.sanger.sccp.stan.repo.*; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; import uk.ac.sanger.sccp.stan.service.BioRiskService; import uk.ac.sanger.sccp.stan.service.Validator; @@ -98,7 +98,7 @@ public Collection validate() { } public void validateDonors() { - for (BlockRegisterRequest block : blocks()) { + for (BlockRegisterRequest_old block : blocks()) { boolean skip = false; Species species = null; if (block.getDonorIdentifier()==null || block.getDonorIdentifier().isEmpty()) { @@ -159,7 +159,7 @@ public void validateDonors() { public void validateSpatialLocations() { Map tissueTypeMap = new HashMap<>(); Set unknownTissueTypes = new LinkedHashSet<>(); - for (BlockRegisterRequest block : blocks()) { + for (BlockRegisterRequest_old block : blocks()) { if (block.getTissueType()==null || block.getTissueType().isEmpty()) { addProblem("Missing tissue type."); continue; @@ -211,7 +211,7 @@ public void validateHmdmcs() { Set unknownHmdmcs = new LinkedHashSet<>(); boolean unwanted = false; boolean missing = false; - for (BlockRegisterRequest block : blocks()) { + for (BlockRegisterRequest_old block : blocks()) { boolean needsHmdmc = false; boolean needsNoHmdmc = false; if (block.getSpecies()!=null && !block.getSpecies().isEmpty()) { @@ -259,15 +259,15 @@ public void validateHmdmcs() { } public void validateLabwareTypes() { - validateByName("labware type", BlockRegisterRequest::getLabwareType, ltRepo::findByName, labwareTypeMap); + validateByName("labware type", BlockRegisterRequest_old::getLabwareType, ltRepo::findByName, labwareTypeMap); } public void validateMediums() { - validateByName("medium", BlockRegisterRequest::getMedium, mediumRepo::findByName, mediumMap); + validateByName("medium", BlockRegisterRequest_old::getMedium, mediumRepo::findByName, mediumMap); } public void validateFixatives() { - validateByName("fixative", BlockRegisterRequest::getFixative, fixativeRepo::findByName, fixativeMap); + validateByName("fixative", BlockRegisterRequest_old::getFixative, fixativeRepo::findByName, fixativeMap); } public void validateCollectionDates() { @@ -275,7 +275,7 @@ public void validateCollectionDates() { LocalDate today = LocalDate.now(); Set badDates = new LinkedHashSet<>(); Map extToDate = new HashMap<>(request.getBlocks().size()); - for (BlockRegisterRequest block : blocks()) { + for (BlockRegisterRequest_old block : blocks()) { if (block.getSampleCollectionDate()==null) { if (block.getLifeStage()==LifeStage.fetal && block.getSpecies()!=null && Species.isHumanName(block.getSpecies())) { @@ -304,17 +304,17 @@ public void validateCollectionDates() { } public void validateExistingTissues() { - List blocksForExistingTissues = blocks().stream() - .filter(BlockRegisterRequest::isExistingTissue) + List blocksForExistingTissues = blocks().stream() + .filter(BlockRegisterRequest_old::isExistingTissue) .toList(); if (blocksForExistingTissues.isEmpty()) { return; } - if (blocksForExistingTissues.stream().map(BlockRegisterRequest::getExternalIdentifier).anyMatch(xn -> xn==null || xn.isEmpty())) { + if (blocksForExistingTissues.stream().map(BlockRegisterRequest_old::getExternalIdentifier).anyMatch(xn -> xn==null || xn.isEmpty())) { addProblem("Missing external identifier."); } Set xns = blocksForExistingTissues.stream() - .map(BlockRegisterRequest::getExternalIdentifier) + .map(BlockRegisterRequest_old::getExternalIdentifier) .filter(Objects::nonNull) .collect(toLinkedHashSet()); if (xns.isEmpty()) { @@ -329,7 +329,7 @@ public void validateExistingTissues() { addProblem("Existing external identifiers not recognised: " + reprCollection(missing)); } - for (BlockRegisterRequest br : blocksForExistingTissues) { + for (BlockRegisterRequest_old br : blocksForExistingTissues) { String xn = br.getExternalIdentifier(); if (xn == null || xn.isEmpty()) { continue; @@ -344,7 +344,7 @@ public void validateExistingTissues() { public void validateNewTissues() { // NB repeated new external identifier in one request is still disallowed Set externalNames = new HashSet<>(); - for (BlockRegisterRequest block : blocks()) { + for (BlockRegisterRequest_old block : blocks()) { if (block.isExistingTissue()) { continue; } @@ -374,13 +374,13 @@ public void validateNewTissues() { public void validateBioRisks() { this.bioRiskMap = bioRiskService.loadAndValidateBioRisks(problems, blocks().stream(), - BlockRegisterRequest::getBioRiskCode, BlockRegisterRequest::setBioRiskCode); + BlockRegisterRequest_old::getBioRiskCode, BlockRegisterRequest_old::setBioRiskCode); } public void validateCellClasses() { Set cellClassNames = new HashSet<>(); boolean anyMissing = false; - for (BlockRegisterRequest block : blocks()) { + for (BlockRegisterRequest_old block : blocks()) { String cellClassName = block.getCellClass(); if (nullOrEmpty(cellClassName)) { anyMissing = true; @@ -415,12 +415,12 @@ public void validateWorks() { } private void validateByName(String entityName, - Function nameFunction, + Function nameFunction, Function> lkp, Map map) { Set unknownNames = new LinkedHashSet<>(); boolean missing = false; - for (BlockRegisterRequest block : blocks()) { + for (BlockRegisterRequest_old block : blocks()) { String name = nameFunction.apply(block); if (name==null || name.isEmpty()) { missing = true; @@ -448,7 +448,7 @@ private void validateByName(String entityName, } } - private Collection blocks() { + private Collection blocks() { return request.getBlocks(); } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/TissueFieldChecker.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/TissueFieldChecker.java index 5facee393..1f3b8d658 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/TissueFieldChecker.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/TissueFieldChecker.java @@ -2,13 +2,13 @@ import org.springframework.stereotype.Service; import uk.ac.sanger.sccp.stan.model.*; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; import java.util.function.Consumer; import java.util.function.Function; /** - * Utility for checking that the description of tissue in a {@link BlockRegisterRequest} + * Utility for checking that the description of tissue in a {@link BlockRegisterRequest_old} * matches the information in an existing tissue. * @author dr6 */ @@ -19,23 +19,23 @@ private static Function chain(Function ab, Function b==null ? null : bc.apply(b)); } enum Field { - DONOR(Tissue::getDonor, Donor::getDonorName, BlockRegisterRequest::getDonorIdentifier, "donor identifier"), - HMDMC(Tissue::getHmdmc, Hmdmc::getHmdmc, BlockRegisterRequest::getHmdmc, "HuMFre number"), - TTYPE(Tissue::getTissueType, TissueType::getName, BlockRegisterRequest::getTissueType, "tissue type"), - SL(Tissue::getSpatialLocation, SpatialLocation::getCode, BlockRegisterRequest::getSpatialLocation, "spatial location"), - REPLICATE(Tissue::getReplicate, BlockRegisterRequest::getReplicateNumber, "replicate number", false), - MEDIUM(Tissue::getMedium, Medium::getName, BlockRegisterRequest::getMedium, "medium"), - FIXATIVE(Tissue::getFixative, Fixative::getName, BlockRegisterRequest::getFixative, "fixative"), - COLLECTION_DATE(Tissue::getCollectionDate, BlockRegisterRequest::getSampleCollectionDate, "sample collection date", true), - CELL_CLASS(Tissue::getCellClass, CellClass::getName, BlockRegisterRequest::getCellClass, "cellular classification"), + DONOR(Tissue::getDonor, Donor::getDonorName, BlockRegisterRequest_old::getDonorIdentifier, "donor identifier"), + HMDMC(Tissue::getHmdmc, Hmdmc::getHmdmc, BlockRegisterRequest_old::getHmdmc, "HuMFre number"), + TTYPE(Tissue::getTissueType, TissueType::getName, BlockRegisterRequest_old::getTissueType, "tissue type"), + SL(Tissue::getSpatialLocation, SpatialLocation::getCode, BlockRegisterRequest_old::getSpatialLocation, "spatial location"), + REPLICATE(Tissue::getReplicate, BlockRegisterRequest_old::getReplicateNumber, "replicate number", false), + MEDIUM(Tissue::getMedium, Medium::getName, BlockRegisterRequest_old::getMedium, "medium"), + FIXATIVE(Tissue::getFixative, Fixative::getName, BlockRegisterRequest_old::getFixative, "fixative"), + COLLECTION_DATE(Tissue::getCollectionDate, BlockRegisterRequest_old::getSampleCollectionDate, "sample collection date", true), + CELL_CLASS(Tissue::getCellClass, CellClass::getName, BlockRegisterRequest_old::getCellClass, "cellular classification"), ; private final Function tissueFunction; - private final Function brFunction; + private final Function brFunction; private final String description; private final boolean replaceMissing; - Field(Function tissueFunction, Function brFunction, + Field(Function tissueFunction, Function brFunction, String description, boolean replaceMissing) { this.tissueFunction = tissueFunction; this.brFunction = brFunction; @@ -43,7 +43,7 @@ enum Field { this.replaceMissing = replaceMissing; } - Field(Function tissueFunction, Function xFunction, Function brFunction, + Field(Function tissueFunction, Function xFunction, Function brFunction, String description) { this(chain(tissueFunction, xFunction), brFunction, description, false); } @@ -51,12 +51,12 @@ Field(Function tissueFunction, Function xFunction, public Object apply(Tissue tissue) { return tissueFunction.apply(tissue); } - public Object apply(BlockRegisterRequest br) { + public Object apply(BlockRegisterRequest_old br) { return brFunction.apply(br); } } - public void check(Consumer problemConsumer, BlockRegisterRequest br, Tissue tissue) { + public void check(Consumer problemConsumer, BlockRegisterRequest_old br, Tissue tissue) { for (Field field : Field.values()) { Object oldValue = field.apply(tissue); if (field.replaceMissing && oldValue==null) { diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReader.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReader.java index 27cc9f353..9ec953a50 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReader.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReader.java @@ -2,7 +2,7 @@ import org.apache.poi.ss.usermodel.*; import org.springframework.web.multipart.MultipartFile; -import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest; import uk.ac.sanger.sccp.stan.service.ValidationException; import java.io.IOException; @@ -13,7 +13,7 @@ /** * Reads a block registration request from an Excel file. */ -public interface BlockRegisterFileReader extends MultipartFileReader { +public interface BlockRegisterFileReader extends MultipartFileReader { /** The relevant sheet in the excel file to read. */ int SHEET_INDEX = 2; @@ -29,6 +29,7 @@ enum Column implements IColumn { Bio_risk(Pattern.compile("bio\\w*\\s+risk.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), HuMFre(Pattern.compile("humfre\\s*(number)?", Pattern.CASE_INSENSITIVE)), Tissue_type, + Slot_address(Pattern.compile("(sample\\s*)?slot\\s*address.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), External_identifier(Pattern.compile("external\\s*id.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), Spatial_location(Integer.class, Pattern.compile("spatial\\s*location\\s*(number)?", Pattern.CASE_INSENSITIVE)), Replicate_number, @@ -36,6 +37,7 @@ enum Column implements IColumn { Labware_type, Fixative, Embedding_medium(Pattern.compile("(embedding)?\\s*medium", Pattern.CASE_INSENSITIVE)), + External_barcode(Pattern.compile("external\\s*barcode.", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), Comment(Void.class, Pattern.compile("(information|comment).*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), ; @@ -86,7 +88,7 @@ public boolean isRequired() { * @exception ValidationException the request is invalid * */ @Override - default RegisterRequest read(MultipartFile multipartFile) throws IOException, ValidationException { + default BlockRegisterRequest read(MultipartFile multipartFile) throws IOException, ValidationException { try (Workbook wb = WorkbookFactory.create(multipartFile.getInputStream())) { if (SHEET_INDEX < 0 || SHEET_INDEX >= wb.getNumberOfSheets()) { throw new ValidationException(List.of("Workbook does not have a worksheet at index "+SHEET_INDEX)); @@ -101,6 +103,6 @@ default RegisterRequest read(MultipartFile multipartFile) throws IOException, Va * @return a request read from the sheet * @exception ValidationException the request is invalid */ - RegisterRequest read(Sheet sheet) throws ValidationException; + BlockRegisterRequest read(Sheet sheet) throws ValidationException; } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp.java index 098e0147c..aa0a0a90b 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp.java @@ -1,19 +1,13 @@ package uk.ac.sanger.sccp.stan.service.register.filereader; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.ss.usermodel.WorkbookFactory; import org.springframework.stereotype.Service; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterLabware; import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest; -import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; import uk.ac.sanger.sccp.stan.service.ValidationException; import uk.ac.sanger.sccp.stan.service.register.filereader.BlockRegisterFileReader.Column; -import java.io.IOException; -import java.nio.file.*; -import java.time.LocalDate; import java.util.*; -import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static uk.ac.sanger.sccp.utils.BasicUtils.nullOrEmpty; @@ -21,7 +15,7 @@ * @author dr6 */ @Service -public class BlockRegisterFileReaderImp extends BaseRegisterFileReader +public class BlockRegisterFileReaderImp extends BaseRegisterFileReader implements BlockRegisterFileReader { protected BlockRegisterFileReaderImp() { @@ -29,16 +23,15 @@ protected BlockRegisterFileReaderImp() { } @Override - protected RegisterRequest createRequest(Collection problems, List> rows) { - List blockRequests = rows.stream() - .map(row -> createBlockRequest(problems, row)) - .collect(toList()); - Set workNumbers = getUnique(rows.stream().map(row -> workNumberSet((String) row.get(Column.Work_number))), + protected BlockRegisterRequest createRequest(Collection problems, List> rows) { + List brlw = createLabwareRequests(problems, rows); + Set workNumberSet = getUnique(rows.stream().map(row -> workNumberSet((String) row.get(Column.Work_number))), () -> problems.add("All rows must list the same work numbers.")); if (!problems.isEmpty()) { throw new ValidationException("The file contents are invalid.", problems); } - return new RegisterRequest(blockRequests, nullOrEmpty(workNumbers) ? List.of() : new ArrayList<>(workNumbers)); + List workNumbers = (nullOrEmpty(workNumberSet) ? List.of() : new ArrayList<>(workNumberSet)); + return new BlockRegisterRequest(workNumbers, brlw); } /** @@ -63,58 +56,12 @@ public static Set workNumberSet(String string) { } /** - * Creates the part of the request for registering one block from a row of data - * @param problems receptacle for problems found - * @param row the data from one row of the excel file - * @return a block register request based on the given row + * Parses the rows and groups them into labware. + * @param problems receptacle for problems + * @param rows rows from the file + * @return the labware requests */ - public BlockRegisterRequest createBlockRequest(Collection problems, Map row) { - BlockRegisterRequest br = new BlockRegisterRequest(); - br.setDonorIdentifier((String) row.get(Column.Donor_identifier)); - br.setFixative((String) row.get(Column.Fixative)); - br.setHmdmc((String) row.get(Column.HuMFre)); - br.setBioRiskCode((String) row.get(Column.Bio_risk)); - br.setMedium((String) row.get(Column.Embedding_medium)); - br.setExternalIdentifier((String) row.get(Column.External_identifier)); - br.setSpecies((String) row.get(Column.Species)); - br.setCellClass((String) row.get(Column.Cell_class)); - br.setSampleCollectionDate((LocalDate) row.get(Column.Collection_date)); - br.setLifeStage(valueToLifeStage(problems, (String) row.get(Column.Life_stage))); - br.setTissueType((String) row.get(Column.Tissue_type)); - if (row.get(Column.Spatial_location)==null) { - problems.add("Spatial location not specified."); - } else { - br.setSpatialLocation((Integer) row.get(Column.Spatial_location)); - } - br.setReplicateNumber((String) row.get(Column.Replicate_number)); - if (row.get(Column.Last_known_section)==null) { - problems.add("Last known section not specified."); - } else { - br.setHighestSection((Integer) row.get(Column.Last_known_section)); - } - br.setLabwareType((String) row.get(Column.Labware_type)); - return br; - } + public List createLabwareRequests(Collection problems, List> rows) { - /** - * Test function to read an Excel file - */ - public static void main(String[] args) throws IOException { - BlockRegisterFileReader r = new BlockRegisterFileReaderImp(); - final Path path = Paths.get("/Users/dr6/Desktop/regtest.xlsx"); - RegisterRequest request; - try (Workbook wb = WorkbookFactory.create(Files.newInputStream(path))) { - request = r.read(wb.getSheetAt(SHEET_INDEX)); - } catch (ValidationException e) { - System.err.println("\n****\nException: "+e); - System.err.println("Problems:"); - for (var problem : e.getProblems()) { - System.err.println(" "+problem); - } - System.err.println("****\n"); - throw e; - } - System.out.println(request); } - } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp_old.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp_old.java new file mode 100644 index 000000000..3d0296bfb --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp_old.java @@ -0,0 +1,120 @@ +package uk.ac.sanger.sccp.stan.service.register.filereader; + +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.springframework.stereotype.Service; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; +import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; +import uk.ac.sanger.sccp.stan.service.ValidationException; +import uk.ac.sanger.sccp.stan.service.register.filereader.BlockRegisterFileReader.Column; + +import java.io.IOException; +import java.nio.file.*; +import java.time.LocalDate; +import java.util.*; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static uk.ac.sanger.sccp.utils.BasicUtils.nullOrEmpty; + +/** + * @author dr6 + */ +@Service +public class BlockRegisterFileReaderImp_old extends BaseRegisterFileReader + implements BlockRegisterFileReader { + + protected BlockRegisterFileReaderImp_old() { + super(Column.class, 1, 3); + } + + @Override + protected RegisterRequest createRequest(Collection problems, List> rows) { + List blockRequests = rows.stream() + .map(row -> createBlockRequest(problems, row)) + .collect(toList()); + Set workNumbers = getUnique(rows.stream().map(row -> workNumberSet((String) row.get(Column.Work_number))), + () -> problems.add("All rows must list the same work numbers.")); + if (!problems.isEmpty()) { + throw new ValidationException("The file contents are invalid.", problems); + } + return new RegisterRequest(blockRequests, nullOrEmpty(workNumbers) ? List.of() : new ArrayList<>(workNumbers)); + } + + /** + * Gets the set of work numbers specified in a row. + * Null if none are specified. + * @param string the string listing zero, one or more work numbers + * @return a nonempty set of work numbers, or null + */ + public static Set workNumberSet(String string) { + if (string == null) { + return null; + } + string = string.trim().toUpperCase(); + if (string.isEmpty()) { + return null; + } + String[] wns = string.replace(',',' ').split("\\s+"); + Set set = Arrays.stream(wns) + .filter(s -> !s.isEmpty()) + .collect(toSet()); + return (set.isEmpty() ? null : set); + } + + /** + * Creates the part of the request for registering one block from a row of data + * @param problems receptacle for problems found + * @param row the data from one row of the excel file + * @return a block register request based on the given row + */ + public BlockRegisterRequest_old createBlockRequest(Collection problems, Map row) { + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); + br.setDonorIdentifier((String) row.get(Column.Donor_identifier)); + br.setFixative((String) row.get(Column.Fixative)); + br.setHmdmc((String) row.get(Column.HuMFre)); + br.setBioRiskCode((String) row.get(Column.Bio_risk)); + br.setMedium((String) row.get(Column.Embedding_medium)); + br.setExternalIdentifier((String) row.get(Column.External_identifier)); + br.setSpecies((String) row.get(Column.Species)); + br.setCellClass((String) row.get(Column.Cell_class)); + br.setSampleCollectionDate((LocalDate) row.get(Column.Collection_date)); + br.setLifeStage(valueToLifeStage(problems, (String) row.get(Column.Life_stage))); + br.setTissueType((String) row.get(Column.Tissue_type)); + if (row.get(Column.Spatial_location)==null) { + problems.add("Spatial location not specified."); + } else { + br.setSpatialLocation((Integer) row.get(Column.Spatial_location)); + } + br.setReplicateNumber((String) row.get(Column.Replicate_number)); + if (row.get(Column.Last_known_section)==null) { + problems.add("Last known section not specified."); + } else { + br.setHighestSection((Integer) row.get(Column.Last_known_section)); + } + br.setLabwareType((String) row.get(Column.Labware_type)); + return br; + } + + /** + * Test function to read an Excel file + */ + public static void main(String[] args) throws IOException { + BlockRegisterFileReader r = new BlockRegisterFileReaderImp_old(); + final Path path = Paths.get("/Users/dr6/Desktop/regtest.xlsx"); + RegisterRequest request; + try (Workbook wb = WorkbookFactory.create(Files.newInputStream(path))) { + request = r.read(wb.getSheetAt(SHEET_INDEX)); + } catch (ValidationException e) { + System.err.println("\n****\nException: "+e); + System.err.println("Problems:"); + for (var problem : e.getProblems()) { + System.err.println(" "+problem); + } + System.err.println("****\n"); + throw e; + } + System.out.println(request); + } + +} diff --git a/src/main/resources/schema.graphqls b/src/main/resources/schema.graphqls index 41aebf9e2..0f0a92b00 100644 --- a/src/main/resources/schema.graphqls +++ b/src/main/resources/schema.graphqls @@ -321,8 +321,10 @@ type ProbePanel { enabled: Boolean! } -"""A request to register a new block of tissue.""" -input BlockRegisterRequest { +"""A sample inside a block being registered.""" +input BlockRegisterSample { + """The slot addresses containing the sample.""" + addresses: [Address!]! """The string to use as the donor name.""" donorIdentifier: String! """The life stage of the donor.""" @@ -339,12 +341,6 @@ input BlockRegisterRequest { externalIdentifier: String! """The highest section already taken from the tissue block.""" highestSection: Int! - """The name of the type of labware containing the block.""" - labwareType: String! - """The medium used for the tissue.""" - medium: String! - """The fixative used for the tissue.""" - fixative: String! """The species of the donor.""" species: String! """The cellular classification of the tissue.""" @@ -357,10 +353,26 @@ input BlockRegisterRequest { bioRiskCode: String! } -"""A request to register one or more blocks of tissue.""" -input RegisterRequest { - blocks: [BlockRegisterRequest!]! +"""A request to register in a piece of labware containing one or more block-samples.""" +input BlockRegisterLabware { + """The name of the type of labware containing the block.""" + labwareType: String! + """The medium used for the tissue.""" + medium: String! + """The fixative used for the tissue.""" + fixative: String! + """The external barcode of the labware.""" + externalBarcode: String! + """The samples in this block.""" + samples: [BlockRegisterSample!]! +} + +"""A request to register new blocks of tissue.""" +input BlockRegisterRequest { + """The work numbers for the request.""" workNumbers: [String!]! + """The labware to register.""" + labware: [BlockRegisterLabware!]! } """Information about a section of tissue (already taken from some a block tracked elsewhere) to register.""" @@ -2409,7 +2421,7 @@ type Mutation { """Log out; end the current login session.""" logout: String """Register blocks of tissue.""" - register(request: RegisterRequest!): RegisterResult! + register(request: BlockRegisterRequest!): RegisterResult! """Register sections of tissue.""" registerSections(request: SectionRegisterRequest): RegisterResult! """Record planned operations.""" diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java index 7dc05d79e..0afe1c429 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java @@ -144,7 +144,7 @@ public void testIgnoreExtNames() throws Exception { verify(mockRegService).register(eq(user), requestCaptor.capture()); RegisterRequest request = requestCaptor.getValue(); assertThat(request.getBlocks()).hasSize(1); - BlockRegisterRequest br = request.getBlocks().getFirst(); + BlockRegisterRequest_old br = request.getBlocks().getFirst(); assertEquals("EXT18", br.getExternalIdentifier()); assertEquals("Bad reg", getProblem(map)); assertEquals("risk1", br.getBioRiskCode()); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java index 87d369a36..eb2c9672d 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java @@ -158,7 +158,7 @@ static Stream regArgs() { @ValueSource(booleans={false,true}) public void testUpdateWithExisting(boolean any) { String[] extNames = { null, "Alpha1", "Beta", "Alpha2" }; - List blocks = Arrays.stream(extNames) + List blocks = Arrays.stream(extNames) .map(TestFileRegisterService::blockRegWithExternalName) .toList(); RegisterRequest request = new RegisterRequest(blocks); @@ -180,11 +180,11 @@ public void testUpdateToRemove(boolean anyToRemove) { List ignore = anyToRemove ? List.of("ALPHA1", "alpha2") : List.of(); service.updateToRemove(request, ignore); String[] remaining = (anyToRemove ? new String[]{null, "Beta"} : extNames); - assertThat(request.getBlocks().stream().map(BlockRegisterRequest::getExternalIdentifier)).containsExactly(remaining); + assertThat(request.getBlocks().stream().map(BlockRegisterRequest_old::getExternalIdentifier)).containsExactly(remaining); } - private static BlockRegisterRequest blockRegWithExternalName(String xn) { - BlockRegisterRequest br = new BlockRegisterRequest(); + private static BlockRegisterRequest_old blockRegWithExternalName(String xn) { + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); br.setExternalIdentifier(xn); return br; } diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterClashChecker.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterClashChecker.java index afc5bfaf5..ed30506f1 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterClashChecker.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterClashChecker.java @@ -89,7 +89,7 @@ private Operation[] createOps() { public void testFindClashes(RegisterRequest request, List tissues) { Set newXns = request.getBlocks().stream() .filter(b -> !b.isExistingTissue()) - .map(BlockRegisterRequest::getExternalIdentifier) + .map(BlockRegisterRequest_old::getExternalIdentifier) .collect(toSet()); if (newXns.isEmpty()) { assertThat(checker.findClashes(request)).isEmpty(); @@ -127,11 +127,11 @@ static Stream findClashesArgs() { } static RegisterRequest makeRequest(Object... data) { - List brs = new ArrayList<>(data.length/2); + List brs = new ArrayList<>(data.length/2); for (int i = 0; i < data.length; i += 2) { String xn = (String) data[i]; boolean exists = (boolean) data[i+1]; - BlockRegisterRequest br = new BlockRegisterRequest(); + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); br.setExternalIdentifier(xn); br.setExistingTissue(exists); brs.add(br); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java index 6feeee27b..9b9aaee4d 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java @@ -96,7 +96,7 @@ public void testRegisterNoBlocks() { @Test public void testRegisterValidBlocks() { - RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest())); + RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest_old())); when(mockValidation.validate()).thenReturn(Set.of()); final RegisterResult result = new RegisterResult(List.of(EntityFactory.getTube())); doNothing().when(registerService).updateExistingTissues(any(), any()); @@ -114,7 +114,7 @@ public void testRegisterValidBlocks() { @Test public void testRegisterWithClashes() { - RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest())); + RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest_old())); List clashes = List.of(new RegisterClash(EntityFactory.getTissue(), List.of())); when(mockClashChecker.findClashes(any())).thenReturn(clashes); assertEquals(RegisterResult.clashes(clashes), registerService.register(user, request)); @@ -126,7 +126,7 @@ public void testRegisterWithClashes() { @Test public void testRegisterInvalidBlocks() { - RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest())); + RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest_old())); final Set problems = Set.of("Everything is bad.", "I spilled my tea."); when(mockValidation.validate()).thenReturn(problems); @@ -148,11 +148,11 @@ public void testCreateDonors() { Donor donor0 = EntityFactory.getDonor(); Species hamster = new Species(2, "Hamster"); Donor donor1 = new Donor(null, "Jeff", LifeStage.paediatric, hamster); - BlockRegisterRequest block0 = new BlockRegisterRequest(); + BlockRegisterRequest_old block0 = new BlockRegisterRequest_old(); block0.setDonorIdentifier(donor0.getDonorName()); block0.setLifeStage(donor0.getLifeStage()); block0.setSpecies(donor0.getSpecies().getName()); - BlockRegisterRequest block1 = new BlockRegisterRequest(); + BlockRegisterRequest_old block1 = new BlockRegisterRequest_old(); block1.setDonorIdentifier(donor1.getDonorName()); block1.setLifeStage(donor1.getLifeStage()); block1.setSpecies(donor1.getSpecies().getName()); @@ -177,18 +177,18 @@ public void testCreateDonors() { @Test public void testUpdateExistingTissues_none() { Tissue tissue1 = EntityFactory.getTissue(); - BlockRegisterRequest brr1 = new BlockRegisterRequest(); + BlockRegisterRequest_old brr1 = new BlockRegisterRequest_old(); brr1.setExternalIdentifier(tissue1.getExternalName()); brr1.setExistingTissue(true); Tissue tissue2 = EntityFactory.makeTissue(tissue1.getDonor(), tissue1.getSpatialLocation()); tissue2.setCollectionDate(LocalDate.of(2020,1,2)); - BlockRegisterRequest brr2 = new BlockRegisterRequest(); + BlockRegisterRequest_old brr2 = new BlockRegisterRequest_old(); brr2.setExternalIdentifier(tissue2.getExternalName().toLowerCase()); brr2.setExistingTissue(true); brr2.setSampleCollectionDate(tissue2.getCollectionDate()); - BlockRegisterRequest brr3 = new BlockRegisterRequest(); + BlockRegisterRequest_old brr3 = new BlockRegisterRequest_old(); when(mockValidation.getTissue(Matchers.eqCi(tissue1.getExternalName()))).thenReturn(tissue1); when(mockValidation.getTissue(Matchers.eqCi(tissue2.getExternalName()))).thenReturn(tissue2); @@ -204,12 +204,12 @@ public void testUpdateExistingTissues() { Tissue tissue = EntityFactory.makeTissue(donor, sl); when(mockValidation.getTissue(eqCi(tissue.getExternalName()))).thenReturn(tissue); - BlockRegisterRequest brr1 = new BlockRegisterRequest(); + BlockRegisterRequest_old brr1 = new BlockRegisterRequest_old(); brr1.setExistingTissue(true); brr1.setExternalIdentifier(tissue.getExternalName().toLowerCase()); brr1.setSampleCollectionDate(LocalDate.of(2010,2,3)); - registerService.updateExistingTissues(new RegisterRequest(List.of(brr1, new BlockRegisterRequest())), mockValidation); + registerService.updateExistingTissues(new RegisterRequest(List.of(brr1, new BlockRegisterRequest_old())), mockValidation); verify(mockTissueRepo).saveAll(List.of(tissue)); assertEquals(brr1.getSampleCollectionDate(), tissue.getCollectionDate()); } @@ -242,7 +242,7 @@ public void testCreateTissues() { when(mockValidation.getMedium(medium.getName())).thenReturn(medium); when(mockValidation.getFixative(fix.getName())).thenReturn(fix); LocalDate colDate = LocalDate.of(2020,5,4); - List brs = List.of( + List brs = List.of( makeBrr(existingTissue.getExternalName(), donor1.getDonorName(), existingTissue.getHmdmc().getHmdmc(), donor1.getSpecies().getName(), existingTissue.getReplicate(), existingTissue.getSpatialLocation(), @@ -287,11 +287,11 @@ public void testCreateTissues() { } } - private BlockRegisterRequest makeBrr(String externalName, String donorName, - String hmdmc, String species, - String replicate, SpatialLocation sl, - String mediumName, String fixName, LocalDate collectionDate) { - BlockRegisterRequest br = new BlockRegisterRequest(); + private BlockRegisterRequest_old makeBrr(String externalName, String donorName, + String hmdmc, String species, + String replicate, SpatialLocation sl, + String mediumName, String fixName, LocalDate collectionDate) { + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); br.setExternalIdentifier(externalName); br.setDonorIdentifier(donorName); br.setHmdmc(hmdmc); @@ -318,7 +318,7 @@ public void testCreate() { CellClass cellClass = EntityFactory.getCellClass(); Hmdmc[] hmdmcs = {new Hmdmc(20000, "20/000"), new Hmdmc(20001, "20/001")}; - BlockRegisterRequest block0 = new BlockRegisterRequest(); + BlockRegisterRequest_old block0 = new BlockRegisterRequest_old(); block0.setDonorIdentifier(donor1.getDonorName()); block0.setLifeStage(donor1.getLifeStage()); block0.setExternalIdentifier("TISSUE0"); @@ -333,7 +333,7 @@ public void testCreate() { block0.setSpecies(donor1.getSpecies().getName()); block0.setCellClass("Tissue"); - BlockRegisterRequest block1 = new BlockRegisterRequest(); + BlockRegisterRequest_old block1 = new BlockRegisterRequest_old(); block1.setDonorIdentifier(donor2.getDonorName()); block1.setLifeStage(donor2.getLifeStage()); block1.setReplicateNumber("5"); @@ -409,9 +409,9 @@ public void testCreate() { verify(registerService).createDonors(request, mockValidation); - List blocks = request.getBlocks(); + List blocks = request.getBlocks(); for (int i = 0; i < blocks.size(); i++) { - BlockRegisterRequest block = blocks.get(i); + BlockRegisterRequest_old block = blocks.get(i); verify(mockTissueRepo).save( new Tissue(null, block.getExternalIdentifier(), @@ -459,7 +459,7 @@ public void testCreateProblems(Species species, Object hmdmcObj, String expected } BioRisk br = new BioRisk(800, "biorisk"); - BlockRegisterRequest block = new BlockRegisterRequest(); + BlockRegisterRequest_old block = new BlockRegisterRequest_old(); block.setDonorIdentifier(donor.getDonorName()); block.setLifeStage(donor.getLifeStage()); block.setExternalIdentifier("TISSUE"); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidation.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidation.java index 857018a42..8bf877fd0 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidation.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidation.java @@ -10,7 +10,7 @@ import uk.ac.sanger.sccp.stan.Matchers; import uk.ac.sanger.sccp.stan.model.*; import uk.ac.sanger.sccp.stan.repo.*; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; import uk.ac.sanger.sccp.stan.service.BioRiskService; import uk.ac.sanger.sccp.stan.service.Validator; @@ -138,7 +138,7 @@ public void testValidateEmptyRequest() { @Test public void testValidateNonemptyRequestWithoutProblems() { - RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest())); + RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest_old())); RegisterValidationImp validation = create(request); stubValidationMethods(validation); assertThat(validation.validate()).isEmpty(); @@ -147,7 +147,7 @@ public void testValidateNonemptyRequestWithoutProblems() { @Test public void testValidateNonemptyRequestWithProblems() { - RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest())); + RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest_old())); RegisterValidationImp validation = create(request); stubValidationMethods(validation); doAnswer(invocation -> validation.problems.add("Problem alpha.")) @@ -169,7 +169,7 @@ public void testValidateDonors(List donorNames, List lifeStag RegisterRequest request = new RegisterRequest( donorNames.stream() .map(donorName -> { - BlockRegisterRequest br = new BlockRegisterRequest(); + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); br.setDonorIdentifier(donorName); br.setLifeStage(lifeStageIter.next()); br.setSpecies(speciesIter.next()); @@ -207,7 +207,7 @@ public void testDonorNameValidation() { RegisterRequest request = new RegisterRequest( Stream.of("Alpha", "Beta", "Gamma*", "Delta*", "Gamma*") .map(s -> { - BlockRegisterRequest br = new BlockRegisterRequest(); + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); br.setDonorIdentifier(s); br.setLifeStage(LifeStage.adult); br.setSpecies(Species.HUMAN_NAME); @@ -228,7 +228,7 @@ public void testValidateSpatialLocations(List tissueTypeNames, List { - BlockRegisterRequest br = new BlockRegisterRequest(); + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); br.setTissueType(name); br.setSpatialLocation(code); return br; @@ -261,7 +261,7 @@ public void testValidateHmdmcs(List knownHmdmcs, List givenHmdmcs RegisterRequest request = new RegisterRequest( Zip.of(givenHmdmcs.stream(), speciesNames.stream()) .map((hmdmc, species) -> { - BlockRegisterRequest br = new BlockRegisterRequest(); + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); br.setHmdmc(hmdmc); br.setSpecies(species); return br; @@ -291,7 +291,7 @@ public void testValidateLabwareTypes(List knownLts, List gi return knownLts.stream().filter(lt -> arg.equalsIgnoreCase(lt.getName())).findAny(); }); testValidateSimpleField(givenLtNames, expectedLts, expectedProblems, - RegisterValidationImp::validateLabwareTypes, LabwareType::getName, BlockRegisterRequest::setLabwareType, + RegisterValidationImp::validateLabwareTypes, LabwareType::getName, BlockRegisterRequest_old::setLabwareType, v -> v.labwareTypeMap, RegisterValidationImp::getLabwareType); } @@ -304,7 +304,7 @@ public void testValidateMediums(List knownItems, List givenNames return knownItems.stream().filter(item -> arg.equalsIgnoreCase(item.getName())).findAny(); }); testValidateSimpleField(givenNames, expectedItems, expectedProblems, - RegisterValidationImp::validateMediums, Medium::getName, BlockRegisterRequest::setMedium, + RegisterValidationImp::validateMediums, Medium::getName, BlockRegisterRequest_old::setMedium, v -> v.mediumMap, RegisterValidationImp::getMedium); } @@ -317,7 +317,7 @@ public void testValidateFixatives(List knownItems, List givenN return knownItems.stream().filter(item -> arg.equalsIgnoreCase(item.getName())).findAny(); }); testValidateSimpleField(givenNames, expectedItems, expectedProblems, - RegisterValidationImp::validateFixatives, Fixative::getName, BlockRegisterRequest::setFixative, + RegisterValidationImp::validateFixatives, Fixative::getName, BlockRegisterRequest_old::setFixative, v -> v.fixativeMap, RegisterValidationImp::getFixative); } @@ -353,7 +353,7 @@ void testValidateNewTissues(final List testData, List { - BlockRegisterRequest br = new BlockRegisterRequest(); + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); br.setExternalIdentifier(td.externalName); br.setReplicateNumber(td.replicate); br.setDonorIdentifier(td.donorName); @@ -386,9 +386,9 @@ public void testValidateExistingTissue(Object testDataObj, Object existingTissue return existingTissues.stream().filter(t -> xns.stream().anyMatch(xn -> t.getExternalName().equalsIgnoreCase(xn))) .collect(toList()); }); - List brs = new ArrayList<>(testData.size()); + List brs = new ArrayList<>(testData.size()); for (ValidateExistingTissueTestData td : testData) { - BlockRegisterRequest br = new BlockRegisterRequest(); + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); br.setExternalIdentifier(td.externalName); br.setExistingTissue(td.existing); if (td.fieldProblem != null) { @@ -429,7 +429,7 @@ static Stream validateExistingTissuesArgs() { @ParameterizedTest @MethodSource("validateCollectionDatesArgs") - public void testValidateCollectionDates(List brrs, List expectedProblems) { + public void testValidateCollectionDates(List brrs, List expectedProblems) { RegisterRequest request = new RegisterRequest(brrs); RegisterValidationImp validation = create(request); validation.validateCollectionDates(); @@ -470,7 +470,7 @@ static Stream validateCollectionDatesArgs() { "Human fetal samples must have a collection date.", "Invalid sample collection date: ["+future1+"]", "Inconsistent collection dates specified for tissue EXT1."}, - }).map(arr -> Arguments.of(Arrays.stream(arr).filter(obj -> obj instanceof BlockRegisterRequest) + }).map(arr -> Arguments.of(Arrays.stream(arr).filter(obj -> obj instanceof BlockRegisterRequest_old) .collect(toList()), Arrays.stream(arr).filter(obj -> obj instanceof String) .collect(toList()))); @@ -479,7 +479,7 @@ static Stream validateCollectionDatesArgs() { @ParameterizedTest @CsvSource({"false,false", "true,false", "true,true"}) public void testValidateWorks(boolean anyWorks, boolean anyProblem) { - List brrs = List.of( + List brrs = List.of( toBrr(Species.HUMAN_NAME, LifeStage.adult, null) ); List workNumbers; @@ -522,8 +522,8 @@ public void testValidateWorks(boolean anyWorks, boolean anyProblem) { } - static BlockRegisterRequest toBrr(String species, LifeStage lifeStage, LocalDate collectionDate, String ext) { - BlockRegisterRequest brr = new BlockRegisterRequest(); + static BlockRegisterRequest_old toBrr(String species, LifeStage lifeStage, LocalDate collectionDate, String ext) { + BlockRegisterRequest_old brr = new BlockRegisterRequest_old(); brr.setSpecies(species); brr.setLifeStage(lifeStage); brr.setSampleCollectionDate(collectionDate); @@ -531,7 +531,7 @@ static BlockRegisterRequest toBrr(String species, LifeStage lifeStage, LocalDate return brr; } - static BlockRegisterRequest toBrr(String species, LifeStage lifeStage, LocalDate collectionDate) { + static BlockRegisterRequest_old toBrr(String species, LifeStage lifeStage, LocalDate collectionDate) { return toBrr(species, lifeStage, collectionDate, null); } @@ -539,13 +539,13 @@ private void testValidateSimpleField(List givenStrings, List expectedItems, List expectedProblems, Consumer validationFunction, Function stringFn, - BiConsumer blockFunction, + BiConsumer blockFunction, Function> mapFunction, BiFunction getter) { RegisterRequest request = new RegisterRequest( givenStrings.stream() .map(string -> { - BlockRegisterRequest br = new BlockRegisterRequest(); + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); blockFunction.accept(br, string); return br; }) @@ -567,9 +567,9 @@ private void testValidateSimpleField(List givenStrings, @SuppressWarnings("unchecked") @Test void testValidateBioRisks() { - BlockRegisterRequest block1 = new BlockRegisterRequest(); + BlockRegisterRequest_old block1 = new BlockRegisterRequest_old(); block1.setBioRiskCode("risk1"); - BlockRegisterRequest block2 = new BlockRegisterRequest(); + BlockRegisterRequest_old block2 = new BlockRegisterRequest_old(); block2.setBioRiskCode("risk2"); RegisterRequest request = new RegisterRequest(List.of(block1, block2)); RegisterValidationImp val = create(request); @@ -579,16 +579,16 @@ void testValidateBioRisks() { val.validateBioRisks(); assertSame(returnedMap, val.bioRiskMap); - ArgumentCaptor> blockStreamCaptor = ArgumentCaptor.forClass(Stream.class); - ArgumentCaptor> getterCaptor = ArgumentCaptor.forClass(Function.class); - ArgumentCaptor> setterCaptor = ArgumentCaptor.forClass(BiConsumer.class); + ArgumentCaptor> blockStreamCaptor = ArgumentCaptor.forClass(Stream.class); + ArgumentCaptor> getterCaptor = ArgumentCaptor.forClass(Function.class); + ArgumentCaptor> setterCaptor = ArgumentCaptor.forClass(BiConsumer.class); verify(mockBioRiskService).loadAndValidateBioRisks(same(val.problems), blockStreamCaptor.capture(), getterCaptor.capture(), setterCaptor.capture()); // Check that the getter and setter are the functions we expect assertThat(blockStreamCaptor.getValue().map(getterCaptor.getValue())).containsExactly("risk1", "risk2"); - BiConsumer setter = setterCaptor.getValue(); - BlockRegisterRequest blk = new BlockRegisterRequest(); + BiConsumer setter = setterCaptor.getValue(); + BlockRegisterRequest_old blk = new BlockRegisterRequest_old(); setter.accept(blk, "v1"); assertEquals("v1", blk.getBioRiskCode()); } @@ -596,7 +596,7 @@ void testValidateBioRisks() { @Test void testValidateCellClasses() { String[] ccNames = {"cc1", "cc2", null, "cc4"}; - List blocks = IntStream.range(0, ccNames.length).mapToObj(i -> new BlockRegisterRequest()).toList(); + List blocks = IntStream.range(0, ccNames.length).mapToObj(i -> new BlockRegisterRequest_old()).toList(); Zip.of(Arrays.stream(ccNames), blocks.stream()).forEach((name, block) -> block.setCellClass(name)); CellClass[] cellClasses = IntStream.rangeClosed(1, 2).mapToObj(i -> new CellClass(i, "cc"+i, false, true)).toArray(CellClass[]::new); UCMap ccMap = UCMap.from(CellClass::getName, cellClasses); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestTissueFieldChecker.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestTissueFieldChecker.java index aaa15c19d..e6ca2ae51 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestTissueFieldChecker.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestTissueFieldChecker.java @@ -6,7 +6,7 @@ import org.junit.jupiter.params.provider.MethodSource; import uk.ac.sanger.sccp.stan.EntityFactory; import uk.ac.sanger.sccp.stan.model.Tissue; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; import java.time.LocalDate; import java.util.*; @@ -53,7 +53,7 @@ static Stream matchArgs() { @ParameterizedTest @MethodSource("checkArgs") - public void testCheck(BlockRegisterRequest br, Tissue tissue, Object problemObj) { + public void testCheck(BlockRegisterRequest_old br, Tissue tissue, Object problemObj) { Collection expectedProblems; if (problemObj==null) { expectedProblems = List.of(); @@ -101,8 +101,8 @@ static Stream checkArgs() { ); } - private static BlockRegisterRequest toBRR(Tissue tissue, Consumer adjuster) { - BlockRegisterRequest br = new BlockRegisterRequest(); + private static BlockRegisterRequest_old toBRR(Tissue tissue, Consumer adjuster) { + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); br.setExistingTissue(true); br.setExternalIdentifier(tissue.getExternalName()); br.setTissueType(tissue.getTissueType().getName()); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/filereader/TestBlockRegisterFileReader.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/filereader/TestBlockRegisterFileReader.java index 6fa9854d6..887a38395 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/filereader/TestBlockRegisterFileReader.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/filereader/TestBlockRegisterFileReader.java @@ -11,7 +11,7 @@ import uk.ac.sanger.sccp.stan.Matchers; import uk.ac.sanger.sccp.stan.model.LifeStage; import uk.ac.sanger.sccp.stan.model.Species; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; import uk.ac.sanger.sccp.stan.service.register.filereader.BlockRegisterFileReader.Column; import uk.ac.sanger.sccp.utils.Zip; @@ -28,16 +28,16 @@ import static org.mockito.Mockito.*; /** - * Tests {@link BlockRegisterFileReaderImp} + * Tests {@link BlockRegisterFileReaderImp_old} */ class TestBlockRegisterFileReader extends BaseTestFileReader { private static final int DATA_ROW = 3; - private BlockRegisterFileReaderImp reader; + private BlockRegisterFileReaderImp_old reader; @BeforeEach void testSetUp() { - reader = spy(new BlockRegisterFileReaderImp()); + reader = spy(new BlockRegisterFileReaderImp_old()); } // Check that the pattern for each column accepts that column's name @@ -308,7 +308,7 @@ void testCreateRequest() { rowMap("sgp1 sgp3 sgp2", "X2"), rowMap(null, null) ); - List brs = IntStream.rangeClosed(1, rows.size()) + List brs = IntStream.rangeClosed(1, rows.size()) .mapToObj(i -> makeBlockRegisterRequest("X"+i)) .collect(toList()); Zip.of(rows.stream(), brs.stream()).forEach((row, br) -> doReturn(br).when(reader).createBlockRequest(any(), same(row))); @@ -325,7 +325,7 @@ void testCreateRequest_problems() { rowMap("SGP1", "X1"), rowMap("sgp2", "X2") ); - List srls = IntStream.range(1, 3) + List srls = IntStream.range(1, 3) .mapToObj(i -> makeBlockRegisterRequest("X"+i)) .toList(); @@ -345,7 +345,7 @@ void testCreateBlockRegisterRequest_basic() { row.put(Column.Replicate_number, "12A"); row.put(Column.Spatial_location, 14); row.put(Column.Last_known_section, 18); - BlockRegisterRequest src = reader.createBlockRequest(problems, row); + BlockRegisterRequest_old src = reader.createBlockRequest(problems, row); assertEquals("Donor1", src.getDonorIdentifier()); assertEquals("12A", src.getReplicateNumber()); assertEquals(14, src.getSpatialLocation()); @@ -372,7 +372,7 @@ void testCreateBlockRegisterRequest_full() { row.put(Column.Collection_date, date); row.put(Column.Last_known_section, 17); row.put(Column.Labware_type, "Eggcup"); - BlockRegisterRequest br = reader.createBlockRequest(problems, row); + BlockRegisterRequest_old br = reader.createBlockRequest(problems, row); assertEquals("Donor1", br.getDonorIdentifier()); assertEquals("X11", br.getExternalIdentifier()); assertEquals("12/234", br.getHmdmc()); @@ -395,7 +395,7 @@ void testCreateRequestContent_problems() { Map row = new EnumMap<>(Column.class); row.put(Column.Donor_identifier, "Donor1"); row.put(Column.Life_stage, "ascended"); - BlockRegisterRequest src = reader.createBlockRequest(problems, row); + BlockRegisterRequest_old src = reader.createBlockRequest(problems, row); assertThat(problems).containsExactlyInAnyOrder( "Unknown life stage: \"ascended\"", "Last known section not specified.", @@ -418,8 +418,8 @@ static Map rowMap(Object workNumber, Object externalName) { return map; } - static BlockRegisterRequest makeBlockRegisterRequest(String externalId) { - BlockRegisterRequest br = new BlockRegisterRequest(); + static BlockRegisterRequest_old makeBlockRegisterRequest(String externalId) { + BlockRegisterRequest_old br = new BlockRegisterRequest_old(); br.setExternalIdentifier(externalId); return br; } From 2464f3b51ce467133e69fea3d7d836ec31c6f943 Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:45:35 +0000 Subject: [PATCH 05/13] x1403 in progress --- .../request/register/BlockRegisterSample.java | 17 +- .../filereader/BlockRegisterFileReader.java | 6 +- .../BlockRegisterFileReaderImp.java | 153 ++++++++- .../BlockRegisterFileReaderImp_old.java | 120 ------- .../TestBlockRegisterFileReader.java | 295 +++++++++++------- 5 files changed, 348 insertions(+), 243 deletions(-) delete mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp_old.java diff --git a/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterSample.java b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterSample.java index 8a69e3cf5..7525bfe1e 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterSample.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterSample.java @@ -1,17 +1,21 @@ package uk.ac.sanger.sccp.stan.request.register; +import uk.ac.sanger.sccp.stan.model.Address; import uk.ac.sanger.sccp.stan.model.LifeStage; import java.time.LocalDate; +import java.util.List; import java.util.Objects; import static uk.ac.sanger.sccp.utils.BasicUtils.describe; +import static uk.ac.sanger.sccp.utils.BasicUtils.nullToEmpty; /** * A sample inside a block being registered. * @author dr6 */ public class BlockRegisterSample { + private List
addresses = List.of(); private String donorIdentifier; private LifeStage lifeStage; private String hmdmc; @@ -26,6 +30,15 @@ public class BlockRegisterSample { private LocalDate sampleCollectionDate; private String bioRiskCode; + /** The addresses of slots containing the sample */ + public List
getAddresses() { + return this.addresses; + } + + public void setAddresses(List
addresses) { + this.addresses = nullToEmpty(addresses); + } + /** The string to use as the donor name. */ public String getDonorIdentifier() { return this.donorIdentifier; @@ -146,6 +159,7 @@ public void setBioRiskCode(String bioRiskCode) { @Override public String toString() { return describe(this) + .add("addresses", addresses) .add("donorIdentifier", donorIdentifier) .add("lifeStage", lifeStage) .add("hmdmc", hmdmc) @@ -169,6 +183,7 @@ public boolean equals(Object o) { if (o == null || o.getClass() != this.getClass()) return false; BlockRegisterSample that = (BlockRegisterSample) o; return (this.existingTissue == that.existingTissue + && Objects.equals(this.addresses, that.addresses) && Objects.equals(this.donorIdentifier, that.donorIdentifier) && Objects.equals(this.lifeStage, that.lifeStage) && Objects.equals(this.hmdmc, that.hmdmc) @@ -186,7 +201,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(donorIdentifier, lifeStage, hmdmc, tissueType, spatialLocation, replicateNumber, + return Objects.hash(addresses, donorIdentifier, lifeStage, hmdmc, tissueType, spatialLocation, replicateNumber, externalIdentifier, highestSection, species, cellClass, existingTissue, sampleCollectionDate, bioRiskCode); } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReader.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReader.java index 9ec953a50..7d2ff86ae 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReader.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReader.java @@ -28,16 +28,16 @@ enum Column implements IColumn { Cell_class(Pattern.compile("cell(ular)?\\s*class(ification)?", Pattern.CASE_INSENSITIVE)), Bio_risk(Pattern.compile("bio\\w*\\s+risk.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), HuMFre(Pattern.compile("humfre\\s*(number)?", Pattern.CASE_INSENSITIVE)), - Tissue_type, Slot_address(Pattern.compile("(sample\\s*)?slot\\s*address.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), - External_identifier(Pattern.compile("external\\s*id.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), + Tissue_type, + External_identifier(Pattern.compile("(sample\\s*)?external\\s*id.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), Spatial_location(Integer.class, Pattern.compile("spatial\\s*location\\s*(number)?", Pattern.CASE_INSENSITIVE)), Replicate_number, Last_known_section(Integer.class, Pattern.compile("last.*section.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), Labware_type, Fixative, Embedding_medium(Pattern.compile("(embedding)?\\s*medium", Pattern.CASE_INSENSITIVE)), - External_barcode(Pattern.compile("external\\s*barcode.", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), + External_barcode(Pattern.compile("external\\s*barcode.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), Comment(Void.class, Pattern.compile("(information|comment).*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), ; diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp.java index aa0a0a90b..b1bf1354a 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp.java @@ -1,15 +1,23 @@ package uk.ac.sanger.sccp.stan.service.register.filereader; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; import org.springframework.stereotype.Service; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterLabware; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest; +import uk.ac.sanger.sccp.stan.model.Address; +import uk.ac.sanger.sccp.stan.request.register.*; import uk.ac.sanger.sccp.stan.service.ValidationException; import uk.ac.sanger.sccp.stan.service.register.filereader.BlockRegisterFileReader.Column; +import java.io.IOException; +import java.nio.file.*; +import java.time.LocalDate; import java.util.*; +import java.util.function.Supplier; +import java.util.stream.Stream; import static java.util.stream.Collectors.toSet; import static uk.ac.sanger.sccp.utils.BasicUtils.nullOrEmpty; +import static uk.ac.sanger.sccp.utils.BasicUtils.repr; /** * @author dr6 @@ -41,18 +49,16 @@ protected BlockRegisterRequest createRequest(Collection problems, List workNumberSet(String string) { - if (string == null) { - return null; + if (string != null) { + string = string.trim().toUpperCase(); } - string = string.trim().toUpperCase(); - if (string.isEmpty()) { + if (nullOrEmpty(string)) { return null; } String[] wns = string.replace(',',' ').split("\\s+"); - Set set = Arrays.stream(wns) + return Arrays.stream(wns) .filter(s -> !s.isEmpty()) .collect(toSet()); - return (set.isEmpty() ? null : set); } /** @@ -62,6 +68,137 @@ public static Set workNumberSet(String string) { * @return the labware requests */ public List createLabwareRequests(Collection problems, List> rows) { + Map>> groups = new LinkedHashMap<>(); + boolean anyMissing = false; + for (var row : rows) { + String xb = (String) row.get(Column.External_barcode); + if (nullOrEmpty(xb)) { + anyMissing = true; + continue; + } + groups.computeIfAbsent(xb.toUpperCase(), k -> new ArrayList<>()).add(row); + } + if (anyMissing) { + problems.add("Cannot process blocks without an external barcode."); + } else if (groups.isEmpty()) { + problems.add("No blocks requested."); + return List.of(); + } + return groups.values().stream().map(group -> toLabwareRequest(problems, group)).toList(); + } + + /** Stream the values in a particular column from a group of rows. */ + static Stream streamValues(Collection> rows, Column column) { + //noinspection unchecked + return rows.stream().map(row -> (T) row.get(column)); + } + + /** Gets the unique value from a column in a group of rows. */ + String uniqueRowValue(final Collection problems, + Collection> rows, Column column, + Supplier tooManyErrorSupplier) { + return getUniqueString(streamValues(rows, column), () -> problems.add(tooManyErrorSupplier.get())); + } + + /** + * Converts a list of rows into a labware request. + * @param problems receptacle for problems + * @param rows rows related to the same labware + * @return the labware request + */ + public BlockRegisterLabware toLabwareRequest(Collection problems, List> rows) { + String externalBarcode = ((String) rows.getFirst().get(Column.External_barcode)).toUpperCase(); + String labwareType = uniqueRowValue(problems, rows, Column.Labware_type, + () -> "Multiple labware types specified for external barcode "+repr(externalBarcode)+"."); + String medium = uniqueRowValue(problems, rows, Column.Embedding_medium, + () -> "Multiple media specified for external barcode "+repr(externalBarcode)+"."); + String fixative = uniqueRowValue(problems, rows, Column.Fixative, + () -> "Multiple fixatives specified for external barcode "+repr(externalBarcode)+"."); + List samples = rows.stream().map(row -> toSample(problems, row)).toList(); + BlockRegisterLabware brl = new BlockRegisterLabware(); + brl.setExternalBarcode(externalBarcode); + brl.setLabwareType(labwareType); + brl.setMedium(medium); + brl.setFixative(fixative); + brl.setSamples(samples); + return brl; + } + + /** + * Reads the info about a single row into a sample request + * @param problems receptacle for problems + * @param row data from one row + * @return the sample information from the row + */ + public BlockRegisterSample toSample(Collection problems, Map row) { + BlockRegisterSample sample = new BlockRegisterSample(); + sample.setBioRiskCode((String) row.get(Column.Bio_risk)); + sample.setCellClass((String) row.get(Column.Cell_class)); + sample.setDonorIdentifier((String) row.get(Column.Donor_identifier)); + sample.setHmdmc((String) row.get(Column.HuMFre)); + sample.setLifeStage(valueToLifeStage(problems, (String) row.get(Column.Life_stage))); + sample.setReplicateNumber((String) row.get(Column.Replicate_number)); + sample.setSpecies((String) row.get(Column.Species)); + sample.setTissueType((String) row.get(Column.Tissue_type)); + if (row.get(Column.Spatial_location)==null) { + problems.add("Spatial location not specified."); + } else { + sample.setSpatialLocation((Integer) row.get(Column.Spatial_location)); + } + if (row.get(Column.Last_known_section)==null) { + problems.add("Last known section not specified."); + } else { + sample.setHighestSection((Integer) row.get(Column.Last_known_section)); + } + sample.setExternalIdentifier((String) row.get(Column.External_identifier)); + sample.setSampleCollectionDate((LocalDate) row.get(Column.Collection_date)); + sample.setAddresses(parseAddresses(problems, (String) row.get(Column.Slot_address))); + return sample; + } + /** Parses a string into a list of slot addresses. */ + static List
parseAddresses(Collection problems, String string) { + if (string != null) { + string = string.trim(); + } + if (nullOrEmpty(string)) { + return List.of(); + } + String stringUpper = string.toUpperCase(); + String[] parts = stringUpper.replace(',',' ').split("\\s+"); + try { + return Arrays.stream(parts).map(Address::valueOf).toList(); + } catch (IllegalArgumentException e) { + // OK, try again with commas included + } + parts = stringUpper.split("\\s+"); + try { + return Arrays.stream(parts).map(Address::valueOf).toList(); + } catch (IllegalArgumentException e) { + problems.add("Couldn't parse slot addresses: "+repr(string)); + } + return List.of(); + } + + + /** + * Test function to read an Excel file + */ + public static void main(String[] args) throws IOException { + BlockRegisterFileReader r = new BlockRegisterFileReaderImp(); + final Path path = Paths.get("/Users/dr6/Desktop/blockreg.xlsx"); + BlockRegisterRequest request; + try (Workbook wb = WorkbookFactory.create(Files.newInputStream(path))) { + request = r.read(wb.getSheetAt(SHEET_INDEX)); + } catch (ValidationException e) { + System.err.println("\n****\nException: "+e); + System.err.println("Problems:"); + for (var problem : e.getProblems()) { + System.err.println(" "+problem); + } + System.err.println("****\n"); + throw e; + } + System.out.println(request); } } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp_old.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp_old.java deleted file mode 100644 index 3d0296bfb..000000000 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/filereader/BlockRegisterFileReaderImp_old.java +++ /dev/null @@ -1,120 +0,0 @@ -package uk.ac.sanger.sccp.stan.service.register.filereader; - -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.ss.usermodel.WorkbookFactory; -import org.springframework.stereotype.Service; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; -import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; -import uk.ac.sanger.sccp.stan.service.ValidationException; -import uk.ac.sanger.sccp.stan.service.register.filereader.BlockRegisterFileReader.Column; - -import java.io.IOException; -import java.nio.file.*; -import java.time.LocalDate; -import java.util.*; - -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static uk.ac.sanger.sccp.utils.BasicUtils.nullOrEmpty; - -/** - * @author dr6 - */ -@Service -public class BlockRegisterFileReaderImp_old extends BaseRegisterFileReader - implements BlockRegisterFileReader { - - protected BlockRegisterFileReaderImp_old() { - super(Column.class, 1, 3); - } - - @Override - protected RegisterRequest createRequest(Collection problems, List> rows) { - List blockRequests = rows.stream() - .map(row -> createBlockRequest(problems, row)) - .collect(toList()); - Set workNumbers = getUnique(rows.stream().map(row -> workNumberSet((String) row.get(Column.Work_number))), - () -> problems.add("All rows must list the same work numbers.")); - if (!problems.isEmpty()) { - throw new ValidationException("The file contents are invalid.", problems); - } - return new RegisterRequest(blockRequests, nullOrEmpty(workNumbers) ? List.of() : new ArrayList<>(workNumbers)); - } - - /** - * Gets the set of work numbers specified in a row. - * Null if none are specified. - * @param string the string listing zero, one or more work numbers - * @return a nonempty set of work numbers, or null - */ - public static Set workNumberSet(String string) { - if (string == null) { - return null; - } - string = string.trim().toUpperCase(); - if (string.isEmpty()) { - return null; - } - String[] wns = string.replace(',',' ').split("\\s+"); - Set set = Arrays.stream(wns) - .filter(s -> !s.isEmpty()) - .collect(toSet()); - return (set.isEmpty() ? null : set); - } - - /** - * Creates the part of the request for registering one block from a row of data - * @param problems receptacle for problems found - * @param row the data from one row of the excel file - * @return a block register request based on the given row - */ - public BlockRegisterRequest_old createBlockRequest(Collection problems, Map row) { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setDonorIdentifier((String) row.get(Column.Donor_identifier)); - br.setFixative((String) row.get(Column.Fixative)); - br.setHmdmc((String) row.get(Column.HuMFre)); - br.setBioRiskCode((String) row.get(Column.Bio_risk)); - br.setMedium((String) row.get(Column.Embedding_medium)); - br.setExternalIdentifier((String) row.get(Column.External_identifier)); - br.setSpecies((String) row.get(Column.Species)); - br.setCellClass((String) row.get(Column.Cell_class)); - br.setSampleCollectionDate((LocalDate) row.get(Column.Collection_date)); - br.setLifeStage(valueToLifeStage(problems, (String) row.get(Column.Life_stage))); - br.setTissueType((String) row.get(Column.Tissue_type)); - if (row.get(Column.Spatial_location)==null) { - problems.add("Spatial location not specified."); - } else { - br.setSpatialLocation((Integer) row.get(Column.Spatial_location)); - } - br.setReplicateNumber((String) row.get(Column.Replicate_number)); - if (row.get(Column.Last_known_section)==null) { - problems.add("Last known section not specified."); - } else { - br.setHighestSection((Integer) row.get(Column.Last_known_section)); - } - br.setLabwareType((String) row.get(Column.Labware_type)); - return br; - } - - /** - * Test function to read an Excel file - */ - public static void main(String[] args) throws IOException { - BlockRegisterFileReader r = new BlockRegisterFileReaderImp_old(); - final Path path = Paths.get("/Users/dr6/Desktop/regtest.xlsx"); - RegisterRequest request; - try (Workbook wb = WorkbookFactory.create(Files.newInputStream(path))) { - request = r.read(wb.getSheetAt(SHEET_INDEX)); - } catch (ValidationException e) { - System.err.println("\n****\nException: "+e); - System.err.println("Problems:"); - for (var problem : e.getProblems()) { - System.err.println(" "+problem); - } - System.err.println("****\n"); - throw e; - } - System.out.println(request); - } - -} diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/filereader/TestBlockRegisterFileReader.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/filereader/TestBlockRegisterFileReader.java index 887a38395..3ca8a0500 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/filereader/TestBlockRegisterFileReader.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/filereader/TestBlockRegisterFileReader.java @@ -5,20 +5,18 @@ import org.apache.poi.ss.util.CellAddress; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.*; import uk.ac.sanger.sccp.stan.Matchers; +import uk.ac.sanger.sccp.stan.model.Address; import uk.ac.sanger.sccp.stan.model.LifeStage; -import uk.ac.sanger.sccp.stan.model.Species; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; -import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; +import uk.ac.sanger.sccp.stan.request.register.*; import uk.ac.sanger.sccp.stan.service.register.filereader.BlockRegisterFileReader.Column; -import uk.ac.sanger.sccp.utils.Zip; import java.io.IOException; import java.time.LocalDate; import java.util.*; +import java.util.function.Supplier; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -26,18 +24,19 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import static uk.ac.sanger.sccp.stan.Matchers.*; /** - * Tests {@link BlockRegisterFileReaderImp_old} + * Tests {@link BlockRegisterFileReaderImp} */ class TestBlockRegisterFileReader extends BaseTestFileReader { private static final int DATA_ROW = 3; - private BlockRegisterFileReaderImp_old reader; + private BlockRegisterFileReaderImp reader; @BeforeEach void testSetUp() { - reader = spy(new BlockRegisterFileReaderImp_old()); + reader = spy(new BlockRegisterFileReaderImp()); } // Check that the pattern for each column accepts that column's name @@ -58,7 +57,7 @@ void testRead_headingProblems() { mockSheet(); mockHeadingRow(); doAnswer(Matchers.addProblem(problem, map)).when(reader).indexColumns(any(), any()); - assertValidationError(() -> reader.read(sheet), problem); + assertValidationException(() -> reader.read(sheet), List.of(problem)); verify(reader).indexColumns(any(), same(headingRow)); verify(reader, never()).readRow(any(), any(), any()); verify(reader, never()).createRequest(any(), any()); @@ -71,7 +70,7 @@ void testRead_noData() { mockRows(0,0); Map map = columnMapOf(Column.Donor_identifier, 3); doReturn(map).when(reader).indexColumns(any(), any()); - assertValidationError(() -> reader.read(sheet), "No registrations requested."); + assertValidationException(() -> reader.read(sheet), List.of("No registrations requested.")); verify(reader, never()).readRow(any(), any(), any()); verify(reader, never()).createRequest(any(), any()); } @@ -93,15 +92,15 @@ void testRead(boolean error) { Matchers.mayAddProblem(rowProblem, rowMaps.get(i)).when(reader).readRow(any(), any(), same(rows.get(DATA_ROW+i))); } - RegisterRequest request; + BlockRegisterRequest request; if (error) { request = null; } else { - request = new RegisterRequest(); + request = new BlockRegisterRequest(); doReturn(request).when(reader).createRequest(any(), any()); } if (error) { - assertValidationError(() -> reader.read(sheet), problem); + assertValidationException(() -> reader.read(sheet), List.of(problem)); } else { assertSame(request, reader.read(sheet)); } @@ -121,8 +120,10 @@ void testRead(boolean error) { void testIndexColumns() { Row row = mockRow("All information is needed", "SGP number", "donor identifier", "life stage", "if then date of collection of stuff", - "species", "cellular classification", "biological risk assessment number", "humfre", "tissue type", "external id", "spatial location", "replicate number", - "last known banana section custard", "labware type", "fixative", "medium", "information", "comment"); + "species", "cellular classification", "biological risk assessment number", "humfre", "slot address of sample", + "tissue type", "external id", "spatial location", "replicate number", + "last known banana section custard", "labware type", "fixative", "medium", "external barcode", + "information", "comment"); List problems = new ArrayList<>(); var result = reader.indexColumns(problems, row); assertThat(problems).isEmpty(); @@ -146,7 +147,7 @@ void testIndexColumnsProblems() { assertThat(problems).containsExactlyInAnyOrder( "Repeated column: Work number", "Unexpected column heading: \"bananas\"", - "Missing columns: [Tissue type, External identifier]"); + "Missing columns: [Slot address, Tissue type, External identifier, External barcode]"); } @Test @@ -301,108 +302,161 @@ static Stream cellValueMocks() { }).map(Arguments::of); } - @Test - void testCreateRequest() { - List> rows = List.of( - rowMap("SGP1, SGP2 sgp3,sgp2", "X1"), - rowMap("sgp1 sgp3 sgp2", "X2"), - rowMap(null, null) - ); - List brs = IntStream.rangeClosed(1, rows.size()) - .mapToObj(i -> makeBlockRegisterRequest("X"+i)) - .collect(toList()); - Zip.of(rows.stream(), brs.stream()).forEach((row, br) -> doReturn(br).when(reader).createBlockRequest(any(), same(row))); - - final List problems = new ArrayList<>(); - RegisterRequest request = reader.createRequest(problems, rows); - assertThat(request.getWorkNumbers()).containsExactlyInAnyOrder("SGP1", "SGP2", "SGP3"); - assertEquals(brs, request.getBlocks()); + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testCreateRequest(boolean ok) { + List> rows; + if (ok) { + rows = List.of(rowMap("SGP1, SGP2", "EXT1"), rowMap("sgp2, sgp1", "EXT2")); + } else { + rows = List.of(rowMap("SGP1", "EXT1"), rowMap("SGP2", "EXT2")); + } + List brlw = List.of(new BlockRegisterLabware()); + String problem = ok ? null : "Bad request."; + mayAddProblem(problem, brlw).when(reader).createLabwareRequests(any(), any()); + if (ok) { + List problems = new ArrayList<>(); + BlockRegisterRequest request = reader.createRequest(problems, rows); + assertSame(brlw, request.getLabware()); + assertThat(request.getWorkNumbers()).containsExactlyInAnyOrder("SGP1", "SGP2"); + } else { + List problems = new ArrayList<>(2); + List expectedProblems = List.of(problem, "All rows must list the same work numbers."); + assertValidationException(() -> reader.createRequest(problems, rows), expectedProblems); + } + verify(reader).createLabwareRequests(any(), same(rows)); } - @Test - void testCreateRequest_problems() { - List> rows = List.of( - rowMap("SGP1", "X1"), - rowMap("sgp2", "X2") - ); - List srls = IntStream.range(1, 3) - .mapToObj(i -> makeBlockRegisterRequest("X"+i)) - .toList(); - - doReturn(srls.get(0)).when(reader).createBlockRequest(any(), same(rows.get(0))); - Matchers.mayAddProblem("Bad stuff.", srls.get(1)).when(reader).createBlockRequest(any(), same(rows.get(1))); - - assertValidationError(() -> reader.createRequest(new ArrayList<>(), rows), - "All rows must list the same work numbers.", - "Bad stuff."); + @ParameterizedTest + @CsvSource(value = { + ";", + "sgp1;SGP1", + "sgp1, SGP2;SGP1,SGP2" + }, delimiter=';') + void testWorkNumberSet(String input, String expected) { + Set workNumbers = BlockRegisterFileReaderImp.workNumberSet(input); + if (expected==null) { + assertThat(workNumbers).isNullOrEmpty(); + } else { + assertThat(workNumbers).containsExactlyInAnyOrder(expected.split(",")); + } } - @Test - void testCreateBlockRegisterRequest_basic() { - List problems = new ArrayList<>(0); - Map row = new EnumMap<>(Column.class); - row.put(Column.Donor_identifier, "Donor1"); - row.put(Column.Replicate_number, "12A"); - row.put(Column.Spatial_location, 14); - row.put(Column.Last_known_section, 18); - BlockRegisterRequest_old src = reader.createBlockRequest(problems, row); - assertEquals("Donor1", src.getDonorIdentifier()); - assertEquals("12A", src.getReplicateNumber()); - assertEquals(14, src.getSpatialLocation()); - assertNull(src.getLifeStage()); - assertThat(problems).isEmpty(); + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testCreateLabwareRequests(boolean xbMissing) { + List> rows = List.of(rowWithExternalBarcode("EXT1", "A"), + rowWithExternalBarcode(xbMissing ? null : "ext1", "B"), + rowWithExternalBarcode("EXT2", "C")); + + List brls = List.of(new BlockRegisterLabware(), new BlockRegisterLabware()); + brls.getFirst().setExternalBarcode("EXT1"); + brls.getLast().setExternalBarcode("EXT2"); + doReturnFrom(brls.iterator()).when(reader).toLabwareRequest(any(), any()); + List problems = new ArrayList<>(xbMissing ? 1 : 0); + assertThat(reader.createLabwareRequests(problems, rows)).containsExactlyElementsOf(brls); + assertProblem(problems, xbMissing ? "Cannot process blocks without an external barcode." : null); + + verify(reader, times(2)).toLabwareRequest(any(), any()); + verify(reader).toLabwareRequest(same(problems), eq(rows.subList(0, xbMissing ? 1 : 2))); + verify(reader).toLabwareRequest(same(problems), eq(rows.subList(2, 3))); } + @ParameterizedTest + @CsvSource({ + ",false,", + "EXT1 ext1,false,EXT1", + "ext1 ext2,true,ext1", + }) + void testUniqueRowValue(String values, boolean error, String expectedValue) { + String[] splitValues = (values==null ? new String[0] : values.split("\\s+")); + final Column column = Column.External_barcode; + List> rows = Arrays.stream(splitValues).map(v -> { + Map map = new EnumMap<>(Column.class); + map.put(column, v); + return map; + }).toList(); + List problems = new ArrayList<>(error ? 1 : 0); + Supplier errorSupplier = () -> "Bad thing."; + assertEquals(expectedValue, reader.uniqueRowValue(problems, rows, column, errorSupplier)); + assertProblem(problems, error ? "Bad thing." : null); + } @Test - void testCreateBlockRegisterRequest_full() { + void testToLabwareRequest() { + List> rows = List.of(rowWithExternalBarcode("BC", "A"), rowWithExternalBarcode("BC", "B")); + rows.forEach(row -> { + row.put(Column.Labware_type, "lt"); + row.put(Column.Fixative, "fix"); + row.put(Column.Embedding_medium, "med"); + }); + BlockRegisterSample brs1 = new BlockRegisterSample(); + BlockRegisterSample brs2 = new BlockRegisterSample(); + brs1.setExternalIdentifier("xn1"); + brs2.setExternalIdentifier("xn2"); + doReturn(brs1, brs2).when(reader).toSample(any(), any()); List problems = new ArrayList<>(0); - Map row = new EnumMap<>(Column.class); - final LocalDate date = LocalDate.of(2022, 5, 5); - row.put(Column.Donor_identifier, "Donor1"); - row.put(Column.External_identifier, "X11"); - row.put(Column.HuMFre, "12/234"); - row.put(Column.Life_stage, "ADULT"); - row.put(Column.Replicate_number, "14"); - row.put(Column.Species, Species.HUMAN_NAME); - row.put(Column.Tissue_type, "Arm"); - row.put(Column.Spatial_location, 2); - row.put(Column.Embedding_medium, "brass"); - row.put(Column.Fixative, "Floop"); - row.put(Column.Collection_date, date); - row.put(Column.Last_known_section, 17); - row.put(Column.Labware_type, "Eggcup"); - BlockRegisterRequest_old br = reader.createBlockRequest(problems, row); - assertEquals("Donor1", br.getDonorIdentifier()); - assertEquals("X11", br.getExternalIdentifier()); - assertEquals("12/234", br.getHmdmc()); - assertEquals(LifeStage.adult, br.getLifeStage()); - assertEquals("14", br.getReplicateNumber()); - assertEquals(Species.HUMAN_NAME, br.getSpecies()); - assertEquals("Arm", br.getTissueType()); - assertEquals(2, br.getSpatialLocation()); - assertEquals("brass", br.getMedium()); - assertEquals("Floop", br.getFixative()); - assertEquals(date, br.getSampleCollectionDate()); - assertEquals(17, br.getHighestSection()); - assertEquals("Eggcup", br.getLabwareType()); + BlockRegisterLabware brl = reader.toLabwareRequest(problems, rows); + verify(reader, times(3)).uniqueRowValue(any(), any(), any(), any()); + rows.forEach(row -> verify(reader).toSample(same(problems), same(row))); assertThat(problems).isEmpty(); + assertEquals("lt", brl.getLabwareType()); + assertEquals("fix", brl.getFixative()); + assertEquals("med", brl.getMedium()); + assertEquals("BC", brl.getExternalBarcode()); + assertThat(brl.getSamples()).containsExactly(brs1, brs2); } - @Test - void testCreateRequestContent_problems() { - List problems = new ArrayList<>(2); + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testToSample(boolean complete) { Map row = new EnumMap<>(Column.class); - row.put(Column.Donor_identifier, "Donor1"); - row.put(Column.Life_stage, "ascended"); - BlockRegisterRequest_old src = reader.createBlockRequest(problems, row); - assertThat(problems).containsExactlyInAnyOrder( - "Unknown life stage: \"ascended\"", - "Last known section not specified.", - "Spatial location not specified." - ); - assertEquals("Donor1", src.getDonorIdentifier()); - assertNull(src.getLifeStage()); + row.put(Column.Bio_risk, "risk1"); + row.put(Column.Cell_class, "cclass1"); + row.put(Column.Donor_identifier, "donor1"); + row.put(Column.HuMFre, "hum1"); + row.put(Column.Life_stage, "fetal"); + row.put(Column.Replicate_number, "17"); + row.put(Column.Species, "mermaid"); + row.put(Column.Tissue_type, "leg"); + LocalDate date; + if (complete) { + row.put(Column.Spatial_location, 3); + row.put(Column.Last_known_section, 5); + date = LocalDate.of(2023, 1, 2); + row.put(Column.Collection_date, date); + row.put(Column.Slot_address, "A1, A2"); + } else { + date = null; + } + row.put(Column.External_identifier, "ext1"); + List problems = new ArrayList<>(complete ? 0 : 2); + BlockRegisterSample brs = reader.toSample(problems, row); + if (complete) { + assertThat(problems).isEmpty(); + } else { + assertThat(problems).containsExactlyInAnyOrder("Spatial location not specified.", "Last known section not specified."); + } + assertEquals("risk1", brs.getBioRiskCode()); + assertEquals("cclass1", brs.getCellClass()); + assertEquals("donor1", brs.getDonorIdentifier()); + assertEquals("hum1", brs.getHmdmc()); + assertEquals(LifeStage.fetal, brs.getLifeStage()); + assertEquals("17", brs.getReplicateNumber()); + assertEquals("mermaid", brs.getSpecies()); + assertEquals("leg", brs.getTissueType()); + if (complete) { + assertEquals(3, brs.getSpatialLocation()); + assertEquals(5, brs.getHighestSection()); + assertEquals(date, brs.getSampleCollectionDate()); + assertThat(brs.getAddresses()).containsExactlyInAnyOrder(new Address(1,1), new Address(1,2)); + } else { + assertNull(brs.getSpatialLocation()); + assertNull(brs.getHighestSection()); + assertNull(brs.getSampleCollectionDate()); + assertThat(brs.getAddresses()).isEmpty(); + } + assertEquals("ext1", brs.getExternalIdentifier()); } static Map columnMapOf(Column k1, V v1) { @@ -418,13 +472,32 @@ static Map rowMap(Object workNumber, Object externalName) { return map; } - static BlockRegisterRequest_old makeBlockRegisterRequest(String externalId) { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setExternalIdentifier(externalId); - return br; + static Map rowWithExternalBarcode(String externalBarcode, String externalName) { + Map map = new EnumMap<>(Column.class); + map.put(Column.External_identifier, externalName); + map.put(Column.External_barcode, externalBarcode); + return map; + } + + @ParameterizedTest + @MethodSource("parseAddressesArgs") + void testParseAddresses(String input, List
expectedAddresses, String expectedError) { + List problems = new ArrayList<>(expectedError==null ? 0 : 1); + assertEquals(expectedAddresses, BlockRegisterFileReaderImp.parseAddresses(problems, input)); + assertProblem(problems, expectedError); } - static void assertValidationError(Executable exec, String... expectedProblems) { - Matchers.assertValidationException(exec, "The file contents are invalid.", expectedProblems); + static Stream parseAddressesArgs() { + Address A1 = new Address(1,1); + Address A2 = new Address(1,2); + Address AA50 = new Address(27,50); + return Arrays.stream(new Object[][] { + {null, List.of(), null}, + {"A1", List.of(A1), null}, + {"a1, A2", List.of(A1, A2), null}, + {"A1 A2", List.of(A1, A2), null}, + {"A2 27,50 a1", List.of(A2, AA50, A1), null}, + {"A,X,W?", List.of(), "Couldn't parse slot addresses: \"A,X,W?\""} + }).map(Arguments::of); } } \ No newline at end of file From 5e1a6c4a10a830af7b73da1294a2a90953e23f80 Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Fri, 13 Feb 2026 09:47:59 +0000 Subject: [PATCH 06/13] x1403 in progress --- .../ac/sanger/sccp/stan/GraphQLMutation.java | 14 +- .../ac/sanger/sccp/stan/GraphQLProvider.java | 2 +- .../sccp/stan/config/FieldValidation.java | 6 +- .../ac/sanger/sccp/stan/repo/LabwareRepo.java | 3 + .../service/register/BlockFieldChecker.java | 82 +++ .../register/BlockRegisterServiceImp.java | 219 +++++++ .../register/BlockRegisterValidationImp.java | 584 ++++++++++++++++++ .../register/FileRegisterServiceImp.java | 58 +- .../register/RegisterClashChecker.java | 16 + .../register/RegisterValidationFactory.java | 14 +- src/main/resources/schema.graphqls | 2 +- .../register/TestFileRegisterService.java | 65 +- .../service/register/TestRegisterService.java | 4 +- .../TestRegisterValidationFactory.java | 39 +- 14 files changed, 1023 insertions(+), 85 deletions(-) create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockFieldChecker.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterServiceImp.java create mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterValidationImp.java diff --git a/src/main/java/uk/ac/sanger/sccp/stan/GraphQLMutation.java b/src/main/java/uk/ac/sanger/sccp/stan/GraphQLMutation.java index b8855462f..4b985decb 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/GraphQLMutation.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/GraphQLMutation.java @@ -44,7 +44,7 @@ public class GraphQLMutation extends BaseGraphQLResource { Logger log = LoggerFactory.getLogger(GraphQLMutation.class); final AuthService authService; - final IRegisterService registerService; + final IRegisterService blockRegisterService; final IRegisterService sectionRegisterService; final PlanService planService; final LabelPrintService labelPrintService; @@ -115,7 +115,7 @@ public class GraphQLMutation extends BaseGraphQLResource { @Autowired public GraphQLMutation(ObjectMapper objectMapper, AuthenticationComponent authComp, AuthService authService, - IRegisterService registerService, + IRegisterService blockRegisterService, IRegisterService sectionRegisterService, PlanService planService, LabelPrintService labelPrintService, ConfirmOperationService confirmOperationService, @@ -150,7 +150,7 @@ public GraphQLMutation(ObjectMapper objectMapper, AuthenticationComponent authCo ProteinPanelAdminService proteinPanelAdminService) { super(objectMapper, authComp, userRepo); this.authService = authService; - this.registerService = registerService; + this.blockRegisterService = blockRegisterService; this.sectionRegisterService = sectionRegisterService; this.planService = planService; this.labelPrintService = labelPrintService; @@ -245,12 +245,12 @@ public DataFetcher userSelfRegister(final User.Role role) { }; } - public DataFetcher register() { + public DataFetcher blockRegister() { return dfe -> { User user = checkUser(dfe, User.Role.normal); - RegisterRequest request = arg(dfe, "request", RegisterRequest.class); - logRequest("Register", user, request); - return registerService.register(user, request); + BlockRegisterRequest request = arg(dfe, "request", BlockRegisterRequest.class); + logRequest("Block register", user, request); + return blockRegisterService.register(user, request); }; } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/GraphQLProvider.java b/src/main/java/uk/ac/sanger/sccp/stan/GraphQLProvider.java index 61ad5d8b8..6b7206940 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/GraphQLProvider.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/GraphQLProvider.java @@ -153,7 +153,7 @@ private RuntimeWiring buildWiring() { .dataFetcher("registerAsEndUser", graphQLMutation.userSelfRegister(User.Role.enduser)) // internal transaction .dataFetcher("login", graphQLMutation.logIn()) .dataFetcher("logout", graphQLMutation.logOut()) - .dataFetcher("register", transact(graphQLMutation.register())) + .dataFetcher("registerBlocks", transact(graphQLMutation.blockRegister())) .dataFetcher("plan", transact(graphQLMutation.recordPlan())) .dataFetcher("printLabware", graphQLMutation.printLabware()) // not transacted .dataFetcher("confirmOperation", transact(graphQLMutation.confirmOperation())) diff --git a/src/main/java/uk/ac/sanger/sccp/stan/config/FieldValidation.java b/src/main/java/uk/ac/sanger/sccp/stan/config/FieldValidation.java index c1a8b769b..04b269275 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/config/FieldValidation.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/config/FieldValidation.java @@ -65,7 +65,7 @@ public Validator externalNameValidator() { Set charTypes = EnumSet.of( CharacterType.ALPHA, CharacterType.DIGIT, CharacterType.HYPHEN, CharacterType.UNDERSCORE, CharacterType.FULL_STOP - ) ; + ); return new StringValidator("External identifier", 3, 64, charTypes); } @@ -73,8 +73,8 @@ public Validator externalNameValidator() { public Validator externalBarcodeValidator() { Set charTypes = EnumSet.of( CharacterType.ALPHA, CharacterType.DIGIT, CharacterType.HYPHEN, - CharacterType.UNDERSCORE - ) ; + CharacterType.UNDERSCORE, CharacterType.FULL_STOP + ); return new StringValidator("External barcode", 3, 32, charTypes); } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/repo/LabwareRepo.java b/src/main/java/uk/ac/sanger/sccp/stan/repo/LabwareRepo.java index d30cbf259..667a3d736 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/repo/LabwareRepo.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/repo/LabwareRepo.java @@ -24,6 +24,9 @@ default Labware getByBarcode(final String barcode) throws EntityNotFoundExceptio @Query("select barcode from Labware where barcode in (?1)") Set findBarcodesByBarcodeIn(Collection barcodes); + @Query("select externalBarcode from Labware where externalBarcode in (?1)") + Set findExternalBarcodesIn(Collection barcodes); + default Labware getById(final Integer id) throws EntityNotFoundException { return findById(id).orElseThrow(() -> new EntityNotFoundException("No labware found with id "+id)); } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockFieldChecker.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockFieldChecker.java new file mode 100644 index 000000000..e39d422d4 --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockFieldChecker.java @@ -0,0 +1,82 @@ +package uk.ac.sanger.sccp.stan.service.register; + +import org.springframework.stereotype.Service; +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.request.register.*; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Utility for checking that the description of tissue in a {@link BlockRegisterRequest} + * matches the information in an existing tissue. + * @author dr6 + */ +@Service +public class BlockFieldChecker { + /** Chains two functions, but skips the second if the first returns null */ + private static Function chain(Function ab, Function bc) { + return ab.andThen(b -> b==null ? null : bc.apply(b)); + } + enum Field { + DONOR(null, BlockRegisterSample::getDonorIdentifier, Tissue::getDonor, Donor::getDonorName, "donor identifier"), + HMDMC(null, BlockRegisterSample::getHmdmc, Tissue::getHmdmc, Hmdmc::getHmdmc, "HuMFre number"), + TTYPE(null, BlockRegisterSample::getTissueType, Tissue::getTissueType, TissueType::getName, "tissue type"), + SL(null, BlockRegisterSample::getSpatialLocation, Tissue::getSpatialLocation, SpatialLocation::getCode, "spatial location"), + REPLICATE(null, BlockRegisterSample::getReplicateNumber, Tissue::getReplicate, null, "replicate number"), + MEDIUM(BlockRegisterLabware::getMedium, null, Tissue::getMedium, Medium::getName, "medium"), + FIXATIVE(BlockRegisterLabware::getFixative, null, Tissue::getFixative, Fixative::getName, "fixative"), + COLLECTION_DATE(null, BlockRegisterSample::getSampleCollectionDate, Tissue::getCollectionDate, null, "sample collection date", true), + CELL_CLASS(null, BlockRegisterSample::getCellClass, Tissue::getCellClass, CellClass::getName, "cellular classification"), + ; + + private final Function tissueFunction; + private final Function brsFunction; + private final Function brlFunction; + private final String description; + private final boolean replaceMissing; + + Field(Function brlFunction, Function brsFunction, Function tissueFunction, Function subFunction, String description, boolean replaceMissing) { + this.brlFunction = brlFunction; + this.brsFunction = brsFunction; + this.tissueFunction = subFunction==null ? tissueFunction : chain(tissueFunction, subFunction); + this.description = description; + this.replaceMissing = replaceMissing; + } + + Field(Function brlFunction, Function brsFunction, Function tissueFunction, Function subFunction, String description) { + this(brlFunction, brsFunction, tissueFunction, subFunction, description, false); + } + + public Object apply(BlockRegisterLabware brl, BlockRegisterSample brs) { + return brlFunction!=null ? brlFunction.apply(brl) : brsFunction.apply(brs); + } + public Object apply(Tissue tissue) { + return tissueFunction.apply(tissue); + } + } + + public void check(Consumer problemConsumer, BlockRegisterLabware brl, BlockRegisterSample brs, Tissue tissue) { + for (Field field : Field.values()) { + Object oldValue = field.apply(tissue); + if (field.replaceMissing && oldValue==null) { + continue; + } + Object newValue = field.apply(brl, brs); + if (!match(oldValue, newValue)) { + problemConsumer.accept(String.format("Expected %s to be %s for existing tissue %s.", + field.description, oldValue, tissue.getExternalName())); + } + } + } + + public static boolean match(Object oldValue, Object newValue) { + if (oldValue==null) { + return (newValue==null || newValue.equals("")); + } + if (oldValue instanceof String && newValue instanceof String) { + return ((String) oldValue).equalsIgnoreCase((String) newValue); + } + return oldValue.equals(newValue); + } +} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterServiceImp.java new file mode 100644 index 000000000..140259b65 --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterServiceImp.java @@ -0,0 +1,219 @@ +package uk.ac.sanger.sccp.stan.service.register; + +import org.springframework.stereotype.Service; +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.repo.*; +import uk.ac.sanger.sccp.stan.request.register.*; +import uk.ac.sanger.sccp.stan.service.*; +import uk.ac.sanger.sccp.stan.service.work.WorkService; +import uk.ac.sanger.sccp.utils.UCMap; + +import javax.persistence.EntityManager; +import java.util.*; +import java.util.stream.Stream; + +import static uk.ac.sanger.sccp.utils.BasicUtils.iter; +import static uk.ac.sanger.sccp.utils.BasicUtils.nullOrEmpty; + +/** + * Register service for {@link BlockRegisterRequest} + * @author dr6 + */ +@Service +public class BlockRegisterServiceImp implements IRegisterService { + private final EntityManager entityManager; + private final RegisterValidationFactory validationFactory; + private final DonorRepo donorRepo; + private final TissueRepo tissueRepo; + private final SampleRepo sampleRepo; + private final SlotRepo slotRepo; + private final BioRiskRepo bioRiskRepo; + private final OperationTypeRepo opTypeRepo; + private final LabwareService labwareService; + private final OperationService operationService; + private final WorkService workService; + private final RegisterClashChecker clashChecker; + + public BlockRegisterServiceImp(EntityManager entityManager, + RegisterValidationFactory validationFactory, + DonorRepo donorRepo, TissueRepo tissueRepo, SampleRepo sampleRepo, + SlotRepo slotRepo, BioRiskRepo bioRiskRepo, OperationTypeRepo opTypeRepo, + LabwareService labwareService, OperationService operationService, + WorkService workService, + RegisterClashChecker clashChecker) { + this.entityManager = entityManager; + this.validationFactory = validationFactory; + this.donorRepo = donorRepo; + this.tissueRepo = tissueRepo; + this.sampleRepo = sampleRepo; + this.slotRepo = slotRepo; + this.bioRiskRepo = bioRiskRepo; + this.opTypeRepo = opTypeRepo; + this.labwareService = labwareService; + this.operationService = operationService; + this.workService = workService; + this.clashChecker = clashChecker; + } + + @Override + public RegisterResult register(User user, BlockRegisterRequest request) throws ValidationException { + if (request.getLabware().isEmpty()) { + return new RegisterResult(); // nothing to do + } + List clashes = clashChecker.findClashes(request); + if (!clashes.isEmpty()) { + return RegisterResult.clashes(clashes); + } + RegisterValidation validation = validationFactory.createBlockRegisterValidation(request); + Collection problems = validation.validate(); + if (!problems.isEmpty()) { + throw new ValidationException("The register request could not be validated.", problems); + } + updateExistingTissues(request, validation); + return create(request, user, validation); + } + + /** + * Updates any existing tissues that now have a collection date + * @param request specification + * @param validation validation result to look up tissues + */ + public void updateExistingTissues(BlockRegisterRequest request, RegisterValidation validation) { + List toUpdate = new ArrayList<>(); + for (BlockRegisterSample brs : iter(requestSamples(request))) { + if (brs.isExistingTissue() && brs.getSampleCollectionDate()!=null) { + Tissue tissue = validation.getTissue(brs.getExternalIdentifier()); + if (tissue!=null && tissue.getCollectionDate()==null) { + tissue.setCollectionDate(brs.getSampleCollectionDate()); + toUpdate.add(tissue); + } + } + } + if (!toUpdate.isEmpty()) { + tissueRepo.saveAll(toUpdate); + } + } + + /** + * Creates donors that are already in the database + * @return map of donor name to donor + */ + public UCMap createDonors(BlockRegisterRequest request, RegisterValidation validation) { + UCMap donors = new UCMap<>(); + for (BlockRegisterSample brs : iter(requestSamples(request))) { + String donorName = brs.getDonorIdentifier(); + if (!donors.containsKey(donorName)) { + Donor donor = validation.getDonor(donorName); + if (donor.getId() == null) { + donor = donorRepo.save(donor); + } + donors.put(donorName, donor); + } + } + return donors; + } + + /** + * Creates tissues that aren't already in the database + * @return map of external identifier to tissue + */ + public UCMap createTissues(BlockRegisterRequest request, RegisterValidation validation) { + UCMap donors = createDonors(request, validation); + UCMap tissueMap = new UCMap<>(); + for (BlockRegisterLabware brl : request.getLabware()) { + for (BlockRegisterSample brs : brl.getSamples()) { + final String tissueKey = brs.getExternalIdentifier(); + if (tissueMap.get(tissueKey) != null) { + continue; + } + Tissue existingTissue = validation.getTissue(tissueKey); + if (existingTissue != null) { + tissueMap.put(tissueKey, existingTissue); + continue; + } + Donor donor = donors.get(brs.getDonorIdentifier().toUpperCase()); + Hmdmc hmdmc; + if (nullOrEmpty(brs.getHmdmc())) { + hmdmc = null; + } else { + hmdmc = validation.getHmdmc(brs.getHmdmc()); + if (hmdmc == null) { + throw new IllegalArgumentException("Unknown HuMFre number: " + brs.getHmdmc()); + } + } + CellClass cellClass = validation.getCellClass(brs.getCellClass()); + if (hmdmc == null && donor.getSpecies().requiresHmdmc() && cellClass.isHmdmcRequired()) { + throw new IllegalArgumentException("No HuMFre number given for tissue " + brs.getExternalIdentifier()); + } + if (!donor.getSpecies().requiresHmdmc() && hmdmc != null) { + throw new IllegalArgumentException("HuMFre number given for non-human tissue " + brs.getExternalIdentifier()); + } + Tissue tissue = new Tissue(null, brs.getExternalIdentifier(), brs.getReplicateNumber().toLowerCase(), + validation.getSpatialLocation(brs.getTissueType(), brs.getSpatialLocation()), + donor, + validation.getMedium(brl.getMedium()), + validation.getFixative(brl.getFixative()), cellClass, + hmdmc, brs.getSampleCollectionDate(), null); + tissueMap.put(tissueKey, tissueRepo.save(tissue)); + } + } + return tissueMap; + } + + /** + * Creates the labware and operations for the given registration request + * @param request the registration request + * @param user the user responsible for the operations + * @param validation the data from validation + * @return result containing the new labware + */ + public RegisterResult create(BlockRegisterRequest request, User user, RegisterValidation validation) { + UCMap tissues = createTissues(request, validation); + List lwList = new ArrayList<>(); + List opList = new ArrayList<>(); + OperationType opType = opTypeRepo.getByName("Register"); + BioState bioState = opType.getNewBioState(); + for (BlockRegisterLabware brl : request.getLabware()) { + LabwareType labwareType = validation.getLabwareType(brl.getLabwareType()); + Labware lw = labwareService.create(labwareType); + lwList.add(lw); + Set slotsToUpdate = new HashSet<>(); + List actions = new ArrayList<>(); + List sampleBioRisks = new ArrayList<>(); + for (BlockRegisterSample brs : brl.getSamples()) { + Tissue tissue = tissues.get(brs.getExternalIdentifier()); + Sample sample = sampleRepo.save(Sample.newBlock(null, tissue, bioState, brs.getHighestSection())); + Set
addressSet = new HashSet<>(brs.getAddresses()); + List slots = lw.getSlots().stream() + .filter(slot -> addressSet.contains(slot.getAddress())) + .toList(); + for (Slot slot : slots) { + slot.addSample(sample); + slotsToUpdate.add(slot); + actions.add(new Action(null, null, slot, slot, sample, sample)); + } + BioRisk bioRisk = validation.getBioRisk(brs.getBioRiskCode()); + sampleBioRisks.add(new SampleBioRisk(sample, bioRisk)); + } + entityManager.refresh(lw); + slotRepo.saveAll(slotsToUpdate); + Operation op = operationService.createOperation(opType, user, actions, null); + opList.add(op); + for (SampleBioRisk sampleBioRisk : sampleBioRisks) { + bioRiskRepo.recordBioRisk(sampleBioRisk.sample, sampleBioRisk.bioRisk, op.getId()); + } + } + if (!opList.isEmpty() && !nullOrEmpty(validation.getWorks())) { + workService.link(validation.getWorks(), opList); + } + + return new RegisterResult(lwList); + } + + /** Stream the samples in the request */ + Stream requestSamples(BlockRegisterRequest request) { + return request.getLabware().stream().flatMap(brl -> brl.getSamples().stream()); + } + + record SampleBioRisk(Sample sample, BioRisk bioRisk) {} +} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterValidationImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterValidationImp.java new file mode 100644 index 000000000..4146bb85d --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterValidationImp.java @@ -0,0 +1,584 @@ +package uk.ac.sanger.sccp.stan.service.register; + +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.repo.*; +import uk.ac.sanger.sccp.stan.request.register.*; +import uk.ac.sanger.sccp.stan.service.BioRiskService; +import uk.ac.sanger.sccp.stan.service.Validator; +import uk.ac.sanger.sccp.stan.service.work.WorkService; +import uk.ac.sanger.sccp.utils.BasicUtils; +import uk.ac.sanger.sccp.utils.UCMap; + +import java.time.LocalDate; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Stream; + +import static uk.ac.sanger.sccp.utils.BasicUtils.*; + +/** + * @author dr6 + */ +public class BlockRegisterValidationImp implements RegisterValidation { + private final BlockRegisterRequest request; + private final DonorRepo donorRepo; + private final HmdmcRepo hmdmcRepo; + private final TissueTypeRepo ttRepo; + private final LabwareTypeRepo ltRepo; + private final MediumRepo mediumRepo; + private final FixativeRepo fixativeRepo; + private final TissueRepo tissueRepo; + private final SpeciesRepo speciesRepo; + private final CellClassRepo cellClassRepo; + private final LabwareRepo lwRepo; + private final Validator donorNameValidation; + private final Validator externalNameValidation; + private final Validator replicateValidator; + private final Validator externalBarcodeValidator; + private final BlockFieldChecker blockFieldChecker; + private final BioRiskService bioRiskService; + private final WorkService workService; + + final UCMap donorMap = new UCMap<>(); + final UCMap tissueMap = new UCMap<>(); + final UCMap hmdmcMap = new UCMap<>(); + final UCMap speciesMap = new UCMap<>(); + final Map spatialLocationMap = new HashMap<>(); + final UCMap labwareTypeMap = new UCMap<>(); + final UCMap mediumMap = new UCMap<>(); + final UCMap fixativeMap = new UCMap<>(); + UCMap cellClassMap; + UCMap bioRiskMap; + Collection works; + final LinkedHashSet problems = new LinkedHashSet<>(); + + public BlockRegisterValidationImp(BlockRegisterRequest request, DonorRepo donorRepo, + HmdmcRepo hmdmcRepo, TissueTypeRepo ttRepo, LabwareTypeRepo ltRepo, + MediumRepo mediumRepo, FixativeRepo fixativeRepo, TissueRepo tissueRepo, + SpeciesRepo speciesRepo, CellClassRepo cellClassRepo, LabwareRepo lwRepo, + Validator donorNameValidation, Validator externalNameValidation, + Validator replicateValidator, Validator externalBarcodeValidator, + BlockFieldChecker blockFieldChecker, + BioRiskService bioRiskService, WorkService workService) { + this.request = request; + this.donorRepo = donorRepo; + this.hmdmcRepo = hmdmcRepo; + this.ttRepo = ttRepo; + this.ltRepo = ltRepo; + this.mediumRepo = mediumRepo; + this.fixativeRepo = fixativeRepo; + this.tissueRepo = tissueRepo; + this.speciesRepo = speciesRepo; + this.cellClassRepo = cellClassRepo; + this.lwRepo = lwRepo; + this.donorNameValidation = donorNameValidation; + this.externalNameValidation = externalNameValidation; + this.replicateValidator = replicateValidator; + this.externalBarcodeValidator = externalBarcodeValidator; + this.blockFieldChecker = blockFieldChecker; + this.bioRiskService = bioRiskService; + this.workService = workService; + } + + @Override + public Collection validate() { + if (request.getLabware().stream().allMatch(blw -> blw.getSamples().isEmpty())) { + problems.add("No labware specified in request."); + return problems; + } + validateDonors(); + validateHmdmcs(); + validateSpatialLocations(); + validateLabwareTypes(); + validateExternalBarcodes(); + validateAddresses(); + validateMediums(); + validateFixatives(); + validateCollectionDates(); + validateExistingTissues(); + validateNewTissues(); + validateBioRisks(); + validateWorks(); + validateCellClasses(); + return problems; + } + + @Override + public Donor getDonor(String name) { + return donorMap.get(name); + } + + @Override + public Hmdmc getHmdmc(String hmdmc) { + return hmdmcMap.get(hmdmc); + } + + @Override + public SpatialLocation getSpatialLocation(String tissueTypeName, int code) { + if (tissueTypeName==null) { + return null; + } + return spatialLocationMap.get(new StringIntKey(tissueTypeName, code)); + } + + @Override + public LabwareType getLabwareType(String name) { + return labwareTypeMap.get(name); + } + + @Override + public Medium getMedium(String name) { + return mediumMap.get(name); + } + + @Override + public Fixative getFixative(String name) { + return fixativeMap.get(name); + } + + @Override + public Tissue getTissue(String externalName) { + return tissueMap.get(externalName); + } + + @Override + public BioRisk getBioRisk(String code) { + return bioRiskMap.get(code); + } + + @Override + public CellClass getCellClass(String name) { + return cellClassMap.get(name); + } + + @Override + public Collection getWorks() { + return works; + } + + public void validateDonors() { + for (BlockRegisterSample brs : iter(blockSamples())) { + boolean skip = false; + Species species = null; + if (nullOrEmpty(brs.getDonorIdentifier())) { + skip = true; + addProblem("Missing donor identifier."); + } else if (donorNameValidation!=null) { + donorNameValidation.validate(brs.getDonorIdentifier(), this::addProblem); + } + if (nullOrEmpty(brs.getSpecies())) { + addProblem("Missing species."); + } else { + species = speciesMap.get(brs.getSpecies()); + if (species==null && !speciesMap.containsKey(brs.getSpecies())) { + species = speciesRepo.findByName(brs.getSpecies()).orElse(null); + speciesMap.put(brs.getSpecies(), species); + if (species==null) { + addProblem("Unknown species: "+repr(brs.getSpecies())); + } else if (!species.isEnabled()) { + addProblem("Species is not enabled: "+species.getName()); + } + } + } + if (skip) { + continue; + } + Donor donor = donorMap.get(brs.getDonorIdentifier()); + if (donor==null) { + donor = new Donor(null, brs.getDonorIdentifier(), brs.getLifeStage(), species); + donorMap.put(brs.getDonorIdentifier(), donor); + } else { + if (donor.getLifeStage()!= brs.getLifeStage()) { + addProblem("Multiple different life stages specified for donor "+donor.getDonorName()); + } + if (species!=null && !species.equals(donor.getSpecies())) { + addProblem("Multiple different species specified for donor "+donor.getDonorName()); + } + } + } + for (Map.Entry entry : donorMap.entrySet()) { + Optional optDonor = donorRepo.findByDonorName(entry.getKey()); + if (optDonor.isEmpty()) { + continue; + } + Donor realDonor = optDonor.get(); + Donor newDonor = entry.getValue(); + if (realDonor.getLifeStage()!=newDonor.getLifeStage()) { + addProblem("Wrong life stage given for existing donor "+realDonor.getDonorName()); + } + if (newDonor.getSpecies()!=null && !newDonor.getSpecies().equals(realDonor.getSpecies())) { + addProblem("Wrong species given for existing donor "+realDonor.getDonorName()); + } + entry.setValue(realDonor); + } + } + + public void validateHmdmcs() { + Set unknownHmdmcs = new LinkedHashSet<>(); + boolean unwanted = false; + boolean missing = false; + for (BlockRegisterSample brs : iter(blockSamples())) { + boolean needsHmdmc = false; + boolean needsNoHmdmc = false; + if (brs.getSpecies()!=null && !brs.getSpecies().isEmpty()) { + needsHmdmc = Species.isHumanName(brs.getSpecies()); + needsNoHmdmc = !needsHmdmc; + } + String hmdmcString = brs.getHmdmc(); + if (hmdmcString==null || hmdmcString.isEmpty()) { + if (needsHmdmc) { + missing = true; + } + continue; + } + if (needsNoHmdmc) { + unwanted = true; + continue; + } + + if (hmdmcMap.containsKey(hmdmcString)) { + continue; + } + Hmdmc hmdmc = hmdmcRepo.findByHmdmc(hmdmcString).orElse(null); + hmdmcMap.put(hmdmcString, hmdmc); + if (hmdmc==null) { + unknownHmdmcs.add(hmdmcString); + } + } + if (missing) { + addProblem("Missing HuMFre number."); + } + if (unwanted) { + addProblem("Non-human tissue should not have a HuMFre number."); + } + if (!unknownHmdmcs.isEmpty()) { + addProblem(pluralise("Unknown HuMFre number{s}: ", unknownHmdmcs.size()) + unknownHmdmcs); + } + List disabledHmdmcs = hmdmcMap.values().stream() + .filter(h -> h!=null && !h.isEnabled()) + .map(Hmdmc::getHmdmc) + .toList(); + if (!disabledHmdmcs.isEmpty()) { + addProblem(pluralise("HuMFre number{s} not enabled: ", disabledHmdmcs.size()) + disabledHmdmcs); + } + } + + public void validateSpatialLocations() { + UCMap tissueTypeMap = new UCMap<>(); + Set unknownTissueTypes = new LinkedHashSet<>(); + for (BlockRegisterSample block : iter(blockSamples())) { + if (nullOrEmpty(block.getTissueType())) { + addProblem("Missing tissue type."); + continue; + } + if (unknownTissueTypes.contains(block.getTissueType())) { + continue; + } + StringIntKey key = new StringIntKey(block.getTissueType(), block.getSpatialLocation()); + if (spatialLocationMap.containsKey(key)) { + continue; + } + TissueType tt = tissueTypeMap.get(key.string); + if (tt==null) { + Optional ttOpt = ttRepo.findByName(key.string); + if (ttOpt.isEmpty()) { + unknownTissueTypes.add(block.getTissueType()); + continue; + } + tt = ttOpt.get(); + tissueTypeMap.put(key.string, tt); + if (!tt.isEnabled()) { + addProblem(String.format("Tissue type \"%s\" is disabled.", tt.getName())); + } + } + final int slCode = block.getSpatialLocation(); + Optional slOpt = tt.getSpatialLocations().stream() + .filter(spl -> spl.getCode()==slCode) + .findAny(); + if (slOpt.isEmpty()) { + addProblem(String.format("Unknown spatial location %s for tissue type %s.", slCode, tt.getName())); + continue; + } + SpatialLocation sl = slOpt.get(); + if (tt.isEnabled() && !sl.isEnabled()) { + addProblem(String.format("Spatial location is disabled: %s for tissue type %s.", sl.getCode(), tt.getName())); + } + spatialLocationMap.put(key, sl); + } + if (!unknownTissueTypes.isEmpty()) { + if (unknownTissueTypes.size()==1) { + addProblem("Unknown tissue type: "+unknownTissueTypes); + } else { + addProblem("Unknown tissue types: " + unknownTissueTypes); + } + } + } + + public void validateLabwareTypes() { + validateByName("labware type", BlockRegisterLabware::getLabwareType, ltRepo::findByName, labwareTypeMap); + } + + public void validateMediums() { + validateByName("medium", BlockRegisterLabware::getMedium, mediumRepo::findByName, mediumMap); + } + + public void validateFixatives() { + validateByName("fixative", BlockRegisterLabware::getFixative, fixativeRepo::findByName, fixativeMap); + } + + public void validateCollectionDates() { + boolean missing = false; + LocalDate today = LocalDate.now(); + Set badDates = new LinkedHashSet<>(); + UCMap extToDate = new UCMap<>(); + for (BlockRegisterSample brs : iter(blockSamples())) { + if (brs.getSampleCollectionDate()==null) { + if (brs.getLifeStage()==LifeStage.fetal && brs.getSpecies()!=null + && Species.isHumanName(brs.getSpecies())) { + missing = true; + } + } else if (brs.getSampleCollectionDate().isAfter(today)) { + badDates.add(brs.getSampleCollectionDate()); + } + if (brs.getExternalIdentifier()!=null && !brs.getExternalIdentifier().isEmpty()) { + String key = brs.getExternalIdentifier().trim().toUpperCase(); + if (!key.isEmpty()) { + if (extToDate.containsKey(key) && !Objects.equals(extToDate.get(key), brs.getSampleCollectionDate())) { + addProblem("Inconsistent collection dates specified for tissue " + key + "."); + } else { + extToDate.put(key, brs.getSampleCollectionDate()); + } + } + } + } + if (missing) { + addProblem("Human fetal samples must have a collection date."); + } + if (!badDates.isEmpty()) { + addProblem(pluralise("Invalid sample collection date{s}: ", badDates.size()) + badDates); + } + } + + public void validateExistingTissues() { + List blocksForExistingTissues = new ArrayList<>(); + for (BlockRegisterLabware blw : request.getLabware()) { + for (BlockRegisterSample brs : blw.getSamples()) { + if (brs.isExistingTissue()) { + blocksForExistingTissues.add(new BlockRegisterLabwareAndSample(blw, brs)); + } + } + } + if (blocksForExistingTissues.isEmpty()) { + return; + } + if (blocksForExistingTissues.stream().anyMatch(brs -> nullOrEmpty(brs.sample().getExternalIdentifier()))) { + addProblem("Missing external identifier."); + } + Set xns = blocksForExistingTissues.stream() + .map(b -> b.sample().getExternalIdentifier()) + .filter(xn -> !nullOrEmpty(xn)) + .collect(toLinkedHashSet()); + if (xns.isEmpty()) { + return; + } + tissueRepo.findAllByExternalNameIn(xns).forEach(t -> tissueMap.put(t.getExternalName(), t)); + + Set missing = xns.stream() + .filter(xn -> !tissueMap.containsKey(xn)) + .collect(toLinkedHashSet()); + if (!missing.isEmpty()) { + addProblem("Existing external identifiers not recognised: " + reprCollection(missing)); + } + + for (BlockRegisterLabwareAndSample b : blocksForExistingTissues) { + String xn = b.sample().getExternalIdentifier(); + if (!nullOrEmpty(xn)) { + Tissue tissue = tissueMap.get(xn.toUpperCase()); + if (tissue != null) { + blockFieldChecker.check(this::addProblem, b.labware(), b.sample(), tissue); + } + } + } + } + + public void validateNewTissues() { + // NB repeated new external identifier in one request is still disallowed + Set externalNames = new HashSet<>(); + for (BlockRegisterSample brs : iter(blockSamples())) { + if (brs.isExistingTissue()) { + continue; + } + if (nullOrEmpty(brs.getReplicateNumber())) { + addProblem("Missing replicate number."); + } else { + replicateValidator.validate(brs.getReplicateNumber(), this::addProblem); + } + if (brs.getHighestSection() < 0) { + addProblem("Highest section number cannot be negative."); + } + if (nullOrEmpty(brs.getExternalIdentifier())) { + addProblem("Missing external identifier."); + } else { + if (externalNameValidation != null) { + externalNameValidation.validate(brs.getExternalIdentifier(), this::addProblem); + } + if (!externalNames.add(brs.getExternalIdentifier().toUpperCase())) { + addProblem("Repeated external identifier: " + brs.getExternalIdentifier()); + } else if (!tissueRepo.findAllByExternalName(brs.getExternalIdentifier()).isEmpty()) { + addProblem(String.format("There is already tissue in the database with external identifier %s.", + brs.getExternalIdentifier())); + } + } + } + } + + public void validateBioRisks() { + this.bioRiskMap = bioRiskService.loadAndValidateBioRisks(problems, blockSamples(), + BlockRegisterSample::getBioRiskCode, BlockRegisterSample::setBioRiskCode); + } + + public void validateCellClasses() { + Set cellClassNames = new HashSet<>(); + boolean anyMissing = false; + for (BlockRegisterSample brs : iter(blockSamples())) { + String cellClassName = brs.getCellClass(); + if (nullOrEmpty(cellClassName)) { + anyMissing = true; + } else { + cellClassNames.add(cellClassName); + } + } + if (anyMissing) { + addProblem("Missing cell class name."); + } + if (cellClassNames.isEmpty()) { + cellClassMap = new UCMap<>(0); + return; + } + cellClassMap = cellClassRepo.findMapByNameIn(cellClassNames); + List missing = cellClassNames.stream() + .filter(name -> cellClassMap.get(name) == null) + .map(BasicUtils::repr) + .toList(); + if (!missing.isEmpty()) { + problems.add("Unknown cell class name: " + missing); + } + } + + public void validateWorks() { + if (request.getWorkNumbers().isEmpty()) { + addProblem("No work number supplied."); + works = List.of(); + } else { + works = workService.validateUsableWorks(problems, request.getWorkNumbers()).values(); + } + } + + public void validateExternalBarcodes() { + Set barcodes = new HashSet<>(); + boolean anyMissing = false; + for (BlockRegisterLabware brl : request.getLabware()) { + String barcode = brl.getExternalBarcode(); + if (nullOrEmpty(barcode)) { + anyMissing = true; + } else if (!barcodes.add(barcode.toUpperCase())) { + addProblem("External barcode given multiple times: " + barcode); + } + } + if (anyMissing) { + problems.add("Missing external barcode."); + } + for (String barcode : barcodes) { + externalBarcodeValidator.validate(barcode, this::addProblem); + } + Set usedBarcodes = lwRepo.findBarcodesByBarcodeIn(barcodes); + if (!usedBarcodes.isEmpty()) { + addProblem("Labware barcode already in use: " + usedBarcodes); + } + barcodes.removeAll(usedBarcodes); + Set usedExternalBarcodes = lwRepo.findExternalBarcodesIn(barcodes); + if (!usedExternalBarcodes.isEmpty()) { + addProblem("External barcode already in use: " + usedExternalBarcodes); + } + } + + public void validateAddresses() { + Map> lwTypeInvalidAddresses = new HashMap<>(); + boolean missing = false; + for (BlockRegisterLabware brl : request.getLabware()) { + LabwareType lt = labwareTypeMap.get(brl.getLabwareType()); + if (lt == null) { + continue; + } + for (BlockRegisterSample brs : brl.getSamples()) { + if (brs.getAddresses().isEmpty()) { + missing = true; + } else { + for (Address ad : brs.getAddresses()) { + if (lt.indexOf(ad) < 0) { + lwTypeInvalidAddresses.computeIfAbsent(lt, k -> new HashSet<>()).add(ad); + } + } + } + } + } + if (missing) { + problems.add("Slot addresses missing from request."); + } + if (!lwTypeInvalidAddresses.isEmpty()) { + lwTypeInvalidAddresses.forEach((lt, invalidAddresses) + -> problems.add(String.format("Invalid slot addresses for labware type %s: %s", lt.getName(), invalidAddresses))); + } + } + + + void validateByName(String entityName, + Function nameFunction, + Function> lkp, + UCMap map) { + Set unknownNames = new LinkedHashSet<>(); + boolean missing = false; + for (BlockRegisterLabware brl : request.getLabware()) { + String name = nameFunction.apply(brl); + if (nullOrEmpty(name)) { + missing = true; + continue; + } + if (unknownNames.contains(name)) { + continue; + } + if (map.containsKey(name)) { + continue; + } + Optional opt = lkp.apply(name); + if (opt.isEmpty()) { + unknownNames.add(repr(name)); + continue; + } + map.put(name, opt.get()); + } + if (missing) { + addProblem(String.format("Missing %s.", entityName)); + } + if (!unknownNames.isEmpty()) { + addProblem(String.format("Unknown %s%s: %s", entityName, unknownNames.size()==1 ? "" : "s", unknownNames)); + } + } + + boolean addProblem(String problem) { + return problems.add(problem); + } + + Stream blockSamples() { + return this.request.getLabware().stream().flatMap(brl -> brl.getSamples().stream()); + } + + record StringIntKey(String string, int number) { + StringIntKey(String string, int number) { + this.string = string.toUpperCase(); + this.number = number; + } + } + + record BlockRegisterLabwareAndSample(BlockRegisterLabware labware, BlockRegisterSample sample) {} +} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/FileRegisterServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/FileRegisterServiceImp.java index d76950967..ed4169101 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/FileRegisterServiceImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/FileRegisterServiceImp.java @@ -23,19 +23,19 @@ @Service public class FileRegisterServiceImp implements FileRegisterService { private final IRegisterService sectionRegisterService; - private final IRegisterService blockRegisterService; + private final IRegisterService blockRegisterService; private final IRegisterService originalSampleRegisterService; private final MultipartFileReader sectionFileReader; - private final MultipartFileReader blockFileReader; + private final MultipartFileReader blockFileReader; private final MultipartFileReader originalSampleFileReader; private final Transactor transactor; @Autowired public FileRegisterServiceImp(IRegisterService sectionRegisterService, - IRegisterService blockRegisterService, + IRegisterService blockRegisterService, IRegisterService originalSampleRegisterService, MultipartFileReader sectionFileReader, - MultipartFileReader blockFileReader, + MultipartFileReader blockFileReader, MultipartFileReader originalSampleFileReader, Transactor transactor) { this.sectionRegisterService = sectionRegisterService; @@ -71,11 +71,11 @@ protected Res register(User user, MultipartFile multipartFile, Multip } catch (IOException e) { throw new UncheckedIOException(e); } - if (!nullOrEmpty(ignoreExternalNames) && req instanceof RegisterRequest) { - updateToRemove((RegisterRequest) req, ignoreExternalNames); + if (!nullOrEmpty(ignoreExternalNames) && req instanceof BlockRegisterRequest) { + updateToRemove((BlockRegisterRequest) req, ignoreExternalNames); } - if (!nullOrEmpty(existingExternalNames) && req instanceof RegisterRequest) { - updateWithExisting((RegisterRequest) req, existingExternalNames); + if (!nullOrEmpty(existingExternalNames) && req instanceof BlockRegisterRequest) { + updateWithExisting((BlockRegisterRequest) req, existingExternalNames); } return transactor.transact("register", () -> service.apply(user, req)); } @@ -86,18 +86,23 @@ protected Res register(User user, MultipartFile multipartFile, Multip * @param request a block register request * @param existingExternalNames list of known existing external names */ - public void updateWithExisting(RegisterRequest request, List existingExternalNames) { - if (nullOrEmpty(existingExternalNames) || nullOrEmpty(request.getBlocks())) { + public void updateWithExisting(BlockRegisterRequest request, List existingExternalNames) { + if (request==null || nullOrEmpty(existingExternalNames) || nullOrEmpty(request.getLabware())) { return; } Set externalNamesUC = existingExternalNames.stream() .filter(Objects::nonNull) .map(String::toUpperCase) .collect(toSet()); - for (BlockRegisterRequest_old block : request.getBlocks()) { - if (block != null && !nullOrEmpty(block.getExternalIdentifier()) - && externalNamesUC.contains(block.getExternalIdentifier().toUpperCase())) { - block.setExistingTissue(true); + for (BlockRegisterLabware brl : request.getLabware()) { + if (brl==null || brl.getSamples()==null) { + continue; + } + for (BlockRegisterSample brs : brl.getSamples()) { + if (brs != null && !nullOrEmpty(brs.getExternalIdentifier()) + && externalNamesUC.contains(brs.getExternalIdentifier().toUpperCase())) { + brs.setExistingTissue(true); + } } } } @@ -107,18 +112,31 @@ public void updateWithExisting(RegisterRequest request, List existingExt * @param request block register request * @param externalNames external names to remove */ - public void updateToRemove(RegisterRequest request, List externalNames) { - if (nullOrEmpty(externalNames) || nullOrEmpty(request.getBlocks())) { + public void updateToRemove(BlockRegisterRequest request, List externalNames) { + if (request==null || nullOrEmpty(externalNames) || nullOrEmpty(request.getLabware())) { return; } Set ignoreUC = externalNames.stream() .filter(Objects::nonNull) .map(String::toUpperCase) .collect(toSet()); - List blocks = request.getBlocks().stream() - .filter(block -> block==null || block.getExternalIdentifier()==null || !ignoreUC.contains(block.getExternalIdentifier().toUpperCase())) - .toList(); - request.setBlocks(blocks); + List brls = new ArrayList<>(request.getLabware().size()); + for (BlockRegisterLabware brl : request.getLabware()) { + if (brl != null && !nullOrEmpty(brl.getSamples())) { + List samples = brl.getSamples().stream() + .filter(brs -> brs==null || brs.getExternalIdentifier()==null + || !ignoreUC.contains(brs.getExternalIdentifier().toUpperCase())) + .toList(); + if (samples.isEmpty()) { + continue; + } + if (samples.size() < brl.getSamples().size()) { + brl.setSamples(samples); + } + } + brls.add(brl); + } + request.setLabware(brls); } @Override diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java index 3a9bc7476..f374afcd3 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java @@ -33,6 +33,22 @@ public RegisterClashChecker(TissueRepo tissueRepo, SampleRepo sampleRepo, Operat this.lwRepo = lwRepo; } + public List findClashes(BlockRegisterRequest request) { + Set externalNames = request.getLabware().stream() + .flatMap(brl -> brl.getSamples().stream()) + .filter(brs -> !brs.isExistingTissue()) + .map(BlockRegisterSample::getExternalIdentifier) + .collect(toSet()); + if (externalNames.isEmpty()) { + return List.of(); + } + List existingTissues = tissueRepo.findAllByExternalNameIn(externalNames); + if (existingTissues.isEmpty()) { + return List.of(); + } + return createClashInfo(existingTissues); + } + public List findClashes(RegisterRequest request) { Set externalNames = request.getBlocks().stream() .filter(br -> !br.isExistingTissue()) diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationFactory.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationFactory.java index e7eb8efd9..414f4f826 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationFactory.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationFactory.java @@ -4,8 +4,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import uk.ac.sanger.sccp.stan.repo.*; -import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; -import uk.ac.sanger.sccp.stan.request.register.SectionRegisterRequest; +import uk.ac.sanger.sccp.stan.request.register.*; import uk.ac.sanger.sccp.stan.service.*; import uk.ac.sanger.sccp.stan.service.sanitiser.Sanitiser; import uk.ac.sanger.sccp.stan.service.work.WorkService; @@ -36,6 +35,7 @@ public class RegisterValidationFactory { private final Validator xeniumLotValidator; private final Sanitiser thicknessSanitiser; private final TissueFieldChecker tissueFieldChecker; + private final BlockFieldChecker blockFieldChecker; private final SlotRegionService slotRegionService; private final BioRiskService bioRiskService; private final WorkService workService; @@ -53,7 +53,7 @@ public RegisterValidationFactory(DonorRepo donorRepo, HmdmcRepo hmdmcRepo, Tissu @Qualifier("thicknessSanitiser") Sanitiser thicknessSanitiser, @Qualifier("xeniumLotValidator") Validator xeniumLotValidator, @Qualifier("replicateValidator") Validator replicateValidator, - TissueFieldChecker tissueFieldChecker, + TissueFieldChecker tissueFieldChecker, BlockFieldChecker blockFieldChecker, SlotRegionService slotRegionService, BioRiskService bioRiskService, WorkService workService) { this.donorRepo = donorRepo; this.hmdmcRepo = hmdmcRepo; @@ -75,6 +75,7 @@ public RegisterValidationFactory(DonorRepo donorRepo, HmdmcRepo hmdmcRepo, Tissu this.xeniumLotValidator = xeniumLotValidator; this.thicknessSanitiser = thicknessSanitiser; this.tissueFieldChecker = tissueFieldChecker; + this.blockFieldChecker = blockFieldChecker; this.slotRegionService = slotRegionService; this.workService = workService; this.bioRiskService = bioRiskService; @@ -86,6 +87,13 @@ public RegisterValidation createRegisterValidation(RegisterRequest request) { tissueFieldChecker, bioRiskService, workService); } + public RegisterValidation createBlockRegisterValidation(BlockRegisterRequest request) { + return new BlockRegisterValidationImp(request, donorRepo, hmdmcRepo, ttRepo, ltRepo, mediumRepo, + fixativeRepo, tissueRepo, speciesRepo, cellClassRepo, labwareRepo, donorNameValidation, + externalNameValidation, replicateValidator, externalBarcodeValidation, + blockFieldChecker, bioRiskService, workService); + } + public SectionRegisterValidation createSectionRegisterValidation(SectionRegisterRequest request) { return new SectionRegisterValidation(request, donorRepo, speciesRepo, ltRepo, labwareRepo, hmdmcRepo, ttRepo, fixativeRepo, cellClassRepo, mediumRepo, tissueRepo, bioStateRepo, diff --git a/src/main/resources/schema.graphqls b/src/main/resources/schema.graphqls index 0f0a92b00..453838128 100644 --- a/src/main/resources/schema.graphqls +++ b/src/main/resources/schema.graphqls @@ -2421,7 +2421,7 @@ type Mutation { """Log out; end the current login session.""" logout: String """Register blocks of tissue.""" - register(request: BlockRegisterRequest!): RegisterResult! + registerBlocks(request: BlockRegisterRequest!): RegisterResult! """Register sections of tissue.""" registerSections(request: SectionRegisterRequest): RegisterResult! """Record planned operations.""" diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java index eb2c9672d..bd442df44 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java @@ -11,13 +11,13 @@ import uk.ac.sanger.sccp.stan.model.User; import uk.ac.sanger.sccp.stan.request.register.*; import uk.ac.sanger.sccp.stan.service.register.filereader.MultipartFileReader; +import uk.ac.sanger.sccp.utils.Zip; import java.io.IOException; import java.io.UncheckedIOException; import java.util.Arrays; import java.util.List; import java.util.function.BiFunction; -import java.util.stream.IntStream; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -32,13 +32,13 @@ class TestFileRegisterService { @Mock private IRegisterService mockSectionRegisterService; @Mock - private IRegisterService mockBlockRegisterService; + private IRegisterService mockBlockRegisterService; @Mock private IRegisterService mockOriginalRegisterService; @Mock private MultipartFileReader mockSectionFileReader; @Mock - private MultipartFileReader mockBlockFileReader; + private MultipartFileReader mockBlockFileReader; @Mock private MultipartFileReader mockOriginalFileReader; @Mock @@ -52,7 +52,7 @@ class TestFileRegisterService { @BeforeEach void setup() { mocking = MockitoAnnotations.openMocks(this); - service = spy(new FileRegisterServiceImp(mockSectionRegisterService, mockBlockRegisterService,mockOriginalRegisterService, + service = spy(new FileRegisterServiceImp(mockSectionRegisterService, mockBlockRegisterService, mockOriginalRegisterService, mockSectionFileReader, mockBlockFileReader, mockOriginalFileReader, mockTransactor)); user = EntityFactory.getUser(); } @@ -93,13 +93,13 @@ void testSectionRegister_success(Object request, List existingExt, List< verify(fileReader).read(file); //noinspection unchecked,rawtypes verify((IRegisterService) regService).register(user, request); - if (request instanceof RegisterRequest && existingExt!=null) { - verify(service).updateWithExisting((RegisterRequest) request, existingExt); + if (request instanceof BlockRegisterRequest && existingExt!=null) { + verify(service).updateWithExisting((BlockRegisterRequest) request, existingExt); } else { verify(service, never()).updateWithExisting(any(), any()); } - if (request instanceof RegisterRequest && ignoreExt!=null) { - verify(service).updateToRemove((RegisterRequest) request, ignoreExt); + if (request instanceof BlockRegisterRequest && ignoreExt!=null) { + verify(service).updateToRemove((BlockRegisterRequest) request, ignoreExt); } else { verify(service, never()).updateToRemove(any(), any()); } @@ -110,13 +110,13 @@ static Stream regExtNamesIgnoreArgs() { return Arrays.stream(new Object[][] { {new SectionRegisterRequest()}, {new OriginalSampleRegisterRequest()}, - {new RegisterRequest()}, - {new RegisterRequest(), List.of("Alpha1"), null}, - {new RegisterRequest(), null, List.of("Beta")}, - {new RegisterRequest(), List.of("Alpha1"), List.of("Beta")}, + {new BlockRegisterRequest()}, + {new BlockRegisterRequest(), List.of("Alpha1"), null}, + {new BlockRegisterRequest(), null, List.of("Beta")}, + {new BlockRegisterRequest(), List.of("Alpha1"), List.of("Beta")}, }).map(arr -> arr.length < 3 ? Arrays.copyOf(arr, 3) : arr) .map(Arguments::of); -} + } @ParameterizedTest @MethodSource("regArgs") @@ -158,34 +158,43 @@ static Stream regArgs() { @ValueSource(booleans={false,true}) public void testUpdateWithExisting(boolean any) { String[] extNames = { null, "Alpha1", "Beta", "Alpha2" }; - List blocks = Arrays.stream(extNames) - .map(TestFileRegisterService::blockRegWithExternalName) - .toList(); - RegisterRequest request = new RegisterRequest(blocks); + BlockRegisterRequest request = requestWithExternalNames(extNames); List existing = any ? List.of("ALPHA1", "alpha2") : List.of(); service.updateWithExisting(request, existing); - IntStream.range(0, blocks.size()).forEach(i -> - assertEquals(any && (i==1 || i==3), blocks.get(i).isExistingTissue()) - ); + Zip.enumerate(streamSamples(request)).forEach((i, brs) -> + assertEquals(any && (i==1 || i==3), brs.isExistingTissue())); } @ParameterizedTest @ValueSource(booleans={false,true}) public void testUpdateToRemove(boolean anyToRemove) { String[] extNames = { null, "Alpha1", "Beta", "Alpha2" }; - RegisterRequest request = new RegisterRequest(Arrays.stream(extNames) - .map(TestFileRegisterService::blockRegWithExternalName) - .toList()); + BlockRegisterRequest request = requestWithExternalNames(extNames); List ignore = anyToRemove ? List.of("ALPHA1", "alpha2") : List.of(); service.updateToRemove(request, ignore); String[] remaining = (anyToRemove ? new String[]{null, "Beta"} : extNames); - assertThat(request.getBlocks().stream().map(BlockRegisterRequest_old::getExternalIdentifier)).containsExactly(remaining); + assertThat(streamSamples(request).map(BlockRegisterSample::getExternalIdentifier)).containsExactly(remaining); + } + + private static BlockRegisterRequest requestWithExternalNames(String... xns) { + List brss = Arrays.stream(xns) + .map(TestFileRegisterService::brsWithExternalName) + .toList(); + BlockRegisterLabware brl = new BlockRegisterLabware(); + brl.setSamples(brss); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl)); + return request; + } + + private static Stream streamSamples(BlockRegisterRequest request) { + return request.getLabware().stream().flatMap(brl -> brl.getSamples().stream()); } - private static BlockRegisterRequest_old blockRegWithExternalName(String xn) { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setExternalIdentifier(xn); - return br; + private static BlockRegisterSample brsWithExternalName(String xn) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setExternalIdentifier(xn); + return brs; } } \ No newline at end of file diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java index 9b9aaee4d..f171f131a 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java @@ -101,7 +101,7 @@ public void testRegisterValidBlocks() { final RegisterResult result = new RegisterResult(List.of(EntityFactory.getTube())); doNothing().when(registerService).updateExistingTissues(any(), any()); doReturn(result).when(registerService).create(any(), any(), any()); - when(mockClashChecker.findClashes(any())).thenReturn(List.of()); + when(mockClashChecker.findClashes(any(BlockRegisterRequest.class))).thenReturn(List.of()); assertSame(result, registerService.register(user, request)); @@ -116,7 +116,7 @@ public void testRegisterValidBlocks() { public void testRegisterWithClashes() { RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest_old())); List clashes = List.of(new RegisterClash(EntityFactory.getTissue(), List.of())); - when(mockClashChecker.findClashes(any())).thenReturn(clashes); + when(mockClashChecker.findClashes(any(BlockRegisterRequest.class))).thenReturn(clashes); assertEquals(RegisterResult.clashes(clashes), registerService.register(user, request)); verifyNoInteractions(mockValidationFactory); verifyNoInteractions(mockValidation); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidationFactory.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidationFactory.java index 29d34b397..8bf0b4693 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidationFactory.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidationFactory.java @@ -1,36 +1,35 @@ package uk.ac.sanger.sccp.stan.service.register; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import uk.ac.sanger.sccp.stan.repo.*; -import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; -import uk.ac.sanger.sccp.stan.request.register.SectionRegisterRequest; -import uk.ac.sanger.sccp.stan.service.*; -import uk.ac.sanger.sccp.stan.service.sanitiser.Sanitiser; -import uk.ac.sanger.sccp.stan.service.work.WorkService; +import org.junit.jupiter.api.*; +import org.mockito.InjectMocks; +import org.mockito.MockitoAnnotations; +import uk.ac.sanger.sccp.stan.request.register.*; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.mock; /** * Tests {@link RegisterValidationFactory} * @author dr6 */ public class TestRegisterValidationFactory { + @InjectMocks RegisterValidationFactory registerValidationFactory; - @SuppressWarnings("unchecked") + + private AutoCloseable mocking; + @BeforeEach void setup() { - Validator mockStringValidator = mock(Validator.class); - Sanitiser mockSanitiser = mock(Sanitiser.class); - registerValidationFactory = new RegisterValidationFactory( - mock(DonorRepo.class), mock(HmdmcRepo.class), mock(TissueTypeRepo.class), - mock(LabwareTypeRepo.class), mock(MediumRepo.class), - mock(FixativeRepo.class), mock(TissueRepo.class), mock(SpeciesRepo.class), mock(LabwareRepo.class), - mock(BioStateRepo.class), mock(CellClassRepo.class), mockStringValidator, mockStringValidator, mockStringValidator, - mockStringValidator, mockStringValidator, mockSanitiser, mockStringValidator, mockStringValidator, - mock(TissueFieldChecker.class), mock(SlotRegionService.class), mock(BioRiskService.class), - mock(WorkService.class)); + mocking = MockitoAnnotations.openMocks(this); + } + + @AfterEach + void cleanup() throws Exception { + mocking.close(); + } + + @Test + public void testCreateBlockRegisterValidation() { + assertNotNull(registerValidationFactory.createBlockRegisterValidation(new BlockRegisterRequest())); } @Test From 2ff6f563cc56f5d9d403f5214e016a8b3f90a318 Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Thu, 19 Feb 2026 14:35:26 +0000 Subject: [PATCH 07/13] x1403 in progress --- .../TestFileBlockRegister.java | 26 +- .../TestFindLatestOpQuery.java | 2 +- .../integrationtest/TestHistoryQuery.java | 2 +- .../integrationtest/TestRegisterMutation.java | 4 +- .../service/register/TestRegisterService.java | 559 ------------------ src/test/resources/graphql/register.graphql | 32 +- src/test/resources/testdata/block_reg.xlsx | Bin 11130 -> 11093 bytes .../testdata/block_reg_existing.xlsx | Bin 11094 -> 11056 bytes src/test/resources/testdata/reg_empty.xlsx | Bin 12242 -> 12057 bytes 9 files changed, 36 insertions(+), 589 deletions(-) delete mode 100644 src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java index 0afe1c429..ca99e105a 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java @@ -50,7 +50,7 @@ public class TestFileBlockRegister { ObjectMapper objectMapper; @MockBean - IRegisterService mockRegService; + IRegisterService mockRegService; @Test @Transactional @@ -117,12 +117,12 @@ public void testExistingExtNames() throws Exception { when(mockRegService.register(any(), any())).thenThrow(new ValidationException(List.of("Bad reg"))); var response = upload("testdata/block_reg_existing.xlsx", List.of("Ext17"), null, false); var map = objectMapper.readValue(response.getContentAsString(), Map.class); - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(RegisterRequest.class); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(BlockRegisterRequest.class); verify(mockRegService).register(eq(user), requestCaptor.capture()); - RegisterRequest request = requestCaptor.getValue(); - assertThat(request.getBlocks()).hasSize(2); - assertTrue(request.getBlocks().get(0).isExistingTissue()); - assertFalse(request.getBlocks().get(1).isExistingTissue()); + BlockRegisterRequest request = requestCaptor.getValue(); + assertThat(request.getLabware()).hasSize(2); + assertTrue(request.getLabware().get(0).getSamples().getFirst().isExistingTissue()); + assertFalse(request.getLabware().get(1).getSamples().getFirst().isExistingTissue()); assertEquals("Bad reg", getProblem(map)); } @@ -139,15 +139,17 @@ public void testIgnoreExtNames() throws Exception { tester.setUser(user); when(mockRegService.register(any(), any())).thenThrow(new ValidationException(List.of("Bad reg"))); var response = upload("testdata/block_reg_existing.xlsx", null, List.of("Ext17"), false); + System.out.printf("%n****%n%s%n****%n", response.getContentAsString()); var map = objectMapper.readValue(response.getContentAsString(), Map.class); - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(RegisterRequest.class); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(BlockRegisterRequest.class); verify(mockRegService).register(eq(user), requestCaptor.capture()); - RegisterRequest request = requestCaptor.getValue(); - assertThat(request.getBlocks()).hasSize(1); - BlockRegisterRequest_old br = request.getBlocks().getFirst(); - assertEquals("EXT18", br.getExternalIdentifier()); + BlockRegisterRequest request = requestCaptor.getValue(); + assertThat(request.getLabware()).hasSize(1); + BlockRegisterLabware brl = request.getLabware().getFirst(); + BlockRegisterSample brs = brl.getSamples().getFirst(); + assertEquals("EXT18", brs.getExternalIdentifier()); assertEquals("Bad reg", getProblem(map)); - assertEquals("risk1", br.getBioRiskCode()); + assertEquals("risk1", brs.getBioRiskCode()); } private MockHttpServletResponse upload(String filename) throws Exception { diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFindLatestOpQuery.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFindLatestOpQuery.java index b307cdcca..8b4c3c727 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFindLatestOpQuery.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFindLatestOpQuery.java @@ -40,7 +40,7 @@ public void testFindLatestOp() throws Exception { User user = entityCreator.createUser("user1"); tester.setUser(user); Object mutationResult = tester.post(mutation); - String barcode = chainGet(mutationResult, "data", "register", "labware", 0, "barcode"); + String barcode = chainGet(mutationResult, "data", "registerBlocks", "labware", 0, "barcode"); String query = tester.readGraphQL("findlatestop.graphql").replace("STAN-A1", barcode); Object result = tester.post(query); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestHistoryQuery.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestHistoryQuery.java index 200ff3fae..98ea2a0ca 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestHistoryQuery.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestHistoryQuery.java @@ -55,7 +55,7 @@ public void testHistory() throws Exception { tester.setUser(user); Map response = tester.post(mutation); - Map lwData = chainGet(response, "data", "register", "labware", 0); + Map lwData = chainGet(response, "data", "registerBlocks", "labware", 0); String barcode = chainGet(lwData, "barcode"); int sampleId = chainGet(lwData, "slots", 0, "samples", 0, "id"); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterMutation.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterMutation.java index 27ba6019a..ebc96531d 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterMutation.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestRegisterMutation.java @@ -51,7 +51,7 @@ public void testRegister() throws Exception { tester.setUser(entityCreator.createUser("dr6")); String mutation = tester.readGraphQL("register.graphql").replace("SGP1", work.getWorkNumber()); Object result = tester.post(mutation); - Object data = chainGet(result, "data", "register"); + Object data = chainGet(result, "data", "registerBlocks"); assertThat(chainGetList(data, "clashes")).isEmpty(); String barcode = chainGet(data, "labware", 0, "barcode"); assertNotNull(barcode); @@ -61,7 +61,7 @@ public void testRegister() throws Exception { assertEquals("2021-02-03", tissueData.get("collectionDate")); result = tester.post(mutation); - data = chainGet(result, "data", "register"); + data = chainGet(result, "data", "registerBlocks"); assertThat(chainGetList(data, "labware")).isEmpty(); List> clashes = chainGet(data, "clashes"); assertThat(clashes).hasSize(1); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java deleted file mode 100644 index f171f131a..000000000 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java +++ /dev/null @@ -1,559 +0,0 @@ -package uk.ac.sanger.sccp.stan.service.register; - -import org.junit.jupiter.api.*; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import uk.ac.sanger.sccp.stan.EntityFactory; -import uk.ac.sanger.sccp.stan.Matchers; -import uk.ac.sanger.sccp.stan.model.*; -import uk.ac.sanger.sccp.stan.repo.*; -import uk.ac.sanger.sccp.stan.request.register.*; -import uk.ac.sanger.sccp.stan.service.*; -import uk.ac.sanger.sccp.stan.service.work.WorkService; - -import javax.persistence.EntityManager; -import java.time.LocalDate; -import java.util.*; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; -import static uk.ac.sanger.sccp.stan.Matchers.eqCi; - -/** - * Test {@link RegisterServiceImp} - * @author dr6 - */ -public class TestRegisterService { - @Mock - private EntityManager mockEntityManager; - @Mock - private RegisterValidationFactory mockValidationFactory; - @Mock - private DonorRepo mockDonorRepo; - @Mock - private TissueRepo mockTissueRepo; - @Mock - private SampleRepo mockSampleRepo; - @Mock - private SlotRepo mockSlotRepo; - @Mock - private BioRiskRepo mockBioRiskRepo; - @Mock - private OperationTypeRepo mockOpTypeRepo; - @Mock - private LabwareService mockLabwareService; - @Mock - private OperationService mockOpService; - @Mock - private RegisterValidation mockValidation; - @Mock - private RegisterClashChecker mockClashChecker; - @Mock - private WorkService mockWorkService; - - private User user; - private OperationType opType; - private int idCounter = 1000; - - private RegisterServiceImp registerService; - - private AutoCloseable mocking; - - @BeforeEach - void setup() { - mocking = MockitoAnnotations.openMocks(this); - user = EntityFactory.getUser(); - when(mockValidationFactory.createRegisterValidation(any())).thenReturn(mockValidation); - BioState bs = EntityFactory.getBioState(); - opType = new OperationType(1, "Register", 0, bs); - when(mockOpTypeRepo.getByName(opType.getName())).thenReturn(opType); - - registerService = spy(new RegisterServiceImp(mockEntityManager, mockValidationFactory, mockDonorRepo, mockTissueRepo, - mockSampleRepo, mockSlotRepo, mockBioRiskRepo, mockOpTypeRepo, mockLabwareService, mockOpService, mockWorkService, mockClashChecker)); - } - - @AfterEach - void tearDown() throws Exception { - mocking.close(); - } - - @Test - public void testRegisterNoBlocks() { - RegisterResult result = registerService.register(user, new RegisterRequest(List.of())); - assertThat(result.getLabware()).isEmpty(); - verifyNoInteractions(mockValidationFactory); - verify(registerService, never()).updateExistingTissues(any(), any()); - verify(registerService, never()).create(any(), any(), any()); - } - - @Test - public void testRegisterValidBlocks() { - RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest_old())); - when(mockValidation.validate()).thenReturn(Set.of()); - final RegisterResult result = new RegisterResult(List.of(EntityFactory.getTube())); - doNothing().when(registerService).updateExistingTissues(any(), any()); - doReturn(result).when(registerService).create(any(), any(), any()); - when(mockClashChecker.findClashes(any(BlockRegisterRequest.class))).thenReturn(List.of()); - - assertSame(result, registerService.register(user, request)); - - verify(mockClashChecker).findClashes(request); - verify(mockValidationFactory).createRegisterValidation(request); - verify(mockValidation).validate(); - verify(registerService).updateExistingTissues(request, mockValidation); - verify(registerService).create(request, user, mockValidation); - } - - @Test - public void testRegisterWithClashes() { - RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest_old())); - List clashes = List.of(new RegisterClash(EntityFactory.getTissue(), List.of())); - when(mockClashChecker.findClashes(any(BlockRegisterRequest.class))).thenReturn(clashes); - assertEquals(RegisterResult.clashes(clashes), registerService.register(user, request)); - verifyNoInteractions(mockValidationFactory); - verifyNoInteractions(mockValidation); - verify(registerService, never()).updateExistingTissues(any(), any()); - verify(registerService, never()).create(any(), any(), any()); - } - - @Test - public void testRegisterInvalidBlocks() { - RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest_old())); - final Set problems = Set.of("Everything is bad.", "I spilled my tea."); - when(mockValidation.validate()).thenReturn(problems); - - try { - registerService.register(user, request); - fail("Expected validation exception."); - } catch (ValidationException ex) { - assertEquals(ex.getProblems(), problems); - } - - verify(mockValidationFactory).createRegisterValidation(request); - verify(mockValidation).validate(); - verify(registerService, never()).updateExistingTissues(any(), any()); - verify(registerService, never()).create(any(), any(), any()); - } - - @Test - public void testCreateDonors() { - Donor donor0 = EntityFactory.getDonor(); - Species hamster = new Species(2, "Hamster"); - Donor donor1 = new Donor(null, "Jeff", LifeStage.paediatric, hamster); - BlockRegisterRequest_old block0 = new BlockRegisterRequest_old(); - block0.setDonorIdentifier(donor0.getDonorName()); - block0.setLifeStage(donor0.getLifeStage()); - block0.setSpecies(donor0.getSpecies().getName()); - BlockRegisterRequest_old block1 = new BlockRegisterRequest_old(); - block1.setDonorIdentifier(donor1.getDonorName()); - block1.setLifeStage(donor1.getLifeStage()); - block1.setSpecies(donor1.getSpecies().getName()); - - when(mockValidation.getDonor(eqCi(donor0.getDonorName()))).thenReturn(donor0); - when(mockValidation.getDonor(eqCi(donor1.getDonorName()))).thenReturn(donor1); - - when(mockDonorRepo.save(any())).then(invocation -> { - Donor donor = invocation.getArgument(0); - assertNull(donor.getId()); - donor.setId(++idCounter); - return donor; - }); - - RegisterRequest request = new RegisterRequest(List.of(block0, block1)); - Map donorMap = registerService.createDonors(request, mockValidation); - assertEquals(donorMap, Stream.of(donor0, donor1).collect(toMap(d -> d.getDonorName().toUpperCase(), d -> d))); - verify(mockDonorRepo).save(donor1); - verifyNoMoreInteractions(mockDonorRepo); - } - - @Test - public void testUpdateExistingTissues_none() { - Tissue tissue1 = EntityFactory.getTissue(); - BlockRegisterRequest_old brr1 = new BlockRegisterRequest_old(); - brr1.setExternalIdentifier(tissue1.getExternalName()); - brr1.setExistingTissue(true); - - Tissue tissue2 = EntityFactory.makeTissue(tissue1.getDonor(), tissue1.getSpatialLocation()); - tissue2.setCollectionDate(LocalDate.of(2020,1,2)); - BlockRegisterRequest_old brr2 = new BlockRegisterRequest_old(); - brr2.setExternalIdentifier(tissue2.getExternalName().toLowerCase()); - brr2.setExistingTissue(true); - brr2.setSampleCollectionDate(tissue2.getCollectionDate()); - - BlockRegisterRequest_old brr3 = new BlockRegisterRequest_old(); - - when(mockValidation.getTissue(Matchers.eqCi(tissue1.getExternalName()))).thenReturn(tissue1); - when(mockValidation.getTissue(Matchers.eqCi(tissue2.getExternalName()))).thenReturn(tissue2); - - registerService.updateExistingTissues(new RegisterRequest(List.of(brr1, brr2, brr3)), mockValidation); - verifyNoInteractions(mockTissueRepo); - } - - @Test - public void testUpdateExistingTissues() { - Donor donor = EntityFactory.getDonor(); - SpatialLocation sl = EntityFactory.getSpatialLocation(); - Tissue tissue = EntityFactory.makeTissue(donor, sl); - - when(mockValidation.getTissue(eqCi(tissue.getExternalName()))).thenReturn(tissue); - BlockRegisterRequest_old brr1 = new BlockRegisterRequest_old(); - brr1.setExistingTissue(true); - brr1.setExternalIdentifier(tissue.getExternalName().toLowerCase()); - brr1.setSampleCollectionDate(LocalDate.of(2010,2,3)); - - registerService.updateExistingTissues(new RegisterRequest(List.of(brr1, new BlockRegisterRequest_old())), mockValidation); - verify(mockTissueRepo).saveAll(List.of(tissue)); - assertEquals(brr1.getSampleCollectionDate(), tissue.getCollectionDate()); - } - - @Test - public void testCreateTissues() { - Tissue existingTissue = EntityFactory.getTissue(); - Species human = EntityFactory.getHuman(); - Species hamster = new Species(2, "Hamster"); - Donor donor1 = existingTissue.getDonor(); - Donor donor2 = new Donor(2, "DONOR2", LifeStage.adult, human); - Donor donor3 = new Donor(3, "DONOR3", LifeStage.fetal, hamster); - Map donorMap = Stream.of(donor1, donor2, donor3) - .collect(toMap(d -> d.getDonorName().toUpperCase(), d -> d)); - doReturn(donorMap).when(registerService).createDonors(any(), any()); - when(mockTissueRepo.save(any())).then(invocation -> { - Tissue tissue = invocation.getArgument(0); - assertNull(tissue.getId()); - tissue.setId(++idCounter); - return tissue; - }); - Hmdmc hmdmc = EntityFactory.getHmdmc(); - SpatialLocation sl = EntityFactory.getSpatialLocation(); - Medium medium = EntityFactory.getMedium(); - Fixative fix = EntityFactory.getFixative(); - - when(mockValidation.getTissue(existingTissue.getExternalName().toUpperCase())).thenReturn(existingTissue); - when(mockValidation.getHmdmc(hmdmc.getHmdmc())).thenReturn(hmdmc); - when(mockValidation.getSpatialLocation(sl.getTissueType().getName(), sl.getCode())).thenReturn(sl); - when(mockValidation.getMedium(medium.getName())).thenReturn(medium); - when(mockValidation.getFixative(fix.getName())).thenReturn(fix); - LocalDate colDate = LocalDate.of(2020,5,4); - List brs = List.of( - makeBrr(existingTissue.getExternalName(), donor1.getDonorName(), - existingTissue.getHmdmc().getHmdmc(), donor1.getSpecies().getName(), - existingTissue.getReplicate(), existingTissue.getSpatialLocation(), - existingTissue.getMedium().getName(), existingTissue.getFixative().getName(), null), - makeBrr("TISSUE2", donor2.getDonorName(), - hmdmc.getHmdmc(), human.getName(), - "7", sl, medium.getName(), fix.getName(), colDate), - makeBrr("TISSUE3", donor3.getDonorName(), - null, hamster.getName(), - "14", sl, medium.getName(), fix.getName(), null) - ); - - RegisterRequest request = new RegisterRequest(brs); - - Map tissueMap = registerService.createTissues(request, mockValidation); - - assertThat(tissueMap).hasSize(3); - assertEquals(3L, tissueMap.values().stream().map(Tissue::getId).distinct().count()); - - assertSame(existingTissue, tissueMap.get(existingTissue.getExternalName().toUpperCase())); - - for (String xn : new String[] {"TISSUE2", "TISSUE3"}) { - Tissue tissue = tissueMap.get(xn); - assertNotNull(tissue); - assertEquals(xn, tissue.getExternalName()); - if (xn.equals("TISSUE2")) { - assertEquals(donor2, tissue.getDonor()); - assertEquals(hmdmc, tissue.getHmdmc()); - assertEquals("7", tissue.getReplicate()); - assertEquals(colDate, tissue.getCollectionDate()); - } else { - assertEquals(donor3, tissue.getDonor()); - assertNull(tissue.getHmdmc()); - assertEquals("14", tissue.getReplicate()); - assertNull(tissue.getCollectionDate()); - } - assertEquals(sl, tissue.getSpatialLocation()); - assertEquals(medium, tissue.getMedium()); - assertEquals(fix, tissue.getFixative()); - - verify(mockTissueRepo).save(tissue); - } - } - - private BlockRegisterRequest_old makeBrr(String externalName, String donorName, - String hmdmc, String species, - String replicate, SpatialLocation sl, - String mediumName, String fixName, LocalDate collectionDate) { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setExternalIdentifier(externalName); - br.setDonorIdentifier(donorName); - br.setHmdmc(hmdmc); - br.setSpecies(species); - br.setReplicateNumber(replicate); - br.setTissueType(sl.getTissueType().getName()); - br.setSpatialLocation(sl.getCode()); - br.setMedium(mediumName); - br.setFixative(fixName); - br.setSampleCollectionDate(collectionDate); - return br; - } - - // This test does not mock out createTissues() so it is actually testing more thoroughly than it needs to - @Test - public void testCreate() { - Species hamster = new Species(2, "Hamster"); - Donor donor1 = EntityFactory.getDonor(); - Donor donor2 = new Donor(donor1.getId()+1, "DONOR2", LifeStage.adult, hamster); - LabwareType[] lts = {EntityFactory.getTubeType(), EntityFactory.makeLabwareType(1, 2)}; - TissueType tissueType = EntityFactory.getTissueType(); - Medium medium = EntityFactory.getMedium(); - Fixative fixative = EntityFactory.getFixative(); - CellClass cellClass = EntityFactory.getCellClass(); - Hmdmc[] hmdmcs = {new Hmdmc(20000, "20/000"), new Hmdmc(20001, "20/001")}; - - BlockRegisterRequest_old block0 = new BlockRegisterRequest_old(); - block0.setDonorIdentifier(donor1.getDonorName()); - block0.setLifeStage(donor1.getLifeStage()); - block0.setExternalIdentifier("TISSUE0"); - block0.setHighestSection(3); - block0.setHmdmc("20/000"); - block0.setLabwareType(lts[0].getName()); - block0.setMedium(medium.getName()); - block0.setFixative(fixative.getName()); - block0.setTissueType(tissueType.getName()); - block0.setReplicateNumber("2"); - block0.setSpatialLocation(1); - block0.setSpecies(donor1.getSpecies().getName()); - block0.setCellClass("Tissue"); - - BlockRegisterRequest_old block1 = new BlockRegisterRequest_old(); - block1.setDonorIdentifier(donor2.getDonorName()); - block1.setLifeStage(donor2.getLifeStage()); - block1.setReplicateNumber("5"); - block1.setTissueType(tissueType.getName()); - block1.setSpatialLocation(0); - block1.setLabwareType(lts[1].getName()); - block1.setMedium(medium.getName().toUpperCase()); - block1.setFixative(fixative.getName().toUpperCase()); - block1.setHighestSection(0); - block1.setExternalIdentifier("TISSUE1"); - block1.setSpecies(donor2.getSpecies().getName()); - block1.setCellClass("Tissue"); - block1.setSampleCollectionDate(LocalDate.of(2020,4,6)); - - Map donorMap = Map.of(donor1.getDonorName().toUpperCase(), donor1, - donor2.getDonorName().toUpperCase(), donor2); - doReturn(donorMap).when(registerService).createDonors(any(), any()); - SpatialLocation[] sls = {new SpatialLocation(1, "SL0", 1, tissueType), - new SpatialLocation(2, "SL1", 0, tissueType)}; - - List works = IntStream.rangeClosed(1,2) - .mapToObj(i -> { - Work work = new Work(); - work.setId(i); - work.setWorkNumber("SGP"+i); - return work; - }).collect(toList()); - when(mockValidation.getWorks()).thenReturn(works); - - when(mockValidation.getSpatialLocation(eqCi(tissueType.getName()), eq(1))) - .thenReturn(sls[0]); - when(mockValidation.getSpatialLocation(eqCi(tissueType.getName()), eq(0))) - .thenReturn(sls[1]); - when(mockValidation.getMedium(eqCi(medium.getName()))).thenReturn(medium); - when(mockValidation.getFixative(eqCi(fixative.getName()))).thenReturn(fixative); - when(mockValidation.getCellClass("Tissue")).thenReturn(cellClass); - Arrays.stream(lts).forEach(lt -> when(mockValidation.getLabwareType(eqCi(lt.getName()))).thenReturn(lt)); - Arrays.stream(hmdmcs).forEach(h -> when(mockValidation.getHmdmc(eqCi(h.getHmdmc()))).thenReturn(h)); - RegisterRequest request = new RegisterRequest(List.of(block0, block1)); - Labware[] lws = Arrays.stream(lts).map(EntityFactory::makeEmptyLabware).toArray(Labware[]::new); - Arrays.stream(lws).forEach(lw -> when(mockLabwareService.create(lw.getLabwareType())).thenReturn(lw)); - - Tissue[] tissues = new Tissue[]{ - new Tissue(5000, block0.getExternalIdentifier(), block0.getReplicateNumber(), - sls[0], donor1, medium, fixative, cellClass, hmdmcs[0], block0.getSampleCollectionDate(), null), - new Tissue(5001, block1.getExternalIdentifier(), block1.getReplicateNumber(), - sls[1], donor2, medium, fixative, cellClass, null, block1.getSampleCollectionDate(), null), - }; - - BioState bioState = opType.getNewBioState(); - Sample[] samples = { - Sample.newBlock(6000, tissues[0], bioState, 3), - Sample.newBlock(6001, tissues[1], bioState, 0), - }; - - when(mockTissueRepo.save(any())).thenReturn(tissues[0], tissues[1]); - when(mockSampleRepo.save(any())).thenReturn(samples[0], samples[1]); - - when(mockSlotRepo.save(any())).then(invocation -> invocation.getArgument(0)); - - List ops = IntStream.rangeClosed(1,request.getBlocks().size()) - .mapToObj(i -> { - Operation op = new Operation(); - op.setId(i); - return op; - }).collect(toList()); - - when(mockOpService.createOperationInPlace(any(), any(), any(), any())).thenReturn(ops.get(0), ops.get(1)); - - RegisterResult result = registerService.create(request, user, mockValidation); - - assertEquals(result, new RegisterResult(Arrays.asList(lws))); - - verify(registerService).createDonors(request, mockValidation); - - List blocks = request.getBlocks(); - for (int i = 0; i < blocks.size(); i++) { - BlockRegisterRequest_old block = blocks.get(i); - verify(mockTissueRepo).save( - new Tissue(null, - block.getExternalIdentifier(), - block.getReplicateNumber(), - sls[i], - i==0 ? donor1 : donor2, - medium, - fixative, - cellClass, - i==0 ? hmdmcs[i] : null, - block.getSampleCollectionDate(), null)); - verify(mockSampleRepo).save(Sample.newBlock(null, tissues[i], bioState, block.getHighestSection())); - verify(mockLabwareService).create(lts[i]); - Labware lw = lws[i]; - verify(mockEntityManager).refresh(lw); - Slot slot = lw.getFirstSlot(); - assertEquals(block.getHighestSection(), slot.getSamples().getFirst().getBlockHighestSection()); - assertEquals(samples[i].getId(), slot.getSamples().getFirst().getId()); - verify(mockSlotRepo).save(slot); - verify(mockOpService).createOperationInPlace(opType, user, slot, samples[i]); - } - verify(mockWorkService).link(works, ops); - } - - @ParameterizedTest - @MethodSource("createArgs") - public void testCreateProblems(Species species, Object hmdmcObj, String expectedErrorMessage) { - Donor donor = new Donor(100, "DONOR1", LifeStage.adult, species); - LabwareType lt = EntityFactory.getTubeType(); - TissueType tissueType = EntityFactory.getTissueType(); - Medium medium = EntityFactory.getMedium(); - Fixative fixative = EntityFactory.getFixative(); - CellClass cellClass = EntityFactory.getCellClass(); - Hmdmc hmdmc; - String hmdmcString; - if (hmdmcObj instanceof Hmdmc) { - hmdmc = (Hmdmc) hmdmcObj; - hmdmcString = hmdmc.getHmdmc(); - } else if (hmdmcObj instanceof String) { - hmdmc = null; - hmdmcString = (String) hmdmcObj; - } else { - hmdmc = null; - hmdmcString = null; - } - BioRisk br = new BioRisk(800, "biorisk"); - - BlockRegisterRequest_old block = new BlockRegisterRequest_old(); - block.setDonorIdentifier(donor.getDonorName()); - block.setLifeStage(donor.getLifeStage()); - block.setExternalIdentifier("TISSUE"); - block.setHighestSection(3); - block.setHmdmc(hmdmcString); - block.setLabwareType(lt.getName()); - block.setMedium(medium.getName()); - block.setFixative(fixative.getName()); - block.setCellClass(cellClass.getName()); - block.setTissueType(tissueType.getName()); - block.setReplicateNumber("2"); - block.setSpatialLocation(1); - block.setSpecies(species.getName()); - block.setBioRiskCode(br.getCode()); - - Map donorMap = Map.of(donor.getDonorName().toUpperCase(), donor); - doReturn(donorMap).when(registerService).createDonors(any(), any()); - final SpatialLocation sl = new SpatialLocation(1, "SL0", 1, tissueType); - - Operation op = new Operation(); - op.setId(700); - when(mockOpService.createOperationInPlace(any(), any(), any(), any())).thenReturn(op); - - when(mockValidation.getBioRisk(br.getCode())).thenReturn(br); - - when(mockValidation.getSpatialLocation(eqCi(tissueType.getName()), eq(1))) - .thenReturn(sl); - when(mockValidation.getMedium(eqCi(medium.getName()))).thenReturn(medium); - when(mockValidation.getFixative(eqCi(fixative.getName()))).thenReturn(fixative); - when(mockValidation.getLabwareType(eqCi(lt.getName()))).thenReturn(lt); - when(mockValidation.getCellClass(eq(cellClass.getName()))).thenReturn(cellClass); - if (hmdmc != null) { - when(mockValidation.getHmdmc(hmdmc.getHmdmc())).thenReturn(hmdmc); - } else if (hmdmcString!=null) { - when(mockValidation.getHmdmc(hmdmcString)).thenReturn(null); - } - RegisterRequest request = new RegisterRequest(List.of(block)); - Labware lw = EntityFactory.makeEmptyLabware(lt); - when(mockLabwareService.create(lt)).thenReturn(lw); - - final Tissue tissue = new Tissue(5000, block.getExternalIdentifier(), block.getReplicateNumber(), - sl, donor, medium, fixative, null, hmdmc, null, null); - - BioState bioState = opType.getNewBioState(); - Sample sample = Sample.newBlock(6000, tissue, bioState, 3); - - when(mockTissueRepo.save(any())).thenReturn(tissue); - when(mockSampleRepo.save(any())).thenReturn(sample); - - when(mockSlotRepo.save(any())).then(invocation -> invocation.getArgument(0)); - - if (expectedErrorMessage!=null) { - assertThat(assertThrows(IllegalArgumentException.class, () -> registerService.create(request, user, mockValidation))) - .hasMessage(expectedErrorMessage); - return; - } else { - registerService.create(request, user, mockValidation); - } - - verify(registerService).createDonors(request, mockValidation); - - verify(mockTissueRepo).save( - new Tissue(null, - block.getExternalIdentifier(), - block.getReplicateNumber(), - sl, - donor, - medium, - fixative, - cellClass, - hmdmc, - null, null)); - verify(mockSampleRepo).save(Sample.newBlock(null, tissue, bioState, 3)); - verify(mockLabwareService).create(lt); - verify(mockEntityManager).refresh(lw); - Slot slot = lw.getFirstSlot(); - assertEquals(block.getHighestSection(), slot.getSamples().getFirst().getBlockHighestSection()); - assertEquals(sample.getId(), slot.getSamples().getFirst().getId()); - verify(mockSlotRepo).save(slot); - verify(mockOpService).createOperationInPlace(opType, user, slot, sample); - verify(mockBioRiskRepo).recordBioRisk(sample, br, op.getId()); - } - - static Stream createArgs() { - Species human = new Species(1, Species.HUMAN_NAME); - Species hamster = new Species(2, "Hamster"); - Hmdmc hmdmc = new Hmdmc(10, "20/001"); - // Species species, Hmdmc hmdmc, String expectedErrorMessage - return Stream.of( - Arguments.of(human, hmdmc, null), - Arguments.of(hamster, null, null), - Arguments.of(human, null, "No HuMFre number given for tissue TISSUE"), - Arguments.of(hamster, hmdmc, "HuMFre number given for non-human tissue TISSUE"), - Arguments.of(human, "20/404", "Unknown HuMFre number: 20/404") - ); - } -} diff --git a/src/test/resources/graphql/register.graphql b/src/test/resources/graphql/register.graphql index 06b55ee52..9b8aaf64e 100644 --- a/src/test/resources/graphql/register.graphql +++ b/src/test/resources/graphql/register.graphql @@ -1,22 +1,26 @@ mutation { - register(request:{ - blocks:[ + registerBlocks(request:{ + labware:[ { labwareType:"proviasette", - donorIdentifier:"DONOR1", - species: "Homo sapiens (Human)", - externalIdentifier:"TISSUE1", - lifeStage:adult, - hmdmc:"20/0002", - spatialLocation:0, - tissueType:"Bone", - replicateNumber:"1", medium:"None", fixative:"None", - highestSection:0, - sampleCollectionDate: "2021-02-03", - bioRiskCode: "biorisk1", - cellClass: "Tissue" + externalBarcode: "XBC1" + samples: { + donorIdentifier:"DONOR1", + species: "Homo sapiens (Human)", + externalIdentifier:"TISSUE1", + lifeStage:adult, + hmdmc:"20/0002", + spatialLocation:0, + tissueType:"Bone", + replicateNumber:"1", + highestSection:0, + sampleCollectionDate: "2021-02-03", + bioRiskCode: "biorisk1", + cellClass: "Tissue" + addresses: ["A1"] + } } ] workNumbers: ["SGP1"] diff --git a/src/test/resources/testdata/block_reg.xlsx b/src/test/resources/testdata/block_reg.xlsx index 1fef254ce7162bd61e558fb9aaa4fb597fe8c073..ff1c3d9cb06ae8f0647353d028cf2d336ac1c8bc 100644 GIT binary patch delta 4366 zcmZvgcQ71`)5i}XI0WIG-rJqtiNvXKN|fk{OAx(tL9|m&OGI#boL-_6C3?nL5kQP3}X;6;p!y=^@YsZh3KEB4A z9ZQ1AmSZ(w7WpO;n#)Rg>H%(?3i1e?M&JT`#wO?{y}r_r@`TCG#T*VTW#O$hVm@Ao zWV`6&V6IN&pdjt$50HQ6R7LA7BlU|3t=JWA!%}Rusi+LLe@owNV?tM2o6}R=xLn#q z)AE5{V6lZ(={Bl_SPHZYw2t84V0@CUZRhMhsvCD?O4PNXHPEP^+*zqBZpvhrVFTn; zZHQ%(Vz7H^GAwbW((t|d;k8O*Y>Uft&4IWRw`+T+kfsqnPlUxm*Om1-4f)TACFLd< z-PB{*XN~^D!N{lfn*%+JG2t)7i-X||5vcZph-}d2q8@6*F-@o^4J{soevZua>?3)i zB-o|A6Rk3LM<;vM=VLqv^NTZ`4Nkv{-)>hM;&W`4oj*n{`TF`5o%@B`4t(c~orpLA zo|{X0xZVnx{DYQq7i_**%wF(ec)YAMHJ<@fC*Ow2a*T5Wp)7;)uEX{&C#3rW8anX~ z$aJ}vsDTPr5VcVl>Byv& zcAr=>XHnc7{99$5f7c~T2(Cyqy zvj%Su)^#EIYNRN+?cSP2!azfGp?Km)xIhp-jx|wulTT_^*|8ES0i^n~GR=iSa2!$@F!Lz1wCz;8xHltu7$om}3+B?*TZm)Wx;KD3(%+LRK! z$Njx?Tq|N^5QfSvi#TK%^pbmFkI_Qmy_{?0FCIdQNia&r1#8 zYc0udG?@u*s9gtpFJVyM*54qKx( z^jutT-fUlb|uC40gDFYuCvSX5T+{;lvS!J#{TztaMX12C(TY|3i z6rc1h2#sj$2ez`hnqZCv^aL{>od#x9$j*~rB#@(xAM5 zJEviQ#!^25T&XO2^Q$xE<{w3g+0a#Gy?+JeL1A>?{3B+>_3hjMp@nlZ{$BG2xGG*> zps?jW^kf!PBc)*|LsodxW)tNHY(f*`916xHB{NKM0~p6yX1S`dZN(VtkxqzEU4_^6 ziSxZ7xl`qN6&VN49s8pz&$IK1lW4a?=aA0m=RqU;)bkP4h^glV@2ZKl@9`>d6QR;8 zY1?0Nyt$5dRAU;Dv?^%0QZZ``0Gp*)bdgMTg-|j?hii}wt;{r^nPwQ^S7VWvs`m^X zEL6TD^Lv`2yzC~|n?CMtR^~SAeO80`c>OGt>Yow&bD_(VpRD@MHac2JM1CbjiNTw5 z3f5e4BcdZgL$!>Y>GUqKd-SZ&rsP+-#RmtW$ZexGFx5 zZSZC^mVQDRwu-*I*c-shU+@gEN)C{&bM<7ev#t^5WnlN@StzQ~hv?}R}OnM3oD zgVx;P1!5KDt1ZanhAe6QUytr&HUW9#9N4N4;vVS8FIbT}RGR31CHQdQsm5T$67P4vhp#C$Ck>1F^6p~NKD*jw z(>y68tOH`7=pLX}CKfkYbjcsnPYAQ`-1}6;JiqqolfWdxFOh_cZLfT3Oq%V|n!fja z!u7uA^tU|=NSohQ1lKnAA8hf(r{fhAD=m?< z)V+9f#fMBtRlI*_(H`vstD)$Iwc8gogU-Xx`9LwBC^!#g?E>0=rF3SKv6mq+mP$u- z#KByKH7TyoWlqxWOdK)?^_)UcMLk!{V`2-MzqNCi8jB@5K(zZNPq(v%FSyblCzN>c zhVML=9>38sUqPC8tbBYr;r8X}g5#OlyZXf+$Zv9Yy|;hPqAvUz$#Z%jP~o-xgljA% zTT8Pzo884Z>(NY|%uK?`?LSnSxo%)B4OKF_t}GW6sZJx^=iYYGf^os!>xC zxYd1Xd(?f};@-k>j*=`EBYvl`_3?q|(pQOy6~6g#?Q=Es%`IqC1o$~`#qaq5s;xG^ zw$&~>=vpaJvc4fx`Lbo!MUE9|p({kToiz0BwX;FaG?(UR0Iq)~b6jd+xaM9Q8G*LJ_HrS@KzQJ}99^lNL3#ryDjKt*55|hO~`+8gA zE7xDbGt=jAGBAgX5g2PLfSoAwGO6zLi`~Lhc4lRqM|rxx@&f${Bks2m%KmLSKU`0Y z8J~8NO{FjEEio~ve1ubILzhn$XCx644@G{nD-@e5kWhM+P;H3HaPmTL*J`DNy$DeE zxhyvNIGH!kNWLQ9WslI_tI7D)TYj~XWUpuseIs@2m@5GFmhU%ZxLy?M{J z0os!Fv%g2J)#>Or8ZeG6Mrh2!#Mze|t=<^pA5E%S7Ze|X{TO{C`+?sNfT~L!*Ph45O1mcI=ro>X?=BD%aad&!xCD+e)m@5Ps>}V#9*BY7s~lx=21yV6WQ95 zU24sFzfc(Mm%q$%s(=ygC_N#Zx$0C=(-QQsYI!oIxp&jvN``DG&aa)Fl3Z;?}!ZY1i zrS>AnDw1Gw_AhJ0DTzYWQW*w0lgU07Fq8bpwGt;U6~2a(`gs2Qri!`EKQ ze7-X@$3k^HXT189oXmdlWC0ZXaQ%q(cCn1*6Cfu}^jjU`kA(C!`#(!Z4!tEgRfL*o z2%ZoCSicPb&;c-G5D+TXctwv(oH|(c7?t)NlNt@o3PS+$uc*C9HN?gSKHdXTF8Nd) zK(6qzIDdafS>;!r9p?(zba%V?_PHX$Wqsz#7yJ&HQ$#Svk~Ew#zaN6a*^~f`doheo zoF(&d2z&qI*kAz+M4Dtgs&0_Eq>tpYuo)UoAJR|cHBhikK|MHwNTq9l-b?4{s{0|T zWP6f48DRxpj`x&n>0u)0*(?FP9&;4Uu01WT3kP3|YWcx>QMEn}EeF*wZJf1Yw$dGl zt}n*?(6`XGx;}#-)z&ZROi4zIvKW-`#9ZGaEHAFQcF0>$&8xP_!2@;H-bUKlT(;JWc!Xo6LJYMC- zpfX>wFy?)O0kXB5S#_$Ou5}+Ta`9SH2q!DLbI?%!wP??{4oBwq3ftVAl{@Tg-KH0( znO`Mi-e~1(-BW9^zh2I zO(uPLhN*t7eA=Vpokg3c-KYgp_pwDh8y2lcE7B$|nlk_94OK$X67fSJ=}85)Se*bz zDg=m96mFqYvH#i&F$K-{KfA?QjM%boi@>2;Y!ZX}wM+eOJ7kvfI{G6TX`Slby}x`R zNfUR_8Ixg`+-4In%hDnC`ymma*`i3hIaZ$p9ZZIPE5_@1a>_DVRDrH3PhtxbCk)|(qXZNgTQjDD zWn_<;vg%5epAhtW^Ei^0N!Haj@PU~dB3@^7$`{-P7DAntPzLTEx^w(XfVduyFlcqs zV{K31&;8KoIL#4~7k>(IRGo~Gw2=7eRe|WEcQav?!`VbwukCX5{mIG+@gMT|;r1;Zo(hgyEM006-{6Qr+M{z4wjB*l!6(yb+`q1tFPMs6%=S-x~XCLSD zo>$su5A=-GY}Gd^@R4s=z}Ig*;>kM4?ok1G&XXNPi{6azmED3?KK$tv;g%59MgE+GaZ1n)%^*!t+%zRO z*1{`Gn)Ts}#-Z(Z3VG*!P2$&WWdNA;pYRX=*0_By8I*EF-8QUvwXmsqE`-iYFjxXw2;-=Q)m z(rzh&KLF&(aEMiT7{}2`AwLX0bw!e6pBIlRG}MlXyWf?#ZKEULZt+E$TBJI|eS}_M z+^f@Ipm^D~I`CF(&!$>qSvu&bB)N)X#P#%IOXm9hiKBbt5^8XJ*bfbcnAe%5X%(6- zO=W5asqf~^2x(mj6yD%y;p=N_hWO>X9Qa0IKzu+^!X6I5vZD)bdan@3WfXzOwxoG7 zFjUH)=$h??B42cBZw)(BRE~$hB9~5JcONWXENJ*tT={Np=oPQqj8vt+im5=2ZO=+% zV?pl-d#bD8qt^U3|LHzR0aUw}`b$H2PEF&jC$J5%5E@P1MSk5nqosv~~JE%I)_;tIl-+$H5c|at&qwN zFX72NE`Np@P>IOF+=KN4yS?@=S6~Z##E7`}82KGs=+N=53+oXsHfU`a>l=K1bsKY; zE=icN(53&vhI?y3Son|u#mpntqroPP2nrYBSSQYe&QoIB_PE0ngmUNRU>nZXE*C*e zoj=HOhv0$Wh_JF~8)g3rG~aI_8goYx?oVspq{74sW@?-gcu|OGHXm!=6e?fJkCB(# zV;}BPiWdA-+VJG4Pc;U6VxMG;RUH=oDBsV*tcgGzhgo6A$c7*7Wxhdp&Ti{2>IK+Y z`7E!GAvCNmz5}cFQ(lU8Pv@&AEI1}u+7pfw=K?r*MSRSBoD~%Jph8e;mT2Lo16`EuR|E2n(XNY=JA^B7|fzaaBgbD0WPKA9s zFq3-|)x3nit)Bs0J?QRw%NBR7wujI$^oR`CNy*wE0nsD_?&I9)d9gB*fgbMJNxE(X zY1;Rs@P|~ykZ;h368Y^zI(D2ho&FQ4b)>7X;<>yu+1k#e?=PSm*Md~b zG9QH9A6<*nY)@n!>0&kVl07-9)U#gEe?ts#GcnIXQ=F8Q zEF&ytFuSiu&M8=2hm8r5{}I*NLnie~>!qU)`?K^VqfaaX_-1LnJ32sJL4Qc&wd?S) zS-X?!q)gGxPTR|CRH(QW?{p|(`+ zI1l?i_d?DEpU&0bLGCul)O2fVe9pAA+ez1V)7s`6!=0ZHWJ0D?-g4V?NBRj%FZ%08 zm9EU0{L*e8FjhZrnsGLk>4Y2rI6DLUsp>_Cw9yhiC^%}Y`Mmd^Zf(7r`)Ih%@SwO^ zFA6PQXDT})cgskmUvD_HPIrgqZ9$0+1oaCB`_(FKsp)E~^*kv^6dns_*a}yMQm?`gtiF3QZeW-Sl19#?)}`fIp)OX*lWBb<}8#b=oD@ zW;SGd|8S6`sHt(CVQ~%P8zMK)(dMHNDy%(Horgd47)obR5ZR|7C#3aEQ9v5OrgIL|;qtR~r`4%dDj$8?k}ZsD zodJ=|4BIKiUrw#ztPJ6U-AiomW+HOucI_R+YU@DUvdfPFec#h}9exR3w!N2)O53-J ze9G+j#n_+j7}MVaaL|yY-@Wk9OnDz>4{ui+kd_4bB-*ms)lhfj^5V%Qf%N zDa#cZyLKuNw|dS2M;f!rtSJ(10{X*2Px~JZW?vxI8(U-@KUt{S#MSyau)P_uj2%v` zYzc35xoW@e#)@!#6A|OZb!O|Iz(0Z4a*I(8Ko1WP$g}8;pK48vX09SSpzn-ArhM{_1d5QDet&n*!t%`Mt zmdsv4U5L6B#IjHdKeoR)ON;eZLz2Y7H>E0_L%T^4#cb(QlFHY1E@@#Wlw!@4 z286c7bcC`@lk^<}ql7EggL6s-oN+^2*D%c@N_vx+uPC#XHM!ht`i9=kwWq(jr%>&^ z2z1lJo$fNnyvNT=k-t+?g8N>IX}PsnAHTY=WNmi!t(<_iNL!aSO$NdBLQSNk>rrp#gbzQCQ0I?R71mqpqKxHD6pIDmIn!Qn|PE z47a3{m$WpaVj$^T8{@Ot$Xb5onZR#}hRZ~G@F!}>tZcF3EaP*n1fAIF^@B2cGUG3J~}L9z7v_v0ZC7-NWb=21i4$y>(1R_+e*7r~Hs z6Cz%Ep!sq$zNK1U4BMTXkAHH0rF%&HFl3(Cg0I82Rc&==uh`$;j3aKFKgqdLW-u{$#%Z}1`9XQ% z;N92TPpA$)?DPHmq$H=#d4{R6Mp$)k0ls>bf96L4&zQy(`IC!oH8I)FG?UDzvorcZ z0l{w&1K$VlgjuzxIqA*`7sr|0n zTyDXw*bXfIxn-qz$|;rxzVI6K=1!x#L`J;3R7UuBc*cy3@xX@s6+!to-@0=&BZncd z-9%&(LNFGVb;(@kvPM(V_sCnjXN5mjD}tgf=pTKWjo zws8j}WlE^+gislwMtJ$V)8$wBt>_slT+EB*9BQwLs*n|-lz`mn6biP?>g_FA6lqp0 z02e*mQ2X~rbWwC3tW$T@kv3kyBpH>-R8232#3nRndG;#7b=pk%W@`r_h-_IENWZ*} zjqfv-+T1Pr7!ODqx~(29RWtDw6;f?K(=kAkSmH*`uw0vulaj*he2vsdT0r^HbG8xw zIB}7c4OEJzt1sWB=y#498kt(aukd&sEXa2J->x;W|NXtzaQ%W01l&R*rHzpR0Jm{% zd^}i-o8QzJA1ZEN(>q#5zY8<`$n{o)PhHYTFnx_Us&7N^Zw?5k*dmcwTU`CK_#fsw z4e2|r3=TGVPGp1zj75o0(Q`WO0}Oh^zW9Emw{#4jz7Ob;&5+KAHigq=E;ji+7?bTG z%SkEWfmeONM(00GOPBF0@lCczr3|GP-)hHFqpP389G^PNG38Ed+_LFuq2)J=Z2Ymf zBNVE>(*g27R70#a(vlk8OWDu`PV8t(L-EEOv)ctK1Mc!UZ}!B|hs3hqrK6)(<{v@_ zECQ4FMYSCS3RzoCgNcup^`DU37b&5(VNZ3luc|(V*DHS`7UE0j z)-b?Lc``Nkla<+#$wcQ0_2ar?-(;^ShgLPEsk;G@VU|)49?&kVtt!7*VfT}#3TMBV zK>kqaGlybH-ZzgO!aXAvd=Yazb8>1aHd_o0B-|?tL;^n^GCCG%AV&s$uV~*mDl8Jr zT=b1Zm1mwn-uvyuwJu_KD^jF(Mz6Ch!9KxDwBu_83N3H<87VceSx<1x$bV0o8oo!Z zg|;dTatMQ%gbzzrc&nUQA4Ma%w-izqPpHOAHud|*0UScFd+D3!CenfB%oT8~Ug*Q$w#O1)siB1Z2SMNhs zjIZT{cwz7^g+Qt0*TT)guXA!JgM{rQF%poo)D=~&Ds>NxpKNMfaRd#r%a^!koy4_L z>7)u9gLB&kB}XL_KtULZwiIH)b4%&+mgeM_r(M=rI!(YcLI zAKfZ4rJIIG=L(`UZs!!_;1xP2Yk5q@D24=bZi`qLZal*V$GY`jUB9>mrS3loGJ_6=dp7ux` zo5Y(x5C}D0;Aw!((QG0r7~Y%6c6RV=KCL!9qxT+mfIcj2*x_7|+P%;(^aXlAXyuCx zl80@_>8+AHLSuhj_QM+H*dE`^R?ZGzG1ODnqSbmQ$G_ogb&C7W_4l3R4MUUv#}`AF zh!F)31OUJxAe=Z^QC9Z-^CY_XJU?eYk7fT9D_Wcv;{}|bD98UGh@t;0XcfJ{{ZFFx z-@V`&&qbU#q!=U4^a3NzKkk!oQetXkd^|WGF+T2pN4fKyk@X+Vd5gq{d&_t6&v>rC foSaV;a~=+v{)=cI_e1O|Fd7FJ7o@x`^2hWqg^?=# diff --git a/src/test/resources/testdata/block_reg_existing.xlsx b/src/test/resources/testdata/block_reg_existing.xlsx index e3cb4aae263ad20d8a9699d2971fafaaf9b1c930..3c5d58150f7afb632a4019df94a3e5ae02374508 100644 GIT binary patch delta 4294 zcmZ9Qbx_m|x5ud^1s0?`cd3OHkXVtFl$KaPN~8p735g%62s{cbOG$@xcb7EMy+})U z2$JvfKKIVO@4e^W@0mGg<}>r1?>W;3G(=wo)R*!fn%Qyc zqu3jjqoE9?#B&QaTPLft>1-p&X{ut~zxqZ);9Ez_*LAn(aW9hG11T&!^$%(e7%Ho- z=Q<))dB$Ub)m{(bSB6iz@irv#FHU7uFoI!x zHbJet)(-g9$pBud%~h#`f9Vbl8%<^G?@R(kvmX;EjFNqAKHJ{oznW`4I6sh4vmpF{ zYOi+JgEbf^!S-2X*GRWhFL^uST2(vVixaRa5LeJ#0OM0GgKE9$uc@6YR%zjCtx-yH z#w`ZADvQvuELa^D^PZ43cd6L;ITCYcQ&XfD)*M0qm3BOWL&0jc5bb>`>$P8vqDJx( z5h`9<@Nd4@r+(GnIA9hSP2lYkwZ;{m0wr9_uap-bib#4RuyrI^TK1;2Hw{fpb&&c= zCUp`AU?4dIRdn`w572DH&5iGRpxOQG*}PwF(T(V_mpi-|LVrMS%*yvg!?F=S!nW%` zRS()?VVM0G04%sB$VfL{k=bn2rbP;$*=8GlQS=m zRGeF6a_zr9mS2mJ0{q0=81XSElG&d=hPuT=kv0p{Cgy|Rw2K~qu)m+d_mo@-G5XF; z@nNtI%bT4!a(3tEZ0gyeft+gwi;*O?SmLyD%XT(fkCH(;WQN~ZM>=PxHR`szLQvZ6-eZ5?l&0N;&eVeq>9xLzf+q^;RQ}LCZ`_y?@#4D0 zpeN3~zGnlB(sZYww#$-xgB0nuOF$!v6KUzq6e- zR=T{WtBvdq2i1X3g}UY;tCAp=z`^cBxjx_mP?!h|9Q@&s#GfGZ8d>Y^!n`UVS?(c3 zY`wi=9MfAHp8F`aQkBR5;xltBY#kynn=!WX6%cKs(SlA?io2L)!@McGQ-bu7Z%k-! zHot6+(GL4X*T%t&c`oUt0ONqWYKbh&H9i}h18IH)33U<-H6*3m=GaLWi!RV=20G~MTFL89O=O26CB_NZQXlMDo)+G7&v1VyJK_o3^||qwLxG?bE73?(v1-qR@(JzW`1jc zI_A;hP0>8{OD++ggBaca8dNMO=2mks=lz_Iq_u7D=oy5c`m~0svCJPu^p<7pLPqzZL@zYb6Eu$%7e#C|z`WY1_&B7I7bQQJ#lsvSmGDkpUR zyFLCM>)%8{ETLsZ-~Wcm0tgFwzjaJOX+-{mH0+Kb(^mlV2MPUdRI93Rq0N)q^pyW( zmZm~fNmq<0_rAsIqc=%IC}_|wXG}yS*>JEIWk1U(Q8uWA`7p5Alts>$auD$R2{(chH>s4e^&MN} zU9|l(hF%fNoZ1_CWW6sPGTyiYf2t#plr}s>34dT|rt#b`S?6Lo%Hvw$iLRZo{GLa4 zSHnj)o5|IN%B}U%%to#LXMCP+XSt$1Gw|Ch4X$jo!WZlCZ~;NdrFa=yclIgS#*%wM zbzus!MUR_pO|l;@HIwASp! z=50>BQGS0I@^;V7Ta9Uw5exd=OmUKcT0Iixd))j+;_7~*cw&{zb?L3ti>P{cYVlT# zS)>dTMwQ&bF<_TtlI#7rA9k*k=n9mjEvR%ywn${4LCN7NOTxLGUzaf`+oSIlXGjjb zq16`Z;So^moc}fEb_h&nn^8L@!>XH~sLc3A}jS=kpeb;BSdK@2$#&Tt(%BN)h zflBO@`Tl1$9u`&(B`TAQK9&yzq##Y)v(&%=GWwe`+RglAY&y?>&WAe-Vt9SP6Y z#qPmHB~DKNIm{ziA1_h0EPQ2?^5w2E?>gI(DZn>Uejg3BXIqUKo@X{)s}PEpYi8MQ zOS36SE6p8`OfchlB|X06QwT8L-_E!|V>dmz8YTnTHI=WF2KL+Gx(ESvO0y#2b%0gvrh_xlP0~>XvH09oP#(|G z!zFto)%F^(0B|zcFcJZzdf5DYrw;5DPW{#fucvAIQ)1O~`RSYS z24n7Q2d=&iUfst3O7?WvnAa@oeP8^`Gw(%jQldy+@7+Ximo*l(LDM*8E5r8E+;Tw? zQVZlK5k((hPax)Wbp5w4Xv3(E<%R-n|Y=wT)@7 zhq|VC9!}oMQV7$~MQS7RY=1e9;8=CLxe1J#S0S%@jid`obdjatL@Em8{>G|B?)S;t z7>A)sO)Wfry>F1Pp}@*aB_npB$GktZ;t~9K-yq6%(!k(rc7B8HLW~SIDlBXAw&f!A zq2ERg7+l1g?;9~A|KtG#ji&|13Kulg&Xq3mSs9WKe51RaXK4jL2}n3Mm_aRiCx)lu zlgkmZ{d~WJ4ko2K9Sb1(OV{*?PDwyc-sq5dZ))Z-G4uSAmCO!ZSK@n)I8R7V+D@YW zEBsKy9)M8ZnQ6_dzW&VG%6#)(ab$%mvpalax&YL>*!!b6Il`tnufh{wY zGdlGBCwcC8Lqaa==HUyu6E}B{^IFYGZlXU8;_O~?%*0;;3n;9gfAaNlVTt0%<4gS z9J5-L@hG25*c-YUquWoQx&bW1OLMi0$>K}i{fO`vuu$AO#E;Hh$5>XcDoLj9D*fe` zghbG(RSbRr>FNp!7`nh%T;;d@wBO^qNXQgMinTEW4> zN=IF=G6RQN9FM@aPOJCuIW02|L2tfA5PX1~{i6lm!&_u+)#&Kki+RCL0H|6nuvgV? zt#>xFwO!mD5_6hP3Z-V?uYRLcQ}qTfpYlgovUv8UeVH3`&^vWkNKL0rwH)Km>d#*o zrV$xc17g`>?tcy?YWs0SY(_jjM{pE~(Yv47Z2%QNNQIqf!v#^tJ~i*DZPoJ$DNT81 zqDprtsMKy?aRc#3@1 z=Dn5FRihbJ11hNNCwK4H5RKqsaM9Du0fOVc1zSSiRptb5`4hf4vD6wHB0{Juluv|PbU$+iMaFN&`<kj00+hsz zEJRG3#XZ-wWW8Ei{C#_h0`@~_X{S8l<;~H<1P*Plj1lixS!mv{q_hvPn_Fsxp^U%P+Dbp=l##A2 zSj_hCClo*Ow=ntdfX29)UGlPOokIj0Aa>`&j^C<;(*ET%c4>eaoF;_X@6O(}fW%&p zUCyStx23sHc+{4uITOqia*I3*2K%rjt;#zISDV3Z7}x2abLZv8MrY#Ei4F=0=^8-Q z?Y+kf@!x{zMq+YQk*S#_HSdBeEKTzBiBc30721ti)6D1iGjaaoq{}=H^6GJb=aMy4 zb+TDc3)CG3NSDAlE{oaUa3(X9pKzw}I2o#)8 zmb8}%3(MP4$lb%o(b^s5%0tEYKi)-vg+=wB1F#ZM#e7i4e^CFufydweM)802Lev4D RH1<;zjGq&qjOX9dzW@qi5rO~! delta 4328 zcmchbXHXMPx5h&Y(xga7dLsQ#FVayd2~CiWbOO?QkzxW8kX{mc2LVMudXs>Ffb=2) zp$pQaBURc(@67eR_sgC4%ex7=RCWW1``IAUxE1T4#!VXSRgap%q>%h zIykrX8gZc&%gly=zPp!iQsi1X?HXKmcbWPfBHblJPyhP70s{E}BcD@6TV@7&Nv-{Q z>gC_!;JB;wj8i|8)02WtyY^{vHKu=ba`TCTQchKwfN{A=Vxg+l$o^;#d4yBZZYp#x z5HON)SAW8np*!5Lz-m>|rvM564BZGfHu@psEG=N{TE(MxGL4YNhkPKoRrNl%6P@n7`}1d`m@PV8e!CDGTnni{hVxgXvd@oTMfiA zQ9WlOTcCddLXa@$;^$*j#gm+1<9#duvH7I=FWR^opS8hLTa$~yYU~s6C8cJ$*{ZR9 zi|QypqkTSlqAP}2L>=(G?c}4onrG4bb;9 zI&{msv?aY41_O*AqVajs*@9|oej<;dRo1~S@7q6f%7VH*pq!~h-BNGMM+9oDD@;h_ zgGP_u=P*^SCF4c}rY;4-1?v#qxbP5zMv)bR#IFhg0M|(}> z@f`zZ2#UEan$HHdd%oANNOLx5JE(Phc}0<{l{A&h-!Y}_kZ`I4zpH7l^MEUwAs8qu zJ=AE8DL2nr%=4n|sNY4(*9_9LD^2ttBzHgAJJ_E)?T5w4$+YhboZsu3ZVBiXzzoJZ ztJeXEKtP@NWzQc5o9w=v>ZU8Nw3gldc2Nm_G!*xA7(Z%umj_WyIuki);rNJo9a0ZE z=zUDw=sTW5}9N!1f4>;m#@8z4(L28>s{D z)tvlc=o0=83@_14V!#i3*nmYzVuco9 zK%`p5N{qjhgd;D6Aa8TS?)(_;BoHWYsN%Lu6b%g|gIzD*6Jp5_U|MjCgLTznCSn#k zP4{)n>;@2C5!P0VB;BmW%lj6p3IJ3)kh+GKSOpj72<1Tvf+wjvbqbO8?7j>pTn_vJ z3Kq!USbteP-#xXwB?Yx%oN4!+OsU$H2N%udrHWLxCvD||HWi-BzR3s*^%+}_QNbio zjdW1!x{3TcZYE~o_Q=@`om6?K62G{zM>>;Y7f z&-%=!$micoBuT0~R3cwo_x21FooD#qA&%fu8>z^vJ8>CGyCcrM11u(pl}xUJM+M4G zi+hz5m>X$=)z2q@fSNA>pjQVd4S5*sRS}MM}BMD7q1N!-Vt94HF#J zV5SXnIW!gcprlSB{`EE|f;c>JYl-Mcj?FFEBEA1!jj8nq;X%-~) z$O0KjW%uogFWH51*R?tiyjiAZ$zp0RxAKs5v$cQK)R^{=Prc?@I>@1t9^A>6xh%po z4{H};LPoD&vB#|4Fu{;YB{Qq9%&$lAn~84BZzg7QjZ3ejLA0*IoannK+$X&i8W{w5aCoMH1j~OUbe@0Hak<=e+f#0rM(p)Ozhup%QI~j(h8r^ zu;U&HuLqaI`biE4#G6f9;SEv{pOL^JO+c6d-7_~0$#{;z;}&?nG@9A%n-*CAA=!og z2Q|N_FJl6ZKVE(EpcU7veZw{IRe;o3KXgCYwkN!yID*^!3ErSu40vRMk>!bE0UvJoz~A|p7e#*gp!B2E%i5UzULJW<#`K(Jz!JCtWIvP z>9FYdf-yVS_eizioL#wL%!>#PH&j@OAuWStFOwCP#;!qqFus6)WLJEsmw4?AWSr<- zZCr%44Y&8N&Zw9)_SKfal3pwIEAv=()W1{QFI8mSzwn4ESm`)hi&*5kGmN zskaUUpT&Be($P4-T|KG3;(^u>lDQ{A%|~rqSXMUf(0#y!ykpo3s~c+xGWQt#*vSS4 z)sv#cg}zBX3zLhsR0{CjALk?@@<{1szzH#}7*sLhAfD1;!k#wM>5+%k$Kj5uLpq&bqYrUIPLE&Yd6nQV5hTIBIXf!|;j4zM7kygxTj@mNdgR4S z;O-ihfUhN26l&^jZ!+UX8YYA_Wa3!}*bS@hR)HAWD5pLQ+?%&Uw2T%`&Gg@!Tq!J% z!?Zhlwrl(pp7}P2B=k$%phuh}%dNZwtB`&&B__{-Lsgt#pHPL{rme*iihJ(nw*3`G zbjqk}s3L_j9Jz<1Y*{|R2BD;();Ys_0QqIhBwT-a?uz_4YwEwKxwbVhG%sUwSb9?3 zf;$z9B|mDwCB10IDbQQx@U)b?L5{bDFTpv=8H^RJeq7B^U1#fM z%hYSGCfMstB^s(a!e@;D9o_D$CI|Gs!na)e)Om)d=*Uf=*35#FRLO;Sy~V%=$;8>J zMK*c#48+RM4#hrvfe(OurBQ_IisP%5R?PL>;}OqHaAZm^R!b95WMM|=NdVQ#ck=Z6 zX7NBws9(&l`|~9;By>z7ILAu}a{*(ZES%dKp^U;rA@Z}DxS0a^>eCSb9&<&Wn^%}3SaM%M z;-*_FLZdy^G9+YkzHT%6rzd_XN2LxB3iI`W4e!;g^n9P1A%d2C ziKo53BeD+o2oW8g)M(#5+33!9fEbtv>;c4?kV&jvb8RmV7DLd?rzvA(VtNtBf#Mgo zUwuTw-HYPM7FAcpyn<`wCl=X7KJ%i?mSCjQ8EPbD(JL%Bs09Pw+;^{4MZ2Kwc<v1$d z-DXu?k*9G$S4EEEwC0Z+mz)KK3 zsYu@ni3CfRqO6{?Q^eKJXK=3{6+}d=C(E1fIfM|Y)<{Mwol$IFHVrijgR2^Nj%^^C z<4N3%#oPkJQgC5SqRyvpY?X;d18<3rS*eZp#NnX%}JbSTQWg!YgegdrEb zS^fAdX!Q~5YAG5uVItzK;V}VLV7_ky5da^1=l)7{I|V0p8OMOw)npAUoJnTkHyv=M(5?Hdr zhDH9t2)|F6j&_PoES?j;ZRGmKM^;p1I~fa47sqfkkQ{z3OwVJonpH4))avJ0~7#M`GTnf1o;huE`?xXpAn;{u|c`s6og_duj>?ga1@ zPe&ftkQZ_kqeQ(}L|L0R7DuY{@dJV}#=8WRSNkR-$!A`9PgI7WQ#RYu*-LTQtfQqx zd3s^`%wnR9#^`Scs;4fOdt>@XNN2w;0F1~!%puQU!&xBBK!s)^ke|5y;|%=>5zGB) zmz43TI@2I_!~$~-c#dR8?j3oLo`_m|8}m+Nh0gVD_hA?)xUlAcaY67WP2at5pySRa zR^)Rr@P3TO+O7*C`b@$Lu9Izjp`CR<>*cDBMh8P=_a{-dO;3v})E3hptxyMR;{Erx zK^8ai<{8AnqfB>nV~mS#JU9JKclkRaQ7?G-?*27I|2JPtfr9hUpr&~QabBW`c@=Rf zm{A(MtW5tSL^nQ;`tNo(t3^~eFBj9F%HEvleIr(s|0_R=`phejV}l~(;{=Lv|4#iE D>^%+> diff --git a/src/test/resources/testdata/reg_empty.xlsx b/src/test/resources/testdata/reg_empty.xlsx index f6cce3c4057d02bab170ac130ce8927f9133345c..af3d705b6302a08d8447332f54b046cc6efbabc7 100644 GIT binary patch delta 4821 zcmZu#byU<{w*cKDP1EWN=ghREl5dS z-}m12-S2+uogDp4UJ02nQE<%2S!%y0xJQJ^?5>f%yI69nNk_hmsE z6Ub#w)BA|HDD`UW{X@lq;)kc=AOsT*z4pKQCE96#Ti>p%T{!P%2{z7)R6RFMTH6}4 zQNOCs4kJVfHKo94WmK)XJ&aGAGT+zh1bkiaU_CDhO9Gq`LK<&bi-Q&$smgm$#;W(Q z1hlbd_^RD;Y(8|yw~DEH+EEl5WydROTi~X@u_0hyLsYpV8q*w9dbCd!rhG|G3hEB0 zjMcWvA}TA*=Jx}A`9L0?srXZvRh4rt|LbbFrhLui(>XwF->~vHrs`8Fe;6nG_wsps z)9Q0|oPlc=i|(1NlAl-V4mWiTb;@HW4<-)O9EV3LBv&YGgDFZdJvOtM<#ml@Dpf#w z>sL&Vf|us6)MW^)r})IAY&No)B5kElt|0`2AI8BZxDsJ}MA!;T+h=BH@mol14IJ)KFtgU8HW$ z^D*%NUMsR({)BlDIc|)tEJHUk@!Qi?sV&Le&x&@lCq(2L6VG2u%E!_s$zf%JD3qyJ zxz|vIsbJeym;vk@B}cH$o^#n*8T#Wz&Y?e9*k1r1LQVJ-51O7r$j;^JINeDp5Rek$ zv(H`h*8khnga<}!&$E8+A1K!4uYf)G2e>?rI^=ra|DF{K+}l5Vk})h;s`U8TZsjt` zzL&+&;S;-QnyZ-lZ}Qiu0a@JMQpJ;1R>!9vm&7ovai>*_HzKl`QtlfQYv_fwXG{u{ zCe|&Qg-r$d#a?Odp|=ztW)3V(7-aYwk93nu4`V6C@qbL#A5L}&4iU&oEA_Q2N@tDemM7Iw{TJ)3`xU1P)Cn9nuUeBUKT>!5E{%qFa`a`jPe<@s5# zuzV=?w!}iFv)U-F>5G9e0%K?1{Qnt8RyYs z8ba2N-JWM}S22KWabj6^?8G|i2p^U`IO4~Xf-();($*Ulo+Sye2VJdAjGJ>$9A*C_ zDE7dSocv{?(-#S8_T@k+0jk_6vaG`Z=~P`*3ZIpEvbM4OX85od(hg76TYF_=$!?Ou8EvYzv^~`bbUk-CtmpHVf#6mhO8wZ930Q$k~q3Lej>l z_~Dm6&)>WOzcl@rt2^6C(QN-2;ub9&<*yN zW$T~LpYIQNry5Q7IqzEsFv_}x>|W8)we9r>ddyO*)B*HSh_F;U z_81PS7*UV4YX92Sg0sn#rx$r`l$=^FOg;$~M@Q1t!Z_lHFH->+e4)9Mm=X!L6n2-V zxEA-gh(c^C+>N_cP~#s0IeF}X@M{|hR(*|EpZQYvndYr0RiBws zu8P6^HAy*~f0L81F)9ZAQSQ)m=}_~tRqykcts7>Uz93`UT2G6U?egn+QdC%aYqq>O z&lS_dZdk;9>s+zLH3^F_o2m7QP;3#|r)rXvUj!sJuH}!M9aw;=mQSQ44)RQ*ox?N! zLMdOeneku9#YbtCP}k^sg6Di9rFR@z?!(LRfKu8364bNz-@#%poLE$lp^WG3uf$4{ z;=dLnjC9~fjT)76sHvP804^?8_tsR@T;|tYZ0zv*>Z$9c)cVtB5*gZPMIr=@5bvEV z*D-b$FoE26vjaAn>yx3MwkQRxa`+|Ng|51Pbxyd9+vT991p?nL&|3?+Y0;+YiQs9C z9El2LCpQG&xy}97@q@TGJhmKqiPxC%)CgW2s`z!?dx-DH8uvN0orIBEem0Ew<!VV&--!%YXT=5oaZ=UG9yNvM4VjNJ-#ltP=79-R*E-9KmrP4IA1bL)=HfA| zPwnxe_`sMGdb>L^7)W`oooKp#AFAT(8EgwgArpj<^L1zRA{GWl13n^yk`@sAX8?`o zz1IH-472MU>xrBLt5R}g)eBMNwWQeRL3ocFg|i2C?bFjEKg6r_y>m?J25A|EZ7*&} zAL5%hT+-5&wTb-aA`?7#(1D|IGvOwzpd~e-gm{82cQ_?&nEu;_!_DoY5C3ek72JzK zclcLAP09#fUG;oYx#4@j5hbQ%SE=u|sxJu3=hrUDe8uIp`dnTL$;O`bNr|zT6>Tc4 zkmZqoGH%G0#QjC-E`-y!(?DaM)tA=@y4Nw_kQ>wgnB!ANR&l@DyiM15X>l^nZp4}L zXr2k~5r#rO?DffA{Ig0MbvN3-BzZm>p_s=M1JhumZpgGVW)3tEU$;)L2ljzdhY50a zTe#zfHlQ91gtVAW2}zhHyQtiS9KD0w{2s{pcG5$%=@x&>j<)5n`Sipnb^T{T9w=s; z1U}R^t=NsTvOBmS;{UUs5(SQ@QGgSM3YCWON}|@X@siq?bb0ih`r~whGgF4c@k#Ct z1|vCfeD?4s&V+!48u4G9z7WIHM9qlg;Tjf>htlL}KV(JHd_#K&d>vQ4NC}~NVhP{N zEXC>tN~cM7Wy`)y{?r|*#h!`J-phl0&Q!!#Ix^S_9!}~kucvJmf7L>Zr>Y!;J<=F- z42~M2$rJBQjql~f3I`Xk3-_#oqZq+=F3~XlEnK^}*D`?WxDW_RK7;j+Y_4zI9p_vm zI7X@Z{d*o(jdK!_+QAj}GS2Hs9OY`|H2mhWlUOBpzR&neIs>?4vt2Rer#>saTMI!& z5;aX+I~K5fmNJPNS4yzJ{HgYj6XE?(h-aJ&7Jk9T@(7VS3Ipc?Xou855^GF-XW z;LpFFC6-k6O}h6j40lLlUc=S}N<~DMVYRF8Fymt;+?79=syBV)(cbv|dGJyZ(Mv#0 zFrnc-$A*o8(Sb;(WC60>|MpUG-8u_E!VRPE+H^F279sK=)^yP$+Pku^5p4q%rb^xW(1Z&|?V{3%zwbOK2(N?QVRr5p(JSOkv7uyw!q1Ee`!9$SuG*Y}P5NTkd&0oTWy zs;o&h;xsFDqJuw0quN)N6-a7=NW4iEIz=_%-Q>JTOy}q)jYOlZs5Y~^VWxTMidVU3 zZ#mHCp4zQ|A#HH_i9$}MTt#z7jOh`=u1{T)_yM!G%T9?R>TWj5+k`PMb;cmRJ(v5{GxibA{pkhi>p4)>G$n^FhvRFvRss>a~g-Cn_1xYDEaSez;m0kE}&D7)Hv zNoa-Ujga^n70!YB&&v)&YbxTK@mfxZ!(gVh_$v`$oa!|XYvM6i#ygK7Ln)J zklY=Bnfz83UQ1vEd-J1Ed&vbWtRlfS|HEu7-L^iuMmx-%Vmn}6o3dV#=|p%`aA90k zqJQ|Ts8Y0Q=(z_)o$Z7HkzMfVQZ&tcmy3hHNBtVgoyczPv=djvEyDc~321)ui;=T( zxwxFg%kBGe)%iK~$De1FSLmERFVOTIKBu<_Oao>=1;?2rOAZR}wZ9ZuYM&RYe`px+ zJP!6g6f}6y)-u!$e4ueg zmi^goGqiTiNlMCRF7o`g)Q)*VW%+#w`;ufCDfL8bR(i!8$Yx|MjDGHM#4gr*Ua2%;24_N)4uP|YSsT*0Q$wr==Lc04*PF`Z{mHhM?`=iSk@li2)qQHeoavQe=_1lmsm;IwN08B_XL5nxlX>~Fv}Jd$t#u}=^P828PAi(D znbTHk)ZYm7IDtWKNC5!45r^}96b=LnKAfRe*IHxjEZ74d^3FN{u9B@v)f8l=UJ=Ky z4m{!SW&rTcJ%#hKPF&vG%ku_xp(PxjkPArhffD(Prpa3_E1bI$C3Ky(FeEANY^wv- zRbOeO;qYa&Zs+?y%zSp|jTK`#dGZ_9cMV%UD%YOezT4T~jWMosQK2@!Pa%roC#OFTkV@t7==MolZ!O*95|L za3ZRv&gqLlED+;t@x(YX@rI;_6)I>-D@Ml55-9)I?cnB-wb(9km$6giIjKrO_z{p>^?cr0VkTlx;-N1dd?J(L%~V z#ABUs>!DuuuHtN{HIIfH66-66A3z+d(i2Q^d)39rmuLpa*Qmz??%lg_nvC`@*|jYp z8EcFWzaNuX>J)TFTha`X`3M6MgqED|*>4FMWY-2cpotG!>IDb0U#JNgM&9#WQ78$1 zu*C3t<|$QIZRGuy1;Ria`Jj|uZC_v~-9}r2n9Oa$yLXumX-%YXl)bv<3KWWb8BVC| z;d9TF*sR|4V#?$pUmC=s4;T~&`|3PSBp;=qGC;KYj8~e{c8x7uhJ!>=GKlj*uyw@ zW6nY_l}YYL*HE8DQ!7wCw|YdPpoK`O^pa31J>6+Uw!N)eT)x4_R%E;8`&sR7{o7$D zzPt~#kyRbAT*azsuWBD*zNjQNFoM-T$-u{H&a5F-$i($Li@!^PXVi;AhCd(*ZG5_Q z7~ZtZ5blw^FlOCptdUA)>sdLr#C{jg3YADEN@bRI;th25f;FeOc6+Sn7~R0&A1#m8Pc6xuSNFOK6PXJc8cH*` zefzgm;_&0m7dKIHb7KP5Ix)gj11nlLB&A2XcLEi}^(r~?Z7#2#pe;P17VOQXwSQfo zL*~+-pzT9Zw`X~OIi0>MI}a>;sVl0gN5)3VVPH9|mHKsay8CHCgQ2fEKY@&Fxnh!$YO@ZTlf+o^3@DrYmu3H2!_C;LhNU%p^ahGy8r~Kz=H`+ye!I z`vbHhbQjm*^*X<4gn(FUlZ(&cM%Ke#CSctR_|w-Qw=5y!Xjex=$IkdF3UEgYX8u}Y z$)c0iOIX{036{y_L6PZX{_1x`NRTF{*v;Zv!45s~5ypk(Q&DkQUg*ejv1KtE3A&%u zK#^pOhpUO7qRbl>*>ojz>NCY)f1{&$D|}w^muPN=H0Q{xU@WTe!&5*%47(|v*8%%~ z{&lVd^f5;ent~!U^_UnK$Ulpg2IJok)t}}NqDE8+($X~kzkuJw{{W5!sTuy))&FOf zb<#f|K8AmpAcFq{oKpOgB}@p4r2#?I3MpdEF(M9ym>K@H{wXjpsQ$e$3`sTwFO-Yn k?{0zpsm%Y+bAtT;0Mxh;?ofHGWgbKolnvCt|99}e0Ac>;wEzGB delta 5054 zcmcJTWmFtn+J&JTYuq8wxCI)AAR$;FxHiFE8VgMb65s+UEWsr-5Hz?332q54L4&(D z32?C>K?4joGi&ZQv%bGmKWf!Fwby&js$I`M^;W9IxaG$=9BfHoV}2C1jKctEF9=cw zWYyl1UPwz2mnDsMb=wSC*2x>x%o{I+!`ACh>~&g^{cRjD=7B=a&h zR{Y6+d8imX3Brntd8F8{&ohV!cC8TjMRpX8Kn%F+M`B6ihs1KNv@w(?y-I=oT*Z`g zB2P3n?V+WA9}%1i){h`(B`5$0?5#`R%|12xIBEBImbK5CRL4xvIG37bnyEu4>h*?A zsgHU1cs;Yp#teqlyMB{ziN-?IfrehTiZL6nH5Z!-HS;4CY3 z#dT(3jfO6zGuNJ#K6WpUHkkzn_VR5@U$eJ|?j~B*Scb&0JTZz&^7I2{YsODdWsY%A zEZbFnAVfja&Y#E?4rL<5wwhi$y4S`xH6qJ*s!A`1@f-9ckKFBdeR;}NC`3a`OrkQe z%?*p{hFH`C zC0sgfmSUeKJop2)P<<_O#MFye)CVg_nhflnBb@@9Vx>GRO!hi_#I7Oqo;?O1&8;Fm z{n8vqok!e_D5QhY>*a@(1pw2wUSFrLa-jo zPuhwzG|x3hEcH*?L-2!uwo`>K2%OMY;g! z0!iEYj9FcKjr#WIYDq@J{YwtFqL$VYi6hV5qUpK1AFkE41I6F z=~~O&*asKdvpHRKxfX_lWLU^{^P8h^thL7et=7jV0F?!%Liz9#*L;J>gD}@oM8u4M z5Wf%7B_pBVwNsGv%|X&7aq>a8YIdEn+_YbA~4t?)Qqh@1WYD)fW@g~QAx*i-3 zeQpbgpfCC+GAsIVG7P6TL?dj}+VNmX#@|oS0n2{*-MFsomE}Ph`yCV88#k_>>Ri9U z0_EbZ{Ix!irbP=0@Qf}h*JFkxk3Sp+!2C-I7rmJ8mN?97L}SA=JP=z!@lDTY>v@v` zQ>mU2aYC7iof!pRS(fxNIhmM+Oe~g9Yib)UG3V;k;a?G}MNrS#IF+HF#P}X0VA9xa zek?dYi*s$hCeSv4IDF<)2(^0|ej$8KMlZy#l|T5avXdSB-70S$^j>!x$-+Sba2fpZ z=I8TJBJGc-O=k55!bK~GD>Gd}PA%t)3`ki|;beT+qWu$SqGW*D{q$(5Lw}yxOTc5? zx$5J~DLU#cHrKf7sG4J~sLEK%#jcw2wnRdzNX7>OIU73r>r`#aJ))w?_d=Cdb+Q5cd+49z+$tK{KQ(#Pz7D|*zNl)# zs`Q0NMGWEh2Q=*wrp2YU0g9%Yf&@4ILPQ`7X8 zb#_3J>}xzNjZ!yt?rR1^{#*f`)vAJ}R zc(Edix1hhx{_VBJ|K4o;bMj=(zb59xL-7*c14Mo^j{3ihud?erWd)VW6)*Bkesjry zRQ*Vpx7VBUK44G0vpt;LZ(X-FRrW@jcCVI|-p#@UHP*2;_j%u%~{oQpLauwY;%sAp6J__iB1*tNR1AAe^n zHzI!`UDzagBB!#%M`%h3Z|{z*!=WH1#f`+>U3pi1q?|_Guqx{P!E1`#z%blMg`=xA z2%h`op()+Y`Qw~@^Tlt$yU=BbOr~wB{dH+Va;Ne0s8J(3NrH|G52gOBoL$8M)OB`d;;sd8x2woCcw_J4&yVxnnmupsU0hR4l;LCLwX-0VEd2m67{5!%kYW2sA~%52hH$Lnm@ zmq|Ba6w%U8^)qR_-lh(2Q=7qa6s^AYj1@k9iBQIph0`FsZ_^Fy%VNEFas@bq^<+*@ z7wsEtfTiOHdJGg6A{5C2v$*FmZmV4qk5j;jcwrb+on-B_YqMdH&mN#cB@cZ2U^g7j z4#{@vG=e~6sCr=H9zHXoIQXGSOflqGWCGN+5G0z^~2CkcYY1iWx=`_}ZD;>BP zSdjZ@F)T8cp%*ZpYG!yV28vm|D_bCjiE0H7DK(mh3BNOY5XX}nJ=)Ap=e;#I|7_62 z=_W(v0v36p+3pe>AjaZy!|naQh(PeaG7#UY*7}#r-E$v;{Ki7`pIDe{cM4LzInew? zqIr-*fB)@k>)cSs_1C(SI~7v4MAs?Ei^#(^zlT_=jVxlO-Y=)64}528#`ct8)u7FS z2a=jC5Du$QzJk=v6QtPQqRu*4oWpQhPOD*cd++?nrncU7?b$y!zn$NkpY!OVHxi7K z^%R(PV*D{ZocbL4qdGtpHm#n3IblZE%x1R_eN)cEZDrE~4}nO9gL}Z!2hZltr8T2G z+|pWls<-hz&?{2H`#&Oc5*^=4kHmO=(%JGuS-g-JBaUNyfBjh_T~>D-F0q58YF4pt zNz>jVnTvn%f;sb2mH=fTgbTYT{)1%d$`VlB{uwlN74q{ILJze%^k{P79?W>f(Uuo7 zBdVp!wcWSv=s$5-oY>ekM)gAH+X?@$eZXBR)&t8NW!JhIAOQ>cAiO|2ZjLHxf@d@@ z*4PTk*)Lzpv%8aR;P1fB%RVhR)YuebMqtvVrV4RnMREolW<7 zyx&m2`bAk^hA^Nl+iM8?jFVh>6`$YLkR<^d-(}766?kM^zw9JpCr^MhR)h zCCRYXeL3)k{5e$^;$hlv=H}dsYU9E#Hvp!%&0YhJjoxI356catr=h~6-WvD9oz-ZG zjfd}pt%te6PJBFUmAE1_VU5X2Z&zBx;%6d-NoUixxI*j15`*gMC(YbMy`Rd)&n(k= zX|GMW{VyQAWAJxbFoupg7>g1E z2Ln|B#;Gx;-XVaLasA{NxZ~0nrOCw;rW!Jj z-5ggdQ4P_{SA$nf+|D!23M}Mo7{w-5WZSHXDp@HiIDct+FS4ImX(OMUO=MVo;I;Dd z5lHxH^q}#|mGHf~ua9halcMoCat@>e4@W|-Kff1BUTqma`P@|3@3$hp2DNbpmP@hN zEAG0J$@RSV7o1~qsj1*uqw}!Lkq9O|erfX3r}M4eMmnvJ;LW2Kkubuah{Cxhx>fGFB3vsq;`n<*#}OCSULp!H0@CMLZ?^EpL-(g!FiZJf<4(ZGq8);GxRhS zLQRg<-5TV#XM(@BaQOHDbdH1%gl%{}4$RvrT+lV8E3<21H$=26Wr`4mETVL5LaVWQ zChhp39z42l4ywgoWd=phWb}{tHpa(3sJpq4l9|=l6(@-GI_}suUM^=eZX}95!lPA& z4?aWhueIc%Q4eDK>g^&w6rv0(bx11czQd&JbJrJg|PC?>dM(a7}hk7R9rKN z@&@~RhUq)aj`iQt!@D0s4*N%Esf=Kz!<217E7buu3 z=1gnNamJ6r(OX-<_cCq}+6Jh6Jp}%Og*)iLZ>}`TC4VL02IH*+%~Ej4_9U0n!lAA- z<3uq#uj+d{hX|#LUfQY$q`H2{d{uP$c6d^viZ<}iPv)tze~!C;a1-RoiL@m|v3N%6 zxw+2zHpTKA8lURJT1D;;y22$tcm|=huDF^ zC&x#ZJVg)l2HNv<>7vnAVC@94ykpVFrk;WA{Tp}G#p54#ukjs!ND9i&QOHh96l86ypbH`0T-?A7Y7vSEwZGK zY3(jqCZF|ot&uV)Aqlf&J1Re1ZPq(~LDazTi-+zF7h|p`avNsQA@simhn22V2FGBd z&_Q`~_L^m;7d?j1S%bYQvv_HT4%8oK8XO+K7S^&H>Aemx2>g4?o_QKrD0xTyBu{e6 zi0$;DD?EItT64%7RrFjlFzSNS!<$7*=!Q$H(}$W9{U&9kT2+|&JtiXk>L@F5<*(+h zX6L>R_+P)Qr3z%95-Roc7U{rjLGL|$07iT#phAiB zQ9=K1BmTRJBI$2Yz@6ytkE;I``BMBAb@2&+=xI Date: Mon, 23 Feb 2026 14:50:01 +0000 Subject: [PATCH 08/13] Fix merged tests --- .../service/TestBlockProcessingService.java | 130 ++++++++++++++++-- .../service/operation/TestAliquotService.java | 2 +- .../confirm/TestConfirmSectionService.java | 2 +- 3 files changed, 123 insertions(+), 11 deletions(-) diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java index d0d0e9329..43d0eab38 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/TestBlockProcessingService.java @@ -1,15 +1,25 @@ package uk.ac.sanger.sccp.stan.service; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import uk.ac.sanger.sccp.stan.Transactor; -import uk.ac.sanger.sccp.stan.service.block.BlockMakerFactory; -import uk.ac.sanger.sccp.stan.service.block.BlockValidatorFactory; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.*; +import uk.ac.sanger.sccp.stan.*; +import uk.ac.sanger.sccp.stan.Matchers; +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.request.OperationResult; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest; +import uk.ac.sanger.sccp.stan.request.TissueBlockRequest.TissueBlockLabware; +import uk.ac.sanger.sccp.stan.service.block.*; import uk.ac.sanger.sccp.stan.service.store.StoreService; -import static org.mockito.Mockito.spy; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; /** * Tests {@link BlockProcessingServiceImp} @@ -39,5 +49,107 @@ void setup() { void cleanup() throws Exception { mocking.close(); } - // TODO + + @ParameterizedTest + @CsvSource({ + "true, true", + "true,false", + "false,true", + "false,false", + }) + void testPerforms(boolean success, boolean discarding) { + TissueBlockRequest request = new TissueBlockRequest(); + request.setDiscardSourceBarcodes(discarding ? List.of("STAN-1", "STAN-2") : List.of()); + Matchers.mockTransactor(mockTransactor); + OperationResult opres; + if (success) { + opres = new OperationResult(List.of(new Operation()), List.of(new Labware())); + doReturn(opres).when(service).performInsideTransaction(any(), any()); + } else { + opres = null; + doThrow(new ValidationException(List.of("Bad request"))).when(service).performInsideTransaction(any(), any()); + } + User user = EntityFactory.getUser(); + + if (success) { + assertSame(opres, service.perform(user, request)); + } else { + Matchers.assertValidationException(() -> service.perform(user, request), List.of("Bad request")); + } + verify(service).performInsideTransaction(user, request); + if (success && discarding) { + verify(mockStoreService).discardStorage(user, request.getDiscardSourceBarcodes()); + } else { + verifyNoInteractions(mockStoreService); + } + } + + @Test + void testPerformInsideTransaction_noUser() { + TissueBlockRequest request = new TissueBlockRequest(); + assertThrows(NullPointerException.class, () -> service.performInsideTransaction(null, request), "User is null."); + } + + @Test + void testPerformInsideTransaction_noRequest() { + User user = EntityFactory.getUser(); + assertThrows(NullPointerException.class, () -> service.performInsideTransaction(user, null), "Request is null."); + } + + @ParameterizedTest + @ValueSource(booleans={false,true}) + void testPerformInsideTransaction(boolean valid) { + List problems = valid ? List.of() : List.of("Bad request"); + BlockValidator val = mock(BlockValidator.class); + User user = EntityFactory.getUser(); + TissueBlockRequest request = new TissueBlockRequest(); + BlockMaker maker; + OperationResult opres; + when(mockBlockValFactory.createBlockValidator(any())).thenReturn(val); + List lwData; + Medium medium; + BioState bs; + Work work; + OperationType opType; + if (!valid) { + doThrow(new ValidationException(problems)).when(val).raiseError(); + maker = null; + opres = null; + lwData = null; + medium = null; + bs = null; + work = null; + opType = null; + } else { + maker = mock(BlockMaker.class); + when(mockBlockMakerFactory.createBlockMaker(any(), any(), any(), any(), any(), any(), any())).thenReturn(maker); + opres = new OperationResult(List.of(new Operation()), List.of(new Labware())); + when(maker.record()).thenReturn(opres); + lwData = List.of(new BlockLabwareData(new TissueBlockLabware())); + medium = EntityFactory.getMedium(); + bs = EntityFactory.getBioState(); + work = EntityFactory.makeWork("SGP1"); + opType = EntityFactory.makeOperationType("opname", null); + when(val.getLwData()).thenReturn(lwData); + when(val.getMedium()).thenReturn(medium); + when(val.getBioState()).thenReturn(bs); + when(val.getWork()).thenReturn(work); + when(val.getOpType()).thenReturn(opType); + } + + if (valid) { + assertSame(opres, service.performInsideTransaction(user, request)); + } else { + Matchers.assertValidationException(() -> service.performInsideTransaction(user, request), problems); + } + InOrder inOrder = inOrder(val, mockBlockMakerFactory); + inOrder.verify(val).validate(); + inOrder.verify(val).raiseError(); + if (valid) { + inOrder.verify(mockBlockMakerFactory).createBlockMaker(request, lwData, medium, bs, work, opType, user); + verify(maker).record(); + } else { + verifyNoInteractions(mockBlockMakerFactory); + } + } } \ No newline at end of file diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestAliquotService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestAliquotService.java index 5ba5e4722..3f1b5ffef 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestAliquotService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/TestAliquotService.java @@ -273,7 +273,7 @@ static Stream validateRequestArgs() { Sample[] samples = EntityFactory.makeSamples(2); samples[0].setBlockHighestSection(0); - samples[1].setSection(1); + samples[1].setSection("1"); Labware block = EntityFactory.makeTube(samples[0]); Labware section = EntityFactory.makeTube(samples[1]); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionService.java index 018a4f740..949bea5eb 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/operation/confirm/TestConfirmSectionService.java @@ -607,7 +607,7 @@ public void testUpdateSourceBlocks() { Map srcSampleHighs = updatedSamples.stream() .collect(toMap(Sample::getId, Sample::getBlockHighestSection)); assertEquals( - Map.of(sourceSamples[0].getId(), sections[1].getSection(), sourceSamples[1].getId(), sections[3].getSection()), + Map.of(sourceSamples[0].getId(), Integer.valueOf(sections[1].getSection()), sourceSamples[1].getId(), Integer.valueOf(sections[3].getSection())), srcSampleHighs ); } From 1e185c826a75291fda074c33d4b978ecec8b5083 Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Tue, 24 Feb 2026 16:18:52 +0000 Subject: [PATCH 09/13] Test BlockFieldChecker --- .../service/register/BlockFieldChecker.java | 25 +++++ .../register/TestBlockFieldChecker.java | 102 ++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockFieldChecker.java diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockFieldChecker.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockFieldChecker.java index e39d422d4..1c99aab92 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockFieldChecker.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockFieldChecker.java @@ -48,14 +48,33 @@ Field(Function brlFunction, Function problemConsumer, BlockRegisterLabware brl, BlockRegisterSample brs, Tissue tissue) { for (Field field : Field.values()) { Object oldValue = field.apply(tissue); @@ -70,6 +89,12 @@ public void check(Consumer problemConsumer, BlockRegisterLabware brl, Bl } } + /** + * Checks whether two values match, ignoring string capitalisation, and ignoring an empty new value if the old value is null + * @param oldValue old value + * @param newValue new value + * @return true if the values match; false otherwise + */ public static boolean match(Object oldValue, Object newValue) { if (oldValue==null) { return (newValue==null || newValue.equals("")); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockFieldChecker.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockFieldChecker.java new file mode 100644 index 000000000..5857d6752 --- /dev/null +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockFieldChecker.java @@ -0,0 +1,102 @@ +package uk.ac.sanger.sccp.stan.service.register; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import uk.ac.sanger.sccp.stan.EntityFactory; +import uk.ac.sanger.sccp.stan.model.Tissue; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterLabware; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterSample; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** Test {@link BlockFieldChecker} */ +class TestBlockFieldChecker { + + @ParameterizedTest + @CsvSource({ + "false,false", + "false,true", + "true,true", + }) + void testCheck_ok(boolean hasOldDate, boolean hasNewDate) { + BlockFieldChecker checker = new BlockFieldChecker(); + Tissue tissue = EntityFactory.getTissue(); + LocalDate date = LocalDate.of(2020,1,1); + tissue.setCollectionDate(hasOldDate ? date : null); + BlockRegisterLabware brl = new BlockRegisterLabware(); + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setDonorIdentifier(tissue.getDonor().getDonorName()); + brs.setTissueType(tissue.getTissueType().getName()); + brs.setHmdmc(tissue.getHmdmc().getHmdmc()); + brs.setSpatialLocation(tissue.getSpatialLocation().getCode()); + brs.setSpecies(tissue.getDonor().getSpecies().getName()); + brs.setTissueType(tissue.getTissueType().getName()); + brs.setReplicateNumber(tissue.getReplicate()); + brs.setCellClass(tissue.getCellClass().getName()); + brs.setSampleCollectionDate(hasNewDate ? date : null); + + brl.setMedium(tissue.getMedium().getName()); + brl.setFixative(tissue.getFixative().getName()); + + List problems = new ArrayList<>(); + checker.check(problems::add, brl, brs, tissue); + assertThat(problems).isEmpty(); + } + + @Test + void testCheck_bad() { + BlockFieldChecker checker = new BlockFieldChecker(); + Tissue tissue = EntityFactory.getTissue(); + tissue.setCollectionDate(LocalDate.of(2020,1,1)); + BlockRegisterLabware brl = new BlockRegisterLabware(); + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setDonorIdentifier("DONORX"); + brs.setTissueType("TYPEX"); + brs.setHmdmc("HMDMCX"); + brs.setSpatialLocation(4); + brs.setSpecies("SPECIESX"); + brs.setTissueType("TTX"); + brs.setReplicateNumber("RX"); + brs.setCellClass("CCX"); + brs.setSampleCollectionDate(LocalDate.of(2020,1,2)); + + brl.setMedium("MEDIUMX"); + brl.setFixative("FIXX"); + final String fxt = " for existing tissue "+tissue.getExternalName()+"."; + List expectedProblems = List.of( + "Expected donor identifier to be "+tissue.getDonor().getDonorName()+fxt, + "Expected HuMFre number to be "+tissue.getHmdmc().getHmdmc()+fxt, + "Expected tissue type to be "+tissue.getTissueType().getName()+fxt, + "Expected spatial location to be "+tissue.getSpatialLocation().getCode()+fxt, + "Expected replicate number to be "+tissue.getReplicate()+fxt, + "Expected medium to be "+tissue.getMedium().getName()+fxt, + "Expected fixative to be "+tissue.getFixative().getName()+fxt, + "Expected cellular classification to be "+tissue.getCellClass().getName()+fxt, + "Expected sample collection date to be "+tissue.getCollectionDate()+fxt + ); + + List problems = new ArrayList<>(expectedProblems.size()); + checker.check(problems::add, brl, brs, tissue); + assertThat(problems).containsExactlyInAnyOrderElementsOf(expectedProblems); + } + + @ParameterizedTest + @CsvSource({ + "a,A,true", + ",,true", + "a,,false", + ",a,false", + "a,b,false", + "'','',true", + ",'',true", + }) + void testMatch(String oldValue, String newValue, boolean expected) { + assertEquals(expected, BlockFieldChecker.match(oldValue, newValue)); + } +} \ No newline at end of file From c02140decebf4b26464aa097604276951fb8d867 Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:45:54 +0000 Subject: [PATCH 10/13] TestBlockRegisterValidation --- .../register/BlockRegisterValidationImp.java | 56 +- .../register/TestBlockRegisterValidation.java | 730 ++++++++++++++++++ 2 files changed, 773 insertions(+), 13 deletions(-) create mode 100644 src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockRegisterValidation.java diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterValidationImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterValidationImp.java index 4146bb85d..4d6fe58e5 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterValidationImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterValidationImp.java @@ -31,8 +31,8 @@ public class BlockRegisterValidationImp implements RegisterValidation { private final SpeciesRepo speciesRepo; private final CellClassRepo cellClassRepo; private final LabwareRepo lwRepo; - private final Validator donorNameValidation; - private final Validator externalNameValidation; + private final Validator donorNameValidator; + private final Validator externalNameValidator; private final Validator replicateValidator; private final Validator externalBarcodeValidator; private final BlockFieldChecker blockFieldChecker; @@ -56,7 +56,7 @@ public BlockRegisterValidationImp(BlockRegisterRequest request, DonorRepo donorR HmdmcRepo hmdmcRepo, TissueTypeRepo ttRepo, LabwareTypeRepo ltRepo, MediumRepo mediumRepo, FixativeRepo fixativeRepo, TissueRepo tissueRepo, SpeciesRepo speciesRepo, CellClassRepo cellClassRepo, LabwareRepo lwRepo, - Validator donorNameValidation, Validator externalNameValidation, + Validator donorNameValidator, Validator externalNameValidator, Validator replicateValidator, Validator externalBarcodeValidator, BlockFieldChecker blockFieldChecker, BioRiskService bioRiskService, WorkService workService) { @@ -71,8 +71,8 @@ public BlockRegisterValidationImp(BlockRegisterRequest request, DonorRepo donorR this.speciesRepo = speciesRepo; this.cellClassRepo = cellClassRepo; this.lwRepo = lwRepo; - this.donorNameValidation = donorNameValidation; - this.externalNameValidation = externalNameValidation; + this.donorNameValidator = donorNameValidator; + this.externalNameValidator = externalNameValidator; this.replicateValidator = replicateValidator; this.externalBarcodeValidator = externalBarcodeValidator; this.blockFieldChecker = blockFieldChecker; @@ -156,6 +156,7 @@ public Collection getWorks() { return works; } + /** Checks the donor info for problems */ public void validateDonors() { for (BlockRegisterSample brs : iter(blockSamples())) { boolean skip = false; @@ -163,8 +164,8 @@ public void validateDonors() { if (nullOrEmpty(brs.getDonorIdentifier())) { skip = true; addProblem("Missing donor identifier."); - } else if (donorNameValidation!=null) { - donorNameValidation.validate(brs.getDonorIdentifier(), this::addProblem); + } else if (donorNameValidator !=null) { + donorNameValidator.validate(brs.getDonorIdentifier(), this::addProblem); } if (nullOrEmpty(brs.getSpecies())) { addProblem("Missing species."); @@ -213,6 +214,7 @@ public void validateDonors() { } } + /** Checks the HMDMCs for problems */ public void validateHmdmcs() { Set unknownHmdmcs = new LinkedHashSet<>(); boolean unwanted = false; @@ -263,6 +265,7 @@ public void validateHmdmcs() { } } + /** Checks tissue types and spatial locations for problems */ public void validateSpatialLocations() { UCMap tissueTypeMap = new UCMap<>(); Set unknownTissueTypes = new LinkedHashSet<>(); @@ -314,18 +317,22 @@ public void validateSpatialLocations() { } } + /** Loads and checks the labware types for problems */ public void validateLabwareTypes() { validateByName("labware type", BlockRegisterLabware::getLabwareType, ltRepo::findByName, labwareTypeMap); } + /** Loads and checks the mediums for problems */ public void validateMediums() { validateByName("medium", BlockRegisterLabware::getMedium, mediumRepo::findByName, mediumMap); } + /** Loads and checks the fixatives for problems */ public void validateFixatives() { validateByName("fixative", BlockRegisterLabware::getFixative, fixativeRepo::findByName, fixativeMap); } + /** Checks the collection dates for problems */ public void validateCollectionDates() { boolean missing = false; LocalDate today = LocalDate.now(); @@ -359,6 +366,7 @@ public void validateCollectionDates() { } } + /** Looks for problems with the information given for existing tissues */ public void validateExistingTissues() { List blocksForExistingTissues = new ArrayList<>(); for (BlockRegisterLabware blw : request.getLabware()) { @@ -387,13 +395,13 @@ public void validateExistingTissues() { .filter(xn -> !tissueMap.containsKey(xn)) .collect(toLinkedHashSet()); if (!missing.isEmpty()) { - addProblem("Existing external identifiers not recognised: " + reprCollection(missing)); + addProblem("Existing external identifier not recognised: " + reprCollection(missing)); } for (BlockRegisterLabwareAndSample b : blocksForExistingTissues) { String xn = b.sample().getExternalIdentifier(); if (!nullOrEmpty(xn)) { - Tissue tissue = tissueMap.get(xn.toUpperCase()); + Tissue tissue = tissueMap.get(xn); if (tissue != null) { blockFieldChecker.check(this::addProblem, b.labware(), b.sample(), tissue); } @@ -401,6 +409,7 @@ public void validateExistingTissues() { } } + /** Looks for problems with the information given for new tissues */ public void validateNewTissues() { // NB repeated new external identifier in one request is still disallowed Set externalNames = new HashSet<>(); @@ -419,8 +428,8 @@ public void validateNewTissues() { if (nullOrEmpty(brs.getExternalIdentifier())) { addProblem("Missing external identifier."); } else { - if (externalNameValidation != null) { - externalNameValidation.validate(brs.getExternalIdentifier(), this::addProblem); + if (externalNameValidator != null) { + externalNameValidator.validate(brs.getExternalIdentifier(), this::addProblem); } if (!externalNames.add(brs.getExternalIdentifier().toUpperCase())) { addProblem("Repeated external identifier: " + brs.getExternalIdentifier()); @@ -432,11 +441,13 @@ public void validateNewTissues() { } } + /** Loads and checks bio risks for problems */ public void validateBioRisks() { this.bioRiskMap = bioRiskService.loadAndValidateBioRisks(problems, blockSamples(), BlockRegisterSample::getBioRiskCode, BlockRegisterSample::setBioRiskCode); } + /** Loads and checks cell classes for problems */ public void validateCellClasses() { Set cellClassNames = new HashSet<>(); boolean anyMissing = false; @@ -465,6 +476,7 @@ public void validateCellClasses() { } } + /** Loads and checks works for problems */ public void validateWorks() { if (request.getWorkNumbers().isEmpty()) { addProblem("No work number supplied."); @@ -474,6 +486,7 @@ public void validateWorks() { } } + /** Checks labware external barcodes for problems */ public void validateExternalBarcodes() { Set barcodes = new HashSet<>(); boolean anyMissing = false; @@ -502,6 +515,7 @@ public void validateExternalBarcodes() { } } + /** Checks slot addresses for problems */ public void validateAddresses() { Map> lwTypeInvalidAddresses = new HashMap<>(); boolean missing = false; @@ -527,11 +541,19 @@ public void validateAddresses() { } if (!lwTypeInvalidAddresses.isEmpty()) { lwTypeInvalidAddresses.forEach((lt, invalidAddresses) - -> problems.add(String.format("Invalid slot addresses for labware type %s: %s", lt.getName(), invalidAddresses))); + -> problems.add(String.format("Invalid slot addresses for labware type %s: %s", + lt.getName(), invalidAddresses.stream().sorted().toList()))); } } - + /** + * Helper method to load entities into a map and check for problems + * @param type of entity to load + * @param entityName name of the type of entity + * @param nameFunction function to extract the name from the request + * @param lkp function to load the entity from the database + * @param map map to load the entity into + */ void validateByName(String entityName, Function nameFunction, Function> lkp, @@ -565,14 +587,21 @@ void validateByName(String entityName, } } + Collection getProblems() { + return problems; + } + + /** Adds a problem to the internal collection of problems found */ boolean addProblem(String problem) { return problems.add(problem); } + /** Helper method to stream samples in the request */ Stream blockSamples() { return this.request.getLabware().stream().flatMap(brl -> brl.getSamples().stream()); } + /** A non-null string and an int, used for case insensitive deduplication. The string is put into upper case. */ record StringIntKey(String string, int number) { StringIntKey(String string, int number) { this.string = string.toUpperCase(); @@ -580,5 +609,6 @@ record StringIntKey(String string, int number) { } } + /** A linked labware and sample from the request. Used when validating existing tissues. */ record BlockRegisterLabwareAndSample(BlockRegisterLabware labware, BlockRegisterSample sample) {} } diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockRegisterValidation.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockRegisterValidation.java new file mode 100644 index 000000000..8a10daf73 --- /dev/null +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockRegisterValidation.java @@ -0,0 +1,730 @@ +package uk.ac.sanger.sccp.stan.service.register; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.*; +import uk.ac.sanger.sccp.stan.EntityFactory; +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.repo.*; +import uk.ac.sanger.sccp.stan.request.register.*; +import uk.ac.sanger.sccp.stan.service.BioRiskService; +import uk.ac.sanger.sccp.stan.service.Validator; +import uk.ac.sanger.sccp.stan.service.work.WorkService; +import uk.ac.sanger.sccp.utils.UCMap; +import uk.ac.sanger.sccp.utils.Zip; + +import java.time.LocalDate; +import java.util.*; +import java.util.function.*; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static uk.ac.sanger.sccp.stan.Matchers.*; +import static uk.ac.sanger.sccp.utils.BasicUtils.nullOrEmpty; + +class TestBlockRegisterValidation { + @Mock + DonorRepo mockDonorRepo; + @Mock + HmdmcRepo mockHmdmcRepo; + @Mock + TissueTypeRepo mockTtRepo; + @Mock + LabwareTypeRepo mockLtRepo; + @Mock + MediumRepo mockMediumRepo; + @Mock + FixativeRepo mockFixativeRepo; + @Mock + TissueRepo mockTissueRepo; + @Mock + SpeciesRepo mockSpeciesRepo; + @Mock + CellClassRepo mockCellClassRepo; + @Mock + LabwareRepo mockLwRepo; + @Mock + Validator mockDonorNameValidator; + @Mock + Validator mockExternalNameValidator; + @Mock + Validator mockReplicateValidator; + @Mock + Validator mockExternalBarcodeValidator; + @Mock + BlockFieldChecker mockBlockFieldChecker; + @Mock + BioRiskService mockBioRiskService; + @Mock + WorkService mockWorkService; + + private AutoCloseable mocking; + + @BeforeEach + void setup() { + mocking = MockitoAnnotations.openMocks(this); + } + + @AfterEach + void cleanup() throws Exception { + mocking.close(); + } + + static BlockRegisterRequest toRequest(List brss) { + BlockRegisterLabware brl = new BlockRegisterLabware(); + brl.setSamples(brss); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl)); + return request; + } + + BlockRegisterValidationImp makeVal(BlockRegisterRequest request) { + return spy(new BlockRegisterValidationImp(request, mockDonorRepo, mockHmdmcRepo, mockTtRepo, mockLtRepo, + mockMediumRepo, mockFixativeRepo, mockTissueRepo, mockSpeciesRepo, mockCellClassRepo, mockLwRepo, + mockDonorNameValidator, mockExternalNameValidator, mockReplicateValidator, mockExternalBarcodeValidator, + mockBlockFieldChecker, mockBioRiskService, mockWorkService)); + } + + private static List> valMethods() { + return List.of(BlockRegisterValidationImp::validateDonors, + BlockRegisterValidationImp::validateHmdmcs, + BlockRegisterValidationImp::validateSpatialLocations, + BlockRegisterValidationImp::validateLabwareTypes, + BlockRegisterValidationImp::validateExternalBarcodes, + BlockRegisterValidationImp::validateAddresses, + BlockRegisterValidationImp::validateMediums, + BlockRegisterValidationImp::validateFixatives, + BlockRegisterValidationImp::validateCollectionDates, + BlockRegisterValidationImp::validateExistingTissues, + BlockRegisterValidationImp::validateNewTissues, + BlockRegisterValidationImp::validateBioRisks, + BlockRegisterValidationImp::validateWorks, + BlockRegisterValidationImp::validateCellClasses + ); + } + + @Test + void testValidate_empty() { + BlockRegisterRequest request = new BlockRegisterRequest(); + BlockRegisterValidationImp val = makeVal(request); + var problems = val.validate(); + for (var method : valMethods()) { + method.accept(verify(val, never())); + } + assertThat(problems).containsExactly("No labware specified in request."); + } + + @Test + void testValidate() { + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(new BlockRegisterLabware())); + request.getLabware().getFirst().setSamples(List.of(new BlockRegisterSample())); + BlockRegisterValidationImp val = makeVal(request); + for (var method : valMethods()) { + method.accept(doNothing().when(val)); + } + assertThat(val.validate()).isEmpty(); + InOrder inOrder = inOrder(val); + for (var method : valMethods()) { + method.accept(inOrder.verify(val)); + } + } + + @Test + void testValidateDonors_problems() { + List brss = List.of( + brsForDonor(null, "Human", LifeStage.adult), + brsForDonor("Donor!", "Human", LifeStage.adult), + brsForDonor("DONOR1", null, LifeStage.adult), + brsForDonor("DONOR2", "Humming", LifeStage.adult), + brsForDonor("DONOR3", "Mutant", LifeStage.adult), + brsForDonor("DONOR4", "Human", LifeStage.adult), + brsForDonor("Donor4", "Hamster", LifeStage.adult), + brsForDonor("DONOR5", "Human", LifeStage.adult), + brsForDonor("DONOR5", "Human", LifeStage.fetal), + brsForDonor("DONORA", "Hamster", LifeStage.adult), + brsForDonor("DONORB", "Human", LifeStage.fetal) + ); + BlockRegisterRequest request = toRequest(brss); + + Species human = new Species(1, "Human"); + Species hamster = new Species(2, "Hamster"); + Species mutant = new Species(3, "Mutant"); + mutant.setEnabled(false); + when(mockSpeciesRepo.findByName(anyString())).thenReturn(Optional.empty()); + Stream.of(human, hamster, mutant).forEach(s -> doReturn(Optional.of(s)).when(mockSpeciesRepo).findByName(eqCi(s.getName()))); + Donor donora = new Donor(10, "DONORA", LifeStage.adult, human); + Donor donorb = new Donor(11, "DONORB", LifeStage.adult, human); + when(mockDonorRepo.findByDonorName(anyString())).thenReturn(Optional.empty()); + Stream.of(donora, donorb).forEach(d -> doReturn(Optional.of(d)).when(mockDonorRepo).findByDonorName(eqCi(d.getDonorName()))); + mockStringValidator(mockDonorNameValidator, "donor name"); + BlockRegisterValidationImp val = makeVal(request); + val.validateDonors(); + var problems = val.getProblems(); + assertThat(problems).containsExactlyInAnyOrder( + "Missing donor identifier.", + "Bad donor name: Donor!", + "Missing species.", + "Unknown species: \"Humming\"", + "Species is not enabled: Mutant", + "Multiple different species specified for donor DONOR4", + "Multiple different life stages specified for donor DONOR5", + "Wrong species given for existing donor DONORA", + "Wrong life stage given for existing donor DONORB" + ); + assertSame(donora, val.getDonor("donorA")); + assertSame(donorb, val.getDonor("donorb")); + assertNotNull(val.getDonor("donor4")); + } + + @Test + void testValidateDonors_ok() { + List brss = List.of( + brsForDonor("donor1", "Human", LifeStage.adult), + brsForDonor("DONOR1", "Human", LifeStage.adult), + brsForDonor("donora", "Human", LifeStage.adult) + ); + Species human = new Species(1, "Human"); + when(mockSpeciesRepo.findByName(eqCi(human.getName()))).thenReturn(Optional.of(human)); + Donor donora = new Donor(1, "DONORA", LifeStage.adult, human); + when(mockDonorRepo.findByDonorName(anyString())).thenReturn(Optional.empty()); + doReturn(Optional.of(donora)).when(mockDonorRepo).findByDonorName(eqCi(donora.getDonorName())); + BlockRegisterRequest request = toRequest(brss); + BlockRegisterValidationImp val = makeVal(request); + val.validateDonors(); + assertSame(donora, val.getDonor("DonorA")); + assertNotNull(val.getDonor("Donor1")); + assertThat(val.getProblems()).isEmpty(); + } + + private static BlockRegisterSample brsForDonor(String donorName, String species, LifeStage lifeStage) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setDonorIdentifier(donorName); + brs.setSpecies(species); + brs.setLifeStage(lifeStage); + return brs; + } + + @Test + void testValidateHmdmcs_problems() { + final String HUMAN_NAME = Species.HUMAN_NAME; + Hmdmc hmdmc0 = new Hmdmc(10, "20/000"); + Hmdmc hmdmcX = new Hmdmc(11, "20/001"); + hmdmcX.setEnabled(false); + when(mockHmdmcRepo.findByHmdmc(anyString())).thenReturn(Optional.empty()); + Stream.of(hmdmc0, hmdmcX).forEach(h -> doReturn(Optional.of(h)).when(mockHmdmcRepo).findByHmdmc(h.getHmdmc())); + + List brss = List.of( + brsForHmdmc(null, HUMAN_NAME), + brsForHmdmc("20/000", HUMAN_NAME), + brsForHmdmc("20/000", "Hamster"), + brsForHmdmc("20/001", HUMAN_NAME), + brsForHmdmc("20/002", HUMAN_NAME) + ); + BlockRegisterRequest request = toRequest(brss); + BlockRegisterValidationImp val = makeVal(request); + val.validateHmdmcs(); + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "Missing HuMFre number.", "Non-human tissue should not have a HuMFre number.", + "Unknown HuMFre number: [20/002]", "HuMFre number not enabled: [20/001]" + ); + assertSame(hmdmc0, val.getHmdmc("20/000")); + assertSame(hmdmcX, val.getHmdmc("20/001")); + } + + @Test + void testValidateHmdmcs_ok() { + final String HUMAN_NAME = Species.HUMAN_NAME; + Hmdmc hmdmc0 = new Hmdmc(10, "20/000"); + when(mockHmdmcRepo.findByHmdmc(hmdmc0.getHmdmc())).thenReturn(Optional.of(hmdmc0)); + + List brss = List.of( + brsForHmdmc("20/000", HUMAN_NAME), + brsForHmdmc(null, "Hamster") + ); + BlockRegisterRequest request = toRequest(brss); + BlockRegisterValidationImp val = makeVal(request); + val.validateHmdmcs(); + assertThat(val.getProblems()).isEmpty(); + assertSame(hmdmc0, val.getHmdmc("20/000")); + } + + private static BlockRegisterSample brsForHmdmc(String hmdmc, String speciesName) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setHmdmc(hmdmc); + brs.setSpecies(speciesName); + return brs; + } + + @Test + void testValidateSpatialLocations_problems() { + List brss = List.of( + brsForSL(null, 0), // missing tissue type + brsForSL("Legg", 0), // unknown tissue type + brsForSL("Tail", 0), // disabled tissue type + brsForSL("Leg", 13), // unknown spatial location + brsForSL("Leg", 1), // disabled spatial location + brsForSL("LEG", 0) // ok + ); + TissueType tail = new TissueType(1, "Tail", "Tail"); + tail.setSpatialLocations(List.of(new SpatialLocation(0, "Alpha", 0, tail))); + tail.setEnabled(false); + TissueType leg = new TissueType(2, "Leg", "Leg"); + leg.setSpatialLocations(List.of(new SpatialLocation(20, "Alpha", 0, leg), + new SpatialLocation(21, "Beta", 1, leg))); + leg.getSpatialLocations().getLast().setEnabled(false); + when(mockTtRepo.findByName(anyString())).thenReturn(Optional.empty()); + Stream.of(tail, leg).forEach(tt -> doReturn(Optional.of(tt)).when(mockTtRepo).findByName(eqCi(tt.getName()))); + BlockRegisterRequest request = toRequest(brss); + BlockRegisterValidationImp val = makeVal(request); + val.validateSpatialLocations(); + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "Missing tissue type.", + "Unknown tissue type: [Legg]", + "Tissue type \"Tail\" is disabled.", + "Unknown spatial location 13 for tissue type Leg.", + "Spatial location is disabled: 1 for tissue type Leg." + ); + assertSame(leg.getSpatialLocations().getFirst(), val.getSpatialLocation("LEG", 0)); + } + + @Test + void testValidateSpatialLocations_ok() { + List brss = List.of(brsForSL("Leg", 0)); + TissueType leg = new TissueType(1, "Leg", "Leg"); + leg.setSpatialLocations(List.of(new SpatialLocation(10, "Alpha", 0, leg))); + when(mockTtRepo.findByName(eqCi(leg.getName()))).thenReturn(Optional.of(leg)); + BlockRegisterRequest request = toRequest(brss); + BlockRegisterValidationImp val = makeVal(request); + val.validateSpatialLocations(); + assertThat(val.getProblems()).isEmpty(); + assertSame(leg.getSpatialLocations().getFirst(), val.getSpatialLocation("LEG", 0)); + } + + private static BlockRegisterSample brsForSL(String ttName, int slCode) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setTissueType(ttName); + brs.setSpatialLocation(slCode); + return brs; + } + + @Test + void testValidateLabwareTypes() { + testGeneric(EntityFactory.getTubeType(), LabwareType::getName, "labware type", + mockLtRepo, BlockRegisterLabware::setLabwareType, LabwareTypeRepo::findByName, + BlockRegisterValidationImp::validateLabwareTypes, + BlockRegisterValidationImp::getLabwareType); + } + + @Test + void testValidateMediums() { + testGeneric(EntityFactory.getMedium(), Medium::getName, "medium", + mockMediumRepo, BlockRegisterLabware::setMedium, MediumRepo::findByName, + BlockRegisterValidationImp::validateMediums, + BlockRegisterValidationImp::getMedium); + } + + @Test + void testValidateFixatives() { + testGeneric(EntityFactory.getFixative(), Fixative::getName, "fixative", + mockFixativeRepo, BlockRegisterLabware::setFixative, FixativeRepo::findByName, + BlockRegisterValidationImp::validateFixatives, + BlockRegisterValidationImp::getFixative); + } + + void testGeneric(E validEntity, Function entityNameGetter, String entityTypeName, + R repo, + BiConsumer nameSetter, + BiFunction> lkp, + Consumer valMethod, + BiFunction retriever) { + String entityName = entityNameGetter.apply(validEntity); + lkp.apply(doReturn(Optional.empty()).when(repo), any()); + lkp.apply(doReturn(Optional.of(validEntity)).when(repo), eqCi(entityName)); + + BlockRegisterLabware brl = new BlockRegisterLabware(); + nameSetter.accept(brl, entityName); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl)); + BlockRegisterValidationImp val = makeVal(request); + valMethod.accept(val); + assertThat(val.getProblems()).isEmpty(); + assertSame(validEntity, retriever.apply(val, entityName)); + + brl = new BlockRegisterLabware(); + nameSetter.accept(brl, "invalid"); + request = new BlockRegisterRequest(); + request.setLabware(List.of(brl)); + val = makeVal(request); + valMethod.accept(val); + assertThat(val.getProblems()).containsExactly("Unknown " + entityTypeName + ": [\"invalid\"]"); + + brl = new BlockRegisterLabware(); + nameSetter.accept(brl, null); + request = new BlockRegisterRequest(); + request.setLabware(List.of(brl)); + val = makeVal(request); + valMethod.accept(val); + assertThat(val.getProblems()).containsExactly("Missing " + entityTypeName + "."); + } + + + @Test + void testValidateCollectionDates_problems() { + String HUMAN_NAME = Species.HUMAN_NAME; + LocalDate today = LocalDate.now(); + LocalDate invalidDate = today.plusDays(2L); + List brss = List.of( + brsForDate("EXT1", HUMAN_NAME, LifeStage.fetal, null), // date missing + brsForDate("ext2", HUMAN_NAME, LifeStage.fetal, invalidDate), // bad date + brsForDate("ext3", HUMAN_NAME, LifeStage.fetal, today.minusDays(2L)), + brsForDate("Ext3", HUMAN_NAME, LifeStage.fetal, today.minusDays(3L)) + ); + BlockRegisterRequest request = toRequest(brss); + BlockRegisterValidationImp val = makeVal(request); + val.validateCollectionDates(); + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "Human fetal samples must have a collection date.", + "Invalid sample collection date: ["+invalidDate+"]", + "Inconsistent collection dates specified for tissue EXT3." + ); + } + + @Test + void testValidateCollectionDates_ok() { + String HUMAN_NAME = Species.HUMAN_NAME; + LocalDate validDate = LocalDate.now().minusDays(2L); + List brss = List.of( + brsForDate("EXT1", HUMAN_NAME, LifeStage.fetal, validDate), + brsForDate("Ext1", HUMAN_NAME, LifeStage.fetal, validDate), // matching date + brsForDate("ext2", HUMAN_NAME, LifeStage.adult, null), + brsForDate("Ext3", "Hamster", LifeStage.fetal, null) + ); + BlockRegisterRequest request = toRequest(brss); + BlockRegisterValidationImp val = makeVal(request); + val.validateCollectionDates(); + assertThat(val.getProblems()).isEmpty(); + } + + static BlockRegisterSample brsForDate(String externalName, String species, LifeStage ls, LocalDate date) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setExternalIdentifier(externalName); + brs.setSpecies(species); + brs.setLifeStage(ls); + brs.setSampleCollectionDate(date); + return brs; + } + + @Test + void testValidateExistingTissues_problems() { + BlockRegisterLabware brl1 = new BlockRegisterLabware(); + brl1.setSamples(List.of( + brsForExistingTissue("Ext1", true), + brsForExistingTissue("EXT404", true) + )); + BlockRegisterLabware brl2 = new BlockRegisterLabware(); + brl2.setSamples(List.of(brsForExistingTissue("ext2", true), + brsForExistingTissue(null, true), + brsForExistingTissue("Exta", false) + )); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl1, brl2)); + + Tissue ext1 = tissueWithExtName("EXT1"); + Tissue ext2 = tissueWithExtName("EXT2"); + when(mockTissueRepo.findAllByExternalNameIn(any())).thenReturn(List.of(ext1, ext2)); + + doAnswer(invocation -> { + Consumer problemConsumer = invocation.getArgument(0); + problemConsumer.accept("Check failed for "+ext2.getExternalName()); + return null; + }).when(mockBlockFieldChecker).check(any(), any(), any(), same(ext2)); + + var val = makeVal(request); + val.validateExistingTissues(); + + verify(mockTissueRepo).findAllByExternalNameIn(Set.of("Ext1", "EXT404", "ext2")); + + verify(mockBlockFieldChecker).check(any(), same(brl1), same(brl1.getSamples().getFirst()), same(ext1)); + verify(mockBlockFieldChecker).check(any(), same(brl2), same(brl2.getSamples().getFirst()), same(ext2)); + verifyNoMoreInteractions(mockBlockFieldChecker); + + assertSame(ext1, val.getTissue("Ext1")); + assertSame(ext2, val.getTissue("ext2")); + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "Missing external identifier.", + "Existing external identifier not recognised: [\"EXT404\"]", + "Check failed for EXT2" + ); + } + + @Test + void testValidateExistingTissues_ok() { + BlockRegisterLabware brl = new BlockRegisterLabware(); + brl.setSamples(List.of(brsForExistingTissue("Ext1", true), brsForExistingTissue("ExtA", false))); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl)); + Tissue ext1 = tissueWithExtName("EXT1"); + when(mockTissueRepo.findAllByExternalNameIn(any())).thenReturn(List.of(ext1)); + var val = makeVal(request); + val.validateExistingTissues(); + assertThat(val.getProblems()).isEmpty(); + assertSame(ext1, val.getTissue("Ext1")); + verify(mockTissueRepo).findAllByExternalNameIn(Set.of("Ext1")); + verify(mockBlockFieldChecker).check(any(), same(brl), same(brl.getSamples().getFirst()), same(ext1)); + verifyNoMoreInteractions(mockBlockFieldChecker); + } + + static BlockRegisterSample brsForExistingTissue(String externalName, boolean existing) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setExternalIdentifier(externalName); + brs.setExistingTissue(existing); + return brs; + } + + static Tissue tissueWithExtName(String externalName) { + Tissue t = EntityFactory.makeTissue(EntityFactory.getDonor(), EntityFactory.getSpatialLocation()); + t.setExternalName(externalName); + return t; + } + + @Test + void testValidateNewTissues() { + List brss = List.of( + brsForNewTissue("Ext1", "R1", -1), // negative highest section + brsForNewTissue("Ext2", "", 1), // missing repl + brsForNewTissue(null, "R1", 1), // missing external name + brsForExistingTissue("EXTA", true), // Existing--skip + brsForNewTissue("EXTB", "R1", 1), // exists unexpectedly + brsForNewTissue("EXT3", "R1", 1), + brsForNewTissue("Ext3", "R1", 1), // repeated + brsForNewTissue("EXT!", "R1", 1), // bad ext name + brsForNewTissue("EXT4", "R!", 1) // bad repl + ); + BlockRegisterRequest request = toRequest(brss); + mockStringValidator(mockExternalNameValidator, "external name"); + mockStringValidator(mockReplicateValidator, "replicate"); + Tissue tis = tissueWithExtName("EXTB"); + when(mockTissueRepo.findAllByExternalName(eqCi("EXTB"))).thenReturn(List.of(tis)); + + var val = makeVal(request); + val.validateNewTissues(); + verify(mockReplicateValidator, times(6)).validate(eq("R1"), any()); + verify(mockReplicateValidator, times(1)).validate(eq("R!"), any()); + verifyNoMoreInteractions(mockReplicateValidator); + Stream.of("Ext1", "Ext2", "EXTB", "EXT3", "Ext3", "EXT!", "EXT4") + .forEach(xn -> verify(mockExternalNameValidator).validate(eq(xn), any())); + verifyNoMoreInteractions(mockExternalNameValidator); + + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "Highest section number cannot be negative.", + "Missing replicate number.", + "Missing external identifier.", + "Repeated external identifier: Ext3", + "Bad external name: EXT!", + "Bad replicate: R!", + "There is already tissue in the database with external identifier EXTB." + ); + } + + @Test + void testValidateNewTissues_ok() { + List brss = List.of( + brsForNewTissue("Ext1", "R1", 1), + brsForExistingTissue("EXTA", true) + ); + BlockRegisterRequest request = toRequest(brss); + var val = makeVal(request); + val.validateNewTissues(); + verify(mockReplicateValidator).validate(eq("R1"), any()); + verifyNoMoreInteractions(mockReplicateValidator); + verify(mockExternalNameValidator).validate(eq("Ext1"), any()); + verifyNoMoreInteractions(mockExternalNameValidator); + verify(mockTissueRepo).findAllByExternalName(eqCi("Ext1")); + verifyNoMoreInteractions(mockTissueRepo); + assertThat(val.getProblems()).isEmpty(); + } + + static BlockRegisterSample brsForNewTissue(String extName, String repl, Integer highestSec) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setExternalIdentifier(extName); + brs.setReplicateNumber(repl); + brs.setHighestSection(highestSec); + return brs; + } + + @Test + void testValidateBioRisks() { + final UCMap knownBioRisks = new UCMap<>(2); + Zip.enumerate(Stream.of("BR1", "BR2")).forEach((i,s) -> knownBioRisks.put(s, new BioRisk(i+1, s))); + when(mockBioRiskService.loadAndValidateBioRisks(any(), any(), any(), any())).then(invocation -> { + Collection problems = invocation.getArgument(0); + Stream stream = invocation.getArgument(1); + Function getter = invocation.getArgument(2); + BiConsumer setter = invocation.getArgument(3); + UCMap brMap = new UCMap<>(); + stream.forEach(brs -> { + String bioRiskCode = getter.apply(brs); + if (nullOrEmpty(bioRiskCode)) { + problems.add("Missing bio risk code."); + setter.accept(brs, null); + } else { + BioRisk br = knownBioRisks.get(bioRiskCode); + if (br == null) { + problems.add("Unknown bio risk code: "+bioRiskCode); + } else { + brMap.put(br.getCode(), br); + } + } + }); + return brMap; + }); + + List brss = Stream.of(null, "", "BR1", "BR2", "BR404") + .map(TestBlockRegisterValidation::brsForBioRisk).toList(); + BlockRegisterRequest request = toRequest(brss); + var val = makeVal(request); + val.validateBioRisks(); + verify(mockBioRiskService).loadAndValidateBioRisks(any(), any(), any(), any()); + assertThat(val.getProblems()).containsExactlyInAnyOrder("Missing bio risk code.", "Unknown bio risk code: BR404"); + assertNull(brss.get(1).getBioRiskCode()); // Empty string has been replaced with null + assertEquals("BR1", brss.get(2).getBioRiskCode()); + Stream.of("BR1", "BR2").forEach(s -> assertSame(knownBioRisks.get(s), val.getBioRisk(s))); + } + + static BlockRegisterSample brsForBioRisk(String bioRiskCode) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setBioRiskCode(bioRiskCode); + return brs; + } + + @Test + void testValidateCellClasses() { + List brss = Stream.of(null, "cc1", "cc404") + .map(TestBlockRegisterValidation::brsForCellClass).toList(); + CellClass cc = new CellClass(1, "CC1", false, true); + when(mockCellClassRepo.findMapByNameIn(any())).thenReturn(UCMap.from(CellClass::getName, cc)); + BlockRegisterRequest request = toRequest(brss); + var val = makeVal(request); + val.validateCellClasses(); + verify(mockCellClassRepo).findMapByNameIn(Set.of("cc1", "cc404")); + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "Missing cell class name.", + "Unknown cell class name: [\"cc404\"]" + ); + assertSame(cc, val.getCellClass("cc1")); + } + + static BlockRegisterSample brsForCellClass(String cellClass) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setCellClass(cellClass); + return brs; + } + + @ParameterizedTest + @CsvSource({ + "false,false", + "true,false", + "true,true", + }) + void testValidateWorks(boolean anyWorks, boolean anyInvalid) { + BlockRegisterRequest request = new BlockRegisterRequest(); + List works; + String problem; + if (anyWorks) { + request.setWorkNumbers(List.of("SGP1")); + works = List.of(EntityFactory.makeWork("SGP1")); + problem = anyInvalid ? "Bad work" : null; + mayAddProblem(problem, UCMap.from(works, Work::getWorkNumber)) + .when(mockWorkService).validateUsableWorks(any(), any()); + } else { + works = null; + problem = "No work number supplied."; + } + var val = makeVal(request); + val.validateWorks(); + assertProblem(val.getProblems(), problem); + if (anyWorks) { + verify(mockWorkService).validateUsableWorks(any(), eq(request.getWorkNumbers())); + assertThat(val.getWorks()).containsExactlyInAnyOrderElementsOf(works); + } else { + verifyNoInteractions(mockWorkService); + assertThat(val.getWorks()).isEmpty(); + } + } + + @Test + void testValidateExternalBarcodes() { + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(Stream.of(null, "XB1", "xb1", "XBX", "LWX", "XB!") + .map(TestBlockRegisterValidation::brlForXb).toList()); + mockStringValidator(mockExternalBarcodeValidator, "external barcode"); + when(mockLwRepo.findBarcodesByBarcodeIn(any())).thenReturn(Set.of("LWX")); + when(mockLwRepo.findExternalBarcodesIn(any())).thenReturn(Set.of("XBX")); + var val = makeVal(request); + val.validateExternalBarcodes(); + Stream.of("XB1", "XBX", "LWX", "XB!") + .forEach(s -> verify(mockExternalBarcodeValidator).validate(eq(s), any())); + verifyNoMoreInteractions(mockExternalBarcodeValidator); + verify(mockLwRepo).findBarcodesByBarcodeIn(any()); + verify(mockLwRepo).findExternalBarcodesIn(Set.of("XB1", "XBX", "XB!")); + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "External barcode given multiple times: xb1", + "Labware barcode already in use: [LWX]", + "External barcode already in use: [XBX]", + "Bad external barcode: XB!", + "Missing external barcode." + ); + } + + static BlockRegisterLabware brlForXb(String xb) { + BlockRegisterLabware brl = new BlockRegisterLabware(); + brl.setExternalBarcode(xb); + return brl; + } + + @Test + void testValidateAddresses() { + final Address A1 = new Address(1,1), A2 = new Address(1,2), A3 = new Address(1,3); + LabwareType lt = new LabwareType(1, "lt", 1, 1, null, false); + BlockRegisterLabware brl1 = new BlockRegisterLabware(); + brl1.setLabwareType("LT"); + brl1.setSamples(List.of( + brsForAddresses(A1, A2), brsForAddresses(A1, A3), new BlockRegisterSample() + )); + BlockRegisterLabware brl2 = new BlockRegisterLabware(); + brl2.setLabwareType("???"); + brl2.setSamples(List.of(brsForAddresses(A1, A2, A3))); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl1, brl2)); + var val = makeVal(request); + val.labwareTypeMap.put(lt.getName(), lt); + + val.validateAddresses(); + assertThat(val.getProblems()).containsExactlyInAnyOrder( + "Slot addresses missing from request.", + "Invalid slot addresses for labware type lt: [A2, A3]" + ); + } + + static BlockRegisterSample brsForAddresses(Address... addresses) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setAddresses(List.of(addresses)); + return brs; + } + + static void mockStringValidator(Validator validator, String name) { + doAnswer(invocation -> { + String string = invocation.getArgument(0); + if (string.indexOf('!') < 0) { + return true; + } + Consumer problemConsumer = invocation.getArgument(1); + problemConsumer.accept("Bad "+name+": "+string); + return false; + }).when(validator).validate(any(), any()); + } +} \ No newline at end of file From f09dac5bed4ec9653babf6fa30d6f08850df60a8 Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:52:58 +0000 Subject: [PATCH 11/13] TestBlockRegisterService --- .../register/BlockRegisterServiceImp.java | 5 +- .../register/TestBlockRegisterService.java | 459 ++++++++++++++++++ 2 files changed, 462 insertions(+), 2 deletions(-) create mode 100644 src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockRegisterService.java diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterServiceImp.java index 140259b65..e5e4df90c 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterServiceImp.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterServiceImp.java @@ -131,7 +131,7 @@ public UCMap createTissues(BlockRegisterRequest request, RegisterValidat tissueMap.put(tissueKey, existingTissue); continue; } - Donor donor = donors.get(brs.getDonorIdentifier().toUpperCase()); + Donor donor = donors.get(brs.getDonorIdentifier()); Hmdmc hmdmc; if (nullOrEmpty(brs.getHmdmc())) { hmdmc = null; @@ -175,7 +175,8 @@ public RegisterResult create(BlockRegisterRequest request, User user, RegisterVa BioState bioState = opType.getNewBioState(); for (BlockRegisterLabware brl : request.getLabware()) { LabwareType labwareType = validation.getLabwareType(brl.getLabwareType()); - Labware lw = labwareService.create(labwareType); + String xb = brl.getExternalBarcode(); + Labware lw = labwareService.create(labwareType, xb, xb); lwList.add(lw); Set slotsToUpdate = new HashSet<>(); List actions = new ArrayList<>(); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockRegisterService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockRegisterService.java new file mode 100644 index 000000000..6ab32fcb8 --- /dev/null +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestBlockRegisterService.java @@ -0,0 +1,459 @@ +package uk.ac.sanger.sccp.stan.service.register; + +import org.junit.jupiter.api.*; +import org.mockito.*; +import uk.ac.sanger.sccp.stan.EntityFactory; +import uk.ac.sanger.sccp.stan.model.*; +import uk.ac.sanger.sccp.stan.repo.*; +import uk.ac.sanger.sccp.stan.request.register.*; +import uk.ac.sanger.sccp.stan.service.LabwareService; +import uk.ac.sanger.sccp.stan.service.OperationService; +import uk.ac.sanger.sccp.stan.service.work.WorkService; +import uk.ac.sanger.sccp.utils.UCMap; +import uk.ac.sanger.sccp.utils.Zip; + +import javax.persistence.EntityManager; +import java.time.LocalDate; +import java.util.*; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static uk.ac.sanger.sccp.stan.Matchers.*; + +/** Tests {@link BlockRegisterServiceImp} */ +class TestBlockRegisterService { + @Mock + EntityManager mockEntityManager; + @Mock + RegisterValidationFactory mockValidationFactory; + @Mock + DonorRepo mockDonorRepo; + @Mock + TissueRepo mockTissueRepo; + @Mock + SampleRepo mockSampleRepo; + @Mock + SlotRepo mockSlotRepo; + @Mock + BioRiskRepo mockBioRiskRepo; + @Mock + OperationTypeRepo mockOpTypeRepo; + @Mock + LabwareService mockLabwareService; + @Mock + OperationService mockOperationService; + @Mock + WorkService mockWorkService; + @Mock + RegisterClashChecker mockClashChecker; + + @InjectMocks + BlockRegisterServiceImp service; + + private AutoCloseable mocking; + + @BeforeEach + void setup() { + mocking = MockitoAnnotations.openMocks(this); + service = spy(service); + } + + @AfterEach + void cleanup() throws Exception { + mocking.close(); + } + + @Test + void testRegister_none() { + RegisterResult result = service.register(EntityFactory.getUser(), new BlockRegisterRequest()); + verifyNoInteractions(mockClashChecker); + verifyNoInteractions(mockValidationFactory); + verify(service, never()).updateExistingTissues(any(), any()); + verify(service, never()).create(any(), any(), any()); + assertThat(result.getLabware()).isEmpty(); + assertThat(result.getClashes()).isEmpty(); + assertThat(result.getLabwareSolutions()).isEmpty(); + } + + @Test + void testRegister_clash() { + List clashes = List.of(new RegisterClash()); + when(mockClashChecker.findClashes(any(BlockRegisterRequest.class))).thenReturn(clashes); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(new BlockRegisterLabware())); + RegisterResult result = service.register(EntityFactory.getUser(), request); + verify(mockClashChecker).findClashes(request); + verifyNoInteractions(mockValidationFactory); + verify(service, never()).updateExistingTissues(any(), any()); + verify(service, never()).create(any(), any(), any()); + + assertThat(result.getLabware()).isEmpty(); + assertEquals(clashes, result.getClashes()); + assertThat(result.getLabwareSolutions()).isEmpty(); + } + + @Test + void testRegister_problems() { + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(new BlockRegisterLabware())); + when(mockClashChecker.findClashes(any(BlockRegisterRequest.class))).thenReturn(List.of()); + RegisterValidation validation = mock(RegisterValidation.class); + when(mockValidationFactory.createBlockRegisterValidation(any())).thenReturn(validation); + List problems = List.of("Bad thing"); + when(validation.validate()).thenReturn(problems); + assertValidationException(() -> service.register(EntityFactory.getUser(), request), problems); + verify(mockClashChecker).findClashes(request); + verify(mockValidationFactory).createBlockRegisterValidation(request); + verify(validation).validate(); + verify(service, never()).updateExistingTissues(any(), any()); + verify(service, never()).create(any(), any(), any()); + } + + @Test + void testRegister_ok() { + User user = EntityFactory.getUser(); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(new BlockRegisterLabware())); + when(mockClashChecker.findClashes(any(BlockRegisterRequest.class))).thenReturn(List.of()); + RegisterValidation validation = mock(RegisterValidation.class); + when(mockValidationFactory.createBlockRegisterValidation(any())).thenReturn(validation); + when(validation.validate()).thenReturn(List.of()); + doNothing().when(service).updateExistingTissues(any(), any()); + RegisterResult result = new RegisterResult(List.of(EntityFactory.getTube())); + doReturn(result).when(service).create(any(), any(), any()); + assertSame(result, service.register(user, request)); + verify(mockClashChecker).findClashes(request); + verify(mockValidationFactory).createBlockRegisterValidation(request); + verify(validation).validate(); + verify(service).updateExistingTissues(request, validation); + verify(service).create(request, user, validation); + } + + @Test + void testUpdateExistingTissues_none() { + BlockRegisterLabware brl = new BlockRegisterLabware(); + brl.setSamples(List.of( + brsForExistingTissue("EXT1", LocalDate.of(2024,1,1), false), + brsForExistingTissue("EXT2", null, true) + )); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl)); + RegisterValidation validation = mock(RegisterValidation.class); + + service.updateExistingTissues(request, validation); + verifyNoInteractions(validation); + verifyNoInteractions(mockTissueRepo); + } + + @Test + void testUpdateExistingTissues_some() { + BlockRegisterLabware brl = new BlockRegisterLabware(); + LocalDate newDate = LocalDate.of(2024,1,1); + brl.setSamples(List.of( + brsForExistingTissue("EXT1", LocalDate.of(2024,1,1), true), + brsForExistingTissue("EXT2", LocalDate.of(2023,1,1), false), + brsForExistingTissue("EXT3", null, true) + )); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl)); + RegisterValidation validation = mock(RegisterValidation.class); + Tissue tissue = new Tissue(); + tissue.setExternalName("EXT1"); + when(validation.getTissue("EXT1")).thenReturn(tissue); + + service.updateExistingTissues(request, validation); + verify(mockTissueRepo).saveAll(List.of(tissue)); + verifyNoMoreInteractions(mockTissueRepo); + assertEquals(newDate, tissue.getCollectionDate()); + } + + static BlockRegisterSample brsForExistingTissue(String externalName, LocalDate date, boolean existing) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setExternalIdentifier(externalName); + brs.setExistingTissue(existing); + brs.setSampleCollectionDate(date); + return brs; + } + + @Test + void testCreateDonors() { + List brss = List.of( + brsForDonor("DONOR1"), + brsForDonor("DONOR2"), + brsForDonor("Donor1"), + brsForDonor("DONORA") + ); + BlockRegisterLabware brl = new BlockRegisterLabware(); + brl.setSamples(brss); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl)); + + Donor donor1 = makeDonor(null, "DONOR1"); + Donor donor2 = makeDonor(null, "DONOR2"); + Donor donorA = makeDonor(1, "DONORA"); + List donors = List.of(donor1, donor2, donorA); + RegisterValidation val = mock(RegisterValidation.class); + donors.forEach(d -> when(val.getDonor(eqCi(d.getDonorName()))).thenReturn(d)); + + final int[] idCounter = {5}; + when(mockDonorRepo.save(any())).then(invocation -> { + Donor d = invocation.getArgument(0); + assertNull(d.getId()); + d.setId(++idCounter[0]); + return d; + }); + + UCMap donorMap = service.createDonors(request, val); + assertThat(donorMap).hasSize(donors.size()); + donors.forEach(d -> assertSame(d, donorMap.get(d.getDonorName()))); + verify(mockDonorRepo).save(donor1); + verify(mockDonorRepo).save(donor2); + verifyNoMoreInteractions(mockDonorRepo); + assertEquals(1, donorA.getId()); + assertNotNull(donor1.getId()); + assertNotNull(donor2.getId()); + assertNotEquals(donor1.getId(), donor2.getId()); + } + + static BlockRegisterSample brsForDonor(String donorName) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setDonorIdentifier(donorName); + return brs; + } + + @Test + void testCreateTissues() { + List donors = IntStream.of(1,2).mapToObj(i -> makeDonor(i, "DONOR"+i)).toList(); + UCMap donorMap = UCMap.from(donors, Donor::getDonorName); + doReturn(donorMap).when(service).createDonors(any(), any()); + List hmdmcs = List.of(new Hmdmc(1, "2000/1"), new Hmdmc(2, "2000/2")); + RegisterValidation val = mock(RegisterValidation.class); + hmdmcs.forEach(h -> when(val.getHmdmc(h.getHmdmc())).thenReturn(h)); + List cellClasses = List.of(new CellClass(1, "CC1", false, true), new CellClass(2, "CC2", false, true)); + cellClasses.forEach(cc -> when(val.getCellClass(eqCi(cc.getName()))).thenReturn(cc)); + TissueType tt = new TissueType(1, "TT1", "TT1"); + tt.setSpatialLocations(List.of(new SpatialLocation(10, "SL0", 0, tt), + new SpatialLocation(11, "SL1", 1, tt))); + tt.getSpatialLocations().forEach(sl -> when(val.getSpatialLocation("TT1", sl.getCode())).thenReturn(sl)); + List mediums = List.of(new Medium(1, "med1"), new Medium(2, "med2")); + mediums.forEach(m -> when(val.getMedium(eqCi(m.getName()))).thenReturn(m)); + List fixs = List.of(new Fixative(1, "fix1"), new Fixative(2, "fix2")); + fixs.forEach(f -> when(val.getFixative(eqCi(f.getName()))).thenReturn(f)); + + BlockRegisterSample brs1 = new BlockRegisterSample(); + brs1.setExternalIdentifier("EXT1"); + brs1.setHmdmc("2000/1"); + brs1.setCellClass("CC1"); + brs1.setReplicateNumber("R1"); + brs1.setSampleCollectionDate(LocalDate.of(2026,1,1)); + brs1.setTissueType("TT1"); + brs1.setSpatialLocation(0); + brs1.setDonorIdentifier("DONOR1"); + + BlockRegisterSample brs2 = new BlockRegisterSample(); + brs2.setExternalIdentifier("EXT2"); + brs2.setHmdmc("2000/2"); + brs2.setCellClass("CC2"); + brs2.setReplicateNumber("R2"); + brs2.setSampleCollectionDate(LocalDate.of(2026,1,2)); + brs2.setTissueType("TT1"); + brs2.setSpatialLocation(1); + brs2.setDonorIdentifier("DONOR2"); + + BlockRegisterSample brs3 = new BlockRegisterSample(); + brs3.setExternalIdentifier("EXT3"); + brs3.setHmdmc("2000/1"); + brs3.setCellClass("CC1"); + brs3.setReplicateNumber("R1"); + brs3.setSampleCollectionDate(LocalDate.of(2026,1,3)); + brs3.setTissueType("TT1"); + brs3.setSpatialLocation(0); + brs3.setDonorIdentifier("DONOR1"); + + Tissue existingTissue = new Tissue(); + existingTissue.setId(1); + existingTissue.setExternalName("EXT3"); + when(val.getTissue(eqCi("EXT3"))).thenReturn(existingTissue); + + final int[] idCounter = {5}; + when(mockTissueRepo.save(any())).then(invocation -> { + Tissue t = invocation.getArgument(0); + assertNull(t.getId()); + t.setId(++idCounter[0]); + return t; + }); + + BlockRegisterLabware brl1 = new BlockRegisterLabware(); + brl1.setSamples(List.of(brs1)); + brl1.setMedium("med1"); + brl1.setFixative("fix1"); + BlockRegisterLabware brl2 = new BlockRegisterLabware(); + brl2.setSamples(List.of(brs2, brs3)); + brl2.setMedium("med2"); + brl2.setFixative("fix2"); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl1, brl2)); + + UCMap tissueMap = service.createTissues(request, val); + + assertThat(tissueMap).hasSize(3); + assertSame(existingTissue, tissueMap.get("EXT3")); + assertEquals(1, existingTissue.getId()); + + List newTissues = Stream.of("EXT1", "EXT2").map(tissueMap::get).toList(); + for (int i = 0; i < newTissues.size(); i++) { + Tissue tis = newTissues.get(i); + assertNotNull(tis); + assertNotNull(tis.getId()); + assertSame(mediums.get(i), tis.getMedium()); + assertSame(fixs.get(i), tis.getFixative()); + assertSame(donors.get(i), tis.getDonor()); + assertSame(hmdmcs.get(i), tis.getHmdmc()); + assertSame(cellClasses.get(i), tis.getCellClass()); + assertSame(tt, tis.getTissueType()); + assertSame(tt.getSpatialLocations().get(i), tis.getSpatialLocation()); + assertEquals(request.getLabware().get(i).getSamples().getFirst().getSampleCollectionDate(), tis.getCollectionDate()); + assertEquals("EXT"+(i+1), tis.getExternalName()); + assertEquals("r"+(i+1), tis.getReplicate()); + } + assertNotEquals(newTissues.get(0).getId(), newTissues.get(1).getId()); + verify(mockTissueRepo, times(2)).save(any()); + } + + @Test + void testCreate() { + User user = EntityFactory.getUser(); + List works = List.of(EntityFactory.makeWork("SGP1")); + RegisterValidation val = mock(RegisterValidation.class); + when(val.getWorks()).thenReturn(works); + Tissue[] tissues = IntStream.of(1,2,3).mapToObj(i -> makeTissue(i, "EXT"+i)).toArray(Tissue[]::new); + doReturn(UCMap.from(Tissue::getExternalName, tissues)).when(service).createTissues(any(), any()); + OperationType opType = EntityFactory.makeOperationType("Register", EntityFactory.getBioState()); + when(mockOpTypeRepo.getByName(eqCi(opType.getName()))).thenReturn(opType); + LabwareType lt = new LabwareType(1, "lt", 1, 2, null, false); + when(val.getLabwareType(eqCi(lt.getName()))).thenReturn(lt); + BioRisk[] bioRisks = IntStream.of(1,2,3).mapToObj(i -> new BioRisk(i, "BR"+i)).toArray(BioRisk[]::new); + Arrays.stream(bioRisks).forEach(br -> when(val.getBioRisk(eqCi(br.getCode()))).thenReturn(br)); + Labware[] lws = Stream.of("XB1", "XB2").map(xb -> { + Labware lw = EntityFactory.makeEmptyLabware(lt); + lw.setExternalBarcode(xb); + lw.setBarcode(xb); + when(mockLabwareService.create(any(), eqCi(xb), eqCi(xb))).thenReturn(lw); + return lw; + }).toArray(Labware[]::new); + + final int[] idCounter = {20}; + when(mockSampleRepo.save(any())).then(invocation -> { + Sample sam = invocation.getArgument(0); + assertNull(sam.getId()); + sam.setId(++idCounter[0]); + return sam; + }); + + Operation[] ops = IntStream.of(100,101).mapToObj(i -> { + Operation op = new Operation(); + op.setOperationType(opType); + op.setId(i); + return op; + }).toArray(Operation[]::new); + when(mockOperationService.createOperation(any(), any(), any(), isNull())).thenReturn(ops[0], ops[1]); + + final Address A1 = new Address(1,1), A2 = new Address(1,2); + + List brs1 = List.of( + brsForCreate("EXT1", 31, "BR1", List.of(A1, A2)) + ); + List brs2 = List.of( + brsForCreate("EXT2", 32, "BR2", List.of(A1)), + brsForCreate("EXT3", 33, "BR3", List.of(A2)) + ); + BlockRegisterLabware brl1 = brlForCreate(lt.getName(), lws[0].getExternalBarcode(), brs1); + BlockRegisterLabware brl2 = brlForCreate(lt.getName(), lws[1].getExternalBarcode(), brs2); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl1, brl2)); + + RegisterResult result = service.create(request, user, val); + + verify(service).createTissues(request, val); + verify(mockOpTypeRepo).getByName(eqCi(opType.getName())); + for (Labware lw : lws) { + verify(mockLabwareService).create(lt, lw.getExternalBarcode(), lw.getBarcode()); + } + verifyNoMoreInteractions(mockLabwareService); + verify(mockSampleRepo, times(3)).save(any()); + Arrays.stream(lws).forEach(lw -> verify(mockEntityManager).refresh(lw)); + verify(mockSlotRepo).saveAll(Set.of(lws[0].getSlot(A1), lws[0].getSlot(A2))); + verify(mockSlotRepo).saveAll(Set.of(lws[1].getSlot(A1), lws[1].getSlot(A2))); + verifyNoMoreInteractions(mockSlotRepo); + ArgumentCaptor> actionsCaptor = genericCaptor(List.class); + verify(mockOperationService, times(2)).createOperation(same(opType), same(user), + actionsCaptor.capture(), isNull()); + verifyNoMoreInteractions(mockOperationService); + verify(mockWorkService).link(same(works), eq(Arrays.asList(ops))); + verifyNoMoreInteractions(mockWorkService); + List> actionses = actionsCaptor.getAllValues(); + assertThat(actionses).hasSize(2); + List actions = actionses.getFirst(); + assertThat(actions).hasSize(2); + List samples = new ArrayList<>(3); + Sample sam1 = actions.getFirst().getSample(); + samples.add(sam1); + Zip.of(actions.stream(), lws[0].getSlots().stream()).forEach((a, slot) -> { + assertSame(slot, a.getSource()); + assertSame(slot, a.getDestination()); + assertSame(sam1, a.getSourceSample()); + assertSame(sam1, a.getSample()); + }); + actions = actionses.get(1); + assertThat(actions).hasSize(2); + Zip.of(actions.stream(), lws[1].getSlots().stream()).forEach((a, slot) -> { + assertSame(slot, a.getSource()); + assertSame(slot, a.getDestination()); + assertSame(a.getSample(), a.getSourceSample()); + samples.add(a.getSample()); + }); + + int[] opIds = {100, 101, 101}; + Zip.enumerate(samples.stream()).forEach((i, sam) -> { + assertNotNull(sam.getId()); + assertEquals(31+i, sam.getBlockHighestSection()); + assertSame(tissues[i], sam.getTissue()); + assertSame(opType.getNewBioState(), sam.getBioState()); + verify(mockBioRiskRepo).recordBioRisk(sam, bioRisks[i], opIds[i]); + }); + verifyNoMoreInteractions(mockBioRiskRepo); + assertThat(result.getLabware()).containsExactly(lws); + } + + static BlockRegisterLabware brlForCreate(String lt, String xb, List samples) { + BlockRegisterLabware brl = new BlockRegisterLabware(); + brl.setLabwareType(lt); + brl.setExternalBarcode(xb); + brl.setSamples(samples); + return brl; + } + + static BlockRegisterSample brsForCreate(String extName, Integer highestSection, String bioRiskCode, + List
addresses) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setExternalIdentifier(extName); + brs.setHighestSection(highestSection); + brs.setBioRiskCode(bioRiskCode); + brs.setAddresses(addresses); + return brs; + } + + static Tissue makeTissue(Integer id, String extName) { + Tissue tis = new Tissue(); + tis.setId(id); + tis.setExternalName(extName); + return tis; + } + + static Donor makeDonor(Integer id, String name) { + return new Donor(id, name, LifeStage.adult, EntityFactory.getHuman()); + } +} From 9c56bf0aacdfad07cdc77f5935c7129e9c81a51c Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:12:45 +0000 Subject: [PATCH 12/13] Delete old registration code and classes --- .../register/BlockRegisterRequest_old.java | 211 ---- .../request/register/RegisterRequest.java | 64 -- .../register/RegisterClashChecker.java | 15 - .../service/register/RegisterServiceImp.java | 179 ---- .../register/RegisterValidationFactory.java | 13 +- .../register/RegisterValidationImp.java | 523 ---------- .../service/register/TissueFieldChecker.java | 82 -- .../register/TestFileRegisterService.java | 2 +- .../register/TestRegisterClashChecker.java | 30 +- .../register/TestRegisterValidation.java | 933 ------------------ .../TestRegisterValidationFactory.java | 8 +- .../register/TestTissueFieldChecker.java | 122 --- 12 files changed, 22 insertions(+), 2160 deletions(-) delete mode 100644 src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest_old.java delete mode 100644 src/main/java/uk/ac/sanger/sccp/stan/request/register/RegisterRequest.java delete mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java delete mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationImp.java delete mode 100644 src/main/java/uk/ac/sanger/sccp/stan/service/register/TissueFieldChecker.java delete mode 100644 src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidation.java delete mode 100644 src/test/java/uk/ac/sanger/sccp/stan/service/register/TestTissueFieldChecker.java diff --git a/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest_old.java b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest_old.java deleted file mode 100644 index 11c13acdf..000000000 --- a/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterRequest_old.java +++ /dev/null @@ -1,211 +0,0 @@ -package uk.ac.sanger.sccp.stan.request.register; - -import uk.ac.sanger.sccp.stan.model.LifeStage; - -import java.time.LocalDate; -import java.util.Objects; - -import static uk.ac.sanger.sccp.utils.BasicUtils.describe; - -/** - * The information required to register a block. - * @author dr6 - */ -public class BlockRegisterRequest_old { - private String donorIdentifier; - private LifeStage lifeStage; - private String hmdmc; - private String tissueType; - private int spatialLocation; - private String replicateNumber; - private String externalIdentifier; - private int highestSection; - private String labwareType; - private String medium; - private String fixative; - private String species; - private boolean existingTissue; - private LocalDate sampleCollectionDate; - private String bioRiskCode; - private String cellClass; - - public String getDonorIdentifier() { - return this.donorIdentifier; - } - - public void setDonorIdentifier(String donorIdentifier) { - this.donorIdentifier = donorIdentifier; - } - - public LifeStage getLifeStage() { - return this.lifeStage; - } - - public void setLifeStage(LifeStage lifeStage) { - this.lifeStage = lifeStage; - } - - public String getHmdmc() { - return this.hmdmc; - } - - public void setHmdmc(String hmdmc) { - this.hmdmc = hmdmc; - } - - public String getTissueType() { - return this.tissueType; - } - - public void setTissueType(String tissueType) { - this.tissueType = tissueType; - } - - public int getSpatialLocation() { - return this.spatialLocation; - } - - public void setSpatialLocation(int spatialLocation) { - this.spatialLocation = spatialLocation; - } - - public String getReplicateNumber() { - return this.replicateNumber; - } - - public void setReplicateNumber(String replicateNumber) { - this.replicateNumber = replicateNumber; - } - - public String getExternalIdentifier() { - return this.externalIdentifier; - } - - public void setExternalIdentifier(String externalIdentifier) { - this.externalIdentifier = externalIdentifier; - } - - public int getHighestSection() { - return this.highestSection; - } - - public void setHighestSection(int highestSection) { - this.highestSection = highestSection; - } - - public String getLabwareType() { - return this.labwareType; - } - - public void setLabwareType(String labwareType) { - this.labwareType = labwareType; - } - - public String getMedium() { - return this.medium; - } - - public void setMedium(String medium) { - this.medium = medium; - } - - public String getFixative() { - return this.fixative; - } - - public void setFixative(String fixative) { - this.fixative = fixative; - } - - public String getSpecies() { - return this.species; - } - - public void setSpecies(String species) { - this.species = species; - } - - public boolean isExistingTissue() { - return this.existingTissue; - } - - public void setExistingTissue(boolean existingTissue) { - this.existingTissue = existingTissue; - } - - public LocalDate getSampleCollectionDate() { - return this.sampleCollectionDate; - } - - public void setSampleCollectionDate(LocalDate sampleCollectionDate) { - this.sampleCollectionDate = sampleCollectionDate; - } - - public String getBioRiskCode() { - return this.bioRiskCode; - } - - public void setBioRiskCode(String bioRiskCode) { - this.bioRiskCode = bioRiskCode; - } - - public String getCellClass() { - return this.cellClass; - } - - public void setCellClass(String cellClass) { - this.cellClass = cellClass; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - BlockRegisterRequest_old that = (BlockRegisterRequest_old) o; - return (this.spatialLocation == that.spatialLocation - && Objects.equals(this.replicateNumber, that.replicateNumber) - && this.highestSection == that.highestSection - && this.existingTissue == that.existingTissue - && Objects.equals(this.donorIdentifier, that.donorIdentifier) - && this.lifeStage == that.lifeStage - && Objects.equals(this.hmdmc, that.hmdmc) - && Objects.equals(this.tissueType, that.tissueType) - && Objects.equals(this.externalIdentifier, that.externalIdentifier) - && Objects.equals(this.labwareType, that.labwareType) - && Objects.equals(this.medium, that.medium) - && Objects.equals(this.fixative, that.fixative) - && Objects.equals(this.species, that.species) - && Objects.equals(this.sampleCollectionDate, that.sampleCollectionDate) - && Objects.equals(this.bioRiskCode, that.bioRiskCode) - && Objects.equals(this.cellClass, that.cellClass) - ); - } - - @Override - public int hashCode() { - return (externalIdentifier!=null ? externalIdentifier.hashCode() : 0); - } - - @Override - public String toString() { - return describe(this) - .add("donorIdentifier", donorIdentifier) - .add("lifeStage", lifeStage) - .add("hmdmc", hmdmc) - .add("tissueType", tissueType) - .add("spatialLocation", spatialLocation) - .add("replicateNumber", replicateNumber) - .add("externalIdentifier", externalIdentifier) - .add("highestSection", highestSection) - .add("labwareType", labwareType) - .add("medium", medium) - .add("fixative", fixative) - .add("species", species) - .add("existingTissue", existingTissue) - .add("sampleCollectionDate", sampleCollectionDate) - .add("bioRiskCode", bioRiskCode) - .add("cellClass", cellClass) - .reprStringValues() - .toString(); - } -} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/request/register/RegisterRequest.java b/src/main/java/uk/ac/sanger/sccp/stan/request/register/RegisterRequest.java deleted file mode 100644 index 8ceaa8690..000000000 --- a/src/main/java/uk/ac/sanger/sccp/stan/request/register/RegisterRequest.java +++ /dev/null @@ -1,64 +0,0 @@ -package uk.ac.sanger.sccp.stan.request.register; - -import java.util.List; - -import static uk.ac.sanger.sccp.utils.BasicUtils.describe; - -/** - * The information required to register some blocks. - * @author dr6 - */ -public class RegisterRequest { - private List blocks; - private List workNumbers; - - public RegisterRequest() { - this(null, null); - } - - public RegisterRequest(List blocks) { - this(blocks, null); - } - - public RegisterRequest(List blocks, List workNumbers) { - setBlocks(blocks); - setWorkNumbers(workNumbers); - } - - public List getBlocks() { - return this.blocks; - } - - public void setBlocks(List blocks) { - this.blocks = (blocks==null ? List.of() : blocks); - } - - public List getWorkNumbers() { - return this.workNumbers; - } - - public void setWorkNumbers(List workNumbers) { - this.workNumbers = (workNumbers==null ? List.of() : workNumbers); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - RegisterRequest that = (RegisterRequest) o; - return (this.blocks.equals(that.blocks) && this.workNumbers.equals(that.workNumbers)); - } - - @Override - public int hashCode() { - return 31*this.blocks.hashCode() + this.workNumbers.hashCode(); - } - - @Override - public String toString() { - return describe(this) - .add("blocks", blocks) - .add("workNumbers", workNumbers) - .toString(); - } -} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java index f374afcd3..33869ab21 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterClashChecker.java @@ -49,21 +49,6 @@ public List findClashes(BlockRegisterRequest request) { return createClashInfo(existingTissues); } - public List findClashes(RegisterRequest request) { - Set externalNames = request.getBlocks().stream() - .filter(br -> !br.isExistingTissue()) - .map(BlockRegisterRequest_old::getExternalIdentifier) - .collect(toSet()); - if (externalNames.isEmpty()) { - return List.of(); - } - List existingTissues = tissueRepo.findAllByExternalNameIn(externalNames); - if (existingTissues.isEmpty()) { - return List.of(); - } - return createClashInfo(existingTissues); - } - public List createClashInfo(List tissues) { Set tissueIds = tissues.stream().map(Tissue::getId).collect(toSet()); Set sampleIds = loadSampleIds(tissueIds); diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java deleted file mode 100644 index cf100683c..000000000 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterServiceImp.java +++ /dev/null @@ -1,179 +0,0 @@ -package uk.ac.sanger.sccp.stan.service.register; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import uk.ac.sanger.sccp.stan.model.*; -import uk.ac.sanger.sccp.stan.repo.*; -import uk.ac.sanger.sccp.stan.request.register.*; -import uk.ac.sanger.sccp.stan.service.*; -import uk.ac.sanger.sccp.stan.service.work.WorkService; - -import javax.persistence.EntityManager; -import java.util.*; - -/** - * @author dr6 - */ -@Service -public class RegisterServiceImp implements IRegisterService { - private final EntityManager entityManager; - private final RegisterValidationFactory validationFactory; - private final DonorRepo donorRepo; - private final TissueRepo tissueRepo; - private final SampleRepo sampleRepo; - private final SlotRepo slotRepo; - private final BioRiskRepo bioRiskRepo; - private final OperationTypeRepo opTypeRepo; - private final LabwareService labwareService; - private final OperationService operationService; - private final WorkService workService; - private final RegisterClashChecker clashChecker; - - @Autowired - public RegisterServiceImp(EntityManager entityManager, RegisterValidationFactory validationFactory, - DonorRepo donorRepo, TissueRepo tissueRepo, SampleRepo sampleRepo, SlotRepo slotRepo, - BioRiskRepo bioRiskRepo, OperationTypeRepo opTypeRepo, - LabwareService labwareService, OperationService operationService, WorkService workService, - RegisterClashChecker clashChecker) { - this.entityManager = entityManager; - this.validationFactory = validationFactory; - this.donorRepo = donorRepo; - this.tissueRepo = tissueRepo; - this.sampleRepo = sampleRepo; - this.slotRepo = slotRepo; - this.bioRiskRepo = bioRiskRepo; - this.opTypeRepo = opTypeRepo; - this.labwareService = labwareService; - this.operationService = operationService; - this.workService = workService; - this.clashChecker = clashChecker; - } - - @Override - public RegisterResult register(User user, RegisterRequest request) { - if (request.getBlocks().isEmpty()) { - return new RegisterResult(); // nothing to do - } - List clashes = clashChecker.findClashes(request); - if (!clashes.isEmpty()) { - return RegisterResult.clashes(clashes); - } - RegisterValidation validation = validationFactory.createRegisterValidation(request); - Collection problems = validation.validate(); - if (!problems.isEmpty()) { - throw new ValidationException("The register request could not be validated.", problems); - } - updateExistingTissues(request, validation); - return create(request, user, validation); - } - - public Map createDonors(RegisterRequest request, RegisterValidation validation) { - Map donors = new HashMap<>(); - for (BlockRegisterRequest_old block : request.getBlocks()) { - String donorName = block.getDonorIdentifier().toUpperCase(); - if (!donors.containsKey(donorName)) { - Donor donor = validation.getDonor(donorName); - if (donor.getId() == null) { - donor = donorRepo.save(donor); - } - donors.put(donorName, donor); - } - } - return donors; - } - - public Map createTissues(RegisterRequest request, RegisterValidation validation) { - Map donors = createDonors(request, validation); - Map tissueMap = new HashMap<>(request.getBlocks().size()); - for (BlockRegisterRequest_old block : request.getBlocks()) { - final String tissueKey = block.getExternalIdentifier().toUpperCase(); - Tissue existingTissue = validation.getTissue(tissueKey); - if (tissueMap.get(tissueKey)!=null) { - continue; - } - if (existingTissue!=null) { - tissueMap.put(tissueKey, existingTissue); - continue; - } - Donor donor = donors.get(block.getDonorIdentifier().toUpperCase()); - Hmdmc hmdmc; - if (block.getHmdmc()==null || block.getHmdmc().isEmpty()) { - hmdmc = null; - } else { - hmdmc = validation.getHmdmc(block.getHmdmc()); - if (hmdmc==null) { - throw new IllegalArgumentException("Unknown HuMFre number: "+block.getHmdmc()); - } - } - CellClass cellClass = validation.getCellClass(block.getCellClass()); - if (hmdmc==null && donor.getSpecies().requiresHmdmc() && cellClass.isHmdmcRequired()) { - throw new IllegalArgumentException("No HuMFre number given for tissue "+block.getExternalIdentifier()); - } - if (!donor.getSpecies().requiresHmdmc() && hmdmc!=null) { - throw new IllegalArgumentException("HuMFre number given for non-human tissue "+block.getExternalIdentifier()); - } - Tissue tissue = new Tissue(null, block.getExternalIdentifier(), block.getReplicateNumber().toLowerCase(), - validation.getSpatialLocation(block.getTissueType(), block.getSpatialLocation()), - donor, - validation.getMedium(block.getMedium()), - validation.getFixative(block.getFixative()), cellClass, - hmdmc, block.getSampleCollectionDate(), null); - tissueMap.put(tissueKey, tissueRepo.save(tissue)); - } - return tissueMap; - } - - /** - * Updates any existing tissues that now have a collection date - * @param request specification - * @param validation validation result to look up tissues - */ - public void updateExistingTissues(RegisterRequest request, RegisterValidation validation) { - List toUpdate = new ArrayList<>(); - for (BlockRegisterRequest_old brr : request.getBlocks()) { - if (brr.isExistingTissue() && brr.getSampleCollectionDate()!=null) { - Tissue tissue = validation.getTissue(brr.getExternalIdentifier()); - if (tissue!=null && tissue.getCollectionDate()==null) { - tissue.setCollectionDate(brr.getSampleCollectionDate()); - toUpdate.add(tissue); - } - } - } - if (!toUpdate.isEmpty()) { - tissueRepo.saveAll(toUpdate); - } - } - - public RegisterResult create(RegisterRequest request, User user, RegisterValidation validation) { - Map tissues = createTissues(request, validation); - - List labwareList = new ArrayList<>(request.getBlocks().size()); - OperationType opType = opTypeRepo.getByName("Register"); - BioState bioState = opType.getNewBioState(); - - List ops = new ArrayList<>(request.getBlocks().size()); - - for (BlockRegisterRequest_old block : request.getBlocks()) { - Tissue tissue = tissues.get(block.getExternalIdentifier().toUpperCase()); - Sample sample = sampleRepo.save(Sample.newBlock(null, tissue, bioState, block.getHighestSection())); - LabwareType labwareType = validation.getLabwareType(block.getLabwareType()); - Labware labware = labwareService.create(labwareType); - Slot slot = labware.getFirstSlot(); - slot.getSamples().add(sample); - slot = slotRepo.save(slot); - entityManager.refresh(labware); - labwareList.add(labware); - final Operation op = operationService.createOperationInPlace(opType, user, slot, sample); - ops.add(op); - BioRisk bioRisk = validation.getBioRisk(block.getBioRiskCode()); - bioRiskRepo.recordBioRisk(sample, bioRisk, op.getId()); - } - - if (!ops.isEmpty() && validation.getWorks()!=null && !validation.getWorks().isEmpty()) { - workService.link(validation.getWorks(), ops); - } - - return new RegisterResult(labwareList); - } - -} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationFactory.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationFactory.java index 548467f6b..944808310 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationFactory.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationFactory.java @@ -4,7 +4,8 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import uk.ac.sanger.sccp.stan.repo.*; -import uk.ac.sanger.sccp.stan.request.register.*; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest; +import uk.ac.sanger.sccp.stan.request.register.SectionRegisterRequest; import uk.ac.sanger.sccp.stan.service.*; import uk.ac.sanger.sccp.stan.service.sanitiser.Sanitiser; import uk.ac.sanger.sccp.stan.service.work.WorkService; @@ -35,7 +36,6 @@ public class RegisterValidationFactory { private final Validator xeniumLotValidator; private final Validator sectionValidator; private final Sanitiser thicknessSanitiser; - private final TissueFieldChecker tissueFieldChecker; private final BlockFieldChecker blockFieldChecker; private final SlotRegionService slotRegionService; private final BioRiskService bioRiskService; @@ -55,7 +55,7 @@ public RegisterValidationFactory(DonorRepo donorRepo, HmdmcRepo hmdmcRepo, Tissu @Qualifier("xeniumLotValidator") Validator xeniumLotValidator, @Qualifier("replicateValidator") Validator replicateValidator, @Qualifier("sectionValidator") Validator sectionValidator, - TissueFieldChecker tissueFieldChecker, BlockFieldChecker blockFieldChecker, + BlockFieldChecker blockFieldChecker, SlotRegionService slotRegionService, BioRiskService bioRiskService, WorkService workService) { this.donorRepo = donorRepo; this.hmdmcRepo = hmdmcRepo; @@ -77,19 +77,12 @@ public RegisterValidationFactory(DonorRepo donorRepo, HmdmcRepo hmdmcRepo, Tissu this.xeniumLotValidator = xeniumLotValidator; this.thicknessSanitiser = thicknessSanitiser; this.sectionValidator = sectionValidator; - this.tissueFieldChecker = tissueFieldChecker; this.blockFieldChecker = blockFieldChecker; this.slotRegionService = slotRegionService; this.workService = workService; this.bioRiskService = bioRiskService; } - public RegisterValidation createRegisterValidation(RegisterRequest request) { - return new RegisterValidationImp(request, donorRepo, hmdmcRepo, ttRepo, ltRepo, mediumRepo, - fixativeRepo, tissueRepo, speciesRepo, cellClassRepo, donorNameValidation, externalNameValidation, replicateValidator, - tissueFieldChecker, bioRiskService, workService); - } - public RegisterValidation createBlockRegisterValidation(BlockRegisterRequest request) { return new BlockRegisterValidationImp(request, donorRepo, hmdmcRepo, ttRepo, ltRepo, mediumRepo, fixativeRepo, tissueRepo, speciesRepo, cellClassRepo, labwareRepo, donorNameValidation, diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationImp.java deleted file mode 100644 index cbcf773bc..000000000 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationImp.java +++ /dev/null @@ -1,523 +0,0 @@ -package uk.ac.sanger.sccp.stan.service.register; - -import uk.ac.sanger.sccp.stan.model.*; -import uk.ac.sanger.sccp.stan.repo.*; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; -import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; -import uk.ac.sanger.sccp.stan.service.BioRiskService; -import uk.ac.sanger.sccp.stan.service.Validator; -import uk.ac.sanger.sccp.stan.service.work.WorkService; -import uk.ac.sanger.sccp.utils.BasicUtils; -import uk.ac.sanger.sccp.utils.UCMap; - -import java.time.LocalDate; -import java.util.*; -import java.util.function.Function; - -import static uk.ac.sanger.sccp.utils.BasicUtils.*; - -/** - * @author dr6 - */ -// This might have been a nice idea, but in practice it's unnecessarily complicated. -public class RegisterValidationImp implements RegisterValidation { - private final RegisterRequest request; - private final DonorRepo donorRepo; - private final HmdmcRepo hmdmcRepo; - private final TissueTypeRepo ttRepo; - private final LabwareTypeRepo ltRepo; - private final MediumRepo mediumRepo; - private final FixativeRepo fixativeRepo; - private final TissueRepo tissueRepo; - private final SpeciesRepo speciesRepo; - private final CellClassRepo cellClassRepo; - private final Validator donorNameValidation; - private final Validator externalNameValidation; - private final Validator replicateValidator; - private final TissueFieldChecker tissueFieldChecker; - private final BioRiskService bioRiskService; - private final WorkService workService; - - final Map donorMap = new HashMap<>(); - final Map tissueMap = new HashMap<>(); - final Map hmdmcMap = new HashMap<>(); - final Map speciesMap = new HashMap<>(); - final Map spatialLocationMap = new HashMap<>(); - final Map labwareTypeMap = new HashMap<>(); - final Map mediumMap = new HashMap<>(); - final Map fixativeMap = new HashMap<>(); - UCMap cellClassMap; - UCMap bioRiskMap; - Collection works; - final LinkedHashSet problems = new LinkedHashSet<>(); - - public RegisterValidationImp(RegisterRequest request, DonorRepo donorRepo, - HmdmcRepo hmdmcRepo, TissueTypeRepo ttRepo, LabwareTypeRepo ltRepo, - MediumRepo mediumRepo, FixativeRepo fixativeRepo, TissueRepo tissueRepo, - SpeciesRepo speciesRepo, CellClassRepo cellClassRepo, - Validator donorNameValidation, Validator externalNameValidation, - Validator replicateValidator, - TissueFieldChecker tissueFieldChecker, - BioRiskService bioRiskService, WorkService workService) { - this.request = request; - this.donorRepo = donorRepo; - this.hmdmcRepo = hmdmcRepo; - this.ttRepo = ttRepo; - this.ltRepo = ltRepo; - this.mediumRepo = mediumRepo; - this.fixativeRepo = fixativeRepo; - this.tissueRepo = tissueRepo; - this.speciesRepo = speciesRepo; - this.cellClassRepo = cellClassRepo; - this.donorNameValidation = donorNameValidation; - this.externalNameValidation = externalNameValidation; - this.replicateValidator = replicateValidator; - this.tissueFieldChecker = tissueFieldChecker; - this.bioRiskService = bioRiskService; - this.workService = workService; - } - - @Override - public Collection validate() { - if (blocks().isEmpty()) { - return Collections.emptySet(); // nothing to do - } - validateDonors(); - validateHmdmcs(); - validateSpatialLocations(); - validateLabwareTypes(); - validateMediums(); - validateFixatives(); - validateCollectionDates(); - validateExistingTissues(); - validateNewTissues(); - validateBioRisks(); - validateWorks(); - validateCellClasses(); - return problems; - } - - public void validateDonors() { - for (BlockRegisterRequest_old block : blocks()) { - boolean skip = false; - Species species = null; - if (block.getDonorIdentifier()==null || block.getDonorIdentifier().isEmpty()) { - skip = true; - addProblem("Missing donor identifier."); - } else if (donorNameValidation!=null) { - donorNameValidation.validate(block.getDonorIdentifier(), this::addProblem); - } - if (block.getSpecies()==null || block.getSpecies().isEmpty()) { - addProblem("Missing species."); - } else { - String speciesUc = block.getSpecies().toUpperCase(); - species = speciesMap.get(speciesUc); - if (species==null && !speciesMap.containsKey(speciesUc)) { - species = speciesRepo.findByName(speciesUc).orElse(null); - speciesMap.put(speciesUc, species); - if (species==null) { - addProblem("Unknown species: "+repr(block.getSpecies())); - } else if (!species.isEnabled()) { - addProblem("Species is not enabled: "+species.getName()); - } - } - } - if (skip) { - continue; - } - String donorNameUc = block.getDonorIdentifier().toUpperCase(); - Donor donor = donorMap.get(donorNameUc); - if (donor==null) { - donor = new Donor(null, block.getDonorIdentifier(), block.getLifeStage(), species); - donorMap.put(donorNameUc, donor); - } else { - if (donor.getLifeStage()!=block.getLifeStage()) { - addProblem("Multiple different life stages specified for donor "+donor.getDonorName()); - } - if (species!=null && !species.equals(donor.getSpecies())) { - addProblem("Multiple different species specified for donor "+donor.getDonorName()); - } - } - } - for (Map.Entry entry : donorMap.entrySet()) { - Optional optDonor = donorRepo.findByDonorName(entry.getKey()); - if (optDonor.isEmpty()) { - continue; - } - Donor realDonor = optDonor.get(); - Donor newDonor = entry.getValue(); - if (realDonor.getLifeStage()!=newDonor.getLifeStage()) { - addProblem("Wrong life stage given for existing donor "+realDonor.getDonorName()); - } - if (newDonor.getSpecies()!=null && !newDonor.getSpecies().equals(realDonor.getSpecies())) { - addProblem("Wrong species given for existing donor "+realDonor.getDonorName()); - } - entry.setValue(realDonor); - } - } - - public void validateSpatialLocations() { - Map tissueTypeMap = new HashMap<>(); - Set unknownTissueTypes = new LinkedHashSet<>(); - for (BlockRegisterRequest_old block : blocks()) { - if (block.getTissueType()==null || block.getTissueType().isEmpty()) { - addProblem("Missing tissue type."); - continue; - } - if (unknownTissueTypes.contains(block.getTissueType())) { - continue; - } - StringIntKey key = new StringIntKey(block.getTissueType(), block.getSpatialLocation()); - if (spatialLocationMap.containsKey(key)) { - continue; - } - TissueType tt = tissueTypeMap.get(key.string); - if (tt==null) { - Optional ttOpt = ttRepo.findByName(key.string); - if (ttOpt.isEmpty()) { - unknownTissueTypes.add(block.getTissueType()); - continue; - } - tt = ttOpt.get(); - tissueTypeMap.put(key.string, tt); - if (!tt.isEnabled()) { - addProblem(String.format("Tissue type \"%s\" is disabled.", tt.getName())); - } - } - final int slCode = block.getSpatialLocation(); - Optional slOpt = tt.getSpatialLocations().stream() - .filter(spl -> spl.getCode()==slCode) - .findAny(); - if (slOpt.isEmpty()) { - addProblem(String.format("Unknown spatial location %s for tissue type %s.", slCode, tt.getName())); - continue; - } - SpatialLocation sl = slOpt.get(); - if (tt.isEnabled() && !sl.isEnabled()) { - addProblem(String.format("Spatial location is disabled: %s for tissue type %s.", sl.getCode(), tt.getName())); - } - spatialLocationMap.put(key, sl); - } - if (!unknownTissueTypes.isEmpty()) { - if (unknownTissueTypes.size()==1) { - addProblem("Unknown tissue type: "+unknownTissueTypes); - } else { - addProblem("Unknown tissue types: " + unknownTissueTypes); - } - } - } - - public void validateHmdmcs() { - Set unknownHmdmcs = new LinkedHashSet<>(); - boolean unwanted = false; - boolean missing = false; - for (BlockRegisterRequest_old block : blocks()) { - boolean needsHmdmc = false; - boolean needsNoHmdmc = false; - if (block.getSpecies()!=null && !block.getSpecies().isEmpty()) { - needsHmdmc = Species.isHumanName(block.getSpecies()); - needsNoHmdmc = !needsHmdmc; - } - String hmdmcString = block.getHmdmc(); - if (hmdmcString==null || hmdmcString.isEmpty()) { - if (needsHmdmc) { - missing = true; - } - continue; - } - if (needsNoHmdmc) { - unwanted = true; - continue; - } - - String hmdmcUc = hmdmcString.toUpperCase(); - if (hmdmcMap.containsKey(hmdmcUc)) { - continue; - } - Hmdmc hmdmc = hmdmcRepo.findByHmdmc(hmdmcString).orElse(null); - hmdmcMap.put(hmdmcUc, hmdmc); - if (hmdmc==null) { - unknownHmdmcs.add(hmdmcString); - } - } - if (missing) { - addProblem("Missing HuMFre number."); - } - if (unwanted) { - addProblem("Non-human tissue should not have a HuMFre number."); - } - if (!unknownHmdmcs.isEmpty()) { - addProblem(pluralise("Unknown HuMFre number{s}: ", unknownHmdmcs.size()) + unknownHmdmcs); - } - List disabledHmdmcs = hmdmcMap.values().stream() - .filter(h -> h!=null && !h.isEnabled()) - .map(Hmdmc::getHmdmc) - .toList(); - if (!disabledHmdmcs.isEmpty()) { - addProblem(pluralise("HuMFre number{s} not enabled: ", disabledHmdmcs.size()) + disabledHmdmcs); - } - } - - public void validateLabwareTypes() { - validateByName("labware type", BlockRegisterRequest_old::getLabwareType, ltRepo::findByName, labwareTypeMap); - } - - public void validateMediums() { - validateByName("medium", BlockRegisterRequest_old::getMedium, mediumRepo::findByName, mediumMap); - } - - public void validateFixatives() { - validateByName("fixative", BlockRegisterRequest_old::getFixative, fixativeRepo::findByName, fixativeMap); - } - - public void validateCollectionDates() { - boolean missing = false; - LocalDate today = LocalDate.now(); - Set badDates = new LinkedHashSet<>(); - Map extToDate = new HashMap<>(request.getBlocks().size()); - for (BlockRegisterRequest_old block : blocks()) { - if (block.getSampleCollectionDate()==null) { - if (block.getLifeStage()==LifeStage.fetal && block.getSpecies()!=null - && Species.isHumanName(block.getSpecies())) { - missing = true; - } - } else if (block.getSampleCollectionDate().isAfter(today)) { - badDates.add(block.getSampleCollectionDate()); - } - if (block.getExternalIdentifier()!=null && !block.getExternalIdentifier().isEmpty()) { - String key = block.getExternalIdentifier().trim().toUpperCase(); - if (!key.isEmpty()) { - if (extToDate.containsKey(key) && !Objects.equals(extToDate.get(key), block.getSampleCollectionDate())) { - addProblem("Inconsistent collection dates specified for tissue " + key + "."); - } else { - extToDate.put(key, block.getSampleCollectionDate()); - } - } - } - } - if (missing) { - addProblem("Human fetal samples must have a collection date."); - } - if (!badDates.isEmpty()) { - addProblem(pluralise("Invalid sample collection date{s}: ", badDates.size()) + badDates); - } - } - - public void validateExistingTissues() { - List blocksForExistingTissues = blocks().stream() - .filter(BlockRegisterRequest_old::isExistingTissue) - .toList(); - if (blocksForExistingTissues.isEmpty()) { - return; - } - if (blocksForExistingTissues.stream().map(BlockRegisterRequest_old::getExternalIdentifier).anyMatch(xn -> xn==null || xn.isEmpty())) { - addProblem("Missing external identifier."); - } - Set xns = blocksForExistingTissues.stream() - .map(BlockRegisterRequest_old::getExternalIdentifier) - .filter(Objects::nonNull) - .collect(toLinkedHashSet()); - if (xns.isEmpty()) { - return; - } - tissueRepo.findAllByExternalNameIn(xns).forEach(t -> tissueMap.put(t.getExternalName().toUpperCase(), t)); - - Set missing = xns.stream() - .filter(xn -> !tissueMap.containsKey(xn.toUpperCase())) - .collect(toLinkedHashSet()); - if (!missing.isEmpty()) { - addProblem("Existing external identifiers not recognised: " + reprCollection(missing)); - } - - for (BlockRegisterRequest_old br : blocksForExistingTissues) { - String xn = br.getExternalIdentifier(); - if (xn == null || xn.isEmpty()) { - continue; - } - Tissue tissue = tissueMap.get(xn.toUpperCase()); - if (tissue!=null) { - tissueFieldChecker.check(this::addProblem, br, tissue); - } - } - } - - public void validateNewTissues() { - // NB repeated new external identifier in one request is still disallowed - Set externalNames = new HashSet<>(); - for (BlockRegisterRequest_old block : blocks()) { - if (block.isExistingTissue()) { - continue; - } - if (block.getReplicateNumber()==null || block.getReplicateNumber().isEmpty()) { - addProblem("Missing replicate number."); - } else { - replicateValidator.validate(block.getReplicateNumber(), this::addProblem); - } - if (block.getHighestSection() < 0) { - addProblem("Highest section number cannot be negative."); - } - if (block.getExternalIdentifier()==null || block.getExternalIdentifier().isEmpty()) { - addProblem("Missing external identifier."); - } else { - if (externalNameValidation != null) { - externalNameValidation.validate(block.getExternalIdentifier(), this::addProblem); - } - if (!externalNames.add(block.getExternalIdentifier().toUpperCase())) { - addProblem("Repeated external identifier: " + block.getExternalIdentifier()); - } else if (!tissueRepo.findAllByExternalName(block.getExternalIdentifier()).isEmpty()) { - addProblem(String.format("There is already tissue in the database with external identifier %s.", - block.getExternalIdentifier())); - } - } - } - } - - public void validateBioRisks() { - this.bioRiskMap = bioRiskService.loadAndValidateBioRisks(problems, blocks().stream(), - BlockRegisterRequest_old::getBioRiskCode, BlockRegisterRequest_old::setBioRiskCode); - } - - public void validateCellClasses() { - Set cellClassNames = new HashSet<>(); - boolean anyMissing = false; - for (BlockRegisterRequest_old block : blocks()) { - String cellClassName = block.getCellClass(); - if (nullOrEmpty(cellClassName)) { - anyMissing = true; - } else { - cellClassNames.add(cellClassName); - } - } - if (anyMissing) { - addProblem("Missing cell class name."); - } - if (cellClassNames.isEmpty()) { - cellClassMap = new UCMap<>(0); - return; - } - cellClassMap = cellClassRepo.findMapByNameIn(cellClassNames); - List missing = cellClassNames.stream() - .filter(name -> cellClassMap.get(name) == null) - .map(BasicUtils::repr) - .toList(); - if (!missing.isEmpty()) { - problems.add("Unknown cell class name: " + missing); - } - } - - public void validateWorks() { - if (request.getWorkNumbers().isEmpty()) { - addProblem("No work number supplied."); - works = List.of(); - } else { - works = workService.validateUsableWorks(problems, request.getWorkNumbers()).values(); - } - } - - private void validateByName(String entityName, - Function nameFunction, - Function> lkp, - Map map) { - Set unknownNames = new LinkedHashSet<>(); - boolean missing = false; - for (BlockRegisterRequest_old block : blocks()) { - String name = nameFunction.apply(block); - if (name==null || name.isEmpty()) { - missing = true; - continue; - } - if (unknownNames.contains(name)) { - continue; - } - String nameUc = name.toUpperCase(); - if (map.containsKey(nameUc)) { - continue; - } - Optional opt = lkp.apply(nameUc); - if (opt.isEmpty()) { - unknownNames.add(name); - continue; - } - map.put(nameUc, opt.get()); - } - if (missing) { - addProblem(String.format("Missing %s.", entityName)); - } - if (!unknownNames.isEmpty()) { - addProblem(String.format("Unknown %s%s: %s", entityName, unknownNames.size()==1 ? "" : "s", unknownNames)); - } - } - - private Collection blocks() { - return request.getBlocks(); - } - - private boolean addProblem(String problem) { - return problems.add(problem); - } - - public Collection getProblems() { - return this.problems; - } - - @Override - public Donor getDonor(String name) { - return ucGet(this.donorMap, name); - } - - @Override - public Hmdmc getHmdmc(String hmdmc) { - return ucGet(hmdmcMap, hmdmc); - } - - @Override - public SpatialLocation getSpatialLocation(String tissueTypeName, int code) { - return (tissueTypeName==null ? null : this.spatialLocationMap.get(new StringIntKey(tissueTypeName, code))); - } - - @Override - public LabwareType getLabwareType(String name) { - return ucGet(this.labwareTypeMap, name); - } - - @Override - public Medium getMedium(String name) { - return ucGet(this.mediumMap, name); - } - - @Override - public Fixative getFixative(String name) { - return ucGet(this.fixativeMap, name); - } - - @Override - public Tissue getTissue(String externalName) { - return ucGet(this.tissueMap, externalName); - } - - @Override - public BioRisk getBioRisk(String code) { - return bioRiskMap.get(code); - } - - @Override - public Collection getWorks() { - return this.works; - } - - @Override - public CellClass getCellClass(String name) { - return cellClassMap.get(name); - } - - private static E ucGet(Map map, String key) { - return (key==null ? null : map.get(key.toUpperCase())); - } - - record StringIntKey(String string, int number) { - StringIntKey(String string, int number) { - this.string = string.toUpperCase(); - this.number = number; - } - } -} diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/register/TissueFieldChecker.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/TissueFieldChecker.java deleted file mode 100644 index 1f3b8d658..000000000 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/register/TissueFieldChecker.java +++ /dev/null @@ -1,82 +0,0 @@ -package uk.ac.sanger.sccp.stan.service.register; - -import org.springframework.stereotype.Service; -import uk.ac.sanger.sccp.stan.model.*; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; - -import java.util.function.Consumer; -import java.util.function.Function; - -/** - * Utility for checking that the description of tissue in a {@link BlockRegisterRequest_old} - * matches the information in an existing tissue. - * @author dr6 - */ -@Service -public class TissueFieldChecker { - /** Chains two functions, but skips the second if the first returns null */ - private static Function chain(Function ab, Function bc) { - return ab.andThen(b -> b==null ? null : bc.apply(b)); - } - enum Field { - DONOR(Tissue::getDonor, Donor::getDonorName, BlockRegisterRequest_old::getDonorIdentifier, "donor identifier"), - HMDMC(Tissue::getHmdmc, Hmdmc::getHmdmc, BlockRegisterRequest_old::getHmdmc, "HuMFre number"), - TTYPE(Tissue::getTissueType, TissueType::getName, BlockRegisterRequest_old::getTissueType, "tissue type"), - SL(Tissue::getSpatialLocation, SpatialLocation::getCode, BlockRegisterRequest_old::getSpatialLocation, "spatial location"), - REPLICATE(Tissue::getReplicate, BlockRegisterRequest_old::getReplicateNumber, "replicate number", false), - MEDIUM(Tissue::getMedium, Medium::getName, BlockRegisterRequest_old::getMedium, "medium"), - FIXATIVE(Tissue::getFixative, Fixative::getName, BlockRegisterRequest_old::getFixative, "fixative"), - COLLECTION_DATE(Tissue::getCollectionDate, BlockRegisterRequest_old::getSampleCollectionDate, "sample collection date", true), - CELL_CLASS(Tissue::getCellClass, CellClass::getName, BlockRegisterRequest_old::getCellClass, "cellular classification"), - ; - - private final Function tissueFunction; - private final Function brFunction; - private final String description; - private final boolean replaceMissing; - - Field(Function tissueFunction, Function brFunction, - String description, boolean replaceMissing) { - this.tissueFunction = tissueFunction; - this.brFunction = brFunction; - this.description = description; - this.replaceMissing = replaceMissing; - } - - Field(Function tissueFunction, Function xFunction, Function brFunction, - String description) { - this(chain(tissueFunction, xFunction), brFunction, description, false); - } - - public Object apply(Tissue tissue) { - return tissueFunction.apply(tissue); - } - public Object apply(BlockRegisterRequest_old br) { - return brFunction.apply(br); - } - } - - public void check(Consumer problemConsumer, BlockRegisterRequest_old br, Tissue tissue) { - for (Field field : Field.values()) { - Object oldValue = field.apply(tissue); - if (field.replaceMissing && oldValue==null) { - continue; - } - Object newValue = field.apply(br); - if (!match(oldValue, newValue)) { - problemConsumer.accept(String.format("Expected %s to be %s for existing tissue %s.", - field.description, oldValue, tissue.getExternalName())); - } - } - } - - public boolean match(Object oldValue, Object newValue) { - if (oldValue==null) { - return (newValue==null || newValue.equals("")); - } - if (oldValue instanceof String && newValue instanceof String) { - return ((String) oldValue).equalsIgnoreCase((String) newValue); - } - return oldValue.equals(newValue); - } -} diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java index bd442df44..bc9790166 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestFileRegisterService.java @@ -149,7 +149,7 @@ void testSectionRegister_IOException(Object request) throws IOException { static Stream regArgs() { return Arrays.stream(new Object[][] { {new SectionRegisterRequest()}, - {new RegisterRequest()}, + {new BlockRegisterRequest()}, {new OriginalSampleRegisterRequest()}, }).map(Arguments::of); } diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterClashChecker.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterClashChecker.java index d0fdf5a6f..4116892e2 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterClashChecker.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterClashChecker.java @@ -86,10 +86,11 @@ private Operation[] createOps() { @ParameterizedTest @MethodSource("findClashesArgs") - public void testFindClashes(RegisterRequest request, List tissues) { - Set newXns = request.getBlocks().stream() + public void testFindClashes(BlockRegisterRequest request, List tissues) { + Set newXns = request.getLabware().stream() + .flatMap(brl -> brl.getSamples().stream()) .filter(b -> !b.isExistingTissue()) - .map(BlockRegisterRequest_old::getExternalIdentifier) + .map(BlockRegisterSample::getExternalIdentifier) .collect(toSet()); if (newXns.isEmpty()) { assertThat(checker.findClashes(request)).isEmpty(); @@ -126,17 +127,19 @@ static Stream findClashesArgs() { ); } - static RegisterRequest makeRequest(Object... data) { - List brs = new ArrayList<>(data.length/2); - for (int i = 0; i < data.length; i += 2) { - String xn = (String) data[i]; - boolean exists = (boolean) data[i+1]; - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setExternalIdentifier(xn); - br.setExistingTissue(exists); - brs.add(br); + static BlockRegisterRequest makeRequest(Object... args) { + List brss = new ArrayList<>(args.length/2); + for (int i = 0; i < args.length; i += 2) { + BlockRegisterSample brs = new BlockRegisterSample(); + brs.setExternalIdentifier((String) args[i]); + brs.setExistingTissue((Boolean) args[i+1]); + brss.add(brs); } - return new RegisterRequest(brs); + BlockRegisterLabware brl = new BlockRegisterLabware(); + brl.setSamples(brss); + BlockRegisterRequest request = new BlockRegisterRequest(); + request.setLabware(List.of(brl)); + return request; } @Test @@ -193,6 +196,5 @@ public void testToRegisterClash() { Map> tissueIdLabwareMap = Map.of(tissues[0].getId(), Arrays.asList(labware)); assertEquals(new RegisterClash(tissues[0], Arrays.asList(labware)), checker.toRegisterClash(tissues[0], tissueIdLabwareMap)); assertEquals(new RegisterClash(tissues[1], List.of()), checker.toRegisterClash(tissues[1], tissueIdLabwareMap)); - } } diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidation.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidation.java deleted file mode 100644 index 8bf877fd0..000000000 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidation.java +++ /dev/null @@ -1,933 +0,0 @@ -package uk.ac.sanger.sccp.stan.service.register; - -import com.google.common.base.MoreObjects; -import org.junit.jupiter.api.*; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.*; -import org.mockito.*; -import org.mockito.verification.VerificationMode; -import uk.ac.sanger.sccp.stan.EntityFactory; -import uk.ac.sanger.sccp.stan.Matchers; -import uk.ac.sanger.sccp.stan.model.*; -import uk.ac.sanger.sccp.stan.repo.*; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; -import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; -import uk.ac.sanger.sccp.stan.service.BioRiskService; -import uk.ac.sanger.sccp.stan.service.Validator; -import uk.ac.sanger.sccp.stan.service.register.RegisterValidationImp.StringIntKey; -import uk.ac.sanger.sccp.stan.service.work.WorkService; -import uk.ac.sanger.sccp.utils.UCMap; -import uk.ac.sanger.sccp.utils.Zip; - -import java.time.LocalDate; -import java.util.*; -import java.util.function.*; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.mockito.Mockito.*; -import static uk.ac.sanger.sccp.stan.EntityFactory.objToList; -import static uk.ac.sanger.sccp.stan.Matchers.assertProblem; - -/** - * Tests for {@link RegisterValidationImp} - * @author dr6 - */ -public class TestRegisterValidation { - @Mock - private DonorRepo mockDonorRepo; - @Mock - private HmdmcRepo mockHmdmcRepo; - @Mock - private TissueTypeRepo mockTtRepo; - @Mock - private LabwareTypeRepo mockLtRepo; - @Mock - private MediumRepo mockMediumRepo; - @Mock - private FixativeRepo mockFixativeRepo; - @Mock - private TissueRepo mockTissueRepo; - @Mock - private SpeciesRepo mockSpeciesRepo; - @Mock - private CellClassRepo mockCellClassRepo; - @Mock - private Validator mockDonorNameValidation; - @Mock - private Validator mockExternalNameValidation; - @Mock - private Validator mockReplicateValidator; - @Mock - private TissueFieldChecker mockFieldChecker; - @Mock - private BioRiskService mockBioRiskService; - @Mock - private WorkService mockWorkService; - - private AutoCloseable mocking; - - @BeforeEach - void setup() { - mocking = MockitoAnnotations.openMocks(this); - } - - @AfterEach - void cleanup() throws Exception { - mocking.close(); - } - - private void loadSpecies(final Collection specieses) { - when(mockSpeciesRepo.findByName(any())).then(invocation -> { - String name = invocation.getArgument(0); - return specieses.stream().filter(sp -> sp.getName().equalsIgnoreCase(name)).findAny(); - }); - } - - private RegisterValidationImp create(RegisterRequest request) { - return spy(new RegisterValidationImp(request, mockDonorRepo, mockHmdmcRepo, mockTtRepo, mockLtRepo, - mockMediumRepo, mockFixativeRepo, mockTissueRepo, mockSpeciesRepo, mockCellClassRepo, - mockDonorNameValidation, mockExternalNameValidation, mockReplicateValidator, mockFieldChecker, - mockBioRiskService, mockWorkService)); - } - - private void stubValidationMethods(RegisterValidationImp validation) { - doNothing().when(validation).validateDonors(); - doNothing().when(validation).validateHmdmcs(); - doNothing().when(validation).validateSpatialLocations(); - doNothing().when(validation).validateLabwareTypes(); - doNothing().when(validation).validateMediums(); - doNothing().when(validation).validateExistingTissues(); - doNothing().when(validation).validateNewTissues(); - doNothing().when(validation).validateFixatives(); - doNothing().when(validation).validateCollectionDates(); - doNothing().when(validation).validateBioRisks(); - doNothing().when(validation).validateWorks(); - doNothing().when(validation).validateCellClasses(); - } - - private void verifyValidateMethods(RegisterValidationImp validation, VerificationMode verificationMode) { - verify(validation, verificationMode).validateDonors(); - verify(validation, verificationMode).validateHmdmcs(); - verify(validation, verificationMode).validateSpatialLocations(); - verify(validation, verificationMode).validateLabwareTypes(); - verify(validation, verificationMode).validateMediums(); - verify(validation, verificationMode).validateExistingTissues(); - verify(validation, verificationMode).validateNewTissues(); - verify(validation, verificationMode).validateFixatives(); - verify(validation, verificationMode).validateCollectionDates(); - verify(validation, verificationMode).validateWorks(); - verify(validation, verificationMode).validateBioRisks(); - verify(validation, verificationMode).validateCellClasses(); - } - - @Test - public void testValidateEmptyRequest() { - RegisterRequest request = new RegisterRequest(List.of()); - RegisterValidationImp validation = create(request); - stubValidationMethods(validation); - - assertThat(validation.validate()).isEmpty(); - verifyValidateMethods(validation, never()); - } - - @Test - public void testValidateNonemptyRequestWithoutProblems() { - RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest_old())); - RegisterValidationImp validation = create(request); - stubValidationMethods(validation); - assertThat(validation.validate()).isEmpty(); - verifyValidateMethods(validation, times(1)); - } - - @Test - public void testValidateNonemptyRequestWithProblems() { - RegisterRequest request = new RegisterRequest(List.of(new BlockRegisterRequest_old())); - RegisterValidationImp validation = create(request); - stubValidationMethods(validation); - doAnswer(invocation -> validation.problems.add("Problem alpha.")) - .when(validation).validateDonors(); - doAnswer(invocation -> validation.problems.add("Problem beta.")) - .when(validation).validateHmdmcs(); - assertThat(validation.validate()).hasSameElementsAs(List.of("Problem alpha.", "Problem beta.")); - verifyValidateMethods(validation, times(1)); - } - - @ParameterizedTest - @MethodSource("donorData") - public void testValidateDonors(List donorNames, List lifeStages, List speciesNames, - List knownDonors, List knownSpecies, List expectedDonors, - List expectedProblems) { - loadSpecies(knownSpecies); - Iterator lifeStageIter = lifeStages.listIterator(); - Iterator speciesIter = speciesNames.iterator(); - RegisterRequest request = new RegisterRequest( - donorNames.stream() - .map(donorName -> { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setDonorIdentifier(donorName); - br.setLifeStage(lifeStageIter.next()); - br.setSpecies(speciesIter.next()); - return br; - }) - .collect(toList()) - ); - when(mockDonorRepo.findByDonorName(any())).then(invocation -> { - final String name = invocation.getArgument(0); - return knownDonors.stream().filter(d -> name.equalsIgnoreCase(d.getDonorName())).findAny(); - }); - - RegisterValidationImp validation = create(request); - validation.validateDonors(); - assertThat(validation.getProblems()).hasSameElementsAs(expectedProblems); - Map expectedDonorMap = expectedDonors.stream().collect(toMap(d -> d.getDonorName().toUpperCase(), d -> d)); - assertEquals(expectedDonorMap, validation.donorMap); - expectedDonors.forEach(donor -> - assertEquals(donor, validation.getDonor(donor.getDonorName())) - ); - } - - @Test - public void testDonorNameValidation() { - when(mockDonorNameValidation.validate(any(), any())).then(invocation -> { - String name = invocation.getArgument(0); - Consumer addProblem = invocation.getArgument(1); - if (name.contains("*")) { - addProblem.accept("Invalid name: "+name); - return false; - } - return true; - }); - loadSpecies(List.of(EntityFactory.getHuman())); - RegisterRequest request = new RegisterRequest( - Stream.of("Alpha", "Beta", "Gamma*", "Delta*", "Gamma*") - .map(s -> { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setDonorIdentifier(s); - br.setLifeStage(LifeStage.adult); - br.setSpecies(Species.HUMAN_NAME); - return br; - }) - .collect(toList()) - ); - RegisterValidationImp validation = create(request); - validation.validateDonors(); - assertThat(validation.getProblems()).hasSameElementsAs(List.of("Invalid name: Gamma*", "Invalid name: Delta*")); - } - - @ParameterizedTest - @MethodSource("slData") - public void testValidateSpatialLocations(List tissueTypeNames, List codes, - List knownTissueTypes, List expectedSLs, - List expectedProblems) { - RegisterRequest request = new RegisterRequest( - Zip.of(tissueTypeNames.stream(), codes.stream()) - .map((name, code) -> { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setTissueType(name); - br.setSpatialLocation(code); - return br; - }).toList()); - when(mockTtRepo.findByName(any())).then(invocation -> { - final String arg = invocation.getArgument(0); - return knownTissueTypes.stream().filter(tt -> arg.equalsIgnoreCase(tt.getName())).findAny(); - }); - - RegisterValidationImp validation = create(request); - validation.validateSpatialLocations(); - assertThat(validation.getProblems()).hasSameElementsAs(expectedProblems); - Map expectedSLMap = expectedSLs.stream() - .collect(toMap(sl -> new StringIntKey(sl.getTissueType().getName(), sl.getCode()), sl -> sl)); - assertEquals(expectedSLMap, validation.spatialLocationMap); - expectedSLs.forEach(sl -> - assertEquals(sl, validation.getSpatialLocation(sl.getTissueType().getName(), sl.getCode())) - ); - } - - @ParameterizedTest - @MethodSource("hmdmcData") - public void testValidateHmdmcs(List knownHmdmcs, List givenHmdmcs, List speciesNames, - List expectedHmdmcs, List expectedProblems) { - when(mockHmdmcRepo.findByHmdmc(any())).then(invocation -> { - final String arg = invocation.getArgument(0); - return knownHmdmcs.stream().filter(h -> arg.equalsIgnoreCase(h.getHmdmc())).findAny(); - }); - - RegisterRequest request = new RegisterRequest( - Zip.of(givenHmdmcs.stream(), speciesNames.stream()) - .map((hmdmc, species) -> { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setHmdmc(hmdmc); - br.setSpecies(species); - return br; - }) - .toList() - ); - - RegisterValidationImp validation = create(request); - validation.validateHmdmcs(); - assertThat(validation.getProblems()).hasSameElementsAs(expectedProblems); - Map expectedItemMap = expectedHmdmcs.stream().collect(toMap(item -> item.getHmdmc().toUpperCase(), h -> h)); - Map actualMap = validation.hmdmcMap.entrySet().stream() - .filter(e -> e.getValue() != null) - .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); - assertEquals(expectedItemMap, actualMap); - for (Hmdmc hmdmc : expectedHmdmcs) { - assertEquals(hmdmc, validation.getHmdmc(hmdmc.getHmdmc())); - } - } - - @ParameterizedTest - @MethodSource("ltData") - public void testValidateLabwareTypes(List knownLts, List givenLtNames, - List expectedLts, List expectedProblems) { - when(mockLtRepo.findByName(any())).then(invocation -> { - final String arg = invocation.getArgument(0); - return knownLts.stream().filter(lt -> arg.equalsIgnoreCase(lt.getName())).findAny(); - }); - testValidateSimpleField(givenLtNames, expectedLts, expectedProblems, - RegisterValidationImp::validateLabwareTypes, LabwareType::getName, BlockRegisterRequest_old::setLabwareType, - v -> v.labwareTypeMap, RegisterValidationImp::getLabwareType); - } - - @ParameterizedTest - @MethodSource("mediumData") - public void testValidateMediums(List knownItems, List givenNames, - List expectedItems, List expectedProblems) { - when(mockMediumRepo.findByName(any())).then(invocation -> { - final String arg = invocation.getArgument(0); - return knownItems.stream().filter(item -> arg.equalsIgnoreCase(item.getName())).findAny(); - }); - testValidateSimpleField(givenNames, expectedItems, expectedProblems, - RegisterValidationImp::validateMediums, Medium::getName, BlockRegisterRequest_old::setMedium, - v -> v.mediumMap, RegisterValidationImp::getMedium); - } - - @ParameterizedTest - @MethodSource("fixativeData") - public void testValidateFixatives(List knownItems, List givenNames, - List expectedItems, List expectedProblems) { - when(mockFixativeRepo.findByName(any())).then(invocation -> { - final String arg = invocation.getArgument(0); - return knownItems.stream().filter(item -> arg.equalsIgnoreCase(item.getName())).findAny(); - }); - testValidateSimpleField(givenNames, expectedItems, expectedProblems, - RegisterValidationImp::validateFixatives, Fixative::getName, BlockRegisterRequest_old::setFixative, - v -> v.fixativeMap, RegisterValidationImp::getFixative); - } - - @ParameterizedTest - @MethodSource("newTissueData") - void testValidateNewTissues(final List testData, List expectedProblems) { - when(mockExternalNameValidation.validate(any(), any())).then(invocation -> { - String name = invocation.getArgument(0); - Consumer addProblem = invocation.getArgument(1); - if (name.contains("*")) { - addProblem.accept("Invalid name: " + name); - return true; - } - return false; - }); - when(mockTissueRepo.findAllByExternalName(anyString())).then(invocation -> { - final String name = invocation.getArgument(0); - return testData.stream() - .filter(td -> td.anyWithSameIdentifier && td.externalName.equalsIgnoreCase(name)) - .map(td -> EntityFactory.getTissue()) - .collect(toList()); - }); - when(mockReplicateValidator.validate(any(), any())).then(invocation -> { - String replicate = invocation.getArgument(0); - Consumer addProblem = invocation.getArgument(1); - if (!replicate.matches("\\d+[a-zA-Z]?")) { - addProblem.accept("Invalid replicate: " + replicate); - return true; - } - return false; - }); - - RegisterRequest request = new RegisterRequest( - testData.stream() - .map(td -> { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setExternalIdentifier(td.externalName); - br.setReplicateNumber(td.replicate); - br.setDonorIdentifier(td.donorName); - br.setTissueType(td.tissueTypeName); - br.setSpatialLocation(td.slCode); - br.setMedium(td.mediumName); - br.setHighestSection(td.highestSection); - br.setFixative(td.fixativeName); - br.setExistingTissue(td.existing); - return br; - }) - .collect(toList()) - ); - - RegisterValidationImp validation = create(request); - - validation.validateNewTissues(); - assertThat(validation.getProblems()).hasSameElementsAs(expectedProblems); - } - - @ParameterizedTest - @MethodSource("validateExistingTissuesArgs") - public void testValidateExistingTissue(Object testDataObj, Object existingTissuesObj, - Object expectedProblemsObj) { - final List testData = objToList(testDataObj); - final List existingTissues = objToList(existingTissuesObj); - final List expectedProblems = objToList(expectedProblemsObj); - when(mockTissueRepo.findAllByExternalNameIn(any())).then(invocation -> { - Collection xns = invocation.getArgument(0); - return existingTissues.stream().filter(t -> xns.stream().anyMatch(xn -> t.getExternalName().equalsIgnoreCase(xn))) - .collect(toList()); - }); - List brs = new ArrayList<>(testData.size()); - for (ValidateExistingTissueTestData td : testData) { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setExternalIdentifier(td.externalName); - br.setExistingTissue(td.existing); - if (td.fieldProblem != null) { - doAnswer(invocation -> { - Consumer problemConsumer = invocation.getArgument(0); - problemConsumer.accept(td.fieldProblem); - return null; - }).when(mockFieldChecker).check(any(), same(br), any()); - } - brs.add(br); - } - RegisterRequest request = new RegisterRequest(brs); - - RegisterValidationImp validation = create(request); - validation.validateExistingTissues(); - assertThat(validation.getProblems()).containsExactlyInAnyOrderElementsOf(expectedProblems); - } - - static Stream validateExistingTissuesArgs() { - Tissue tissue = EntityFactory.getTissue(); - Tissue tissue2 = EntityFactory.makeTissue(tissue.getDonor(), tissue.getSpatialLocation()); - return Stream.of( - Arguments.of(List.of(ValidateExistingTissueTestData.externalName("X1").existing(false), - ValidateExistingTissueTestData.externalName(tissue.getExternalName())), tissue, null), - Arguments.of(ValidateExistingTissueTestData.externalName("X1").existing(false), null, null), - - Arguments.of(List.of(ValidateExistingTissueTestData.externalName(null), - ValidateExistingTissueTestData.externalName(tissue.getExternalName())), - tissue, "Missing external identifier."), - Arguments.of(List.of(ValidateExistingTissueTestData.externalName("Bananas"), - ValidateExistingTissueTestData.externalName("Golf")), - null, "Existing external identifiers not recognised: [\"Bananas\", \"Golf\"]"), - Arguments.of(List.of(ValidateExistingTissueTestData.externalName(tissue.getExternalName()).fieldProblem("Bad tissue type."), - ValidateExistingTissueTestData.externalName(tissue2.getExternalName()).fieldProblem("Bad spatial location.")), - List.of(tissue, tissue2), List.of("Bad tissue type.", "Bad spatial location.")) - ); - } - - @ParameterizedTest - @MethodSource("validateCollectionDatesArgs") - public void testValidateCollectionDates(List brrs, List expectedProblems) { - RegisterRequest request = new RegisterRequest(brrs); - RegisterValidationImp validation = create(request); - validation.validateCollectionDates(); - assertThat(validation.getProblems()).containsExactlyInAnyOrderElementsOf(expectedProblems); - } - - static Stream validateCollectionDatesArgs() { - LocalDate future1 = LocalDate.now().plusDays(7); - LocalDate future2 = future1.plusDays(2); - return Arrays.stream(new Object[][]{ - { toBrr(Species.HUMAN_NAME, LifeStage.fetal, LocalDate.of(2022,1,20)) }, - { toBrr("Hamster", LifeStage.fetal, null) }, - { toBrr(Species.HUMAN_NAME, LifeStage.adult, null) }, - { toBrr(Species.HUMAN_NAME, LifeStage.paediatric, null) }, - - { toBrr(Species.HUMAN_NAME, LifeStage.fetal, LocalDate.of(2021,5,6)), - toBrr(Species.HUMAN_NAME, LifeStage.paediatric, null), - toBrr("Hamster", LifeStage.fetal, null), - toBrr(null, LifeStage.fetal, null), - toBrr(Species.HUMAN_NAME, null, null), - }, - - { toBrr(Species.HUMAN_NAME, LifeStage.fetal, null), - "Human fetal samples must have a collection date." }, - { toBrr(Species.HUMAN_NAME, LifeStage.fetal, future1), - toBrr("Hamster", LifeStage.adult, future2), - "Invalid sample collection dates: ["+future1+", "+future2+"]"}, - { toBrr(Species.HUMAN_NAME, LifeStage.fetal, LocalDate.of(2021,1,2), "Ext1"), - toBrr(Species.HUMAN_NAME, LifeStage.fetal, LocalDate.of(2021,1,3), "ext1"), - "Inconsistent collection dates specified for tissue EXT1."}, - - { toBrr(Species.HUMAN_NAME, LifeStage.fetal, LocalDate.of(2020,2,1), "EXT2"), - toBrr(Species.HUMAN_NAME, LifeStage.adult, LocalDate.of(2020,2,2), "ext1"), - toBrr(Species.HUMAN_NAME, LifeStage.adult, null, "ext1"), - toBrr(null, null, null), - toBrr(Species.HUMAN_NAME, LifeStage.fetal, null), - toBrr(Species.HUMAN_NAME, LifeStage.adult, future1), - "Human fetal samples must have a collection date.", - "Invalid sample collection date: ["+future1+"]", - "Inconsistent collection dates specified for tissue EXT1."}, - }).map(arr -> Arguments.of(Arrays.stream(arr).filter(obj -> obj instanceof BlockRegisterRequest_old) - .collect(toList()), - Arrays.stream(arr).filter(obj -> obj instanceof String) - .collect(toList()))); - } - - @ParameterizedTest - @CsvSource({"false,false", "true,false", "true,true"}) - public void testValidateWorks(boolean anyWorks, boolean anyProblem) { - List brrs = List.of( - toBrr(Species.HUMAN_NAME, LifeStage.adult, null) - ); - List workNumbers; - List works; - if (!anyWorks) { - workNumbers = List.of(); - works = List.of(); - } else { - workNumbers = List.of("SGP1", "SGP2"); - works = IntStream.rangeClosed(1,2).mapToObj(i -> { - Work w = new Work(); - w.setId(i); - w.setWorkNumber("SGP"+i); - return w; - }).collect(toList()); - } - - RegisterRequest request = new RegisterRequest(brrs, workNumbers); - var validation = create(request); - if (anyProblem) { - when(mockWorkService.validateUsableWorks(any(), any())).then( - Matchers.addProblem("Bad work", UCMap.from(works, Work::getWorkNumber)) - ); - } else if (anyWorks) { - when(mockWorkService.validateUsableWorks(any(), any())).thenReturn(UCMap.from(works, Work::getWorkNumber)); - } - validation.validateWorks(); - if (!anyWorks) { - verifyNoInteractions(mockWorkService); - assertProblem(validation.getProblems(), "No work number supplied."); - } else { - if (anyProblem) { - assertThat(validation.getProblems()).containsExactly("Bad work"); - } else { - assertThat(validation.getProblems()).isEmpty(); - } - verify(mockWorkService).validateUsableWorks(any(), eq(workNumbers)); - } - assertThat(validation.getWorks()).containsExactlyInAnyOrderElementsOf(works); - - } - - static BlockRegisterRequest_old toBrr(String species, LifeStage lifeStage, LocalDate collectionDate, String ext) { - BlockRegisterRequest_old brr = new BlockRegisterRequest_old(); - brr.setSpecies(species); - brr.setLifeStage(lifeStage); - brr.setSampleCollectionDate(collectionDate); - brr.setExternalIdentifier(ext); - return brr; - } - - static BlockRegisterRequest_old toBrr(String species, LifeStage lifeStage, LocalDate collectionDate) { - return toBrr(species, lifeStage, collectionDate, null); - } - - private void testValidateSimpleField(List givenStrings, - List expectedItems, List expectedProblems, - Consumer validationFunction, - Function stringFn, - BiConsumer blockFunction, - Function> mapFunction, - BiFunction getter) { - RegisterRequest request = new RegisterRequest( - givenStrings.stream() - .map(string -> { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - blockFunction.accept(br, string); - return br; - }) - .collect(toList())); - - RegisterValidationImp validation = create(request); - validationFunction.accept(validation); - assertThat(validation.getProblems()).hasSameElementsAs(expectedProblems); - Map expectedItemMap = expectedItems.stream().collect(toMap(item -> stringFn.apply(item).toUpperCase(), h -> h)); - Map actualMap = mapFunction.apply(validation).entrySet().stream() - .filter(e -> e.getValue()!=null) - .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); - assertEquals(expectedItemMap, actualMap); - for (E item : expectedItems) { - assertEquals(item, getter.apply(validation, stringFn.apply(item))); - } - } - - @SuppressWarnings("unchecked") - @Test - void testValidateBioRisks() { - BlockRegisterRequest_old block1 = new BlockRegisterRequest_old(); - block1.setBioRiskCode("risk1"); - BlockRegisterRequest_old block2 = new BlockRegisterRequest_old(); - block2.setBioRiskCode("risk2"); - RegisterRequest request = new RegisterRequest(List.of(block1, block2)); - RegisterValidationImp val = create(request); - UCMap returnedMap = UCMap.from(BioRisk::getCode, new BioRisk(1, "risk1")); - when(mockBioRiskService.loadAndValidateBioRisks(any(), any(), any(), any())).thenReturn(returnedMap); - - val.validateBioRisks(); - - assertSame(returnedMap, val.bioRiskMap); - ArgumentCaptor> blockStreamCaptor = ArgumentCaptor.forClass(Stream.class); - ArgumentCaptor> getterCaptor = ArgumentCaptor.forClass(Function.class); - ArgumentCaptor> setterCaptor = ArgumentCaptor.forClass(BiConsumer.class); - verify(mockBioRiskService).loadAndValidateBioRisks(same(val.problems), blockStreamCaptor.capture(), - getterCaptor.capture(), setterCaptor.capture()); - - // Check that the getter and setter are the functions we expect - assertThat(blockStreamCaptor.getValue().map(getterCaptor.getValue())).containsExactly("risk1", "risk2"); - BiConsumer setter = setterCaptor.getValue(); - BlockRegisterRequest_old blk = new BlockRegisterRequest_old(); - setter.accept(blk, "v1"); - assertEquals("v1", blk.getBioRiskCode()); - } - - @Test - void testValidateCellClasses() { - String[] ccNames = {"cc1", "cc2", null, "cc4"}; - List blocks = IntStream.range(0, ccNames.length).mapToObj(i -> new BlockRegisterRequest_old()).toList(); - Zip.of(Arrays.stream(ccNames), blocks.stream()).forEach((name, block) -> block.setCellClass(name)); - CellClass[] cellClasses = IntStream.rangeClosed(1, 2).mapToObj(i -> new CellClass(i, "cc"+i, false, true)).toArray(CellClass[]::new); - UCMap ccMap = UCMap.from(CellClass::getName, cellClasses); - when(mockCellClassRepo.findMapByNameIn(any())).thenReturn(ccMap); - RegisterRequest request = new RegisterRequest(blocks); - RegisterValidationImp val = create(request); - val.validateCellClasses(); - assertThat(val.problems).containsExactlyInAnyOrder("Missing cell class name.", "Unknown cell class name: [\"cc4\"]"); - verify(mockCellClassRepo).findMapByNameIn(Set.of("cc1", "cc2", "cc4")); - assertSame(ccMap, val.cellClassMap); - } - - /** @see #testValidateDonors */ - private static Stream donorData() { - Species human = new Species(1, Species.HUMAN_NAME); - Species hamster = new Species(2, "Hamster"); - Species dodo = new Species(3, "Dodo"); - dodo.setEnabled(false); - List knownSpecies = List.of(human, hamster, dodo); - Donor dirk = new Donor(1, "Dirk", LifeStage.adult, human); - Donor jeff = new Donor(2, "Jeff", LifeStage.fetal, hamster); - Donor dodonor = new Donor(3, "Dodonor", LifeStage.adult, dodo); - // List donorNames, List lifeStages, List speciesNames, - // List knownDonors, List knownSpecies, List expectedDonors, - // List expectedProblems - return Stream.of( - // Valid: - Arguments.of(List.of("DONOR1", "Donor2"), List.of(LifeStage.adult, LifeStage.fetal), - List.of(Species.HUMAN_NAME, "hamster"), - List.of(), knownSpecies, - List.of(new Donor(null, "DONOR1", LifeStage.adult, human), - new Donor(null, "Donor2", LifeStage.fetal, hamster)), - List.of()), - Arguments.of(List.of("Donor1", "DONOR1"), List.of(LifeStage.adult, LifeStage.adult), - List.of(Species.HUMAN_NAME, Species.HUMAN_NAME), - List.of(), knownSpecies, - List.of(new Donor(null, "Donor1", LifeStage.adult, human)), - List.of()), - Arguments.of(List.of("DIRK", "jeff"), - List.of(dirk.getLifeStage(), jeff.getLifeStage()), List.of(Species.HUMAN_NAME, "hamster"), - List.of(dirk, jeff), knownSpecies, List.of(dirk, jeff), - List.of()), - - // Invalid: - Arguments.of(List.of("Dirk", "jeff"), List.of(LifeStage.adult, LifeStage.paediatric), - List.of(Species.HUMAN_NAME, "hamster"), - List.of(dirk, jeff), knownSpecies, List.of(dirk, jeff), - List.of("Wrong life stage given for existing donor Jeff")), - Arguments.of(List.of("Donor1", "DONOR1"), List.of(LifeStage.adult, LifeStage.fetal), - List.of(Species.HUMAN_NAME, Species.HUMAN_NAME), - List.of(), knownSpecies, List.of(new Donor(null, "Donor1", LifeStage.adult, human)), - List.of("Multiple different life stages specified for donor Donor1")), - Arguments.of(Arrays.asList(null, null), Arrays.asList(null, null), - List.of(Species.HUMAN_NAME, Species.HUMAN_NAME), - List.of(), knownSpecies, List.of(), List.of("Missing donor identifier.")), - Arguments.of(List.of(""), List.of(LifeStage.adult), - List.of(Species.HUMAN_NAME, Species.HUMAN_NAME), - List.of(), knownSpecies, List.of(), List.of("Missing donor identifier.")), - Arguments.of(List.of("Donor1"), List.of(LifeStage.adult), - List.of(""), List.of(), knownSpecies, List.of(new Donor(null, "Donor1", LifeStage.adult, null)), - List.of("Missing species.")), - Arguments.of(List.of("Donor1"), List.of(LifeStage.adult), List.of("Bananas"), - List.of(), knownSpecies, List.of(new Donor(null, "Donor1", LifeStage.adult, null)), - List.of("Unknown species: \"Bananas\"")), - Arguments.of(List.of("Donor1", "DONOR1"), List.of(LifeStage.adult, LifeStage.adult), - List.of(Species.HUMAN_NAME, "hamster"), - List.of(), knownSpecies, List.of(new Donor(null, "Donor1", LifeStage.adult, human)), - List.of("Multiple different species specified for donor Donor1")), - Arguments.of(List.of("Donor1", "Jeff"), List.of(LifeStage.adult, LifeStage.fetal), - List.of(Species.HUMAN_NAME, Species.HUMAN_NAME), - List.of(jeff), knownSpecies, List.of(jeff, new Donor(null, "Donor1", LifeStage.adult, human)), - List.of("Wrong species given for existing donor Jeff")), - Arguments.of(List.of("dodonor"), - List.of(dodonor.getLifeStage()), List.of("dodo"), - List.of(dodonor), knownSpecies, List.of(dodonor), - List.of("Species is not enabled: Dodo")), - Arguments.of(List.of("Donor1"), List.of(LifeStage.adult), List.of("dodo"), List.of(), knownSpecies, - List.of(new Donor(null, "Donor1", LifeStage.adult, dodo)), - List.of("Species is not enabled: Dodo")), - - Arguments.of(List.of("Donor1", "DONOR1", "jeff", "dirk", "", ""), - List.of(LifeStage.adult, LifeStage.fetal, LifeStage.paediatric, LifeStage.paediatric, LifeStage.adult, LifeStage.adult), - List.of(Species.HUMAN_NAME, Species.HUMAN_NAME, "hamster", Species.HUMAN_NAME, Species.HUMAN_NAME, Species.HUMAN_NAME), - List.of(dirk, jeff),knownSpecies, List.of(new Donor(null, "Donor1", LifeStage.adult, human), dirk, jeff), - List.of("Multiple different life stages specified for donor Donor1", - "Wrong life stage given for existing donor Dirk", - "Wrong life stage given for existing donor Jeff", - "Missing donor identifier.")) - - ); - } - - private static Stream slData() { - final TissueType tt = new TissueType(50, "Arm", "ARM"); - final String name = tt.getName(); - final SpatialLocation sl0 = new SpatialLocation(500, "Alpha", 0, tt); - final SpatialLocation sl1 = new SpatialLocation(501, "Beta", 1, tt); - final SpatialLocation sl2 = new SpatialLocation(502, "Gamma", 2, tt); - sl2.setEnabled(false); - tt.setSpatialLocations(List.of(sl0, sl1, sl2)); - final TissueType tt2 = new TissueType(51, "Leg", "LEG"); - tt2.setEnabled(false); - final SpatialLocation sl0a = new SpatialLocation(601, "Alabama", 0, tt2); - tt2.setSpatialLocations(List.of(sl0a)); - return Stream.of( - // Valid: - Arguments.of(List.of(name, name.toUpperCase(), name.toLowerCase()), List.of(0, 0, 1), - List.of(tt), List.of(sl0, sl1), List.of()), - - // Invalid: - Arguments.of(List.of(name, "Plumbus", "Slime", "Slime"), List.of(1, 2, 3, 4), - List.of(tt), List.of(sl1), List.of("Unknown tissue types: [Plumbus, Slime]")), - Arguments.of(List.of(name, name, name), List.of(0,1,2), List.of(tt), List.of(sl0, sl1, sl2), - List.of("Spatial location is disabled: 2 for tissue type Arm.")), - Arguments.of(List.of(tt2.getName()), List.of(0), List.of(tt2), List.of(sl0a), - List.of("Tissue type \""+tt2.getName()+"\" is disabled.")), - Arguments.of(List.of(name, name, name, name, name, name), List.of(0, 0, 1, 3, 3, 4), - List.of(tt), List.of(sl0, sl1), List.of("Unknown spatial location 3 for tissue type Arm.", - "Unknown spatial location 4 for tissue type Arm.")), - Arguments.of(Arrays.asList(null, null), List.of(1,2), - List.of(tt), List.of(), List.of("Missing tissue type.")), - Arguments.of(List.of(name, "", "Plumbus", name), List.of(0, 0, 0, 5), - List.of(tt), List.of(sl0), - List.of("Missing tissue type.", "Unknown tissue type: [Plumbus]", - "Unknown spatial location 5 for tissue type Arm.")) - ); - } - - /** @see #testValidateHmdmcs */ - private static Stream hmdmcData() { - Hmdmc h0 = new Hmdmc(20000, "20/000"); - Hmdmc h1 = new Hmdmc(20001, "20/001"); - Hmdmc h2 = new Hmdmc(20002, "20/002"); - Hmdmc h3 = new Hmdmc(20003, "20/003"); - h2.setEnabled(false); - h3.setEnabled(false); - // List knownHmdmcs, List givenHmdmcs, List speciesNames, - // List expectedHmdmcs, List expectedProblems - return Stream.of( - Arguments.of(List.of(h0, h1), List.of("20/001", "20/000", "20/000", ""), List.of(Species.HUMAN_NAME, Species.HUMAN_NAME, Species.HUMAN_NAME, "Hamster"), - List.of(h0, h1), List.of()), - Arguments.of(List.of(h0, h1), List.of("20/001", "20/404", "20/405"), List.of(Species.HUMAN_NAME, Species.HUMAN_NAME, Species.HUMAN_NAME), - List.of(h1), List.of("Unknown HuMFre numbers: [20/404, 20/405]")), - Arguments.of(List.of(h0), Arrays.asList(null, "20/000", null), List.of(Species.HUMAN_NAME, Species.HUMAN_NAME, Species.HUMAN_NAME), - List.of(h0), List.of("Missing HuMFre number.")), - Arguments.of(List.of(h0, h1), List.of("20/000", "20/001"), List.of(Species.HUMAN_NAME, "Hamster"), - List.of(h0), List.of("Non-human tissue should not have a HuMFre number.")), - Arguments.of(List.of(h0, h2, h3), List.of("20/000", "20/002", "20/003", "20/002"), List.of(Species.HUMAN_NAME, Species.HUMAN_NAME, Species.HUMAN_NAME, Species.HUMAN_NAME), - List.of(h0, h2, h3), List.of("HuMFre numbers not enabled: [20/002, 20/003]")), - Arguments.of(List.of(h0, h1, h2), List.of("20/000", "20/001", "20/000", "", "", "20/404", "20/002"), - List.of(Species.HUMAN_NAME, Species.HUMAN_NAME, Species.HUMAN_NAME, Species.HUMAN_NAME, Species.HUMAN_NAME, Species.HUMAN_NAME, Species.HUMAN_NAME), - List.of(h0, h1, h2), List.of("Missing HuMFre number.", "Unknown HuMFre number: [20/404]", - "HuMFre number not enabled: [20/002]")) - ); - } - - private static Stream ltData() { - LabwareType lt0 = EntityFactory.getTubeType(); - LabwareType lt1 = EntityFactory.makeLabwareType(2, 3); - String name0 = lt0.getName(); - String name1 = lt1.getName(); - return Stream.of( - Arguments.of(List.of(lt0, lt1), List.of(name1, name0, name0), List.of(lt0, lt1), List.of()), - Arguments.of(List.of(lt0, lt1), List.of(name1, "Custard", "Banana"), List.of(lt1), List.of("Unknown labware types: [Custard, Banana]")), - Arguments.of(List.of(lt0), Arrays.asList(null, name0, null), List.of(lt0), List.of("Missing labware type.")), - Arguments.of(List.of(lt0, lt1), List.of(name0, name1, name0, "", "", "Banana"), List.of(lt0, lt1), List.of("Missing labware type.", "Unknown labware type: [Banana]")) - ); - } - - private static Stream mediumData() { - Medium med = EntityFactory.getMedium(); - return Stream.of(Arguments.of(List.of(med), List.of(med.getName()), List.of(med), List.of()), - Arguments.of(List.of(), Arrays.asList(null, ""), List.of(), List.of("Missing medium.")), - Arguments.of(List.of(med), List.of(med.getName(), "Sausage", "Sausage"), List.of(med), List.of("Unknown medium: [Sausage]"))); - } - - - private static Stream fixativeData() { - Fixative fix = EntityFactory.getFixative(); - return Stream.of(Arguments.of(List.of(fix), List.of(fix.getName()), List.of(fix), List.of()), - Arguments.of(List.of(), Arrays.asList(null, ""), List.of(), List.of("Missing fixative.")), - Arguments.of(List.of(fix), List.of(fix.getName(), "Sausage", "Sausage"), List.of(fix), List.of("Unknown fixative: [Sausage]"))); - } - - - private static Stream newTissueData() { - return Stream.of( - // No problems - Arguments.of(List.of(ValidateTissueTestData.externalName("X1").replicate("1"), - ValidateTissueTestData.externalName("X2").replicate("2a")), - List.of()), - Arguments.of(List.of(ValidateTissueTestData.externalName("X1").replicate("1").anyWithSameIdentifier(true).existing(true), - ValidateTissueTestData.externalName("X2").replicate("2a")), - List.of()), - - // Some problems - Arguments.of(List.of(ValidateTissueTestData.externalName("X1").replicate("1"), - ValidateTissueTestData.externalName("X2").replicate("-4")), - List.of("Invalid replicate: -4")), - Arguments.of(List.of(ValidateTissueTestData.externalName("X1").replicate("1"), - ValidateTissueTestData.externalName("X2").replicate("2").highestSection(-2)), - List.of("Highest section number cannot be negative.")), - Arguments.of(List.of(ValidateTissueTestData.externalName(null)), - List.of("Missing external identifier.")), - Arguments.of(List.of(ValidateTissueTestData.externalName("")), - List.of("Missing external identifier.")), - Arguments.of(List.of(ValidateTissueTestData.externalName("Banana*")), - List.of("Invalid name: Banana*")), - Arguments.of(List.of(ValidateTissueTestData.externalName("xyz").replicate("1"), - ValidateTissueTestData.externalName("Xyz").replicate("2")), - List.of("Repeated external identifier: Xyz")), - Arguments.of(List.of(ValidateTissueTestData.externalName("X1").replicate("1"), - ValidateTissueTestData.externalName("X2").replicate("2").anyWithSameIdentifier(true)), - List.of("There is already tissue in the database with external identifier X2.")), - Arguments.of(List.of(ValidateTissueTestData.externalName("X1").replicate("1").anyWithSameIdentifier(true), - ValidateTissueTestData.externalName("X1").replicate("2").anyWithSameIdentifier(true), - ValidateTissueTestData.externalName("X2").replicate("3").anyWithSameIdentifier(true)), - List.of("There is already tissue in the database with external identifier X1.", - "There is already tissue in the database with external identifier X2.", - "Repeated external identifier: X1")), - - // Many problems - Arguments.of(List.of(ValidateTissueTestData.externalName("X1*").replicate("-1").highestSection(-1), - ValidateTissueTestData.externalName(null).replicate("2"), - ValidateTissueTestData.externalName("X1").replicate("3").anySimilarInDatabase(true), - ValidateTissueTestData.externalName("X2").replicate("4").anyWithSameIdentifier(true), - ValidateTissueTestData.externalName("X3").replicate("5"), - ValidateTissueTestData.externalName("X4").replicate("5"), - ValidateTissueTestData.externalName("X4").replicate("6")), - List.of("Invalid replicate: -1", - "Highest section number cannot be negative.", - "Missing external identifier.", - "Invalid name: X1*", - "Repeated external identifier: X4", - "There is already tissue in the database with external identifier X2.")) - ); - } - - static class ValidateTissueTestData { - String externalName; - String replicate = "1"; - String donorName = "D"; - String tissueTypeName = "TT"; - int slCode = 2; - String mediumName = "M"; - String fixativeName = "F"; - int highestSection = 0; - boolean anySimilarInDatabase; - boolean anyWithSameIdentifier; - boolean existing; - - public ValidateTissueTestData(String externalName) { - this.externalName = externalName; - } - - public static ValidateTissueTestData externalName(String externalName) { - return new ValidateTissueTestData(externalName); - } - - public ValidateTissueTestData replicate(String replicate) { - this.replicate = replicate; - return this; - } - - public ValidateTissueTestData highestSection(int highestSection) { - this.highestSection = highestSection; - return this; - } - - public ValidateTissueTestData anySimilarInDatabase(boolean anySimilarInDatabase) { - this.anySimilarInDatabase = anySimilarInDatabase; - return this; - } - - public ValidateTissueTestData anyWithSameIdentifier(boolean anyWithSameIdentifier) { - this.anyWithSameIdentifier = anyWithSameIdentifier; - return this; - } - - public ValidateTissueTestData existing(boolean existing) { - this.existing = existing; - return this; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("externalName", externalName) - .add("replicate", replicate) - .add("donorName", donorName) - .add("tissueTypeName", tissueTypeName) - .add("slCode", slCode) - .add("mediumName", mediumName) - .add("highestSection", highestSection) - .add("fixativeName", fixativeName) - .add("anySimilarInDatabase", anySimilarInDatabase) - .add("anyWithSameIdentifier", anyWithSameIdentifier) - .add("existing", existing) - .toString(); - } - } - - private static class ValidateExistingTissueTestData { - String externalName; - boolean existing = true; - String fieldProblem = null; - - static ValidateExistingTissueTestData externalName(String externalName) { - ValidateExistingTissueTestData td = new ValidateExistingTissueTestData(); - td.externalName = externalName; - return td; - } - - ValidateExistingTissueTestData existing(boolean existing) { - this.existing = existing; - return this; - } - - ValidateExistingTissueTestData fieldProblem(String fieldProblem) { - this.fieldProblem = fieldProblem; - return this; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("externalName", externalName) - .add("existing", existing) - .add("fieldProblem", fieldProblem) - .omitNullValues() - .toString(); - } - } -} diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidationFactory.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidationFactory.java index 8bf0b4693..7c5afa3d8 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidationFactory.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterValidationFactory.java @@ -3,7 +3,8 @@ import org.junit.jupiter.api.*; import org.mockito.InjectMocks; import org.mockito.MockitoAnnotations; -import uk.ac.sanger.sccp.stan.request.register.*; +import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest; +import uk.ac.sanger.sccp.stan.request.register.SectionRegisterRequest; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -32,11 +33,6 @@ public void testCreateBlockRegisterValidation() { assertNotNull(registerValidationFactory.createBlockRegisterValidation(new BlockRegisterRequest())); } - @Test - public void testCreateRegisterValidation() { - assertNotNull(registerValidationFactory.createRegisterValidation(new RegisterRequest())); - } - @Test public void testCreateSectionRegisterValidation() { assertNotNull(registerValidationFactory.createSectionRegisterValidation(new SectionRegisterRequest())); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestTissueFieldChecker.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestTissueFieldChecker.java deleted file mode 100644 index e6ca2ae51..000000000 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestTissueFieldChecker.java +++ /dev/null @@ -1,122 +0,0 @@ -package uk.ac.sanger.sccp.stan.service.register; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import uk.ac.sanger.sccp.stan.EntityFactory; -import uk.ac.sanger.sccp.stan.model.Tissue; -import uk.ac.sanger.sccp.stan.request.register.BlockRegisterRequest_old; - -import java.time.LocalDate; -import java.util.*; -import java.util.function.Consumer; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Tests {@link TissueFieldChecker} - * @author dr6 - */ -public class TestTissueFieldChecker { - TissueFieldChecker checker; - - @BeforeEach - void setup() { - checker = new TissueFieldChecker(); - } - - @ParameterizedTest - @MethodSource("matchArgs") - public void testMatch(Object a, Object b, boolean expected) { - assertEquals(expected, checker.match(a, b)); - } - - static Stream matchArgs() { - return Stream.of( - Arguments.of("Alpha", "alpha", true), - Arguments.of(null, null, true), - Arguments.of(null, "", true), - Arguments.of(11, 11, true), - - Arguments.of("Alpha", "beta", false), - Arguments.of("Alpha", null, false), - Arguments.of("Alpha", 11, false), - Arguments.of(null, "hi", false), - Arguments.of(null, 11, false), - Arguments.of(11, null, false), - Arguments.of(11, "bananas", false) - ); - } - - @ParameterizedTest - @MethodSource("checkArgs") - public void testCheck(BlockRegisterRequest_old br, Tissue tissue, Object problemObj) { - Collection expectedProblems; - if (problemObj==null) { - expectedProblems = List.of(); - } else if (problemObj instanceof Collection) { - //noinspection unchecked - expectedProblems = (Collection) problemObj; - } else { - expectedProblems = List.of((String) problemObj); - } - - List problems = new ArrayList<>(); - checker.check(problems::add, br, tissue); - - assertThat(problems).containsExactlyInAnyOrderElementsOf(expectedProblems); - } - - static Stream checkArgs() { - final Tissue tissue = EntityFactory.getTissue(); - final String forTissue = " for existing tissue "+tissue.getExternalName()+"."; - final Tissue tissueWithDate = EntityFactory.makeTissue(tissue.getDonor(), tissue.getSpatialLocation()); - tissueWithDate.setCollectionDate(LocalDate.of(2021,2,3)); - - return Stream.of( - Arguments.of(toBRR(tissue, null), tissue, null), - Arguments.of(toBRR(tissueWithDate, null), tissueWithDate, null), - Arguments.of(toBRR(tissue, br -> br.setSampleCollectionDate(LocalDate.of(2020,1,2))), tissue, null), - Arguments.of(toBRR(tissue, br -> br.setDonorIdentifier("Foo")), tissue, "Expected donor identifier to be "+tissue.getDonor().getDonorName()+forTissue), - Arguments.of(toBRR(tissue, br -> br.setHmdmc("12/345")), tissue, "Expected HuMFre number to be "+tissue.getHmdmc().getHmdmc()+forTissue), - Arguments.of(toBRR(tissue, br -> br.setTissueType("Plumbus")), tissue, "Expected tissue type to be "+tissue.getTissueType().getName()+forTissue), - Arguments.of(toBRR(tissue, br -> br.setSpatialLocation(18)), tissue, "Expected spatial location to be "+tissue.getSpatialLocation().getCode()+forTissue), - Arguments.of(toBRR(tissue, br -> br.setReplicateNumber("-5")), tissue, "Expected replicate number to be "+tissue.getReplicate()+forTissue), - Arguments.of(toBRR(tissue, br -> br.setMedium("Custard")), tissue, "Expected medium to be "+tissue.getMedium().getName()+forTissue), - Arguments.of(toBRR(tissue, br -> br.setFixative("Glue")), tissue, "Expected fixative to be "+tissue.getFixative().getName()+forTissue), - Arguments.of(toBRR(tissue, br -> br.setCellClass("cc4")), tissue, "Expected cellular classification to be Tissue"+forTissue), - Arguments.of(toBRR(tissueWithDate, br -> br.setSampleCollectionDate(LocalDate.of(2020,1,2))), tissueWithDate, "Expected sample collection date to be "+tissueWithDate.getCollectionDate()+" for existing tissue "+tissueWithDate.getExternalName()+"."), - Arguments.of(toBRR(tissue, br -> { - br.setDonorIdentifier("Foo"); - br.setHmdmc("12/345"); - br.setTissueType("Plumbus"); - }), tissue, - List.of("Expected donor identifier to be "+tissue.getDonor().getDonorName()+forTissue, - "Expected HuMFre number to be "+tissue.getHmdmc().getHmdmc()+forTissue, - "Expected tissue type to be "+tissue.getTissueType().getName()+forTissue) - ) - ); - } - - private static BlockRegisterRequest_old toBRR(Tissue tissue, Consumer adjuster) { - BlockRegisterRequest_old br = new BlockRegisterRequest_old(); - br.setExistingTissue(true); - br.setExternalIdentifier(tissue.getExternalName()); - br.setTissueType(tissue.getTissueType().getName()); - br.setDonorIdentifier(tissue.getDonor().getDonorName()); - br.setHmdmc(tissue.getHmdmc().getHmdmc()); - br.setSpatialLocation(tissue.getSpatialLocation().getCode()); - br.setReplicateNumber(tissue.getReplicate()); - br.setMedium(tissue.getMedium().getName()); - br.setFixative(tissue.getFixative().getName()); - br.setSampleCollectionDate(tissue.getCollectionDate()); - br.setCellClass(tissue.getCellClass().getName()); - if (adjuster!=null) { - adjuster.accept(br); - } - return br; - } -} From 6fe7d28f069304491179c14e9504281c5cbecf1f Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Wed, 11 Mar 2026 12:11:08 +0000 Subject: [PATCH 13/13] code review: remove debugging print --- .../sanger/sccp/stan/integrationtest/TestFileBlockRegister.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java index ca99e105a..ef3146a51 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java @@ -139,7 +139,6 @@ public void testIgnoreExtNames() throws Exception { tester.setUser(user); when(mockRegService.register(any(), any())).thenThrow(new ValidationException(List.of("Bad reg"))); var response = upload("testdata/block_reg_existing.xlsx", null, List.of("Ext17"), false); - System.out.printf("%n****%n%s%n****%n", response.getContentAsString()); var map = objectMapper.readValue(response.getContentAsString(), Map.class); ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(BlockRegisterRequest.class); verify(mockRegService).register(eq(user), requestCaptor.capture());