diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/LocalDynamicMetadataProviderControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/LocalDynamicMetadataProviderControllerTests.groovy new file mode 100644 index 000000000..bc13df607 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/LocalDynamicMetadataProviderControllerTests.groovy @@ -0,0 +1,275 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.MetadataResolverConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.repository.LocalDynamicMetadataResolverRepository +import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator +import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator +import edu.internet2.tier.shibboleth.admin.util.AttributeUtility +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import spock.lang.Specification + +import static org.hamcrest.CoreMatchers.containsString +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.* + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@DataJpaTest +@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, MetadataResolverConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +class LocalDynamicMetadataProviderControllerTests extends Specification { + RandomGenerator randomGenerator + TestObjectGenerator testObjectGenerator + ObjectMapper mapper + + def repository = Mock(LocalDynamicMetadataResolverRepository) + def controller + def mockMvc + + @Autowired + AttributeUtility attributeUtility + + def setup() { + randomGenerator = new RandomGenerator() + testObjectGenerator = new TestObjectGenerator(attributeUtility) + mapper = new ObjectMapper() + + controller = new LocalDynamicMetadataProviderController ( + repository: repository + ) + + mockMvc = MockMvcBuilders.standaloneSetup(controller).build() + } + + def "DELETE deletes the desired resolver"() { + given: + def randomResourceId = randomGenerator.randomId() + + 1 * repository.deleteByResourceId(randomResourceId) >> true + + when: + def result = mockMvc.perform( + delete("/api/MetadataProvider/LocalDynamic/$randomResourceId")) + + then: + result.andExpect(status().isAccepted()) + } + + def "DELETE returns error when desired resolver is not found"() { + given: + def randomResourceId = randomGenerator.randomId() + + 1 * repository.deleteByResourceId(randomResourceId) >> false + + when: + def result = mockMvc.perform( + delete("/api/MetadataProvider/LocalDynamic/$randomResourceId")) + + then: + result.andExpect(status().isNotFound()) + } + + def "POST a new resolver properly persists and returns the new persisted resolver"() { + given: + def resolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + resolver.version = resolver.hashCode() + def postedJsonBody = mapper.writeValueAsString(resolver) + + 1 * repository.findByName(resolver.getName()) >> null + 1 * repository.save(_) >> resolver + + def expectedResolverUUID = resolver.getResourceId() + def expectedResponseHeader = 'Location' + def expectedResponseHeaderValue = "/api/MetadataProvider/LocalDynamic/$expectedResolverUUID" + + when: + def result = mockMvc.perform( + post('/api/MetadataProvider/LocalDynamic') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isCreated()) + .andExpect(content().json(postedJsonBody, false)) + .andExpect(header().string(expectedResponseHeader, containsString(expectedResponseHeaderValue))) + } + + def "POST a new resolver that has a name of a persisted resolver returns conflict"() { + given: + def resolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + resolver.version = resolver.hashCode() + def postedJsonBody = mapper.writeValueAsString(resolver) + + 1 * repository.findByName(resolver.name) >> resolver + 0 * repository.save(_) + + when: + def result = mockMvc.perform( + post('/api/MetadataProvider/LocalDynamic') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isConflict()) + } + + def "GET by resourceId returns the desired persisted resolver"() { + given: + def resolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + resolver.version = resolver.hashCode() + def resolverJson = mapper.writeValueAsString(resolver) + def resolverId = resolver.resourceId + + 1 * repository.findByResourceId(resolverId) >> resolver + + def expectedResponseContentType = APPLICATION_JSON_UTF8 + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/LocalDynamic/$resolverId")) + + then: + result.andExpect(status().isOk()) + .andExpect(content().contentType(expectedResponseContentType)) + .andExpect(content().json(resolverJson, false)) + } + + def "GET by unknown resource id returns not found"() { + given: + def randomResourceId = randomGenerator.randomId() + + 1 * repository.findByResourceId(randomResourceId) >> null + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/LocalDynamic/$randomResourceId")) + + then: + result.andExpect(status().isNotFound()) + } + + def "GET by resolver name returns the desired persisted resolver"() { + given: + def resolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + resolver.version = resolver.hashCode() + def resolverName = resolver.name + def resolverJson = mapper.writeValueAsString(resolver) + + 1 * repository.findByName(resolverName) >> resolver + + def expectedResponseContentType = APPLICATION_JSON_UTF8 + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/LocalDynamic/name/$resolverName")) + + then: + result.andExpect(status().isOk()) + .andExpect(content().contentType(expectedResponseContentType)) + .andExpect(content().json(resolverJson, false)) + } + + def "GET by unknown resolver name returns not found"() { + given: + def randomResolverName = randomGenerator.randomString(10) + + 1 * repository.findByName(randomResolverName) >> null + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/LocalDynamic/name/$randomResolverName")) + + then: + result.andExpect(status().isNotFound()) + } + + def "PUT allows for a successful update of an already-persisted resolver"() { + given: + def existingResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + existingResolver.version = existingResolver.hashCode() + def resourceId = existingResolver.resourceId + def updatedResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + updatedResolver.setResourceId(resourceId) + updatedResolver.setVersion(existingResolver.hashCode()) + def postedJsonBody = mapper.writeValueAsString(updatedResolver) + + 1 * repository.findByResourceId(resourceId) >> existingResolver + 1 * repository.save(_) >> updatedResolver + + def expectedResponseContentType = APPLICATION_JSON_UTF8 + + when: + def result = mockMvc.perform( + put('/api/MetadataProvider/LocalDynamic') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + def expectedJson = new JsonSlurper().parseText(postedJsonBody) + expectedJson << [version: updatedResolver.hashCode()] + result.andExpect(status().isOk()) + .andExpect(content().contentType(expectedResponseContentType)) + .andExpect(content().json(JsonOutput.toJson(expectedJson), false)) + } + + def "PUT of an updated resolver with an incorrect version returns a conflict"() { + given: + def existingResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + existingResolver.version = existingResolver.hashCode() + def resourceId = existingResolver.resourceId + + def updatedResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + updatedResolver.resourceId = resourceId + updatedResolver.version = updatedResolver.hashCode() + def postedJsonBody = mapper.writeValueAsString(updatedResolver) + + 1 * repository.findByResourceId(resourceId) >> existingResolver + 0 * repository.save(_) + + when: + def result = mockMvc.perform( + put('/api/MetadataProvider/LocalDynamic') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isConflict()) + } + + def "PUT of a resolver that is not persisted returns not found"() { + given: + def existingResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + existingResolver.version = existingResolver.hashCode() + def resourceId = existingResolver.resourceId + + def updatedResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + updatedResolver.resourceId = resourceId + updatedResolver.version = updatedResolver.hashCode() + def postedJsonBody = mapper.writeValueAsString(updatedResolver) + + 1 * repository.findByResourceId(resourceId) >> null + 0 * repository.save(_) + + when: + def result = mockMvc.perform( + put('/api/MetadataProvider/LocalDynamic') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isNotFound()) + } +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy index fce929b75..cd5026ede 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy @@ -14,6 +14,8 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterTargetRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicMetadataResolverAttributes +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver import edu.internet2.tier.shibboleth.admin.util.AttributeUtility import edu.internet2.tier.shibboleth.admin.util.MDDCConstants import org.opensaml.saml.saml2.metadata.Organization @@ -33,6 +35,46 @@ class TestObjectGenerator { this.attributeUtility = attributeUtility } + LocalDynamicMetadataResolver buildLocalDynamicMetadataResolver() { + def resolver = new LocalDynamicMetadataResolver().with { + it.dynamicMetadataResolverAttributes = buildDynamicMetadataResolverAttributes() + it.sourceDirectory = generator.randomString(10) + it.sourceKeyGeneratorRef = generator.randomString(10) + it.sourceManagerRef = generator.randomString(10) + it.failFastInitialization = generator.randomBoolean() + it.name = generator.randomString(10) + it.requireValidMetadata = generator.randomBoolean() + it.useDefaultPredicateRegistry = generator.randomBoolean() + it.criterionPredicateRegistryRef = generator.randomString(10) + it.satisfyAnyPredicates = generator.randomBoolean() + it.sortKey = generator.randomInt(1, 10) + it.metadataFilters = buildAllTypesOfFilterList() + it + } + return resolver + } + + DynamicMetadataResolverAttributes buildDynamicMetadataResolverAttributes() { + def attributes = new DynamicMetadataResolverAttributes().with { + it.backgroundInitializationFromCacheDelay = generator.randomString(10) + it.cleanupTaskInterval = generator.randomString(10) + it.initializationFromCachePredicateRef = generator.randomString(10) + it.initializeFromPersistentCacheInBackground = generator.randomBoolean() + it.maxCacheDuration = generator.randomString(5) + it.maxIdleEntityData = generator.randomString(5) + it.minCacheDuration = generator.randomString(5) + it.parserPoolRef = generator.randomString(10) + it.persistentCacheKeyGeneratorRef = generator.randomString(10) + it.persistentCacheManagerDirectory = generator.randomString(10) + it.persistentCacheManagerRef = generator.randomString(10) + it.refreshDelayFactor = generator.randomInt(1, 5) + it.removeIdleEntityData = generator.randomBoolean() + it.taskTimerRef = generator.randomString() + it + } + attributes + } + List buildAllTypesOfFilterList() { List filterList = new ArrayList<>() (1..generator.randomInt(4, 10)).each {