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 6bce7af7b..af7c2b0ef 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 @@ -151,7 +151,7 @@ public ResponseEntity update(@PathVariable String resourceId, @RequestBody Me MetadataResolver persistedResolver = resolverRepository.save(updatedResolver); doResolverInitialization(persistedResolver); - return ResponseEntity.ok(persistedResolver); + return ResponseEntity.ok(resolverRepository.findByResourceId(resourceId)); } //Versioning endpoints @@ -212,4 +212,4 @@ private void doResolverInitialization(MetadataResolver persistedResolver) throws OpenSamlChainingMetadataResolverUtil.updateChainingMetadataResolver((OpenSamlChainingMetadataResolver) chainingMetadataResolver, openSamlRepresentation); } } -} +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java index 9e0f7117e..d7cb7a61e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java @@ -1,21 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.service; -import edu.internet2.tier.shibboleth.admin.ui.domain.Attribute; -import edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributes; -import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; -import edu.internet2.tier.shibboleth.admin.ui.domain.KeyDescriptor; -import edu.internet2.tier.shibboleth.admin.ui.domain.IRelyingPartyOverrideProperty; -import edu.internet2.tier.shibboleth.admin.ui.domain.UIInfo; -import edu.internet2.tier.shibboleth.admin.ui.domain.XSBoolean; -import edu.internet2.tier.shibboleth.admin.ui.domain.XSInteger; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.AssertionConsumerServiceRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ContactRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.LogoutEndpointRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.MduiRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.OrganizationRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.SecurityInfoRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ServiceProviderSsoDescriptorRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.domain.*; +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.*; import edu.internet2.tier.shibboleth.admin.ui.exception.EntityIdExistsException; import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; @@ -29,23 +15,14 @@ import edu.internet2.tier.shibboleth.admin.util.MDDCConstants; import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions; import lombok.extern.slf4j.Slf4j; - -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; -import javax.transaction.Transactional; - import static edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils.*; -import static edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions.*; +import static edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions.getStringListOfAttributeValues; @Slf4j @Service @@ -407,4 +384,4 @@ public void updateDescriptorFromRepresentation(org.opensaml.saml.saml2.metadata. } buildDescriptorFromRepresentation((EntityDescriptor) entityDescriptor, representation); } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestSetup.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestSetup.groovy new file mode 100644 index 000000000..2fc57fe70 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestSetup.groovy @@ -0,0 +1,82 @@ +package edu.internet2.tier.shibboleth.admin.ui + + +import edu.internet2.tier.shibboleth.admin.ui.security.model.Role +import edu.internet2.tier.shibboleth.admin.ui.security.model.User +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository +import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceForTesting +import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService +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.transaction.annotation.Transactional +import spock.lang.Shared +import spock.lang.Specification + +@DataJpaTest +@ContextConfiguration(classes = [BaseDataJpaTestSetupConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +abstract class BaseDataJpaTestSetup extends Specification { + static boolean setupRun = false + + @Autowired + GroupServiceForTesting groupService + + @Autowired + OwnershipRepository ownershipRepository + + @Autowired + RoleRepository roleRepository + + @Autowired + UserRepository userRepository + + @Autowired + UserService userService + + def setup() { + runOnce() + } + + // One time setup to ensure roles are in a known good state and that we have an admin user and group + @Transactional + runOnce() { + if (setupRun) return + + groupService.clearAllForTesting() + userRepository.deleteAll() + ownershipRepository.deleteAll() + roleRepository.deleteAll() + + def roles = [new Role().with { + name = 'ROLE_ADMIN' + it + }, new Role().with { + name = 'ROLE_USER' + it + }, new Role().with { + name = 'ROLE_ENABLE' + it + }, new Role().with { + name = 'ROLE_NONE' + it + }] + roles.each { + if (roleRepository.findByName(it.name).isEmpty()) { + roleRepository.save(it) + } + } + + if (userRepository.findByUsername("admin").isEmpty()) { + Optional adminRole = roleRepository.findByName("ROLE_ADMIN") + User adminUser = new User(username: "admin", roles: [adminRole.get()], password: "foo") + userService.save(adminUser) + } + setupRun = true + } +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestSetupConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestSetupConfiguration.groovy new file mode 100644 index 000000000..6316b9755 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestSetupConfiguration.groovy @@ -0,0 +1,51 @@ +package edu.internet2.tier.shibboleth.admin.ui + +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration +import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects +import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.GroupUpdatedEntityListener +import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener +import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository +import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceForTesting +import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import +import org.springframework.context.annotation.Primary + +@Configuration +@Import(ShibUIConfiguration.class) +@ComponentScan(basePackages=[ "edu.internet2.tier.shibboleth.admin.ui.service", "edu.internet2.tier.shibboleth.admin.ui.security.service" ]) +class BaseDataJpaTestSetupConfiguration { + @Bean + @Primary + GroupServiceForTesting groupServiceForTesting(GroupsRepository groupRepo, OwnershipRepository ownershipRepository) { + GroupServiceForTesting result = new GroupServiceForTesting(new GroupServiceImpl().with { + it.groupRepository = groupRepo + it.ownershipRepository = ownershipRepository + return it + }) + result.ensureAdminGroupExists() + return result + } + + @Bean + GroupUpdatedEntityListener groupUpdatedEntityListener(OwnershipRepository ownershipRepository) { + GroupUpdatedEntityListener listener = new GroupUpdatedEntityListener() + listener.init(ownershipRepository) + return listener + } + + @Bean + OpenSamlObjects openSamlObjects() { + return new OpenSamlObjects() + } + + @Bean + UserUpdatedEntityListener userUpdatedEntityListener(OwnershipRepository ownershipRepository, GroupsRepository groupRepo) { + UserUpdatedEntityListener listener = new UserUpdatedEntityListener() + listener.init(ownershipRepository, groupRepo) + return listener + } +} \ No newline at end of file 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 b8487c9c8..623f4717a 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 @@ -3,83 +3,89 @@ 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.configuration.CustomPropertiesConfiguration +import edu.internet2.tier.shibboleth.admin.ui.BaseDataJpaTestSetup +import edu.internet2.tier.shibboleth.admin.ui.configuration.* import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter -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.domain.resolvers.LocalDynamicMetadataResolver -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataQueryProtocolScheme -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlChainingMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.* +import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolversPositionOrderContainerRepository +import edu.internet2.tier.shibboleth.admin.ui.service.* import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator +import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin import edu.internet2.tier.shibboleth.admin.util.AttributeUtility -import groovy.json.JsonOutput +import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions import groovy.json.JsonSlurper import org.opensaml.saml.metadata.resolver.MetadataResolver import org.springframework.beans.factory.annotation.Autowired -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.http.converter.json.MappingJackson2HttpMessageConverter import org.springframework.test.context.ActiveProfiles -import spock.lang.Specification +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.MvcResult +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional import spock.lang.Unroll -import static com.fasterxml.jackson.annotation.JsonInclude.Include.* -import static org.springframework.http.HttpMethod.PUT - -/** - * @author Dmitriy Kopylenko - */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles("no-auth") -class MetadataResolversControllerIntegrationTests extends Specification { - +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL +import static org.springframework.http.MediaType.APPLICATION_JSON +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.* + +@ContextConfiguration(classes=[MetadataResolverValidationConfiguration, MetadataResolverConverterConfiguration, + SearchConfiguration, MetadataResolverConfiguration, EntitiesVersioningConfiguration, + edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration, + PlaceholderResolverComponentsConfiguration, MRCILocalConfig, CustomPropertiesConfiguration]) +@ActiveProfiles(["no-auth"]) +class MetadataResolversControllerIntegrationTests extends BaseDataJpaTestSetup { @Autowired - private TestRestTemplate restTemplate + AttributeUtility attributeUtility @Autowired - MetadataResolverRepository metadataResolverRepository + CustomPropertiesConfiguration customPropertiesConfiguration @Autowired - AttributeUtility attributeUtility + MetadataResolversController controller @Autowired - CustomPropertiesConfiguration customPropertiesConfiguration + MetadataResolverRepository metadataResolverRepository ObjectMapper mapper TestObjectGenerator generator + MockMvc mockMvc - JsonSlurper jsonSlurper = new JsonSlurper() - - static BASE_URI = '/api/MetadataResolvers' + static String BASE_URI = '/api/MetadataResolvers' + @Transactional def setup() { generator = new TestObjectGenerator(attributeUtility, customPropertiesConfiguration) mapper = new ObjectMapper() mapper.enable(SerializationFeature.INDENT_OUTPUT) mapper.setSerializationInclusion(NON_NULL) mapper.registerModule(new JavaTimeModule()) + mapper.registerModule(new StringTrimModule()) metadataResolverRepository.deleteAll() + + mockMvc = MockMvcBuilders.standaloneSetup(controller).setMessageConverters(new MappingJackson2HttpMessageConverter(mapper)).build() } def cleanup() { metadataResolverRepository.deleteAll() } + @WithMockAdmin 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) + def result = mockMvc.perform(get(BASE_URI)) then: - result.statusCodeValue == 200 - returnedResolvers.size() == 0 + result.andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$").isEmpty()) } + @WithMockAdmin def "GET one available MetadataResolver -> /api/MetadataResolvers"() { given: 'One resolver is available in data store' def resolver = new DynamicHttpMetadataResolver().with { @@ -89,17 +95,16 @@ class MetadataResolversControllerIntegrationTests extends Specification { metadataResolverRepository.save(resolver) when: 'GET request is made' - def result = this.restTemplate.getForEntity(BASE_URI, String) - def returnedResolvers = jsonSlurper.parseText(result.body) + def result = mockMvc.perform(get(BASE_URI)) then: - result.statusCodeValue == 200 - returnedResolvers.size() == 1 - returnedResolvers[0]['@type'] == 'DynamicHttpMetadataResolver' - returnedResolvers[0].name == 'Test DynamicHttpMetadataResolver' + result.andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.[0].name").value("Test DynamicHttpMetadataResolver")) + .andExpect(jsonPath("\$.[0].['@type']").value("DynamicHttpMetadataResolver")) } + @WithMockAdmin def "GET multiple available MetadataResolvers -> /api/MetadataResolvers"() { given: 'Two resolvers are available in data store' def resolvers = [ @@ -117,19 +122,17 @@ class MetadataResolversControllerIntegrationTests extends Specification { } when: 'GET request is made' - def result = this.restTemplate.getForEntity(BASE_URI, String) - def returnedResolvers = jsonSlurper.parseText(result.body) + def result = mockMvc.perform(get(BASE_URI)) 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' - + result.andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.[0].name").value("Test DynamicHttpMetadataResolver")) + .andExpect(jsonPath("\$.[0].['@type']").value("DynamicHttpMetadataResolver")) + .andExpect(jsonPath("\$.[1].name").value("Test FileBackedHttpMetadataResolver")) + .andExpect(jsonPath("\$.[1].['@type']").value("FileBackedHttpMetadataResolver")) } + @WithMockAdmin def "GET concrete MetadataResolver -> /api/MetadataResolvers/{resourceId}"() { given: 'One resolver is available in data store' def resolver = new DynamicHttpMetadataResolver().with { @@ -140,25 +143,25 @@ class MetadataResolversControllerIntegrationTests extends Specification { 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) + def result = mockMvc.perform(get("$BASE_URI/$resolverResourceId")) then: - result.statusCodeValue == 200 - returnedResolver['@type'] == 'DynamicHttpMetadataResolver' - returnedResolver.name == 'Test DynamicHttpMetadataResolver' + result.andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.name").value("Test DynamicHttpMetadataResolver")) + .andExpect(jsonPath("\$.['@type']").value("DynamicHttpMetadataResolver")) } + @WithMockAdmin 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) + def result = mockMvc.perform(get("$BASE_URI/bogus-resource-id")) then: - result.statusCodeValue == 404 + result.andExpect(status().isNotFound()) } - @DirtiesContext + @WithMockAdmin def "SHIBUI-839 - POST resolver with spaces in the provider name results in trimmed name"() { given: def resolver = generator.buildRandomMetadataResolverOfType('DynamicHttp') @@ -166,29 +169,29 @@ class MetadataResolversControllerIntegrationTests extends Specification { def expectedName = 'This name has spaces' when: - def result = this.restTemplate.postForEntity(BASE_URI, createRequestHttpEntityFor { mapper.writeValueAsString(resolver) }, String) + def result = mockMvc.perform(post(BASE_URI).contentType(APPLICATION_JSON).content(mapper.writeValueAsString(resolver))) then: - def metadataResolverMap = new JsonSlurper().parseText(result.body) - metadataResolverMap.name == expectedName + result.andExpect(status().isCreated()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.name").value(expectedName)) } + @WithMockAdmin @Unroll - @DirtiesContext def "POST new concrete MetadataResolver of type #resolverType -> /api/MetadataResolvers"(String resolverType) { given: 'New MetadataResolver JSON representation' def resolver = generator.buildRandomMetadataResolverOfType(resolverType) String sourceDirectory - if (resolverType.equals('LocalDynamic')) { + if (resolverType == 'LocalDynamic') { sourceDirectory = ((LocalDynamicMetadataResolver) resolver).sourceDirectory } - when: 'POST request is made with new DynamicHttpMetadataResolver JSON representation' - def result = this.restTemplate.postForEntity(BASE_URI, createRequestHttpEntityFor { mapper.writeValueAsString(resolver) }, String) + when: 'POST request is made with new Resolver JSON representation' + def result = mockMvc.perform(post(BASE_URI).contentType(APPLICATION_JSON).content(mapper.writeValueAsString(resolver))) then: - result.statusCodeValue == 201 - result.headers.Location[0].contains(BASE_URI) + result.andExpect(status().isCreated()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.['@type']").value(resolver.getType())) cleanup: if (sourceDirectory != null) { @@ -206,8 +209,8 @@ class MetadataResolversControllerIntegrationTests extends Specification { 'ResourceBacked' | _ 'Filesystem' | _ } - - @DirtiesContext + + @WithMockAdmin def "SHIBUI-1992 - error creating FileBackedHTTPMetadata"() { def resolver = new FileBackedHttpMetadataResolver().with { it.name = 'FBHMR' @@ -220,42 +223,36 @@ class MetadataResolversControllerIntegrationTests extends Specification { } when: - def result = this.restTemplate.postForEntity(BASE_URI, createRequestHttpEntityFor { mapper.writeValueAsString(resolver) }, String) + def result = mockMvc.perform(post(BASE_URI).contentType(APPLICATION_JSON).content(mapper.writeValueAsString(resolver))) then: - result.statusCodeValue == 201 + result.andExpect(status().isCreated()) } + @WithMockAdmin @Unroll def "PUT concrete MetadataResolver of type #resolverType with updated changes -> /api/MetadataResolvers/{resourceId}"(String resolverType) { given: 'One resolver is available in data store' def resolver = generator.buildRandomMetadataResolverOfType(resolverType) String sourceDirectory - if (resolverType.equals('Localdynamic')) { + if (resolverType == 'Localdynamic') { sourceDirectory = ((LocalDynamicMetadataResolver) resolver).sourceDirectory } 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 result = mockMvc.perform(get("$BASE_URI/$resolverResourceId")).andReturn() 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) - then: - updatedResult.statusCodeValue == 200 + def metadataResolverMap = new JsonSlurper().parseText(result.getResponse().getContentAsString()) - and: - def updatedResolverMap = new JsonSlurper().parseText(updatedResult.body) + metadataResolverMap.name = 'Updated Resolver Name' + def updatedResult = mockMvc.perform(put("$BASE_URI/${metadataResolverMap.resourceId}").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(metadataResolverMap))) then: - updatedResolverMap.name == 'Updated DynamicHttpMetadataResolver' + updatedResult.andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.name").value('Updated Resolver Name')) cleanup: if (sourceDirectory != null) { @@ -274,6 +271,7 @@ class MetadataResolversControllerIntegrationTests extends Specification { 'Filesystem' | _ } + @WithMockAdmin def "PUT concrete MetadataResolver with version conflict -> /api/MetadataResolvers/{resourceId}"() { given: 'One resolver is available in data store' def resolver = new DynamicHttpMetadataResolver().with { @@ -290,44 +288,45 @@ class MetadataResolversControllerIntegrationTests extends Specification { 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) + MvcResult result = mockMvc.perform(get("$BASE_URI/$resolverResourceId")).andReturn() 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) + + def metadataResolverMap = mapper.readValue(result.getResponse().getContentAsString(), DynamicHttpMetadataResolver.class) metadataResolverMap.name = 'Updated DynamicHttpMetadataResolver' - def updatedResult = this.restTemplate.exchange( - "$BASE_URI/${metadataResolverMap.resourceId}", - PUT, - createRequestHttpEntityFor { JsonOutput.toJson(metadataResolverMap) }, - String) + def updatedResult = mockMvc.perform(put("$BASE_URI/${metadataResolverMap.resourceId}").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(metadataResolverMap))) then: - updatedResult.statusCodeValue == 409 + updatedResult.andExpect(status().isConflict()) } + @WithMockAdmin def "POST new MetadataResolver with one EntityAttributesFilters attached -> /api/MetadataResolvers"() { given: 'New MetadataResolver with attached entity attributes filter JSON representation' def resolver = generator.buildRandomMetadataResolverOfType('FileBacked') resolver.metadataFilters << generator.entityAttributesFilter() when: 'POST request is made with new FileBackedMetadataResolver with EntityAttributesFilter JSON representation' - def result = this.restTemplate.postForEntity(BASE_URI, createRequestHttpEntityFor { mapper.writeValueAsString(resolver) }, String) + def result = mockMvc.perform(post(BASE_URI).contentType(APPLICATION_JSON).content(mapper.writeValueAsString(resolver))) then: - result.statusCodeValue == 201 - result.headers.Location[0].contains(BASE_URI) + def location = result.andExpect(status().isCreated()).andReturn().getResponse().getHeaderValue("Location") + + location.contains(BASE_URI) when: 'Query REST API for newly created resolver' - def createdResolverResult = this.restTemplate.getForEntity(result.headers.Location[0], String) - def createdResolver = mapper.readValue(createdResolverResult.body, edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver) + def createdResolverResult = mockMvc.perform(get(location)).andReturn().getResponse().getContentAsString() + def createdResolver = mapper.readValue(createdResolverResult, edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver) then: createdResolver.metadataFilters.size() == 1 createdResolver.metadataFilters[0] instanceof EntityAttributesFilter } + @WithMockAdmin + @Transactional def "PUT MetadataResolver with one EntityAttributesFilters attached and check version -> /api/MetadataResolvers"() { given: 'MetadataResolver with attached entity attributes is available in data store' def resolver = generator.buildRandomMetadataResolverOfType('FileBacked') @@ -336,34 +335,74 @@ class MetadataResolversControllerIntegrationTests extends Specification { 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 existingMetadataResolverMap = new JsonSlurper().parseText(result.body) - def existingMetadataVersion = existingMetadataResolverMap.version + def result = mockMvc.perform(get("$BASE_URI/$resolverResourceId")).andReturn().getResponse().getContentAsString() + def existingMetadataResolverMap = new JsonSlurper().parseText(result) and: 'PUT call is made with' existingMetadataResolverMap.name = 'Updated' - def updatedResultFromPUT = this.restTemplate.exchange( - "$BASE_URI/${existingMetadataResolverMap.resourceId}", - PUT, - createRequestHttpEntityFor { JsonOutput.toJson(existingMetadataResolverMap) }, - String) - def updatedResultFromGET = this.restTemplate.getForEntity("$BASE_URI/$resolverResourceId", String) - def updatedVersionReturnedFromPUT = new JsonSlurper().parseText(updatedResultFromPUT.body).version - def updatedVersionReturnedFromGET = new JsonSlurper().parseText(updatedResultFromGET.body).version + def updatedResultFromPUT = mockMvc.perform(put("$BASE_URI/${existingMetadataResolverMap.resourceId}") + .contentType(APPLICATION_JSON).content(mapper.writeValueAsString(existingMetadataResolverMap))) + .andReturn().getResponse().getContentAsString() + def updatedResultFromGET = mockMvc.perform(get("$BASE_URI/$existingMetadataResolverMap.resourceId")).andReturn().getResponse().getContentAsString() then: - updatedVersionReturnedFromPUT == updatedVersionReturnedFromGET - } - - private HttpEntity createRequestHttpEntityFor(Closure jsonBodySupplier) { - new HttpEntity(jsonBodySupplier(), ['Content-Type': 'application/json'] as HttpHeaders) + updatedResultFromPUT == updatedResultFromGET } @TestConfiguration - static class Config { + private static class MRCILocalConfig { + @Bean + public AttributeUtility attributeUtility(OpenSamlObjects openSamlObjects) { + return new AttributeUtility(openSamlObjects); + } + + @Bean + DirectoryService directoryService() { + return new DirectoryServiceImpl() + } + + @Bean + JPAMetadataResolverServiceImpl jpaMetadataResolverService(MetadataResolver metadataResolver, MetadataResolverRepository metadataResolverRepository, + OpenSamlObjects openSamlObjects, MetadataResolversPositionOrderContainerService resolversPositionOrderContainerService, + ShibUIConfiguration shibUIConfiguration) { + return new JPAMetadataResolverServiceImpl().with { + it.metadataResolver = metadataResolver + it.metadataResolverRepository = metadataResolverRepository + it.openSamlObjects = openSamlObjects + it.resolversPositionOrderContainerService = resolversPositionOrderContainerService + it.shibUIConfiguration = shibUIConfiguration + it + } + } + + @Bean + MetadataResolversController metadataResolversController(MetadataResolverRepository metadataResolverRepository, MetadataResolverValidationService metadataResolverValidationService, + MetadataResolverService metadataResolverService, MetadataResolversPositionOrderContainerService positionOrderContainerService, + IndexWriterService indexWriterService, MetadataResolver chainingMetadataResolver, + MetadataResolverConverterService metadataResolverConverterService, MetadataResolverVersionService versionService) { + MetadataResolversController mrc = new MetadataResolversController().with { + it.resolverRepository = metadataResolverRepository + it.metadataResolverValidationService = metadataResolverValidationService + it.metadataResolverService = metadataResolverService + it.positionOrderContainerService = positionOrderContainerService + it.indexWriterService = indexWriterService + it.chainingMetadataResolver = chainingMetadataResolver + it.metadataResolverConverterService = metadataResolverConverterService + it.versionService = versionService + it + } + return mrc + } + + @Bean + public ModelRepresentationConversions modelRepresentationConversions(CustomPropertiesConfiguration customPropertiesConfiguration) { + return new ModelRepresentationConversions(customPropertiesConfiguration); + } + @Bean - MetadataResolver metadataResolver() { - new OpenSamlChainingMetadataResolver() + public MetadataResolversPositionOrderContainerService metadataResolversPositionOrderContainerService(MetadataResolversPositionOrderContainerRepository positionOrderContainerRepository, + MetadataResolverRepository resolverRepository) { + return new DefaultMetadataResolversPositionOrderContainerService(positionOrderContainerRepository, resolverRepository); } } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy index 0cde7c38b..80b3fad04 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy @@ -112,7 +112,7 @@ class IncommonJPAMetadataResolverServiceImplTests extends Specification { @TestConfiguration @Profile("local") - static class LocalConfig { + private static class LocalConfig { @Autowired OpenSamlObjects openSamlObjects diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy index 13847472c..e5f55b539 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy @@ -1,95 +1,36 @@ package edu.internet2.tier.shibboleth.admin.ui.service -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.context.TestConfiguration -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Profile -import org.springframework.context.annotation.PropertySource -import org.springframework.security.test.context.support.WithMockUser -import org.springframework.test.annotation.DirtiesContext -import org.springframework.test.annotation.Rollback -import org.springframework.test.context.ActiveProfiles -import org.springframework.test.context.ContextConfiguration -import org.springframework.transaction.annotation.Transactional - -import edu.internet2.tier.shibboleth.admin.ui.ShibbolethUiApplication -import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration -import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration +import edu.internet2.tier.shibboleth.admin.ui.BaseDataJpaTestSetup import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User -import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository -import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository -import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository -import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceForTesting -import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl -import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService -import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService -import spock.lang.Specification - -@ContextConfiguration(classes=[CoreShibUiConfiguration, CustomPropertiesConfiguration, LocalConfig]) -@SpringBootTest(classes = ShibbolethUiApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) -@PropertySource("classpath:application.yml") -@DirtiesContext -@ActiveProfiles(value="local") -class JPAEntityDescriptorServiceImplTests2 extends Specification { - - @Autowired - GroupServiceForTesting groupService +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.test.annotation.Rollback +import org.springframework.transaction.annotation.Transactional - @Autowired - RoleRepository roleRepository - +class JPAEntityDescriptorServiceImplTests2 extends BaseDataJpaTestSetup { @Autowired JPAEntityDescriptorServiceImpl entityDescriptorService - + @Autowired UserRepository userRepository - @Autowired - UserService userService - @Transactional def setup() { // ensure we start fresh with only expected users and roles and groups userRepository.deleteAll() - roleRepository.deleteAll() - groupService.clearAllForTesting() - - Group ga = new Group() - ga.setResourceId("testingGroup") - ga.setName("Group A") - ga = groupService.createGroup(ga) - - Group gb = new Group(); + + Group gb = new Group() gb.setResourceId("testingGroupBBB") gb.setName("Group BBB") gb = groupService.createGroup(gb) - - def roles = [new Role().with { - name = 'ROLE_ADMIN' - it - }, new Role().with { - name = 'ROLE_USER' - it - }, new Role().with { - name = 'ROLE_NONE' - it - }] - roles.each { - roleRepository.save(it) - } - - Optional adminRole = roleRepository.findByName("ROLE_ADMIN") - User adminUser = new User(username: "admin", roles: [adminRole.get()], password: "foo") - userService.save(adminUser) - + Optional userRole = roleRepository.findByName("ROLE_USER") - User user = new User(username: "someUser", roles:[userRole.get()], password: "foo", group: gb) + User user = new User(username: "someUser", roles: [userRole.get()], password: "foo", group: gb) userService.save(user) } @@ -100,32 +41,15 @@ class JPAEntityDescriptorServiceImplTests2 extends Specification { User current = userService.getCurrentUser() current.setGroupId("testingGroupBBB") - def expectedCreationDate = '2017-10-23T11:11:11' def expectedEntityId = 'https://shib' def expectedSpName = 'sp1' def expectedUUID = 'uuid-1' - def expectedResponseHeader = 'Location' - def expectedResponseHeaderValue = "/api/EntityDescriptor/$expectedUUID" def entityDescriptor = new EntityDescriptor(resourceId: expectedUUID, entityID: expectedEntityId, serviceProviderName: expectedSpName, serviceEnabled: false) - + when: def result = entityDescriptorService.createNew(entityDescriptor) - + then: - ((EntityDescriptorRepresentation)result).getIdOfOwner() == "testingGroupBBB" - } - - @TestConfiguration - @Profile("local") - static class LocalConfig { - @Bean - GroupServiceForTesting groupServiceForTesting(GroupsRepository repo, OwnershipRepository ownershipRepository) { - GroupServiceForTesting result = new GroupServiceForTesting(new GroupServiceImpl().with { - it.groupRepository = repo - it.ownershipRepository = ownershipRepository - return it - }) - return result - } + ((EntityDescriptorRepresentation) result).getIdOfOwner() == "testingGroupBBB" } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy index e91f3fbab..b6092c8e3 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy @@ -486,7 +486,7 @@ class JPAMetadataResolverServiceImplTests extends Specification { @TestConfiguration @Profile("local") - static class Config { + private static class Config { @Autowired OpenSamlObjects openSamlObjects @@ -526,4 +526,4 @@ class JPAMetadataResolverServiceImplTests extends Specification { } } } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/WithMockAdmin.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/WithMockAdmin.groovy new file mode 100644 index 000000000..9bd6f825a --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/WithMockAdmin.groovy @@ -0,0 +1,11 @@ +package edu.internet2.tier.shibboleth.admin.ui.util + + +import org.springframework.security.test.context.support.WithMockUser + +import java.lang.annotation.Retention +import java.lang.annotation.RetentionPolicy + +@Retention(RetentionPolicy.RUNTIME) +@WithMockUser(value = "admin", roles = ["ADMIN"]) +@interface WithMockAdmin {} \ No newline at end of file