Skip to content

Commit

Permalink
WIP: "NULL not allowed for column CREATED_BY" even after checking imp…
Browse files Browse the repository at this point in the history
…orted Folder associations

Error encountered in FolderFunctionalSpec.FE07
  • Loading branch information
adjl committed Mar 21, 2022
1 parent c99d28e commit 9ad71b6
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,27 @@
*/
package uk.ac.ox.softeng.maurodatamapper.core.container

import uk.ac.ox.softeng.maurodatamapper.api.exception.ApiException
import uk.ac.ox.softeng.maurodatamapper.core.container.provider.importer.FolderImporterProviderService
import uk.ac.ox.softeng.maurodatamapper.core.container.provider.importer.parameter.FolderImporterProviderServiceParameters
import uk.ac.ox.softeng.maurodatamapper.core.controller.EditLoggingController
import uk.ac.ox.softeng.maurodatamapper.core.importer.ImporterService
import uk.ac.ox.softeng.maurodatamapper.core.model.CatalogueItem
import uk.ac.ox.softeng.maurodatamapper.core.provider.MauroDataMapperServiceProviderService
import uk.ac.ox.softeng.maurodatamapper.core.provider.exporter.ExporterProviderService
import uk.ac.ox.softeng.maurodatamapper.core.provider.importer.ImporterProviderService
import uk.ac.ox.softeng.maurodatamapper.core.rest.transport.search.SearchParams
import uk.ac.ox.softeng.maurodatamapper.core.search.SearchService
import uk.ac.ox.softeng.maurodatamapper.hibernate.search.PaginatedHibernateSearchResult
import uk.ac.ox.softeng.maurodatamapper.security.SecurityPolicyManagerService

import grails.gorm.transactions.Transactional
import grails.web.http.HttpHeaders
import grails.web.mime.MimeType
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.validation.Errors
import org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest

import static org.springframework.http.HttpStatus.CREATED
import static org.springframework.http.HttpStatus.NO_CONTENT
Expand All @@ -41,6 +49,7 @@ class FolderController extends EditLoggingController<Folder> {
static responseFormats = ['json', 'xml']

MauroDataMapperServiceProviderService mauroDataMapperServiceProviderService
ImporterService importerService
FolderService folderService
SearchService mdmCoreSearchService
VersionedFolderService versionedFolderService
Expand Down Expand Up @@ -185,11 +194,54 @@ class FolderController extends EditLoggingController<Folder> {
log.info("Exporting Folder using ${exporter.displayName}")
ByteArrayOutputStream outputStream = exporter.exportDomain(currentUser, params.folderId)
if (!outputStream) return errorResponse(UNPROCESSABLE_ENTITY, 'Folder could not be exported')
log.info('Export complete')
log.info('Single Folder Export complete')

render(file: outputStream.toByteArray(), fileName: "${instance.label}.${exporter.fileExtension}", contentType: exporter.fileType)
}

@Transactional
def importFolder() throws ApiException {
FolderImporterProviderService importer =
mauroDataMapperServiceProviderService.findImporterProvider(params.importerNamespace, params.importerName, params.importerVersion) as FolderImporterProviderService
if (!importer) return notFound(ImporterProviderService, "${params.importerNamespace}:${params.importerName}:${params.importerVersion}")

FolderImporterProviderServiceParameters importerProviderServiceParameters = request.contentType.startsWith(MimeType.MULTIPART_FORM.name)
? importerService.extractImporterProviderServiceParameters(importer, request as AbstractMultipartHttpServletRequest)
: importerService.extractImporterProviderServiceParameters(importer, request)

Errors errors = importerService.validateParameters(importerProviderServiceParameters, importer.importerProviderServiceParametersClass)
if (errors.hasErrors()) {
transactionStatus.setRollbackOnly()
return respond(errors)
}

if (!currentUserSecurityPolicyManager.userCanCreateResourceId(resource, null, Folder, importerProviderServiceParameters.parentFolderId)) {
if (!currentUserSecurityPolicyManager.userCanReadSecuredResourceId(Folder, importerProviderServiceParameters.parentFolderId)) {
return notFound(Folder, importerProviderServiceParameters.parentFolderId)
}
return forbiddenDueToPermissions()
}

Folder folder = importer.importDomain(currentUser, importerProviderServiceParameters) as Folder
if (!folder) {
transactionStatus.setRollbackOnly()
return errorResponse(UNPROCESSABLE_ENTITY, 'No folder imported')
}

folder.parentFolder = folderService.get(importerProviderServiceParameters.parentFolderId)
folderService.validate(folder)
if (folder.hasErrors()) {
transactionStatus.setRollbackOnly()
return respond(folder.errors)
}

log.debug('No errors in imported folder')
log.info('Single Folder Import complete')

saveResource(folder)
saveResponse(folder)
}

@Override
protected Folder queryForResource(Serializable id) {
folderService.get(id)
Expand Down Expand Up @@ -239,7 +291,6 @@ class FolderController extends EditLoggingController<Folder> {
namespace: hasProperty('namespace') ? this.namespace : null))
respond instance, [status: OK, view: 'update', model: [userSecurityPolicyManager: currentUserSecurityPolicyManager, folder: instance]]
}

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ class FolderInterceptor extends SecurableResourceInterceptor {
return true
}

