diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java index 0078284e1..336e32d44 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java @@ -49,7 +49,7 @@ public ResponseEntity getOne(@PathVariable String resourceId) { @PostMapping("/MetadataResolvers") @Transactional public ResponseEntity create(@RequestBody MetadataResolver newResolver) { - //TODO: we are disregarding attached filters if any sent from UI below. + //TODO: we are disregarding attached filters if any sent from UI. //Only deal with filters via filters endpoints? newResolver.clearAllFilters(); @@ -62,8 +62,6 @@ public ResponseEntity create(@RequestBody MetadataResolver newResolver) { @PutMapping("/MetadataResolvers/{resourceId}") @Transactional public ResponseEntity update(@PathVariable String resourceId, @RequestBody MetadataResolver updatedResolver) { - //TODO disregard attached filters if any sent from UI? - //Only deal with filters via filters endpoints? And here only update the resolver pieces?? MetadataResolver existingResolver = resolverRepository.findByResourceId(resourceId); if (existingResolver == null) { return ResponseEntity.notFound().build(); @@ -75,6 +73,11 @@ public ResponseEntity update(@PathVariable String resourceId, @RequestBody Me } updatedResolver.setAudId(existingResolver.getAudId()); + + //TODO: we are disregarding attached filters if any sent from UI. + //Only deal with filters via filters endpoints? + updatedResolver.setMetadataFilters(existingResolver.getMetadataFilters()); + MetadataResolver persistedResolver = resolverRepository.save(updatedResolver); persistedResolver.updateVersion(); diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index ac9a01597..733962eb0 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -5,6 +5,8 @@ #logging.config=classpath:log4j2.xml #logging.level.org.springframework.web=ERROR +logging.level.edu.internet2.tier.shibboleth.admin.ui=INFO + # Database Credentials spring.datasource.username=shibui spring.datasource.password=shibui diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy index a60e5a1ad..256285f94 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy @@ -1,10 +1,10 @@ package edu.internet2.tier.shibboleth.admin.ui.controller -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +import groovy.json.JsonOutput +import groovy.json.JsonSlurper import org.opensaml.saml.metadata.resolver.MetadataResolver import org.opensaml.saml.metadata.resolver.impl.FilesystemMetadataResolver import org.springframework.beans.factory.annotation.Autowired @@ -12,16 +12,21 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.TestConfiguration import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.context.annotation.Bean +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders + import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.ActiveProfiles import spock.lang.Specification -import javax.persistence.EntityManager +import static org.springframework.http.HttpMethod.PUT +/** + * @author Dmitriy Kopylenko + */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("no-auth") -@DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD) class MetadataResolversControllerIntegrationTests extends Specification { @Autowired @@ -30,47 +35,171 @@ class MetadataResolversControllerIntegrationTests extends Specification { @Autowired MetadataResolverRepository metadataResolverRepository - @Autowired - EntityManager entityManager + JsonSlurper jsonSlurper = new JsonSlurper() - ObjectMapper mapper + static BASE_URI = '/api/MetadataResolvers' - def setup() { - mapper = new ObjectMapper() - mapper.registerModule(new JavaTimeModule()) - mapper.enable(SerializationFeature.INDENT_OUTPUT) + def cleanup() { + metadataResolverRepository.deleteAll() } - def "GET empty /api/MetadataResolvers"() { - when: - def result = this.restTemplate.getForEntity("/api/MetadataResolvers", String) - def returnedResolvers = mapper.readValue(result.body, - edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver[]) + def "GET empty -> /api/MetadataResolvers"() { + when: 'No resolvers are available in data store' + def result = this.restTemplate.getForEntity(BASE_URI, String) + def returnedResolvers = jsonSlurper.parseText(result.body) then: result.statusCodeValue == 200 returnedResolvers.size() == 0 } - def "GET one DynamicHttpMetadataResolver /api/MetadataResolvers"() { - given: + def "GET one available MetadataResolver -> /api/MetadataResolvers"() { + given: 'One resolver is available in data store' def resolver = new DynamicHttpMetadataResolver().with { it.name = 'Test DynamicHttpMetadataResolver' it } metadataResolverRepository.save(resolver) - when: - def result = this.restTemplate.getForEntity("/api/MetadataResolvers", String) - def returnedResolvers = mapper.readValue(result.body, - edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver[]) + when: 'GET request is made' + def result = this.restTemplate.getForEntity(BASE_URI, String) + def returnedResolvers = jsonSlurper.parseText(result.body) then: result.statusCodeValue == 200 returnedResolvers.size() == 1 - returnedResolvers[0] instanceof DynamicHttpMetadataResolver + returnedResolvers[0]['@type'] == 'DynamicHttpMetadataResolver' + returnedResolvers[0].name == 'Test DynamicHttpMetadataResolver' + + } + + def "GET multiple available MetadataResolvers -> /api/MetadataResolvers"() { + given: 'Two resolvers are available in data store' + def resolvers = [ + new DynamicHttpMetadataResolver().with { + it.name = 'Test DynamicHttpMetadataResolver' + it + }, + new FileBackedHttpMetadataResolver().with { + it.name = 'Test FileBackedHttpMetadataResolver' + it + } + ] + resolvers.each { + metadataResolverRepository.save(it) + } + + when: 'GET request is made' + def result = this.restTemplate.getForEntity(BASE_URI, String) + def returnedResolvers = jsonSlurper.parseText(result.body) + + then: + result.statusCodeValue == 200 + returnedResolvers.size() == 2 + returnedResolvers[0]['@type'] == 'DynamicHttpMetadataResolver' returnedResolvers[0].name == 'Test DynamicHttpMetadataResolver' + returnedResolvers[1]['@type'] == 'FileBackedHttpMetadataResolver' + returnedResolvers[1].name == 'Test FileBackedHttpMetadataResolver' + + } + + def "GET concrete MetadataResolver -> /api/MetadataResolvers/{resourceId}"() { + given: 'One resolver is available in data store' + def resolver = new DynamicHttpMetadataResolver().with { + it.name = 'Test DynamicHttpMetadataResolver' + it + } + def resolverResourceId = resolver.resourceId + metadataResolverRepository.save(resolver) + + when: 'GET request is made with resource Id matching the existing resolver' + def result = this.restTemplate.getForEntity("$BASE_URI/$resolverResourceId", String) + def returnedResolver = jsonSlurper.parseText(result.body) + + then: + result.statusCodeValue == 200 + returnedResolver['@type'] == 'DynamicHttpMetadataResolver' + returnedResolver.name == 'Test DynamicHttpMetadataResolver' + + } + + def "GET non-existent MetadataResolver -> /api/MetadataResolvers/{resourceId}"() { + when: 'GET request is made with resource Id not matching any resolvers' + def result = this.restTemplate.getForEntity("$BASE_URI/bogus-resource-id", String) + + then: + result.statusCodeValue == 404 + } + + def "POST new DynamicHttpMetadataResolver -> /api/MetadataResolvers"() { + given: 'New MetadataResolver JSON representation' + def resolver = [name: 'Test DynamicHttpMetadataResolver', '@type': 'DynamicHttpMetadataResolver'] + + when: 'POST request is made with new DynamicHttpMetadataResolver JSON representation' + def result = this.restTemplate.postForEntity(BASE_URI, createRequestHttpEntityFor { JsonOutput.toJson(resolver) }, String) + + then: + result.statusCodeValue == 201 + result.headers.Location[0].contains(BASE_URI) + } + + def "PUT concrete MetadataResolver with updated changes -> /api/MetadataResolvers/{resourceId}"() { + given: 'One resolver is available in data store' + def resolver = new DynamicHttpMetadataResolver().with { + it.name = 'Test DynamicHttpMetadataResolver' + it + } + def resolverResourceId = resolver.resourceId + metadataResolverRepository.save(resolver) + + when: 'GET request is made with resource Id matching the existing resolver' + def result = this.restTemplate.getForEntity("$BASE_URI/$resolverResourceId", String) + + and: 'Resolver data is updated and sent back to the server' + def metadataResolverMap = new JsonSlurper().parseText(result.body) + metadataResolverMap.name = 'Updated DynamicHttpMetadataResolver' + def updatedResult = this.restTemplate.exchange( + "$BASE_URI/${metadataResolverMap.resourceId}", + PUT, + createRequestHttpEntityFor { JsonOutput.toJson(metadataResolverMap) }, + String) + def updatedResolverMap = new JsonSlurper().parseText(updatedResult.body) + + then: + updatedResult.statusCodeValue == 200 + updatedResolverMap.name == 'Updated DynamicHttpMetadataResolver' + + } + + def "PUT concrete MetadataResolver with version conflict -> /api/MetadataResolvers/{resourceId}"() { + given: 'One resolver is available in data store' + def resolver = new DynamicHttpMetadataResolver().with { + it.name = 'Test DynamicHttpMetadataResolver' + it + } + def resolverResourceId = resolver.resourceId + def persistedResolver = metadataResolverRepository.save(resolver) + + when: 'GET request is made with resource Id matching the existing resolver' + def result = this.restTemplate.getForEntity("$BASE_URI/$resolverResourceId", String) + + and: 'Resolver data is updated and sent back to the server, but then original resolver is changed in data store' + persistedResolver.name = 'Some other name' + metadataResolverRepository.save(persistedResolver) + def metadataResolverMap = new JsonSlurper().parseText(result.body) + metadataResolverMap.name = 'Updated DynamicHttpMetadataResolver' + def updatedResult = this.restTemplate.exchange( + "$BASE_URI/${metadataResolverMap.resourceId}", + PUT, + createRequestHttpEntityFor { JsonOutput.toJson(metadataResolverMap) }, + String) + + then: + updatedResult.statusCodeValue == 409 + } + private HttpEntity createRequestHttpEntityFor(Closure jsonBodySupplier) { + new HttpEntity(jsonBodySupplier(), ['Content-Type': 'application/json'] as HttpHeaders) } @TestConfiguration