Docu for target type endpoints in Target API (#1185)

* add assign/unassign type endpoints to target api docs

Signed-off-by: Natalia Kislicyn <natalia.kislicyn@bosch.io>

* fix target type creation for api rest docs test

Signed-off-by: Natalia Kislicyn <natalia.kislicyn@bosch.io>

* fix review comments: rename tests

Signed-off-by: Natalia Kislicyn <natalia.kislicyn@bosch.io>

* adapt test to avoid failing when translated

Signed-off-by: Natalia Kislicyn <natalia.kislicyn@bosch.io>
This commit is contained in:
Natalia Kislicyn
2021-10-15 16:22:20 +02:00
committed by GitHub
parent e5dbbbbc7b
commit 745a0c6a10
8 changed files with 246 additions and 8 deletions

View File

@@ -632,7 +632,7 @@ public interface TargetManagement {
* if TargetType with given target ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
Target assignType(@NotEmpty String controllerID, @NotNull long targetTypeId);
Target assignType(@NotEmpty String controllerID, @NotNull Long targetTypeId);
/**
* updates the {@link Target}.

View File

@@ -607,7 +607,7 @@ public class JpaTargetManagement implements TargetManagement {
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Target assignType(final String controllerID, final long targetTypeId) {
public Target assignType(final String controllerID, final Long targetTypeId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerID);
final JpaTargetType targetType = getTargetTypeByIdAndThrowIfNotFound(targetTypeId);
target.setTargetType(targetType);

View File

@@ -42,6 +42,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetTagCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidTargetAddressException;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
@@ -1137,6 +1138,58 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
return target;
}
@Test
@WithUser(allSpPermissions = true)
@Description("Checks that target type is not assigned to target if invalid.")
public void assignInvalidTargetTypeToTarget() {
// create a target
final Target target = testdataFactory.createTarget("target1", "testtarget");
// initial opt lock revision must be one
Optional<JpaTarget> targetFound = targetRepository.findById(target.getId());
assertThat(targetFound).isPresent();
assertThat(targetFound.get().getOptLockRevision()).isEqualTo(1);
assertThat(targetFound.get().getTargetType()).isNull();
// assign target type to target
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetManagement.assignType(targetFound.get().getControllerId(), null))
.as("target type with id=null cannot be assigned");
assertThatExceptionOfType(EntityNotFoundException.class)
.isThrownBy(() -> targetManagement.assignType(targetFound.get().getControllerId(), 114L))
.as("target type with id that does not exists cannot be assigned");
// opt lock revision is not changed
Optional<JpaTarget> targetFound1 = targetRepository.findById(target.getId());
assertThat(targetFound1).isPresent();
assertThat(targetFound1.get().getOptLockRevision()).isEqualTo(1);
}
@Test
@WithUser(allSpPermissions = true)
@Description("Checks that target type can be unassigned from target.")
public void unAssignTargetTypeFromTarget() {
// create a target type
TargetType targetType = testdataFactory.findOrCreateTargetType("targettype");
assertThat(targetType).isNotNull();
// create a target
final Target target = testdataFactory.createTarget("target1", "testtarget", targetType.getId());
// initial opt lock revision must be one
Optional<JpaTarget> targetFound = targetRepository.findById(target.getId());
assertThat(targetFound).isPresent();
assertThat(targetFound.get().getOptLockRevision()).isEqualTo(1);
assertThat(targetFound.get().getTargetType().getName()).isEqualTo(targetType.getName());
// un-assign target type from target
targetManagement.unAssignType(targetFound.get().getControllerId());
// opt lock revision must be changed
Optional<JpaTarget> targetFound1 = targetRepository.findById(target.getId());
assertThat(targetFound1).isPresent();
assertThat(targetFound1.get().getOptLockRevision()).isEqualTo(2);
assertThat(targetFound1.get().getTargetType()).isNull();
}
@Test
@Description("Test that RSQL filter finds targets with metadata and/or controllerId.")
public void findTargetsByRsqlWithMetadata() {

View File

@@ -2246,6 +2246,16 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + targetControllerId + "/targettype")
.content("{\"id\":" + invalidTargetTypeId + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// verify response json exception message if body does not include id field
final MvcResult mvcResult = mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + targetControllerId + "/targettype")
.content("{\"unknownfield\":" + invalidTargetTypeId + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()).andReturn();
final ExceptionInfo exceptionInfo = ResourceUtility
.convertException(mvcResult.getResponse().getContentAsString());
assertThat(exceptionInfo.getExceptionClass()).isEqualTo(ConstraintViolationException.class.getName());
assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_REPO_CONSTRAINT_VIOLATION.getKey());
assertThat(exceptionInfo.getMessage()).contains("targetTypeId");
}
@Test
@@ -2268,4 +2278,41 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
assertThat(targetManagement.getByControllerID(targetControllerId).get().getTargetType()).isNull();
}
@Test
public void invalidRequestsOnTargetTypeResource() throws Exception {
final String knownTargetId = "targetId";
final Target target = testdataFactory.createTarget(knownTargetId);
final TargetType targettype = testdataFactory.createTargetType("targettype", Collections.emptyList());
// GET is not allowed
mvc.perform(get(
MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING,
knownTargetId)).andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
// PUT is not allowed
mvc.perform(put(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING
+ MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING, knownTargetId))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
// POST does not exist with path parameter targettype
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING
+ MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING + "/123", knownTargetId))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// DELETE does not exist with path parameter targettype
mvc.perform(delete(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING
+ MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING + "/123", knownTargetId))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// Invalid content
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING,
knownTargetId)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isUnsupportedMediaType());
// Bad request if id field is missing
mvc.perform(post(
MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING,
knownTargetId).content("{\"unknownfield\":123}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
}
}

View File

@@ -976,6 +976,86 @@ include::../errors/415.adoc[]
include::../errors/429.adoc[]
|===
== POST /rest/v1/targets/{targetId}/targettype
=== Implementation Notes
Assign or update the target type of a target. Required permission: UPDATE_TARGET
=== Assign a target type to a target
==== Curl
include::{snippets}/targets/post-assign-target-type/curl-request.adoc[]
==== Request URL
include::{snippets}/targets/post-assign-target-type/http-request.adoc[]
==== Request path parameter
include::{snippets}/targets/post-assign-target-type/path-parameters.adoc[]
==== Request fields
include::{snippets}/targets/post-assign-target-type/request-fields.adoc[]
=== Response (Status 200)
==== Response example
include::{snippets}/targets/post-assign-target-type/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
include::../errors/405.adoc[]
include::../errors/429.adoc[]
|===
== DELETE /rest/v1/targets/{targetId}/targettype
=== Implementation Notes
Remove the target type from a target. The target type will be set to null. Required permission: UPDATE_TARGET
=== Remove a target type from a target
==== Curl
include::{snippets}/targets/delete-unassign-target-type/curl-request.adoc[]
==== Request URL
include::{snippets}/targets/delete-unassign-target-type/http-request.adoc[]
==== Request path parameter
include::{snippets}/targets/delete-unassign-target-type/path-parameters.adoc[]
=== Response (Status 200)
==== Response example
include::{snippets}/targets/delete-unassign-target-type/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
include::../errors/405.adoc[]
include::../errors/429.adoc[]
|===
== Additional content
[[error-body]]

View File

@@ -36,6 +36,7 @@ import org.eclipse.hawkbit.repository.model.DeploymentRequestBuilder;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.TestConfiguration;
import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest;
@@ -179,9 +180,10 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
final String maintenanceWindowDuration, final String maintenanceWindowTimeZone,
final boolean createRollout) {
final TargetType targetType = testdataFactory.findOrCreateTargetType("defaultType");
final Target savedTarget = targetManagement.create(entityFactory.target().create().controllerId(name)
.status(TargetUpdateStatus.UNKNOWN).address("http://192.168.0.1").description("My name is " + name)
.lastTargetQuery(System.currentTimeMillis()));
.targetType(targetType.getId()).lastTargetQuery(System.currentTimeMillis()));
final List<Target> updatedTargets;
if (createRollout) {
@@ -280,6 +282,8 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
.type("String"),
fieldWithPath(fieldArrayPrefix + "lastControllerRequestAt")
.description(MgmtApiModelProperties.LAST_REQUEST_AT).type("Number"),
fieldWithPath(fieldArrayPrefix + "targetType")
.description(MgmtApiModelProperties.TARGETTYPE_ID).type("Number"),
fieldWithPath(fieldArrayPrefix + "_links.self").ignored());
if (!isArray) {
@@ -299,7 +303,9 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
.description(MgmtApiModelProperties.LINKS_ATTRIBUTES),
fieldWithPath(fieldArrayPrefix + "_links.actions")
.description(MgmtApiModelProperties.LINKS_ACTIONS),
fieldWithPath(fieldArrayPrefix + "_links.metadata").description(MgmtApiModelProperties.META_DATA)));
fieldWithPath(fieldArrayPrefix + "_links.metadata").description(MgmtApiModelProperties.META_DATA),
fieldWithPath(fieldArrayPrefix + "_links.targetType")
.description(MgmtApiModelProperties.LINK_TO_TARGET_TYPE)));
}
fields.addAll(Arrays.asList(descriptors));

View File

@@ -32,6 +32,7 @@ public final class MgmtApiModelProperties {
public static final String LINK_TO_MANDATORY_SMT = "Link to mandatory software modules types in this distribution set type.";
public static final String LINK_TO_OPTIONAL_SMT = "Link to optional software modules types in this distribution set type.";
public static final String LINK_TO_ROLLOUT = "The link to the rollout.";
public static final String LINK_TO_TARGET_TYPE = "The link to the target type.";
// software module types
public static final String SMT_TYPE = "The type of the software module identified by its key.";
@@ -81,6 +82,7 @@ public final class MgmtApiModelProperties {
public static final String POLL_OVERDUE = "Defines if the target poll time is overdue based on the next expected poll time plus the configured overdue poll time threshold.";
// Target type
public static final String TARGETTYPE_ID = "ID of the target type";
public static final String COMPATIBLE_DS_TYPES = "Array of distribution set types that are compatible to that target type";
public static final String LINK_COMPATIBLE_DS_TYPES = "Link to the compatible distribution set types in this target type";

View File

@@ -24,6 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -35,7 +36,9 @@ import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.rest.documentation.AbstractApiRestDocumentation;
import org.eclipse.hawkbit.rest.documentation.ApiModelPropertiesGeneric;
import org.eclipse.hawkbit.rest.documentation.MgmtApiModelProperties;
@@ -104,6 +107,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
.type("String"),
fieldWithPath("content[].lastControllerRequestAt")
.description(MgmtApiModelProperties.LAST_REQUEST_AT).type("Number"),
fieldWithPath("content[].targetType")
.description(MgmtApiModelProperties.TARGETTYPE_ID).type("Number"),
fieldWithPath("content[]._links.self").ignored())));
}
@@ -118,7 +123,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
@Test
@Description("Handles the POST request of creating new targets within SP. The request body must always be a list of targets. Required Permission: CREATE_TARGET.")
public void postTargets() throws Exception {
final String target = createTargetJsonForPostRequest("123456", "controllerId", "test");
final TargetType defaultType = testdataFactory.createTargetType("defaultType", Collections.emptyList());
final String target = createTargetJsonForPostRequest("123456", "controllerId", "test", defaultType);
mockMvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING)
.contentType(MediaType.APPLICATION_JSON).content(target)).andExpect(status().isCreated())
@@ -129,9 +135,11 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
optionalRequestFieldWithPath("[]description").description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("[]address").description(MgmtApiModelProperties.ADDRESS),
optionalRequestFieldWithPath("[]securityToken")
.description(MgmtApiModelProperties.SECURITY_TOKEN)),
.description(MgmtApiModelProperties.SECURITY_TOKEN),
optionalRequestFieldWithPath("[]targetType").description(MgmtApiModelProperties.TARGETTYPE_ID)),
responseFields(fieldWithPath("[]controllerId").description(ApiModelPropertiesGeneric.ITEM_ID),
fieldWithPath("[]name").description(ApiModelPropertiesGeneric.NAME),
fieldWithPath(
"[]name").description(ApiModelPropertiesGeneric.NAME),
fieldWithPath("[]description").description(ApiModelPropertiesGeneric.DESCRPTION),
fieldWithPath("[]address").description(MgmtApiModelProperties.ADDRESS),
fieldWithPath("[]createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
@@ -148,6 +156,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
fieldWithPath("[]securityToken").description(MgmtApiModelProperties.SECURITY_TOKEN),
fieldWithPath("[]requestAttributes")
.description(MgmtApiModelProperties.REQUEST_ATTRIBUTES),
fieldWithPath("[]targetType")
.description(MgmtApiModelProperties.TARGETTYPE_ID),
fieldWithPath("[]_links.self").ignored())));
}
@@ -801,14 +811,54 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
.description(MgmtApiModelProperties.META_DATA_VALUE))));
}
@Test
@Description("Update the target type of a target." + " Required Permission: " + SpPermission.UPDATE_TARGET)
public void postAssignTargetType() throws Exception {
final Target testTarget = testdataFactory.createTarget(targetId);
final DistributionSetType distributionSetTypeA = testdataFactory.findOrCreateDistributionSetType("jar", "jar");
final DistributionSetType distributionSetTypeB = testdataFactory.findOrCreateDistributionSetType("zip", "zip");
final TargetType targetType = testdataFactory.createTargetType("deviceType-A",
Arrays.asList(distributionSetTypeA, distributionSetTypeB));
final JSONObject jsonObject = new JSONObject();
jsonObject.put("id", targetType.getId());
mockMvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/targettype",
testTarget.getControllerId(), targetType.getId()).content(String.valueOf(jsonObject))
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andDo(this.document.document(
pathParameters(parameterWithName("targetId").description(ApiModelPropertiesGeneric.ITEM_ID)),
requestFields(requestFieldWithPath("id").description(MgmtApiModelProperties.TARGETTYPE_ID))));
}
@Test
@Description("Reset the target type of a target." + " Required Permission: " + SpPermission.UPDATE_TARGET)
public void deleteUnassignTargetType() throws Exception {
final Target testTarget = testdataFactory.createTarget(targetId);
final DistributionSetType distributionSetTypeA = testdataFactory.findOrCreateDistributionSetType("jar", "jar");
final DistributionSetType distributionSetTypeB = testdataFactory.findOrCreateDistributionSetType("zip", "zip");
final TargetType targetType = testdataFactory.createTargetType("deviceType-A",
Arrays.asList(distributionSetTypeA, distributionSetTypeB));
targetManagement.assignType(testTarget.getControllerId(), targetType.getId());
mockMvc.perform(delete(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/targettype",
testTarget.getControllerId()).contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andDo(this.document.document(
pathParameters(parameterWithName("targetId").description(ApiModelPropertiesGeneric.ITEM_ID))
));
}
private String createTargetJsonForPostRequest(final String controllerId, final String name,
final String description) throws JsonProcessingException {
final String description, final TargetType targetType) throws JsonProcessingException {
final Map<String, Object> target = new HashMap<>();
target.put("controllerId", controllerId);
target.put("description", description);
target.put("name", name);
target.put("address", "https://192.168.0.1");
target.put("securityToken", "2345678DGGDGFTDzztgf");
target.put("targetType", targetType.getId());
return "[" + this.objectMapper.writeValueAsString(target) + "]";
}