if (actionName == 'importFolder') {
if (!currentUserSecurityPolicyManager.userCanEditSecuredResourceId(Folder, id)) {
return forbiddenOrNotFound(false, Folder, id)
}
return true
}

checkActionAuthorisationOnSecuredResource(Folder, getId(), true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,6 @@ class FolderService extends ContainerService<Folder> {
throw new ApiNotYetImplementedException('MSXX', 'Folder permission copying')
}
log.warn('Permission copying is not yet implemented')

}
log.debug('Validating and saving copy')
setFolderRefinesFolder(copiedFolder, original, copier)
Expand Down Expand Up @@ -643,21 +642,19 @@ class FolderService extends ContainerService<Folder> {
if (parentCache) parentCache.addDiffCache(folder.path, fDiffCache)
fDiffCache
}
Folder checkImportedFolderAssociations(User importingUser, Folder folder) {
folder.createdBy = importingUser.emailAddress
if (!folder.id) save(folder, insert: true, validate: false) // Skip validation to avoid error on null folderId/multiFacetAwareItemId
checkFacetsAfterImportingMultiFacetAware(folder)

// Check imported child Folder assocations breadth-first to avoid recursion
List<Folder> childFolders = folder.childFolders?.toList() ?: []
for (int i = 0; i < childFolders.size(); i++) {
childFolders[i].childFolders?.each {
if (!childFolders.contains(it)) childFolders << it

void checkImportedFolderAssociations(User importingUser, Folder folder) {
// Traverse breadth-first to avoid recursion
List<Folder> folders = [folder]
for (int i = 0; i < folders.size(); i++) {
folders[i].checkPath()
folders[i].createdBy = importingUser.emailAddress
if (!folders[i].id) save(folders[i], validate: false) // Skip validation to avoid error on null folderId/multiFacetAwareItemId
checkFacetsAfterImportingMultiFacetAware(folders[i])
folders[i].childFolders?.each {
if (!folders.contains(it)) folders << it
}
}
childFolders.each { checkImportedFolderAssociations(importingUser, it) }

log.debug('Folder associations checked')
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ import uk.ac.ox.softeng.maurodatamapper.test.functional.ResourceFunctionalSpec
import grails.gorm.transactions.Rollback
import grails.testing.mixin.integration.Integration
import grails.testing.spock.RunOnce
import grails.web.mime.MimeType
import groovy.util.logging.Slf4j
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpStatus
import spock.lang.Shared

import static uk.ac.ox.softeng.maurodatamapper.core.bootstrap.StandardEmailAddress.getFUNCTIONAL_TEST

/**
* <pre>
Expand All @@ -42,17 +46,25 @@ import io.micronaut.http.HttpStatus
* | POST | /api/folders/${folderId}/search | Action: search
*
* | GET | /api/folders/${folderId}/export | Action: export
* | POST | /api/folders/${folderId}/import | Action: import
* </pre>
* @see uk.ac.ox.softeng.maurodatamapper.core.container.FolderController
*/
@Integration
@Slf4j
class FolderFunctionalSpec extends ResourceFunctionalSpec<Folder> {

@Shared
UUID parentFolderId

@RunOnce
@Rollback
def setup() {
assert Folder.count() == 0
log.debug('Check and setup test data')
sessionFactory.currentSession.flush()
parentFolderId = new Folder(label: 'Parent Functional Test Folder', createdBy: FUNCTIONAL_TEST).save(flush: true).id
assert parentFolderId
assert Folder.count() == 1
}

@Override
Expand Down Expand Up @@ -94,6 +106,14 @@ class FolderFunctionalSpec extends ResourceFunctionalSpec<Folder> {
}'''
}

@Override
void verifyR1EmptyIndexResponse() {
verifyResponse(HttpStatus.OK, response)
assert response.body().count == 1
assert response.body().items.size() == 1
assert response.body().items[0].label == 'Parent Functional Test Folder'
}

void 'Test the save action fails when using the same label persists an instance'() {
given:
List<String> createdIds = []
Expand Down Expand Up @@ -544,4 +564,120 @@ class FolderFunctionalSpec extends ResourceFunctionalSpec<Folder> {
cleanup:
ids.reverseEach { cleanUpData(it) }
}

void 'FE06 : test import Folder JSON'() {
when: 'The save action is executed with valid data'
POST('', validJson)

then: 'The response is correct'
response.status == HttpStatus.CREATED
response.body().id

when: 'The export action is executed with a valid instance'
String id = response.body().id
GET("${id}/export/uk.ac.ox.softeng.maurodatamapper.core.container.provider.exporter/FolderJsonExporterService/1.0", STRING_ARG)

then: 'The response is correct'
verifyResponse(HttpStatus.OK, jsonCapableResponse)

when: 'The delete action is executed with a valid instance'
String exportedJson = jsonCapableResponse.body()
DELETE(getDeleteEndpoint(id))

then: 'The response is correct'
response.status == HttpStatus.NO_CONTENT

when: 'The import action is executed with valid data'
POST('import/uk.ac.ox.softeng.maurodatamapper.core.container.provider.importer/FolderJsonImporterService/1.0', [
parentFolderId: parentFolderId.toString(),
importFile : [
fileName : 'FT Import',
fileType : MimeType.JSON_API.name,
fileContents: exportedJson.bytes.toList()
]
], STRING_ARG)

then: 'The response is correct'
verifyJsonResponse(HttpStatus.CREATED, '''{
"id": "${json-unit.matches:id}",
"label": "Functional Test Folder",
"lastUpdated": "${json-unit.matches:offsetDateTime}",
"domainType": "Folder",
"hasChildFolders": false,
"readableByEveryone": false,
"readableByAuthenticatedUsers": false,
"availableActions": [
"delete",
"show",
"update"
]
}''')
}

void 'FE07 : test import Folder with child Folders JSON'() {
given:
List<HttpStatus> responseStatuses = []
List<String> ids = []
Closure<Void> saveResponse = { ->
responseStatuses << response.status
ids << response.body()?.id
}

when: 'The save actions are executed with valid data'
POST('', validJson)
saveResponse()
POST("${ids[0]}/folders", [label: 'Functional Test Folder 2'])
saveResponse()
POST("${ids[0]}/folders", [label: 'Functional Test Folder 3'])
saveResponse()
POST("${ids[2]}/folders", [label: 'Functional Test Folder 4'])
saveResponse()

then: 'The responses are correct'
responseStatuses.every { it == HttpStatus.CREATED }
ids.every()

when: 'The export action is executed with a valid instance'
GET("${ids[0]}/export/uk.ac.ox.softeng.maurodatamapper.core.container.provider.exporter/FolderJsonExporterService/1.0", STRING_ARG)

then: 'The response is correct'
verifyResponse(HttpStatus.OK, jsonCapableResponse)

when: 'The delete action is executed with a valid instance'
String exportedJson = jsonCapableResponse.body()
responseStatuses.clear()
ids.reverseEach {
DELETE(getDeleteEndpoint(it))
responseStatuses << response.status
}

then: 'The response is correct'
responseStatuses.every { it == HttpStatus.NO_CONTENT }

when: 'The import action is executed with valid data'
POST('import/uk.ac.ox.softeng.maurodatamapper.core.container.provider.importer/FolderJsonImporterService/1.0', [
parentFolderId: parentFolderId.toString(),
importFile : [
fileName : 'FT Import',
fileType : MimeType.JSON_API.name,
fileContents: exportedJson.bytes.toList()
]
], STRING_ARG)

then: 'The response is correct'
verifyJsonResponse(HttpStatus.CREATED, '''{
"id": "${json-unit.matches:id}",
"label": "Functional Test Folder",
"lastUpdated": "${json-unit.matches:offsetDateTime}",
"domainType": "Folder",
"hasChildFolders": true,
"readableByEveryone": false,
"readableByAuthenticatedUsers": false,
"availableActions": [
"delete",
"show",
"update"
]
}''')
}
}

0 comments on commit 9ad71b6

Please sign in to comment.