Skip to content

Commit

Permalink
DSP-804: create a child node with a custom IRI (#1741)
Browse files Browse the repository at this point in the history
* feature (customNodeIri) create a child node with a custom IRI

* refactor (customChildNodeIRI) fix typo

* test (createChildNodeCustomIRI) test data

* feat (customChildNodeIRI) add test data automatically to expected-client-test-data list
  • Loading branch information
SepidehAlassi committed Oct 28, 2020
1 parent 23395fc commit 5354bbb
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 15 deletions.
19 changes: 16 additions & 3 deletions docs/03-apis/api-admin/lists.md
Expand Up @@ -27,7 +27,7 @@ License along with Knora. If not, see <http://www.gnu.org/licenses/>.
- `GET: /admin/lists/<listIri>` : return complete list with children
- `POST: /admin/lists` : create new list
- `PUT: /admin/lists/<listIri>` : update list information
- `POST: /admin/lists/<nodeIri>` : create new child node under the supplied parent node IRI
- `POST: /admin/lists/<parentNodeIri>` : create new child node under the supplied parent node IRI
- NOT IMPLEMENTED: `DELETE: /admin/lists/<listIri>` : delete list including children if not used
- `GET: /admin/lists/infos/<listIri>` : return list information (without children)

Expand Down Expand Up @@ -83,19 +83,32 @@ Additionally, each list can have an optional custom IRI (of [Knora IRI](../api-v
- Appends a new child node under the supplied nodeIri. If the supplied nodeIri
is the listIri, then a new child node is appended to the top level. Children
are currently only appended.
- POST: `/admin/lists/<nodeIri>`
- POST: `/admin/lists/<parentNodeIri>`
- BODY:

```json
{
"parentNodeIri": "nodeIri",
"parentNodeIri": "parentNodeIri",
"projectIri": "someprojectiri",
"name": "first",
"labels": [{ "value": "New First Child List Node Value", "language": "en"}],
"comments": [{ "value": "New First Child List Node Comment", "language": "en"}]
}
```

Additionally, each child node can have an optional custom IRI (of [Knora IRI](../api-v2/knora-iris.md#iris-for-data) form) specified by the `id` in the request body as below:

```json
{
"id": "http://rdfh.ch/lists/0001/a-child-node-with-IRI",
"parentNodeIri": "http://rdfh.ch/lists/0001/a-list-with-IRI",
"projectIri": "http://rdfh.ch/projects/0001",
"name": "child node with a custom IRI",
"labels": [{ "value": "New child node with IRI", "language": "en"}],
"comments": [{ "value": "New child node comment", "language": "en"}]
}
```

### Get list's information

- Required permission: none
Expand Down
2 changes: 2 additions & 0 deletions webapi/scripts/expected-client-test-data.txt
Expand Up @@ -18,6 +18,8 @@ test-data/admin/lists/add-second-child-to-root-request.json
test-data/admin/lists/add-second-child-to-root-response.json
test-data/admin/lists/create-child-node-request.json
test-data/admin/lists/create-child-node-response.json
test-data/admin/lists/create-child-node-with-custom-IRI-request.json
test-data/admin/lists/create-child-node-with-custom-IRI-response.json
test-data/admin/lists/create-list-request.json
test-data/admin/lists/create-list-response.json
test-data/admin/lists/create-list-with-custom-IRI-request.json
Expand Down
Expand Up @@ -79,17 +79,22 @@ case class CreateListApiRequestADM(id: Option[IRI] = None,
* is added can be either a root list node or a child list node. At least one label needs to be supplied. If other
* child nodes exist, the newly created list node will be appended to the end.
*
* @param parentNodeIri
* @param labels
* @param comments
* @param id the optional custom IRI of the list node.
* @param parentNodeIri the IRI of the parent node.
* @param projectIri the IRI of the project.
* @param name the optional name of the list node.
* @param labels labels of the list node.
* @param comments comments of the list node.
*/
case class CreateChildNodeApiRequestADM(parentNodeIri: IRI,
case class CreateChildNodeApiRequestADM(id: Option[IRI] = None,
parentNodeIri: IRI,
projectIri: IRI,
name: Option[String],
labels: Seq[StringLiteralV2],
comments: Seq[StringLiteralV2]) extends ListADMJsonProtocol {

private val stringFormatter = StringFormatter.getInstanceForConstantOntologies
stringFormatter.validateOptionalListIri(id, throw BadRequestException(s"Invalid list node IRI"))

if (parentNodeIri.isEmpty) {
// println(this)
Expand Down Expand Up @@ -967,7 +972,7 @@ trait ListADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol with


implicit val createListApiRequestADMFormat: RootJsonFormat[CreateListApiRequestADM] = jsonFormat(CreateListApiRequestADM, "id", "projectIri", "name", "labels", "comments")
implicit val createListNodeApiRequestADMFormat: RootJsonFormat[CreateChildNodeApiRequestADM] = jsonFormat(CreateChildNodeApiRequestADM, "parentNodeIri", "projectIri", "name", "labels", "comments")
implicit val createListNodeApiRequestADMFormat: RootJsonFormat[CreateChildNodeApiRequestADM] = jsonFormat(CreateChildNodeApiRequestADM, "id" , "parentNodeIri", "projectIri", "name", "labels", "comments")
implicit val changeListInfoApiRequestADMFormat: RootJsonFormat[ChangeListInfoApiRequestADM] = jsonFormat(ChangeListInfoApiRequestADM, "listIri", "projectIri", "name", "labels", "comments")
implicit val nodePathGetResponseADMFormat: RootJsonFormat[NodePathGetResponseADM] = jsonFormat(NodePathGetResponseADM, "elements")
implicit val listsGetResponseADMFormat: RootJsonFormat[ListsGetResponseADM] = jsonFormat(ListsGetResponseADM, "lists")
Expand Down
Expand Up @@ -617,13 +617,12 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde
throw BadRequestException(s"The node name ${createListRequest.name.get} is already used by a list inside the project ${createListRequest.projectIri}.")
}

maybeShortcode = project.shortcode
dataNamedGraph = stringFormatter.projectDataNamedGraphV2(project)

// check the custom IRI; if not given, create an unused IRI
customListIri: Option[SmartIri] = createListRequest.id.map(iri => iri.toSmartIri)
maybeShortcode = project.shortcode
listIri: IRI <- checkOrCreateEntityIri(customListIri, stringFormatter.makeRandomListIri(maybeShortcode))

dataNamedGraph = stringFormatter.projectDataNamedGraphV2(project)
// Create the new list
createNewListSparqlString = org.knora.webapi.messages.twirl.queries.sparql.admin.txt.createNewList(
dataNamedGraph = dataNamedGraph,
Expand Down Expand Up @@ -826,9 +825,10 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde
// calculate the data named graph
dataNamedGraph = stringFormatter.projectDataNamedGraphV2(project)

// calculate the new node's IRI
// check the custom IRI; if not given, create an unused IRI
customListIri: Option[SmartIri] = createChildNodeRequest.id.map(iri => iri.toSmartIri)
maybeShortcode = project.shortcode
newListNodeIri = stringFormatter.makeRandomListIri(maybeShortcode)
newListNodeIri: IRI <- checkOrCreateEntityIri(customListIri, stringFormatter.makeRandomListIri(maybeShortcode))

// Create the new list node
createNewListSparqlString = org.knora.webapi.messages.twirl.queries.sparql.admin.txt.createNewListChildNode(
Expand Down
Expand Up @@ -319,6 +319,59 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr
val invalidIri: Boolean = errorMessage.contains(s"IRI: '${SharedTestDataADM.customListIRI}' already exists, try another one.")
invalidIri should be(true)
}

"add a child with a custom IRI" in {

val customChildNodeIRI = "http://rdfh.ch/lists/0001/a-child-node-with-IRI"

val createChildNodeWithCustomIriRequest =
s"""
|{ "id": "$customChildNodeIRI",
| "parentNodeIri": "${SharedTestDataADM.customListIRI}",
| "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}",
| "name": "node with a custom IRI",
| "labels": [{ "value": "New List Node", "language": "en"}],
| "comments": []
|}""".stripMargin

clientTestDataCollector.addFile(
TestDataFileContent(
filePath = TestDataFilePath(
directoryPath = clientTestDataPath,
filename = "create-child-node-with-custom-IRI-request",
fileExtension = "json"
),
text = createChildNodeWithCustomIriRequest
)
)

val encodedParentNodeUrl = java.net.URLEncoder.encode(SharedTestDataADM.customListIRI, "utf-8")

val request = Post(baseApiUrl + s"/admin/lists/" + encodedParentNodeUrl, HttpEntity(ContentTypes.`application/json`, createChildNodeWithCustomIriRequest)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials)
val response: HttpResponse = singleAwaitingRequest(request)
// println(s"response: ${response.toString}")
response.status should be(StatusCodes.OK)

val received: ListNodeInfoADM = AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListNodeInfoADM]

// check correct node info
val childNodeInfo = received match {
case info: ListChildNodeInfoADM => info
case something => fail(s"expecting ListChildNodeInfoADM but got ${something.getClass.toString} instead.")
}
childNodeInfo.id should be (customChildNodeIRI)

clientTestDataCollector.addFile(
TestDataFileContent(
filePath = TestDataFilePath(
directoryPath = clientTestDataPath,
filename = "create-child-node-with-custom-IRI-response",
fileExtension = "json"
),
text = responseToString(response)
)
)
}
}

"used to modify list information" should {
Expand Down
Expand Up @@ -262,7 +262,7 @@ class ListsMessagesADMSpec extends AnyWordSpecLike with Matchers with ListADMJso
thrown.getMessage should equal (UPDATE_REQUEST_EMPTY_LABEL_OR_COMMENT_ERROR)
}

"throw 'BadRequestException' for `CreateChildNodeApiRequestADM` when list node iri is empty" in {
"throw 'BadRequestException' for `CreateChildNodeApiRequestADM` when no parent node iri is given" in {

val payload =
s"""
Expand All @@ -280,7 +280,7 @@ class ListsMessagesADMSpec extends AnyWordSpecLike with Matchers with ListADMJso

}

"throw 'BadRequestException' for `CreateChildNodeApiRequestADM` when list node iri is invalid" in {
"throw 'BadRequestException' for `CreateChildNodeApiRequestADM` when parent node iri is invalid" in {

val payload =
s"""
Expand Down Expand Up @@ -352,5 +352,23 @@ class ListsMessagesADMSpec extends AnyWordSpecLike with Matchers with ListADMJso

}

"throw 'BadRequestException' for `CreateChildNodeApiRequestADM` when custom iri of the child node is invalid" in {

val payload =
s"""
|{ "id": "invalid-list-node-IRI",
| "parentNodeIri": "$exampleListIri",
| "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}",
| "labels": [{ "value": "Neuer List Node", "language": "de"}],
| "comments": []
|}
""".stripMargin

val thrown = the [BadRequestException] thrownBy payload.parseJson.convertTo[CreateChildNodeApiRequestADM]

thrown.getMessage should equal ("Invalid list node IRI")

}

}
}

0 comments on commit 5354bbb

Please sign in to comment.