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 0e98fd3c2..80cf81a61 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 @@ -71,7 +71,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); } @@ -79,8 +79,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/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/BlockRegisterSample.java b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterSample.java new file mode 100644 index 000000000..7525bfe1e --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/request/register/BlockRegisterSample.java @@ -0,0 +1,208 @@ +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; + 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 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; + } + + 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("addresses", addresses) + .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.addresses, that.addresses) + && 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(addresses, 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 deleted file mode 100644 index 635857836..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/BlockFieldChecker.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockFieldChecker.java new file mode 100644 index 000000000..1c99aab92 --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockFieldChecker.java @@ -0,0 +1,107 @@ +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); + } + + /** + * Gets the value of the field from the given request parts. + * @param brl the labware part of the request + * @param brs the sample part of the request + * @return the value of the field + */ + public Object apply(BlockRegisterLabware brl, BlockRegisterSample brs) { + return brlFunction!=null ? brlFunction.apply(brl) : brsFunction.apply(brs); + } + + /** + * Gets the value of the field from the given tissue. + * @param tissue existing tissue + * @return the value of the field + */ + public Object apply(Tissue tissue) { + return tissueFunction.apply(tissue); + } + } + + /** + * Checks for discrepancies between the existing tissue and the values in the request + * @param problemConsumer receptacle for problems found + * @param brl labware part of the request + * @param brs sample part of the request + * @param tissue existing 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())); + } + } + } + + /** + * 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("")); + } + 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..e5e4df90c --- /dev/null +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterServiceImp.java @@ -0,0 +1,220 @@ +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()); + 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()); + String xb = brl.getExternalBarcode(); + Labware lw = labwareService.create(labwareType, xb, xb); + 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/RegisterValidationImp.java b/src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterValidationImp.java similarity index 51% rename from src/main/java/uk/ac/sanger/sccp/stan/service/register/RegisterValidationImp.java rename to src/main/java/uk/ac/sanger/sccp/stan/service/register/BlockRegisterValidationImp.java index 906424ab7..4d6fe58e5 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/BlockRegisterValidationImp.java @@ -2,8 +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.RegisterRequest; +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; @@ -13,15 +12,15 @@ 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 */ -// This might have been a nice idea, but in practice it's unnecessarily complicated. -public class RegisterValidationImp implements RegisterValidation { - private final RegisterRequest request; +public class BlockRegisterValidationImp implements RegisterValidation { + private final BlockRegisterRequest request; private final DonorRepo donorRepo; private final HmdmcRepo hmdmcRepo; private final TissueTypeRepo ttRepo; @@ -31,34 +30,36 @@ public class RegisterValidationImp implements RegisterValidation { private final TissueRepo tissueRepo; private final SpeciesRepo speciesRepo; private final CellClassRepo cellClassRepo; - private final Validator donorNameValidation; - private final Validator externalNameValidation; + private final LabwareRepo lwRepo; + private final Validator donorNameValidator; + private final Validator externalNameValidator; private final Validator replicateValidator; - private final TissueFieldChecker tissueFieldChecker; + private final Validator externalBarcodeValidator; + private final BlockFieldChecker blockFieldChecker; 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 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 Map labwareTypeMap = new HashMap<>(); - final Map mediumMap = new HashMap<>(); - final Map fixativeMap = 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 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) { + 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 donorNameValidator, Validator externalNameValidator, + Validator replicateValidator, Validator externalBarcodeValidator, + BlockFieldChecker blockFieldChecker, + BioRiskService bioRiskService, WorkService workService) { this.request = request; this.donorRepo = donorRepo; this.hmdmcRepo = hmdmcRepo; @@ -69,23 +70,28 @@ public RegisterValidationImp(RegisterRequest request, DonorRepo donorRepo, this.tissueRepo = tissueRepo; this.speciesRepo = speciesRepo; this.cellClassRepo = cellClassRepo; - this.donorNameValidation = donorNameValidation; - this.externalNameValidation = externalNameValidation; + this.lwRepo = lwRepo; + this.donorNameValidator = donorNameValidator; + this.externalNameValidator = externalNameValidator; this.replicateValidator = replicateValidator; - this.tissueFieldChecker = tissueFieldChecker; + this.externalBarcodeValidator = externalBarcodeValidator; + this.blockFieldChecker = blockFieldChecker; this.bioRiskService = bioRiskService; this.workService = workService; } @Override public Collection validate() { - if (blocks().isEmpty()) { - return Collections.emptySet(); // nothing to do + 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(); @@ -97,26 +103,79 @@ public Collection validate() { 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; + } + + /** Checks the donor info for problems */ public void validateDonors() { - for (BlockRegisterRequest block : blocks()) { + for (BlockRegisterSample brs : iter(blockSamples())) { boolean skip = false; Species species = null; - if (block.getDonorIdentifier()==null || block.getDonorIdentifier().isEmpty()) { + if (nullOrEmpty(brs.getDonorIdentifier())) { skip = true; addProblem("Missing donor identifier."); - } else if (donorNameValidation!=null) { - donorNameValidation.validate(block.getDonorIdentifier(), this::addProblem); + } else if (donorNameValidator !=null) { + donorNameValidator.validate(brs.getDonorIdentifier(), this::addProblem); } - if (block.getSpecies()==null || block.getSpecies().isEmpty()) { + if (nullOrEmpty(brs.getSpecies())) { 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); + 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(block.getSpecies())); + addProblem("Unknown species: "+repr(brs.getSpecies())); } else if (!species.isEnabled()) { addProblem("Species is not enabled: "+species.getName()); } @@ -125,13 +184,12 @@ public void validateDonors() { if (skip) { continue; } - String donorNameUc = block.getDonorIdentifier().toUpperCase(); - Donor donor = donorMap.get(donorNameUc); + Donor donor = donorMap.get(brs.getDonorIdentifier()); if (donor==null) { - donor = new Donor(null, block.getDonorIdentifier(), block.getLifeStage(), species); - donorMap.put(donorNameUc, donor); + donor = new Donor(null, brs.getDonorIdentifier(), brs.getLifeStage(), species); + donorMap.put(brs.getDonorIdentifier(), donor); } else { - if (donor.getLifeStage()!=block.getLifeStage()) { + if (donor.getLifeStage()!= brs.getLifeStage()) { addProblem("Multiple different life stages specified for donor "+donor.getDonorName()); } if (species!=null && !species.equals(donor.getSpecies())) { @@ -156,11 +214,63 @@ public void validateDonors() { } } + /** Checks the HMDMCs for problems */ + 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); + } + } + + /** Checks tissue types and spatial locations for problems */ public void validateSpatialLocations() { - Map tissueTypeMap = new HashMap<>(); + UCMap tissueTypeMap = new UCMap<>(); Set unknownTissueTypes = new LinkedHashSet<>(); - for (BlockRegisterRequest block : blocks()) { - if (block.getTissueType()==null || block.getTissueType().isEmpty()) { + for (BlockRegisterSample block : iter(blockSamples())) { + if (nullOrEmpty(block.getTissueType())) { addProblem("Missing tissue type."); continue; } @@ -207,90 +317,43 @@ public void validateSpatialLocations() { } } - public void validateHmdmcs() { - Set unknownHmdmcs = new LinkedHashSet<>(); - boolean unwanted = false; - boolean missing = false; - for (BlockRegisterRequest 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); - } - } - + /** Loads and checks the labware types for problems */ public void validateLabwareTypes() { - validateByName("labware type", BlockRegisterRequest::getLabwareType, ltRepo::findByName, labwareTypeMap); + validateByName("labware type", BlockRegisterLabware::getLabwareType, ltRepo::findByName, labwareTypeMap); } + /** Loads and checks the mediums for problems */ public void validateMediums() { - validateByName("medium", BlockRegisterRequest::getMedium, mediumRepo::findByName, mediumMap); + validateByName("medium", BlockRegisterLabware::getMedium, mediumRepo::findByName, mediumMap); } + /** Loads and checks the fixatives for problems */ public void validateFixatives() { - validateByName("fixative", BlockRegisterRequest::getFixative, fixativeRepo::findByName, fixativeMap); + validateByName("fixative", BlockRegisterLabware::getFixative, fixativeRepo::findByName, fixativeMap); } + /** Checks the collection dates for problems */ public void validateCollectionDates() { boolean missing = false; LocalDate today = LocalDate.now(); Set badDates = new LinkedHashSet<>(); - Map extToDate = new HashMap<>(request.getBlocks().size()); - for (BlockRegisterRequest block : blocks()) { - if (block.getSampleCollectionDate()==null) { - if (block.getLifeStage()==LifeStage.fetal && block.getSpecies()!=null - && Species.isHumanName(block.getSpecies())) { + 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 (block.getSampleCollectionDate().isAfter(today)) { - badDates.add(block.getSampleCollectionDate()); + } else if (brs.getSampleCollectionDate().isAfter(today)) { + badDates.add(brs.getSampleCollectionDate()); } - if (block.getExternalIdentifier()!=null && !block.getExternalIdentifier().isEmpty()) { - String key = block.getExternalIdentifier().trim().toUpperCase(); + 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), block.getSampleCollectionDate())) { + if (extToDate.containsKey(key) && !Objects.equals(extToDate.get(key), brs.getSampleCollectionDate())) { addProblem("Inconsistent collection dates specified for tissue " + key + "."); } else { - extToDate.put(key, block.getSampleCollectionDate()); + extToDate.put(key, brs.getSampleCollectionDate()); } } } @@ -303,85 +366,93 @@ public void validateCollectionDates() { } } + /** Looks for problems with the information given for existing tissues */ public void validateExistingTissues() { - List blocksForExistingTissues = blocks().stream() - .filter(BlockRegisterRequest::isExistingTissue) - .toList(); + 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().map(BlockRegisterRequest::getExternalIdentifier).anyMatch(xn -> xn==null || xn.isEmpty())) { + if (blocksForExistingTissues.stream().anyMatch(brs -> nullOrEmpty(brs.sample().getExternalIdentifier()))) { addProblem("Missing external identifier."); } Set xns = blocksForExistingTissues.stream() - .map(BlockRegisterRequest::getExternalIdentifier) - .filter(Objects::nonNull) + .map(b -> b.sample().getExternalIdentifier()) + .filter(xn -> !nullOrEmpty(xn)) .collect(toLinkedHashSet()); if (xns.isEmpty()) { return; } - tissueRepo.findAllByExternalNameIn(xns).forEach(t -> tissueMap.put(t.getExternalName().toUpperCase(), t)); + tissueRepo.findAllByExternalNameIn(xns).forEach(t -> tissueMap.put(t.getExternalName(), t)); Set missing = xns.stream() - .filter(xn -> !tissueMap.containsKey(xn.toUpperCase())) + .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 (BlockRegisterRequest 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); + for (BlockRegisterLabwareAndSample b : blocksForExistingTissues) { + String xn = b.sample().getExternalIdentifier(); + if (!nullOrEmpty(xn)) { + Tissue tissue = tissueMap.get(xn); + if (tissue != null) { + blockFieldChecker.check(this::addProblem, b.labware(), b.sample(), tissue); + } } } } + /** 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<>(); - for (BlockRegisterRequest block : blocks()) { - if (block.isExistingTissue()) { + for (BlockRegisterSample brs : iter(blockSamples())) { + if (brs.isExistingTissue()) { continue; } - if (block.getReplicateNumber()==null || block.getReplicateNumber().isEmpty()) { + if (nullOrEmpty(brs.getReplicateNumber())) { addProblem("Missing replicate number."); } else { - replicateValidator.validate(block.getReplicateNumber(), this::addProblem); + replicateValidator.validate(brs.getReplicateNumber(), this::addProblem); } - if (block.getHighestSection() < 0) { + if (brs.getHighestSection() < 0) { addProblem("Highest section number cannot be negative."); } - if (block.getExternalIdentifier()==null || block.getExternalIdentifier().isEmpty()) { + if (nullOrEmpty(brs.getExternalIdentifier())) { addProblem("Missing external identifier."); } else { - if (externalNameValidation != null) { - externalNameValidation.validate(block.getExternalIdentifier(), this::addProblem); + if (externalNameValidator != null) { + externalNameValidator.validate(brs.getExternalIdentifier(), this::addProblem); } - if (!externalNames.add(block.getExternalIdentifier().toUpperCase())) { - addProblem("Repeated external identifier: " + block.getExternalIdentifier()); - } else if (!tissueRepo.findAllByExternalName(block.getExternalIdentifier()).isEmpty()) { + 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.", - block.getExternalIdentifier())); + brs.getExternalIdentifier())); } } } } + /** Loads and checks bio risks for problems */ public void validateBioRisks() { - this.bioRiskMap = bioRiskService.loadAndValidateBioRisks(problems, blocks().stream(), - BlockRegisterRequest::getBioRiskCode, BlockRegisterRequest::setBioRiskCode); + 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; - for (BlockRegisterRequest block : blocks()) { - String cellClassName = block.getCellClass(); + for (BlockRegisterSample brs : iter(blockSamples())) { + String cellClassName = brs.getCellClass(); if (nullOrEmpty(cellClassName)) { anyMissing = true; } else { @@ -405,6 +476,7 @@ public void validateCellClasses() { } } + /** Loads and checks works for problems */ public void validateWorks() { if (request.getWorkNumbers().isEmpty()) { addProblem("No work number supplied."); @@ -414,31 +486,98 @@ public void validateWorks() { } } - private void validateByName(String entityName, - Function nameFunction, - Function> lkp, - Map map) { + /** Checks labware external barcodes for problems */ + 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); + } + } + + /** Checks slot addresses for problems */ + 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.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, + UCMap map) { Set unknownNames = new LinkedHashSet<>(); boolean missing = false; - for (BlockRegisterRequest block : blocks()) { - String name = nameFunction.apply(block); - if (name==null || name.isEmpty()) { + for (BlockRegisterLabware brl : request.getLabware()) { + String name = nameFunction.apply(brl); + if (nullOrEmpty(name)) { missing = true; continue; } if (unknownNames.contains(name)) { continue; } - String nameUc = name.toUpperCase(); - if (map.containsKey(nameUc)) { + if (map.containsKey(name)) { continue; } - Optional opt = lkp.apply(nameUc); + Optional opt = lkp.apply(name); if (opt.isEmpty()) { - unknownNames.add(name); + unknownNames.add(repr(name)); continue; } - map.put(nameUc, opt.get()); + map.put(name, opt.get()); } if (missing) { addProblem(String.format("Missing %s.", entityName)); @@ -448,76 +587,28 @@ private void validateByName(String entityName, } } - private Collection blocks() { - return request.getBlocks(); + Collection getProblems() { + return problems; } - private boolean addProblem(String problem) { + /** Adds a problem to the internal collection of problems found */ + 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())); + /** 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(); this.number = number; } } + + /** A linked labware and sample from the request. Used when validating existing tissues. */ + 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 9dd34f9b3..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 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 97551fe9b..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 @@ -33,10 +33,11 @@ public RegisterClashChecker(TissueRepo tissueRepo, SampleRepo sampleRepo, Operat this.lwRepo = lwRepo; } - public List findClashes(RegisterRequest request) { - Set externalNames = request.getBlocks().stream() - .filter(br -> !br.isExistingTissue()) - .map(BlockRegisterRequest::getExternalIdentifier) + 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(); 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 a2c3992f9..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 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 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 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 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 e502e1e1c..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,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.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; @@ -36,7 +36,7 @@ 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; private final WorkService workService; @@ -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, SlotRegionService slotRegionService, BioRiskService bioRiskService, WorkService workService) { this.donorRepo = donorRepo; this.hmdmcRepo = hmdmcRepo; @@ -77,16 +77,17 @@ 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, + externalNameValidation, replicateValidator, externalBarcodeValidation, + blockFieldChecker, bioRiskService, workService); } public SectionRegisterValidation createSectionRegisterValidation(SectionRegisterRequest request) { 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 5facee393..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; - -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 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::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"), - ; - - 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 br) { - return brFunction.apply(br); - } - } - - public void check(Consumer problemConsumer, BlockRegisterRequest 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/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..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 @@ -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; @@ -28,14 +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)), + Slot_address(Pattern.compile("(sample\\s*)?slot\\s*address.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), Tissue_type, - External_identifier(Pattern.compile("external\\s*id.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)), + 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)), 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..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 @@ -3,8 +3,8 @@ 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; -import uk.ac.sanger.sccp.stan.request.register.RegisterRequest; +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; @@ -12,16 +12,18 @@ 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.toList; 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 */ @Service -public class BlockRegisterFileReaderImp extends BaseRegisterFileReader +public class BlockRegisterFileReaderImp extends BaseRegisterFileReader implements BlockRegisterFileReader { protected BlockRegisterFileReaderImp() { @@ -29,16 +31,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); } /** @@ -48,61 +49,145 @@ protected RegisterRequest 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); } /** - * 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 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 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)); + 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 { - br.setSpatialLocation((Integer) row.get(Column.Spatial_location)); + sample.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)); + 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 } - br.setLabwareType((String) row.get(Column.Labware_type)); - return br; + 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/regtest.xlsx"); - RegisterRequest request; + 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) { @@ -116,5 +201,4 @@ public static void main(String[] args) throws IOException { } System.out.println(request); } - } diff --git a/src/main/resources/schema.graphqls b/src/main/resources/schema.graphqls index cb57cd32b..6e34e8e5a 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! + 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/integrationtest/TestFileBlockRegister.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestFileBlockRegister.java index 7dc05d79e..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 @@ -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)); } @@ -140,14 +140,15 @@ public void testIgnoreExtNames() throws Exception { 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); 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 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 e73d8bc58..d33a14b12 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/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 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()); + } +} 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 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..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 @@ -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") @@ -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); } @@ -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::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 blockRegWithExternalName(String xn) { - BlockRegisterRequest br = new BlockRegisterRequest(); - 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/TestRegisterClashChecker.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterClashChecker.java index 36eb65c64..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::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 br = new BlockRegisterRequest(); - 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/TestRegisterService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/register/TestRegisterService.java deleted file mode 100644 index 6feeee27b..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())); - 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())).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())); - List clashes = List.of(new RegisterClash(EntityFactory.getTissue(), List.of())); - when(mockClashChecker.findClashes(any())).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())); - 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 block0 = new BlockRegisterRequest(); - block0.setDonorIdentifier(donor0.getDonorName()); - block0.setLifeStage(donor0.getLifeStage()); - block0.setSpecies(donor0.getSpecies().getName()); - BlockRegisterRequest block1 = new BlockRegisterRequest(); - 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 brr1 = new BlockRegisterRequest(); - 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(); - brr2.setExternalIdentifier(tissue2.getExternalName().toLowerCase()); - brr2.setExistingTissue(true); - brr2.setSampleCollectionDate(tissue2.getCollectionDate()); - - BlockRegisterRequest brr3 = new BlockRegisterRequest(); - - 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 brr1 = new BlockRegisterRequest(); - 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); - 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 makeBrr(String externalName, String donorName, - String hmdmc, String species, - String replicate, SpatialLocation sl, - String mediumName, String fixName, LocalDate collectionDate) { - BlockRegisterRequest br = new BlockRegisterRequest(); - 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 block0 = new BlockRegisterRequest(); - 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 block1 = new BlockRegisterRequest(); - 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 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 block = new BlockRegisterRequest(); - 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/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 857018a42..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; -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())); - 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())); - 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 br = new BlockRegisterRequest(); - 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 br = new BlockRegisterRequest(); - 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 br = new BlockRegisterRequest(); - 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 br = new BlockRegisterRequest(); - 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::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::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::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 br = new BlockRegisterRequest(); - 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 br = new BlockRegisterRequest(); - 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) - .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 toBrr(String species, LifeStage lifeStage, LocalDate collectionDate, String ext) { - BlockRegisterRequest brr = new BlockRegisterRequest(); - brr.setSpecies(species); - brr.setLifeStage(lifeStage); - brr.setSampleCollectionDate(collectionDate); - brr.setExternalIdentifier(ext); - return brr; - } - - static BlockRegisterRequest 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 br = new BlockRegisterRequest(); - 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 block1 = new BlockRegisterRequest(); - block1.setBioRiskCode("risk1"); - BlockRegisterRequest block2 = new BlockRegisterRequest(); - 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 blk = new BlockRegisterRequest(); - 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()).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 caa638e02..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 @@ -1,42 +1,36 @@ 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 org.junit.jupiter.api.*; +import org.mockito.InjectMocks; +import org.mockito.MockitoAnnotations; +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; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.mock; -import static uk.ac.sanger.sccp.stan.Matchers.genericMock; /** * Tests {@link RegisterValidationFactory} * @author dr6 */ public class TestRegisterValidationFactory { + @InjectMocks RegisterValidationFactory registerValidationFactory; + private AutoCloseable mocking; + @BeforeEach void setup() { - Validator mockStringValidator = genericMock(Validator.class); - Sanitiser mockSanitiser = genericMock(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, 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 testCreateRegisterValidation() { - assertNotNull(registerValidationFactory.createRegisterValidation(new RegisterRequest())); + public void testCreateBlockRegisterValidation() { + assertNotNull(registerValidationFactory.createBlockRegisterValidation(new BlockRegisterRequest())); } @Test 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 aaa15c19d..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; - -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 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 toBRR(Tissue tissue, Consumer adjuster) { - BlockRegisterRequest br = new BlockRegisterRequest(); - 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; - } -} 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..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; -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,6 +24,7 @@ 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} @@ -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 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 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 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 makeBlockRegisterRequest(String externalId) { - BlockRegisterRequest br = new BlockRegisterRequest(); - 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 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 1fef254ce..ff1c3d9cb 100644 Binary files a/src/test/resources/testdata/block_reg.xlsx and b/src/test/resources/testdata/block_reg.xlsx differ diff --git a/src/test/resources/testdata/block_reg_existing.xlsx b/src/test/resources/testdata/block_reg_existing.xlsx index e3cb4aae2..3c5d58150 100644 Binary files a/src/test/resources/testdata/block_reg_existing.xlsx and b/src/test/resources/testdata/block_reg_existing.xlsx differ diff --git a/src/test/resources/testdata/reg_empty.xlsx b/src/test/resources/testdata/reg_empty.xlsx index f6cce3c40..af3d705b6 100644 Binary files a/src/test/resources/testdata/reg_empty.xlsx and b/src/test/resources/testdata/reg_empty.xlsx differ