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 1/9] 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 2/9] 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 3/9] 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 4/9] 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 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 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 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 RH1<;zjGq&qjOX9dzW@qi5rO~! delta 4328 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 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 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 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: Tue, 24 Feb 2026 16:18:52 +0000 Subject: [PATCH 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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());