feat: as a developer I want to edit projects
This commit is contained in:
parent
29cf100a18
commit
7f2dbcacf4
|
@ -98,6 +98,45 @@
|
||||||
"url": null
|
"url": null
|
||||||
},
|
},
|
||||||
"response": []
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updateProject",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "oauth2",
|
||||||
|
"oauth2": [
|
||||||
|
{
|
||||||
|
"key": "addTokenTo",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "PATCH",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"client\": \"updatedProject\",\n \"title\": \"log4j pentest\",\n \"tester\" : \"Stipe\"\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost:8443/projects/f2738715-4005-4aca-8d34-27ce9b8efffe",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "8443",
|
||||||
|
"path": [
|
||||||
|
"projects",
|
||||||
|
"f2738715-4005-4aca-8d34-27ce9b8efffe"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -71,10 +71,28 @@ include::{snippets}/deleteProject/http-response.adoc[]
|
||||||
|
|
||||||
include::{snippets}/deleteProject/response-fields.adoc[]
|
include::{snippets}/deleteProject/response-fields.adoc[]
|
||||||
|
|
||||||
|
=== Update project
|
||||||
|
|
||||||
|
To update a project, call the PATCH request /projects/{projectId}
|
||||||
|
|
||||||
|
==== Request example
|
||||||
|
|
||||||
|
include::{snippets}/updateProject/http-request.adoc[]
|
||||||
|
|
||||||
|
==== Response example
|
||||||
|
|
||||||
|
include::{snippets}/updateProject/http-response.adoc[]
|
||||||
|
|
||||||
|
==== Response structure
|
||||||
|
|
||||||
|
include::{snippets}/updateProject/response-fields.adoc[]
|
||||||
|
|
||||||
== Change History
|
== Change History
|
||||||
|
|
||||||
|===
|
|===
|
||||||
|Date |Change
|
|Date |Change
|
||||||
|
|2022-03-07
|
||||||
|
|Added PATCH endpoint to update Projects
|
||||||
|2022-02-01
|
|2022-02-01
|
||||||
|Added DELETE endpoint to save Projects
|
|Added DELETE endpoint to save Projects
|
||||||
|2021-12-22
|
|2021-12-22
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CO
|
||||||
import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
|
import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
|
||||||
import org.springframework.data.annotation.Id
|
import org.springframework.data.annotation.Id
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
@SuppressFBWarnings(
|
@SuppressFBWarnings(
|
||||||
NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR,
|
NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR,
|
||||||
|
@ -16,4 +17,11 @@ abstract class BaseEntity<T>(
|
||||||
) {
|
) {
|
||||||
@Id
|
@Id
|
||||||
lateinit var id: String
|
lateinit var id: String
|
||||||
|
|
||||||
|
var lastModified: Instant = Instant.now()
|
||||||
|
|
||||||
|
fun setLastModifiedToCurrentInstant() {
|
||||||
|
this.lastModified = Instant.now()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,10 +50,6 @@ data class ProjectRequestBody(
|
||||||
val tester: String? = null
|
val tester: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ProjectDeleteRequestBody(
|
|
||||||
val id: String
|
|
||||||
)
|
|
||||||
|
|
||||||
fun ProjectRequestBody.toProject(): Project {
|
fun ProjectRequestBody.toProject(): Project {
|
||||||
return Project(
|
return Project(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
|
@ -65,5 +61,4 @@ fun ProjectRequestBody.toProject(): Project {
|
||||||
createdBy = UUID.randomUUID().toString()
|
createdBy = UUID.randomUUID().toString()
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import java.util.*
|
||||||
origins = [],
|
origins = [],
|
||||||
allowCredentials = "false",
|
allowCredentials = "false",
|
||||||
allowedHeaders = ["*"],
|
allowedHeaders = ["*"],
|
||||||
methods = [RequestMethod.GET, RequestMethod.DELETE, RequestMethod.POST]
|
methods = [RequestMethod.GET, RequestMethod.DELETE, RequestMethod.POST, RequestMethod.PATCH]
|
||||||
)
|
)
|
||||||
|
|
||||||
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION)
|
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION)
|
||||||
|
@ -49,4 +49,14 @@ class ProjectController(private val projectService: ProjectService) {
|
||||||
ResponseEntity.ok().body(it.toProjectDeleteResponseBody())
|
ResponseEntity.ok().body(it.toProjectDeleteResponseBody())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/{id}")
|
||||||
|
fun updateProject(
|
||||||
|
@PathVariable(value = "id") id: String,
|
||||||
|
@RequestBody body: ProjectRequestBody
|
||||||
|
): Mono<ResponseEntity<ResponseBody>> {
|
||||||
|
return this.projectService.updateProject(id, body).map {
|
||||||
|
ResponseEntity.accepted().body(it.toProjectResponseBody())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,4 +14,6 @@ interface ProjectRepository: ReactiveMongoRepository<ProjectEntity, String> {
|
||||||
|
|
||||||
@DeleteQuery("{'data._id' : ?0}")
|
@DeleteQuery("{'data._id' : ?0}")
|
||||||
fun deleteProjectById(id: String): Mono<Long>
|
fun deleteProjectById(id: String): Mono<Long>
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,6 +7,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
import reactor.kotlin.core.publisher.switchIfEmpty
|
import reactor.kotlin.core.publisher.switchIfEmpty
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -45,4 +47,30 @@ class ProjectService(private val projectRepository: ProjectRepository) {
|
||||||
projectRepository.deleteProjectById(id).map{project}
|
projectRepository.deleteProjectById(id).map{project}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateProject(id: String, body: ProjectRequestBody): Mono<Project> {
|
||||||
|
return projectRepository.findProjectById(id).switchIfEmpty{
|
||||||
|
logger.info("Project with id $id not found. Updating not possible.")
|
||||||
|
Mono.empty()
|
||||||
|
}.flatMap{projectEntity: ProjectEntity ->
|
||||||
|
projectEntity.lastModified = Instant.now()
|
||||||
|
projectEntity.data = buildProject(body, projectEntity)
|
||||||
|
projectRepository.save(projectEntity).map{
|
||||||
|
it.toProject()
|
||||||
|
}.doOnError {
|
||||||
|
logger.warn("Project could not be updated in Database. Thrown exception: ", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildProject(body: ProjectRequestBody, projectEntity: ProjectEntity): Project{
|
||||||
|
return Project(
|
||||||
|
id = projectEntity.data.id,
|
||||||
|
client = body.client,
|
||||||
|
title = body.title,
|
||||||
|
createdAt = projectEntity.data.createdAt,
|
||||||
|
tester = body.tester,
|
||||||
|
createdBy = projectEntity.data.createdBy
|
||||||
|
)
|
||||||
|
}
|
|
@ -189,6 +189,56 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class UpdateProject {
|
||||||
|
@Test
|
||||||
|
fun updateProject() {
|
||||||
|
webTestClient.patch().uri("/projects/${projectUpdate.id}")
|
||||||
|
.header("Authorization", "Bearer $tokenAdmin")
|
||||||
|
.body(Mono.just(projectUpdate), ProjectRequestBody::class.java)
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isAccepted
|
||||||
|
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
||||||
|
.expectBody().json(Json.write(projectUpdate))
|
||||||
|
.consumeWith(
|
||||||
|
WebTestClientRestDocumentation.document(
|
||||||
|
"{methodName}",
|
||||||
|
Preprocessors.preprocessRequest(
|
||||||
|
Preprocessors.prettyPrint(),
|
||||||
|
Preprocessors.modifyUris().removePort(),
|
||||||
|
Preprocessors.removeHeaders("Host", "Content-Length")
|
||||||
|
),
|
||||||
|
Preprocessors.preprocessResponse(
|
||||||
|
Preprocessors.prettyPrint()
|
||||||
|
),
|
||||||
|
PayloadDocumentation.relaxedResponseFields(
|
||||||
|
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
|
||||||
|
.description("The id of the updated project"),
|
||||||
|
PayloadDocumentation.fieldWithPath("client").type(JsonFieldType.STRING)
|
||||||
|
.description("The updated name of the client of the project"),
|
||||||
|
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
|
||||||
|
.description("The updated title of the project"),
|
||||||
|
PayloadDocumentation.fieldWithPath("createdAt").type(JsonFieldType.STRING)
|
||||||
|
.description("The date where the project was created at"),
|
||||||
|
PayloadDocumentation.fieldWithPath("tester").type(JsonFieldType.STRING)
|
||||||
|
.description("The updated user that is assigned as a tester in the project"),
|
||||||
|
PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING)
|
||||||
|
.description("The id of the user that created the project")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val projectUpdate = Project(
|
||||||
|
id = "4f6567a8-76fd-487b-8602-f82d0ca4d1f9",
|
||||||
|
client = "Novatec_updated",
|
||||||
|
title = "log4j Pentest_updated",
|
||||||
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
|
tester = "Stipe_updated",
|
||||||
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun persistBasicTestScenario() {
|
private fun persistBasicTestScenario() {
|
||||||
// setup test data
|
// setup test data
|
||||||
val projectOne = Project(
|
val projectOne = Project(
|
||||||
|
|
|
@ -127,15 +127,7 @@ class ProjectControllerIntTest : BaseIntTest() {
|
||||||
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
||||||
.expectBody().json(Json.write(projectTwo.toProjectDeleteResponseBody()))
|
.expectBody().json(Json.write(projectTwo.toProjectDeleteResponseBody()))
|
||||||
}
|
}
|
||||||
/*@Test
|
|
||||||
fun `delete project by non-existing id`() {
|
|
||||||
webTestClient.delete().uri("/projects/{id}", "98754a47-796b-4b3f-abf9-c46c668596c5")
|
|
||||||
.header("Authorization", "Bearer $tokenAdmin")
|
|
||||||
.exchange()
|
|
||||||
.expectStatus().isNoContent
|
|
||||||
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
|
||||||
.expectBody().isEmpty
|
|
||||||
}*/
|
|
||||||
val projectTwo = Project(
|
val projectTwo = Project(
|
||||||
id = "61360a47-796b-4b3f-abf9-c46c668596c5",
|
id = "61360a47-796b-4b3f-abf9-c46c668596c5",
|
||||||
client = "Allsafe",
|
client = "Allsafe",
|
||||||
|
@ -146,6 +138,32 @@ class ProjectControllerIntTest : BaseIntTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class UpdateProject {
|
||||||
|
@Test
|
||||||
|
fun `updated project successfully`() {
|
||||||
|
webTestClient.patch().uri("/projects/{id}", projectUpdate.id)
|
||||||
|
.header("Authorization", "Bearer $tokenAdmin")
|
||||||
|
.body(Mono.just(projectUpdate), ProjectRequestBody::class.java)
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isAccepted
|
||||||
|
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
||||||
|
.expectBody()
|
||||||
|
.jsonPath("$.client").isEqualTo("Novatec_updated")
|
||||||
|
.jsonPath("$.title").isEqualTo("log4j Pentest_updated")
|
||||||
|
.jsonPath("$.tester").isEqualTo("Stipe_updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
val projectUpdate = Project(
|
||||||
|
id = "4f6567a8-76fd-487b-8602-f82d0ca4d1f9",
|
||||||
|
client = "Novatec_updated",
|
||||||
|
title = "log4j Pentest_updated",
|
||||||
|
createdAt = "2021-04-10T18:05:00Z",
|
||||||
|
tester = "Stipe_updated",
|
||||||
|
createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun persistBasicTestScenario() {
|
private fun persistBasicTestScenario() {
|
||||||
// setup test data
|
// setup test data
|
||||||
val projectOne = Project(
|
val projectOne = Project(
|
||||||
|
|
Loading…
Reference in New Issue