diff --git a/build.gradle b/build.gradle index ef2cdfb03b..f0bad28e59 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ sourceCompatibility = JavaVersion.VERSION_11 allprojects { group = "bio.terra" - version = "0.9.0-SNAPSHOT" + version = "0.10.0-SNAPSHOT" ext { artifactGroup = "${group}.workspace" swaggerOutputDir = "${buildDir}/swagger-code" diff --git a/src/main/java/bio/terra/workspace/app/controller/WorkspaceApiController.java b/src/main/java/bio/terra/workspace/app/controller/WorkspaceApiController.java index 28d150ec88..291bbc093d 100644 --- a/src/main/java/bio/terra/workspace/app/controller/WorkspaceApiController.java +++ b/src/main/java/bio/terra/workspace/app/controller/WorkspaceApiController.java @@ -4,6 +4,7 @@ import bio.terra.workspace.generated.controller.WorkspaceApi; import bio.terra.workspace.generated.model.*; import bio.terra.workspace.service.datareference.DataReferenceService; +import bio.terra.workspace.service.datareference.exception.InvalidDataReferenceException; import bio.terra.workspace.service.datareference.model.CloningInstructions; import bio.terra.workspace.service.datareference.model.DataReference; import bio.terra.workspace.service.datareference.model.DataReferenceRequest; @@ -157,6 +158,7 @@ public ResponseEntity createDataReference( DataReferenceRequest.builder() .workspaceId(id) .name(body.getName()) + .referenceDescription(body.getReferenceDescription()) .referenceType(referenceType) .cloningInstructions(CloningInstructions.fromApiModel(body.getCloningInstructions())) .referenceObject(snapshot) @@ -209,6 +211,36 @@ public ResponseEntity getDataReferenceByName( return new ResponseEntity<>(ref.toApiModel(), HttpStatus.OK); } + @Override + public ResponseEntity updateDataReference( + @PathVariable("id") UUID id, + @PathVariable("referenceId") UUID referenceId, + @RequestBody UpdateDataReferenceRequestBody body) { + AuthenticatedUserRequest userReq = getAuthenticatedInfo(); + + if (body.getName() == null && body.getReferenceDescription() == null) { + throw new InvalidDataReferenceException( + "Must specify name or referenceDescription to update."); + } + + if (body.getName() != null) { + DataReferenceValidationUtils.validateReferenceName(body.getName()); + } + + logger.info( + String.format( + "Updating data reference by id %s in workspace %s for %s with body %s", + referenceId.toString(), id.toString(), userReq.getEmail(), body.toString())); + + dataReferenceService.updateDataReference(id, referenceId, body, userReq); + logger.info( + String.format( + "Updating data reference by id %s in workspace %s for %s with body %s", + referenceId.toString(), id.toString(), userReq.getEmail(), body.toString())); + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + @Override public ResponseEntity deleteDataReference( @PathVariable("id") UUID workspaceId, @PathVariable("referenceId") UUID referenceId) { diff --git a/src/main/java/bio/terra/workspace/db/DataReferenceDao.java b/src/main/java/bio/terra/workspace/db/DataReferenceDao.java index d43974704a..5fd50483fa 100644 --- a/src/main/java/bio/terra/workspace/db/DataReferenceDao.java +++ b/src/main/java/bio/terra/workspace/db/DataReferenceDao.java @@ -2,6 +2,7 @@ import bio.terra.workspace.common.exception.DataReferenceNotFoundException; import bio.terra.workspace.common.exception.DuplicateDataReferenceException; +import bio.terra.workspace.db.exception.InvalidDaoRequestException; import bio.terra.workspace.service.datareference.model.CloningInstructions; import bio.terra.workspace.service.datareference.model.DataReference; import bio.terra.workspace.service.datareference.model.DataReferenceRequest; @@ -10,6 +11,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; import java.util.Optional; +import java.util.StringJoiner; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,14 +45,15 @@ public DataReferenceDao(NamedParameterJdbcTemplate jdbcTemplate) { public String createDataReference(DataReferenceRequest request, UUID referenceId) throws DuplicateDataReferenceException { String sql = - "INSERT INTO workspace_data_reference (workspace_id, reference_id, name, cloning_instructions, reference_type, reference) VALUES " - + "(:workspace_id, :reference_id, :name, :cloning_instructions, :reference_type, cast(:reference AS json))"; + "INSERT INTO workspace_data_reference (workspace_id, reference_id, name, reference_description, cloning_instructions, reference_type, reference) VALUES " + + "(:workspace_id, :reference_id, :name, :reference_description, :cloning_instructions, :reference_type, cast(:reference AS json))"; MapSqlParameterSource params = new MapSqlParameterSource() .addValue("workspace_id", request.workspaceId().toString()) .addValue("reference_id", referenceId.toString()) .addValue("name", request.name()) + .addValue("reference_description", request.referenceDescription()) .addValue("cloning_instructions", request.cloningInstructions().toSql()) .addValue("reference_type", request.referenceType().toSql()) .addValue("reference", request.referenceObject().toJson()); @@ -71,7 +74,7 @@ public String createDataReference(DataReferenceRequest request, UUID referenceId /** Retrieve a data reference by ID from the DB. */ public DataReference getDataReference(UUID workspaceId, UUID referenceId) { String sql = - "SELECT workspace_id, reference_id, name, cloning_instructions, reference_type, reference from workspace_data_reference where workspace_id = :workspace_id AND reference_id = :reference_id"; + "SELECT workspace_id, reference_id, name, reference_description, cloning_instructions, reference_type, reference from workspace_data_reference where workspace_id = :workspace_id AND reference_id = :reference_id"; MapSqlParameterSource params = new MapSqlParameterSource() @@ -97,7 +100,7 @@ public DataReference getDataReference(UUID workspaceId, UUID referenceId) { public DataReference getDataReferenceByName( UUID workspaceId, DataReferenceType type, String name) { String sql = - "SELECT workspace_id, reference_id, name, cloning_instructions, reference_type, reference from workspace_data_reference where workspace_id = :id AND reference_type = :type AND name = :name"; + "SELECT workspace_id, reference_id, name, reference_description, cloning_instructions, reference_type, reference from workspace_data_reference where workspace_id = :id AND reference_type = :type AND name = :name"; MapSqlParameterSource params = new MapSqlParameterSource() @@ -135,6 +138,49 @@ public boolean isControlled(UUID workspaceId, UUID referenceId) { } } + public boolean updateDataReference( + UUID workspaceId, UUID referenceId, String name, String referenceDescription) { + if (name == null && referenceDescription == null) { + throw new InvalidDaoRequestException("Must specify name or referenceDescription to update."); + } + + MapSqlParameterSource params = + new MapSqlParameterSource() + .addValue("id", referenceId.toString()) + .addValue("workspace_id", workspaceId.toString()) + .addValue("name", name) + .addValue("reference_description", referenceDescription); + + StringJoiner updateStatement = + new StringJoiner( + ", ", + "UPDATE workspace_data_reference SET ", + " WHERE reference_id = :id AND workspace_id = :workspace_id"); + + if (name != null) { + updateStatement.add("name = :name"); + } + if (referenceDescription != null) { + updateStatement.add("reference_description = :reference_description"); + } + + int rowsAffected = jdbcTemplate.update(updateStatement.toString(), params); + boolean updated = rowsAffected > 0; + + if (updated) + logger.info( + String.format( + "Updated record for data reference %s in workspace %s", + referenceId.toString(), workspaceId.toString())); + else + logger.info( + String.format( + "Failed to update record for data reference %s in workspace %s", + referenceId.toString(), workspaceId.toString())); + + return updated; + } + public boolean deleteDataReference(UUID workspaceId, UUID referenceId) { MapSqlParameterSource params = new MapSqlParameterSource() @@ -164,7 +210,7 @@ public boolean deleteDataReference(UUID workspaceId, UUID referenceId) { // should consider joining and listing those entries here. public List enumerateDataReferences(UUID workspaceId, int offset, int limit) { String sql = - "SELECT workspace_id, reference_id, name, cloning_instructions, reference_type, reference" + "SELECT workspace_id, reference_id, name, reference_description, cloning_instructions, reference_type, reference" + " FROM workspace_data_reference" + " WHERE workspace_id = :id" + " ORDER BY reference_id" @@ -189,6 +235,7 @@ public List enumerateDataReferences(UUID workspaceId, int offset, .workspaceId(UUID.fromString(rs.getString("workspace_id"))) .referenceId(UUID.fromString(rs.getString("reference_id"))) .name(rs.getString("name")) + .referenceDescription(rs.getString("reference_description")) .referenceType(referenceType) .cloningInstructions(CloningInstructions.fromSql(rs.getString("cloning_instructions"))) .referenceObject(deserializedReferenceObject) diff --git a/src/main/java/bio/terra/workspace/db/exception/InvalidDaoRequestException.java b/src/main/java/bio/terra/workspace/db/exception/InvalidDaoRequestException.java new file mode 100644 index 0000000000..26b2078be4 --- /dev/null +++ b/src/main/java/bio/terra/workspace/db/exception/InvalidDaoRequestException.java @@ -0,0 +1,10 @@ +package bio.terra.workspace.db.exception; + +import bio.terra.common.exception.InternalServerErrorException; + +public class InvalidDaoRequestException extends InternalServerErrorException { + + public InvalidDaoRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/bio/terra/workspace/service/datareference/DataReferenceService.java b/src/main/java/bio/terra/workspace/service/datareference/DataReferenceService.java index ee35b3afa6..5c8cf5ecb4 100644 --- a/src/main/java/bio/terra/workspace/service/datareference/DataReferenceService.java +++ b/src/main/java/bio/terra/workspace/service/datareference/DataReferenceService.java @@ -2,6 +2,7 @@ import bio.terra.workspace.common.exception.*; import bio.terra.workspace.db.DataReferenceDao; +import bio.terra.workspace.generated.model.UpdateDataReferenceRequestBody; import bio.terra.workspace.service.datareference.exception.ControlledResourceNotImplementedException; import bio.terra.workspace.service.datareference.flight.CreateDataReferenceFlight; import bio.terra.workspace.service.datareference.flight.DataReferenceFlightMapKeys; @@ -98,6 +99,9 @@ public DataReference createDataReference( userReq) .addParameter(DataReferenceFlightMapKeys.WORKSPACE_ID, referenceRequest.workspaceId()) .addParameter(DataReferenceFlightMapKeys.NAME, referenceRequest.name()) + .addParameter( + DataReferenceFlightMapKeys.REFERENCE_DESCRIPTION, + referenceRequest.referenceDescription()) .addParameter( DataReferenceFlightMapKeys.REFERENCE_TYPE, referenceRequest.referenceType()) .addParameter( @@ -128,6 +132,24 @@ public List enumerateDataReferences( return dataReferenceDao.enumerateDataReferences(workspaceId, offset, limit); } + @Traced + public void updateDataReference( + UUID workspaceId, + UUID referenceId, + UpdateDataReferenceRequestBody updateRequest, + AuthenticatedUserRequest userReq) { + workspaceService.validateWorkspaceAndAction( + userReq, workspaceId, SamConstants.SAM_WORKSPACE_WRITE_ACTION); + + if (!dataReferenceDao.updateDataReference( + workspaceId, + referenceId, + updateRequest.getName(), + updateRequest.getReferenceDescription())) { + throw new DataReferenceNotFoundException("Data Reference not found."); + } + } + /** * Delete a data reference, or throw an exception if the specified reference does not exist. * Verifies workspace existence and write permission before deleting the reference. diff --git a/src/main/java/bio/terra/workspace/service/datareference/flight/CreateDataReferenceStep.java b/src/main/java/bio/terra/workspace/service/datareference/flight/CreateDataReferenceStep.java index c64a42df9c..abe0650c77 100644 --- a/src/main/java/bio/terra/workspace/service/datareference/flight/CreateDataReferenceStep.java +++ b/src/main/java/bio/terra/workspace/service/datareference/flight/CreateDataReferenceStep.java @@ -32,6 +32,8 @@ public StepResult doStep(FlightContext flightContext) throws RetryException { UUID referenceId = workingMap.get(DataReferenceFlightMapKeys.REFERENCE_ID, UUID.class); UUID workspaceId = inputMap.get(DataReferenceFlightMapKeys.WORKSPACE_ID, UUID.class); String name = inputMap.get(DataReferenceFlightMapKeys.NAME, String.class); + String referenceDescription = + inputMap.get(DataReferenceFlightMapKeys.REFERENCE_DESCRIPTION, String.class); DataReferenceType type = inputMap.get(DataReferenceFlightMapKeys.REFERENCE_TYPE, DataReferenceType.class); CloningInstructions cloningInstructions = @@ -43,6 +45,7 @@ public StepResult doStep(FlightContext flightContext) throws RetryException { DataReferenceRequest.builder() .workspaceId(workspaceId) .name(name) + .referenceDescription(referenceDescription) .referenceType(type) .cloningInstructions(cloningInstructions) .referenceObject(referenceObject) diff --git a/src/main/java/bio/terra/workspace/service/datareference/flight/DataReferenceFlightMapKeys.java b/src/main/java/bio/terra/workspace/service/datareference/flight/DataReferenceFlightMapKeys.java index 8cfb9037b0..89e40eb933 100644 --- a/src/main/java/bio/terra/workspace/service/datareference/flight/DataReferenceFlightMapKeys.java +++ b/src/main/java/bio/terra/workspace/service/datareference/flight/DataReferenceFlightMapKeys.java @@ -4,6 +4,7 @@ public final class DataReferenceFlightMapKeys { public static final String REFERENCE_ID = "referenceId"; public static final String WORKSPACE_ID = "workspaceId"; public static final String NAME = "name"; + public static final String REFERENCE_DESCRIPTION = "referenceDescription"; public static final String REFERENCE_TYPE = "referenceType"; public static final String CLONING_INSTRUCTIONS = "cloningInstructions"; public static final String REFERENCE_OBJECT = "referenceObject"; diff --git a/src/main/java/bio/terra/workspace/service/datareference/model/DataReference.java b/src/main/java/bio/terra/workspace/service/datareference/model/DataReference.java index d5c87bac21..b33b874179 100644 --- a/src/main/java/bio/terra/workspace/service/datareference/model/DataReference.java +++ b/src/main/java/bio/terra/workspace/service/datareference/model/DataReference.java @@ -3,6 +3,7 @@ import bio.terra.workspace.generated.model.DataReferenceDescription; import com.google.auto.value.AutoValue; import java.util.UUID; +import javax.annotation.Nullable; /** * Internal representation of an uncontrolled data reference. @@ -21,6 +22,10 @@ public abstract class DataReference { /** Name of the reference. Names are unique per workspace, per reference type. */ public abstract String name(); + /** Description of the data reference. */ + @Nullable + public abstract String referenceDescription(); + /** Type of this data reference. */ public abstract DataReferenceType referenceType(); @@ -35,6 +40,7 @@ public DataReferenceDescription toApiModel() { return new DataReferenceDescription() .referenceId(referenceId()) .name(name()) + .referenceDescription(referenceDescription()) .workspaceId(workspaceId()) .referenceType(referenceType().toApiModel()) .reference(((SnapshotReference) referenceObject()).toApiModel()) @@ -53,6 +59,8 @@ public abstract static class Builder { public abstract DataReference.Builder name(String value); + public abstract DataReference.Builder referenceDescription(String value); + public abstract DataReference.Builder referenceType(DataReferenceType value); public abstract DataReference.Builder cloningInstructions(CloningInstructions value); diff --git a/src/main/java/bio/terra/workspace/service/datareference/model/DataReferenceRequest.java b/src/main/java/bio/terra/workspace/service/datareference/model/DataReferenceRequest.java index 63815a72f5..123735c267 100644 --- a/src/main/java/bio/terra/workspace/service/datareference/model/DataReferenceRequest.java +++ b/src/main/java/bio/terra/workspace/service/datareference/model/DataReferenceRequest.java @@ -2,6 +2,7 @@ import com.google.auto.value.AutoValue; import java.util.UUID; +import javax.annotation.Nullable; /** * This is an internal representation of a request to create a data reference. @@ -22,6 +23,10 @@ public abstract class DataReferenceRequest { */ public abstract String name(); + /** Description of the reference. */ + @Nullable + public abstract String referenceDescription(); + /** Type of this data reference. */ public abstract DataReferenceType referenceType(); @@ -41,6 +46,8 @@ public abstract static class Builder { public abstract DataReferenceRequest.Builder name(String value); + public abstract DataReferenceRequest.Builder referenceDescription(String value); + public abstract DataReferenceRequest.Builder referenceType(DataReferenceType value); public abstract DataReferenceRequest.Builder cloningInstructions(CloningInstructions value); diff --git a/src/main/resources/api/service_openapi.yaml b/src/main/resources/api/service_openapi.yaml index 0044915051..113a5dea76 100644 --- a/src/main/resources/api/service_openapi.yaml +++ b/src/main/resources/api/service_openapi.yaml @@ -158,6 +158,25 @@ paths: $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/ServerError' + patch: + summary: Update name or description of a data reference in a workspace. + operationId: updateDataReference + tags: [Workspace] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateDataReferenceRequestBody' + responses: + '204': + description: OK + '403': + $ref: '#/components/responses/PermissionDenied' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/ServerError' delete: summary: Deletes a data reference from a workspace. operationId: deleteDataReference @@ -606,6 +625,8 @@ components: properties: name: $ref: "#/components/schemas/Name" + referenceDescription: + type: string resourceId: description: The ID of the resource type: string @@ -617,6 +638,14 @@ components: cloningInstructions: $ref: '#/components/schemas/CloningInstructionsEnum' + UpdateDataReferenceRequestBody: + type: object + properties: + name: + $ref: "#/components/schemas/Name" + referenceDescription: + type: string + DataReferenceDescription: type: object required: [referenceId, name, workspaceId, cloningInstructions] @@ -628,6 +657,8 @@ components: name: description: The name of the data reference; used to refer to the reference type: string + referenceDescription: + type: string workspaceId: description: The ID of the workspace containing the reference type: string diff --git a/src/main/resources/db/changelog.xml b/src/main/resources/db/changelog.xml index efd3779a46..ff057d2340 100644 --- a/src/main/resources/db/changelog.xml +++ b/src/main/resources/db/changelog.xml @@ -6,4 +6,5 @@ + diff --git a/src/main/resources/db/changesets/20210205_add_description.yaml b/src/main/resources/db/changesets/20210205_add_description.yaml new file mode 100644 index 0000000000..837174ef26 --- /dev/null +++ b/src/main/resources/db/changesets/20210205_add_description.yaml @@ -0,0 +1,11 @@ +databaseChangeLog: + - changeSet: + id: addDescriptionColumn + author: zarsky-broad + changes: + - addColumn: + columns: + - column: + name: reference_description + type: text + tableName: workspace_data_reference diff --git a/src/test/java/bio/terra/workspace/db/DataReferenceDaoTest.java b/src/test/java/bio/terra/workspace/db/DataReferenceDaoTest.java index c42d2f85cd..9c3b1dac0c 100644 --- a/src/test/java/bio/terra/workspace/db/DataReferenceDaoTest.java +++ b/src/test/java/bio/terra/workspace/db/DataReferenceDaoTest.java @@ -11,6 +11,7 @@ import bio.terra.workspace.common.BaseUnitTest; import bio.terra.workspace.common.exception.DataReferenceNotFoundException; import bio.terra.workspace.common.exception.DuplicateDataReferenceException; +import bio.terra.workspace.db.exception.InvalidDaoRequestException; import bio.terra.workspace.service.datareference.model.CloningInstructions; import bio.terra.workspace.service.datareference.model.DataReference; import bio.terra.workspace.service.datareference.model.DataReferenceRequest; @@ -22,6 +23,10 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.Supplier; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; @@ -34,134 +39,172 @@ public class DataReferenceDaoTest extends BaseUnitTest { @Autowired private WorkspaceDao workspaceDao; @Autowired private ObjectMapper objectMapper; - @Test - public void verifyCreatedDataReferenceExists() { - UUID workspaceId = createDefaultWorkspace(); - UUID referenceId = UUID.randomUUID(); - DataReferenceRequest referenceRequest = defaultReferenceRequest(workspaceId).build(); - dataReferenceDao.createDataReference(referenceRequest, referenceId); - DataReference reference = dataReferenceDao.getDataReference(workspaceId, referenceId); - - assertThat(reference.referenceId(), equalTo(referenceId)); - } + UUID workspaceId; + UUID referenceId; + DataReferenceRequest referenceRequest; - @Test - public void createReferenceWithoutWorkspaceFails() { - // This reference uses a random workspaceID, so the corresponding workspace does not exist. - DataReferenceRequest referenceRequest = defaultReferenceRequest(UUID.randomUUID()).build(); - assertThrows( - DataIntegrityViolationException.class, - () -> dataReferenceDao.createDataReference(referenceRequest, UUID.randomUUID())); - } + Supplier currentReference = + () -> dataReferenceDao.getDataReference(workspaceId, referenceId); - @Test - public void verifyCreateDuplicateNameFails() { - UUID workspaceId = createDefaultWorkspace(); - UUID referenceId = UUID.randomUUID(); - DataReferenceRequest referenceRequest = defaultReferenceRequest(workspaceId).build(); + @BeforeEach + void setup() { + workspaceId = createDefaultWorkspace(); + referenceId = UUID.randomUUID(); + referenceRequest = defaultReferenceRequest(workspaceId).build(); dataReferenceDao.createDataReference(referenceRequest, referenceId); - - assertThrows( - DuplicateDataReferenceException.class, - () -> dataReferenceDao.createDataReference(referenceRequest, referenceId)); } - @Test - public void verifyGetDataReferenceByName() { - UUID workspaceId = createDefaultWorkspace(); - UUID referenceId = UUID.randomUUID(); - DataReferenceRequest referenceRequest = defaultReferenceRequest(workspaceId).build(); - dataReferenceDao.createDataReference(referenceRequest, referenceId); - - DataReference ref = - dataReferenceDao.getDataReferenceByName( - workspaceId, referenceRequest.referenceType(), referenceRequest.name()); - assertThat(ref.referenceId(), equalTo(referenceId)); + @Nested + class CreateDataReference { + + @Test + public void verifyCreatedDataReferenceExists() { + assertThat(currentReference.get().referenceId(), equalTo(referenceId)); + } + + @Test + public void createReferenceWithoutWorkspaceFails() { + // This reference uses a random workspaceID, so the corresponding workspace does not exist. + DataReferenceRequest referenceRequest = defaultReferenceRequest(UUID.randomUUID()).build(); + assertThrows( + DataIntegrityViolationException.class, + () -> dataReferenceDao.createDataReference(referenceRequest, UUID.randomUUID())); + } + + @Test + public void verifyCreateDuplicateNameFails() { + assertThrows( + DuplicateDataReferenceException.class, + () -> dataReferenceDao.createDataReference(referenceRequest, referenceId)); + } } - @Test - public void verifyGetDataReference() { - UUID workspaceId = createDefaultWorkspace(); - UUID referenceId = UUID.randomUUID(); - DataReferenceRequest referenceRequest = defaultReferenceRequest(workspaceId).build(); - dataReferenceDao.createDataReference(referenceRequest, referenceId); - - DataReference result = dataReferenceDao.getDataReference(workspaceId, referenceId); - - assertThat(result.workspaceId(), equalTo(workspaceId)); - assertThat(result.referenceId(), equalTo(referenceId)); - assertThat(result.name(), equalTo(referenceRequest.name())); - assertThat(result.referenceType(), equalTo(referenceRequest.referenceType())); - assertThat(result.referenceObject(), equalTo(referenceRequest.referenceObject())); + @Nested + class GetDataReference { + + @Test + public void verifyGetDataReferenceByName() { + DataReference ref = + dataReferenceDao.getDataReferenceByName( + workspaceId, referenceRequest.referenceType(), referenceRequest.name()); + assertThat(ref.referenceId(), equalTo(referenceId)); + } + + @Test + public void verifyGetDataReference() { + DataReference result = currentReference.get(); + + assertThat(result.workspaceId(), equalTo(workspaceId)); + assertThat(result.referenceId(), equalTo(referenceId)); + assertThat(result.name(), equalTo(referenceRequest.name())); + assertThat(result.referenceType(), equalTo(referenceRequest.referenceType())); + assertThat(result.referenceObject(), equalTo(referenceRequest.referenceObject())); + } + + @Test + public void verifyGetDataReferenceNotInWorkspaceNotFound() { + Workspace decoyWorkspace = + Workspace.builder() + .workspaceId(UUID.randomUUID()) + .workspaceStage(WorkspaceStage.RAWLS_WORKSPACE) + .build(); + UUID decoyId = workspaceDao.createWorkspace(decoyWorkspace); + assertThrows( + DataReferenceNotFoundException.class, + () -> dataReferenceDao.getDataReference(decoyId, referenceId)); + } } - @Test - public void verifyGetDataReferenceNotInWorkspaceNotFound() { - UUID workspaceId = createDefaultWorkspace(); - UUID referenceId = UUID.randomUUID(); - DataReferenceRequest referenceRequest = defaultReferenceRequest(workspaceId).build(); - dataReferenceDao.createDataReference(referenceRequest, referenceId); - - Workspace decoyWorkspace = - Workspace.builder() - .workspaceId(UUID.randomUUID()) - .workspaceStage(WorkspaceStage.RAWLS_WORKSPACE) - .build(); - UUID decoyId = workspaceDao.createWorkspace(decoyWorkspace); - assertThrows( - DataReferenceNotFoundException.class, - () -> dataReferenceDao.getDataReference(decoyId, referenceId)); + @Nested + class UpdateDataReference { + + BiFunction updateReference = + (String name, String description) -> + dataReferenceDao.updateDataReference(workspaceId, referenceId, name, description); + + @Test + public void verifyUpdateName() { + String updatedName = "rename"; + + updateReference.apply(updatedName, null); + assertThat(currentReference.get().name(), equalTo(updatedName)); + assertThat( + currentReference.get().referenceDescription(), + equalTo(referenceRequest.referenceDescription())); + } + + @Test + public void verifyUpdateReferenceDescription() { + String updatedDescription = "updated description"; + + updateReference.apply(null, updatedDescription); + assertThat(currentReference.get().name(), equalTo(referenceRequest.name())); + assertThat(currentReference.get().referenceDescription(), equalTo(updatedDescription)); + } + + @Test + public void verifyUpdateAllFields() { + String updatedName2 = "rename_again"; + String updatedDescription2 = "updated description again"; + + updateReference.apply(updatedName2, updatedDescription2); + assertThat(currentReference.get().name(), equalTo(updatedName2)); + assertThat(currentReference.get().referenceDescription(), equalTo(updatedDescription2)); + } + + @Test + public void updateNothingFails() { + assertThrows(InvalidDaoRequestException.class, () -> updateReference.apply(null, null)); + } } - @Test - public void verifyDeleteDataReference() { - UUID workspaceId = createDefaultWorkspace(); - UUID referenceId = UUID.randomUUID(); - DataReferenceRequest referenceRequest = defaultReferenceRequest(workspaceId).build(); - dataReferenceDao.createDataReference(referenceRequest, referenceId); + @Nested + class DeleteDataReference { - assertTrue(dataReferenceDao.deleteDataReference(workspaceId, referenceId)); + @Test + public void verifyDeleteDataReference() { + assertTrue(dataReferenceDao.deleteDataReference(workspaceId, referenceId)); - // try to delete again to make sure it's not there - assertFalse(dataReferenceDao.deleteDataReference(workspaceId, referenceId)); - } + // try to delete again to make sure it's not there + assertFalse(dataReferenceDao.deleteDataReference(workspaceId, referenceId)); + } - @Test - public void deleteNonExistentWorkspaceFails() { - assertFalse(dataReferenceDao.deleteDataReference(UUID.randomUUID(), UUID.randomUUID())); + @Test + public void deleteNonExistentWorkspaceFails() { + assertFalse(dataReferenceDao.deleteDataReference(UUID.randomUUID(), UUID.randomUUID())); + } } - @Test - public void enumerateWorkspaceReferences() { - UUID workspaceId = createDefaultWorkspace(); - UUID firstReferenceId = UUID.randomUUID(); - DataReferenceRequest firstRequest = defaultReferenceRequest(workspaceId).build(); - - // Create two references in the same workspace. - dataReferenceDao.createDataReference(firstRequest, firstReferenceId); - DataReference firstReference = dataReferenceDao.getDataReference(workspaceId, firstReferenceId); - - // This needs a non-default name as we enforce name uniqueness per type per workspace. - DataReferenceRequest secondRequest = defaultReferenceRequest(workspaceId).name("bar").build(); - UUID secondReferenceId = UUID.randomUUID(); - dataReferenceDao.createDataReference(secondRequest, secondReferenceId); - DataReference secondReference = - dataReferenceDao.getDataReference(workspaceId, secondReferenceId); - - // Validate that both DataReferences are enumerated - List enumerateResult = - dataReferenceDao.enumerateDataReferences(workspaceId, 0, 10); - assertThat(enumerateResult.size(), equalTo(2)); - assertThat( - enumerateResult, containsInAnyOrder(equalTo(firstReference), equalTo(secondReference))); - } - - @Test - public void enumerateEmptyReferenceList() { - UUID workspaceId = createDefaultWorkspace(); - - List result = dataReferenceDao.enumerateDataReferences(workspaceId, 0, 10); - assertTrue(result.isEmpty()); + @Nested + class EnumerateDataReferences { + + @Test + public void enumerateWorkspaceReferences() { + // Create two references in the same workspace. + DataReference firstReference = currentReference.get(); + + // This needs a non-default name as we enforce name uniqueness per type per workspace. + DataReferenceRequest secondRequest = defaultReferenceRequest(workspaceId).name("bar").build(); + UUID secondReferenceId = UUID.randomUUID(); + dataReferenceDao.createDataReference(secondRequest, secondReferenceId); + DataReference secondReference = + dataReferenceDao.getDataReference(workspaceId, secondReferenceId); + + // Validate that both DataReferences are enumerated + List enumerateResult = + dataReferenceDao.enumerateDataReferences(workspaceId, 0, 10); + assertThat(enumerateResult.size(), equalTo(2)); + assertThat( + enumerateResult, containsInAnyOrder(equalTo(firstReference), equalTo(secondReference))); + } + + @Test + public void enumerateEmptyReferenceList() { + UUID newWorkspaceId = createDefaultWorkspace(); + + List result = dataReferenceDao.enumerateDataReferences(newWorkspaceId, 0, 10); + assertTrue(result.isEmpty()); + } } /** @@ -189,6 +232,7 @@ private DataReferenceRequest.Builder defaultReferenceRequest(UUID workspaceId) { return DataReferenceRequest.builder() .workspaceId(workspaceId) .name("some_name") + .referenceDescription("some description, too") .cloningInstructions(CloningInstructions.COPY_NOTHING) .referenceType(DataReferenceType.DATA_REPO_SNAPSHOT) .referenceObject(snapshot); diff --git a/src/test/java/bio/terra/workspace/service/datareference/DataReferenceServiceTest.java b/src/test/java/bio/terra/workspace/service/datareference/DataReferenceServiceTest.java index 78b4e6ba08..977cd0c636 100644 --- a/src/test/java/bio/terra/workspace/service/datareference/DataReferenceServiceTest.java +++ b/src/test/java/bio/terra/workspace/service/datareference/DataReferenceServiceTest.java @@ -11,6 +11,8 @@ import bio.terra.workspace.common.BaseUnitTest; import bio.terra.workspace.common.exception.DataReferenceNotFoundException; import bio.terra.workspace.common.exception.SamUnauthorizedException; +import bio.terra.workspace.db.exception.InvalidDaoRequestException; +import bio.terra.workspace.generated.model.UpdateDataReferenceRequestBody; import bio.terra.workspace.service.datareference.model.CloningInstructions; import bio.terra.workspace.service.datareference.model.DataReference; import bio.terra.workspace.service.datareference.model.DataReferenceRequest; @@ -25,7 +27,10 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; @@ -46,115 +51,160 @@ public class DataReferenceServiceTest extends BaseUnitTest { .email("fake@email.com") .subjectId("fakeID123"); + UUID workspaceId; + DataReferenceRequest request; + DataReference reference; + + Supplier currentReference = + () -> + dataReferenceService.getDataReference(workspaceId, reference.referenceId(), USER_REQUEST); + @BeforeEach public void setup() { doReturn(true).when(mockDataRepoService).snapshotExists(any(), any(), any()); - } - - @Test - public void testCreateDataReference() { - UUID workspaceId = createDefaultWorkspace(); - DataReferenceRequest request = defaultReferenceRequest(workspaceId).build(); - DataReference ref = dataReferenceService.createDataReference(request, USER_REQUEST); - assertThat(ref.workspaceId(), equalTo(workspaceId)); - assertThat(ref.name(), equalTo(request.name())); + workspaceId = createDefaultWorkspace(); + request = defaultReferenceRequest(workspaceId).build(); + reference = dataReferenceService.createDataReference(request, USER_REQUEST); } @Test - public void testGetDataReference() { - UUID workspaceId = createDefaultWorkspace(); - DataReferenceRequest request = defaultReferenceRequest(workspaceId).build(); - UUID referenceId = - dataReferenceService.createDataReference(request, USER_REQUEST).referenceId(); - DataReference ref = - dataReferenceService.getDataReference(workspaceId, referenceId, USER_REQUEST); - - assertThat(ref.workspaceId(), equalTo(workspaceId)); - assertThat(ref.name(), equalTo(request.name())); - } - - @Test - public void testGetDataReferenceByName() { - UUID workspaceId = createDefaultWorkspace(); - DataReferenceRequest request = defaultReferenceRequest(workspaceId).build(); - UUID referenceId = - dataReferenceService.createDataReference(request, USER_REQUEST).referenceId(); - - DataReference ref = - dataReferenceService.getDataReferenceByName( - workspaceId, request.referenceType(), request.name(), USER_REQUEST); - - assertThat(ref.workspaceId(), equalTo(workspaceId)); - assertThat(ref.name(), equalTo(request.name())); - } - - @Test - public void testGetMissingDataReference() { - UUID workspaceId = createDefaultWorkspace(); - assertThrows( - DataReferenceNotFoundException.class, - () -> dataReferenceService.getDataReference(workspaceId, UUID.randomUUID(), USER_REQUEST)); + public void testCreateDataReference() { + assertThat(reference.workspaceId(), equalTo(workspaceId)); + assertThat(reference.name(), equalTo(request.name())); } - @Test - public void enumerateDataReferences() { - UUID workspaceId = createDefaultWorkspace(); - DataReferenceRequest firstRequest = defaultReferenceRequest(workspaceId).build(); - - DataReference firstReference = - dataReferenceService.createDataReference(firstRequest, USER_REQUEST); - - // Uses a different name because names are unique per reference type, per workspace. - DataReferenceRequest secondRequest = - defaultReferenceRequest(workspaceId).name("different_name").build(); - - DataReference secondReference = - dataReferenceService.createDataReference(secondRequest, USER_REQUEST); - - List result = - dataReferenceService.enumerateDataReferences(workspaceId, 0, 10, USER_REQUEST); - assertThat(result.size(), equalTo(2)); - assertThat(result, containsInAnyOrder(equalTo(firstReference), equalTo(secondReference))); + @Nested + class GetDataReference { + + @Test + public void testGetDataReference() { + DataReference ref = currentReference.get(); + + assertThat(ref.workspaceId(), equalTo(workspaceId)); + assertThat(ref.name(), equalTo(request.name())); + } + + @Test + public void testGetDataReferenceByName() { + DataReference ref = + dataReferenceService.getDataReferenceByName( + workspaceId, request.referenceType(), request.name(), USER_REQUEST); + + assertThat(ref.workspaceId(), equalTo(workspaceId)); + assertThat(ref.name(), equalTo(request.name())); + } + + @Test + public void testGetMissingDataReference() { + assertThrows( + DataReferenceNotFoundException.class, + () -> + dataReferenceService.getDataReference(workspaceId, UUID.randomUUID(), USER_REQUEST)); + } } - @Test - public void enumerateFailsUnauthorized() { - String samMessage = "Fake Sam unauthorized message"; - doThrow(new SamUnauthorizedException(samMessage)) - .when(mockSamService) - .workspaceAuthzOnly(any(), any(), any()); - UUID workspaceId = createDefaultWorkspace(); - assertThrows( - SamUnauthorizedException.class, - () -> dataReferenceService.enumerateDataReferences(workspaceId, 0, 10, USER_REQUEST)); + @Nested + class EnumerateDataReferences { + + @Test + public void enumerateDataReferences() { + // Uses a different name because names are unique per reference type, per workspace. + DataReferenceRequest secondRequest = + defaultReferenceRequest(workspaceId).name("different_name").build(); + + DataReference secondReference = + dataReferenceService.createDataReference(secondRequest, USER_REQUEST); + + List result = + dataReferenceService.enumerateDataReferences(workspaceId, 0, 10, USER_REQUEST); + assertThat(result.size(), equalTo(2)); + assertThat(result, containsInAnyOrder(equalTo(reference), equalTo(secondReference))); + } + + @Test + public void enumerateFailsUnauthorized() { + String samMessage = "Fake Sam unauthorized message"; + doThrow(new SamUnauthorizedException(samMessage)) + .when(mockSamService) + .workspaceAuthzOnly(any(), any(), any()); + + assertThrows( + SamUnauthorizedException.class, + () -> dataReferenceService.enumerateDataReferences(workspaceId, 0, 10, USER_REQUEST)); + } } - @Test - public void testDeleteDataReference() { - UUID workspaceId = createDefaultWorkspace(); - DataReferenceRequest request = defaultReferenceRequest(workspaceId).build(); - - DataReference ref = dataReferenceService.createDataReference(request, USER_REQUEST); - // Validate the reference exists and is readable. - DataReference getReference = - dataReferenceService.getDataReference(workspaceId, ref.referenceId(), USER_REQUEST); - assertThat(getReference, equalTo(ref)); - - dataReferenceService.deleteDataReference(workspaceId, ref.referenceId(), USER_REQUEST); - // Validate that reference is now deleted. - assertThrows( - DataReferenceNotFoundException.class, - () -> dataReferenceService.getDataReference(workspaceId, ref.referenceId(), USER_REQUEST)); + @Nested + class UpdateDataReference { + + Consumer updateReference = + updateBody -> + dataReferenceService.updateDataReference( + workspaceId, reference.referenceId(), updateBody, USER_REQUEST); + + @Test + public void testUpdateName() { + String updatedName = "rename"; + + updateReference.accept(new UpdateDataReferenceRequestBody().name(updatedName)); + assertThat(currentReference.get().name(), equalTo(updatedName)); + assertThat( + currentReference.get().referenceDescription(), equalTo(reference.referenceDescription())); + } + + @Test + void testUpdateDescription() { + String updatedDescription = "updated description"; + + updateReference.accept( + new UpdateDataReferenceRequestBody().referenceDescription(updatedDescription)); + assertThat(currentReference.get().name(), equalTo(reference.name())); + assertThat(currentReference.get().referenceDescription(), equalTo(updatedDescription)); + } + + @Test + void testUpdateAllFields() { + String updatedName2 = "rename_again"; + String updatedDescription2 = "updated description again"; + + updateReference.accept( + new UpdateDataReferenceRequestBody() + .name(updatedName2) + .referenceDescription(updatedDescription2)); + assertThat(currentReference.get().name(), equalTo(updatedName2)); + assertThat(currentReference.get().referenceDescription(), equalTo(updatedDescription2)); + } + + @Test + void updateNothingFails() { + assertThrows( + InvalidDaoRequestException.class, + () -> updateReference.accept(new UpdateDataReferenceRequestBody())); + } } - @Test - public void testDeleteMissingDataReference() { - UUID workspaceId = createDefaultWorkspace(); - assertThrows( - DataReferenceNotFoundException.class, - () -> - dataReferenceService.deleteDataReference(workspaceId, UUID.randomUUID(), USER_REQUEST)); + @Nested + class DeleteDataReference { + + @Test + public void testDeleteDataReference() { + // Validate the reference exists and is readable. + assertThat(currentReference.get(), equalTo(reference)); + + dataReferenceService.deleteDataReference(workspaceId, reference.referenceId(), USER_REQUEST); + // Validate that reference is now deleted. + assertThrows(DataReferenceNotFoundException.class, () -> currentReference.get()); + } + + @Test + public void testDeleteMissingDataReference() { + assertThrows( + DataReferenceNotFoundException.class, + () -> + dataReferenceService.deleteDataReference( + workspaceId, UUID.randomUUID(), USER_REQUEST)); + } } /** @@ -183,6 +233,7 @@ private DataReferenceRequest.Builder defaultReferenceRequest(UUID workspaceId) { return DataReferenceRequest.builder() .workspaceId(workspaceId) .name("some_name") + .referenceDescription("some description, too") .cloningInstructions(CloningInstructions.COPY_NOTHING) .referenceType(DataReferenceType.DATA_REPO_SNAPSHOT) .referenceObject(snapshot);