Skip to content

Commit

Permalink
Merge branch 'master' into SHIBUI-723
Browse files Browse the repository at this point in the history
  • Loading branch information
Bill Smith committed Aug 22, 2018
2 parents 027d8c6 + 7885efc commit 1179a46
Show file tree
Hide file tree
Showing 30 changed files with 950 additions and 144 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package edu.internet2.tier.shibboleth.admin.ui.controller;

import edu.internet2.tier.shibboleth.admin.ui.controller.support.RestControllersSupport;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver;
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

/**
* @author Dmitriy Kopylenko
*/
@RestController
@RequestMapping("/api/MetadataResolvers/{metadataResolverId}/FiltersPositionOrder")
public class MetadataFiltersPositionOrderController {

@Autowired
MetadataResolverRepository metadataResolverRepository;

@Autowired
RestControllersSupport restControllersSupport;

@PostMapping
@Transactional
public ResponseEntity<?> updateFiltersPositionOrder(@PathVariable String metadataResolverId,
@RequestBody List<String> filtersResourceIds) {

MetadataResolver resolver = restControllersSupport.findResolverOrThrowHttp404(metadataResolverId);
List<MetadataFilter> currentFilters = resolver.getMetadataFilters();

//Check for bad data upfront. We could avoid this check and take wrong size and/or filter ids and blindly pass to sort below.
//In that case, the sort operation will silently NOT do anything and leave original filters order,
//but we will not be able to indicate to calling clients HTTP 400 in that case.
if ((filtersResourceIds.size() != currentFilters.size()) ||
(!currentFilters.stream()
.map(MetadataFilter::getResourceId)
.collect(toList())
.containsAll(filtersResourceIds))) {

return ResponseEntity
.badRequest()
.body("Number of filters to reorder or filters resource ids do not match current filters");
}

//This is needed in order to set reference to persistent filters collection to be able to merge the persistent collection
//Otherwise if we manipulate the original collection directly and try to save, we'll get RDBMS constraint violation exception
List<MetadataFilter> reOrderedFilters = new ArrayList<>(currentFilters);

//Main re-ordering operation
reOrderedFilters.sort(Comparator.comparingInt(f -> filtersResourceIds.indexOf(f.getResourceId())));

//re-set the reference and save to DB
resolver.setMetadataFilters(reOrderedFilters);
metadataResolverRepository.save(resolver);

return ResponseEntity.noContent().build();
}

@GetMapping
@Transactional(readOnly = true)
public ResponseEntity<?> getFiltersPositionOrder(@PathVariable String metadataResolverId) {
MetadataResolver resolver = restControllersSupport.findResolverOrThrowHttp404(metadataResolverId);
List<String> resourceIds = resolver.getMetadataFilters().stream()
.map(MetadataFilter::getResourceId)
.collect(toList());

return ResponseEntity.ok(resourceIds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package edu.internet2.tier.shibboleth.admin.ui.controller.support;

import com.google.common.collect.ImmutableMap;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver;
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpClientErrorException;

import static org.springframework.http.HttpStatus.NOT_FOUND;

/**
* Common functionality for REST controllers.
*
* @author Dmitriy Kopylenko
*/
@RestControllerAdvice
public class RestControllersSupport {

@Autowired
MetadataResolverRepository resolverRepository;

public MetadataResolver findResolverOrThrowHttp404(String resolverResourceId) {
MetadataResolver resolver = resolverRepository.findByResourceId(resolverResourceId);
if(resolver == null) {
throw new HttpClientErrorException(NOT_FOUND, "Metadata resolver is not found");
}
return resolver;
}


@ExceptionHandler
public ResponseEntity<?> notFoundHandler(HttpClientErrorException ex) {
if(ex.getStatusCode() == NOT_FOUND) {
return ResponseEntity.status(NOT_FOUND).body(ex.getStatusText());
}
throw ex;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package edu.internet2.tier.shibboleth.admin.ui.controller

import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository
import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator
import edu.internet2.tier.shibboleth.admin.util.AttributeUtility

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.test.context.ActiveProfiles

import spock.lang.Specification


/**
* @author Dmitriy Kopylenko
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("no-auth")
class MetadataFiltersPositionOrderControllerIntegrationTests extends Specification {

@Autowired
private TestRestTemplate restTemplate

@Autowired
MetadataResolverRepository metadataResolverRepository

@Autowired
AttributeUtility attributeUtility

TestObjectGenerator generator

static BASE_URI = '/api/MetadataResolvers'

static RESOURCE_URI = "$BASE_URI/%s/FiltersPositionOrder"

def setup() {
generator = new TestObjectGenerator(attributeUtility)
}

def cleanup() {
metadataResolverRepository.deleteAll()
}

def "GET Filter Position Order for non-existent resolver"() {
when: 'GET request is made with resolver resource id NOT matching any existing filter'
def result = getFiltersPositionOrderFor('non-existent-resolver-id', String)

then: "Request completed successfully"
result.statusCodeValue == 404
}

def "GET Default Filter Position Order for filters originally attached to a resolver"() {
given: 'MetadataResolver with 2 filters in data store'
def resolver = createResolverWithTwoFilters()

when: 'GET request is made to retrieve position order of filters'
def result = getFiltersPositionOrderFor(resolver.resourceId, List)

then: 'Original filters order is preserved'
result.statusCodeValue == 200
result.body == [resolver.firstFilterResourceId, resolver.secondFilterResourceId]
}

def "Reorder filters and verify the position within resolver persisted accordingly"() {
given: 'MetadataResolver with 2 filters in data store'
def resolver = createResolverWithTwoFilters()
def reOrderedFiltersPosition = [resolver.secondFilterResourceId, resolver.firstFilterResourceId]

when: 'POST is made to re-order filters position'
def reorderPOSTResult = reorderFilters(resolver.resourceId, reOrderedFiltersPosition)

then: 'Request completed successfully'
reorderPOSTResult.statusCodeValue == 204

and: 'GET request is made to retrieve position order of filters'
def positionOrderResult = getFiltersPositionOrderFor(resolver.resourceId, List)

then:
positionOrderResult.body == reOrderedFiltersPosition

and: "Request is made to retrieve the resolver with affected filters"
def resolverResult = getResolver(resolver.resourceId)

then:
resolverResult.statusCodeValue == 200
resolverResult.body.metadataFilters.collect {it.resourceId} == reOrderedFiltersPosition
}

def "Reorder filters with bad data"() {
given: 'MetadataResolver with 2 filters in data store'
def resolver = createResolverWithTwoFilters()
def originalFiltersPosition = [resolver.firstFilterResourceId, resolver.secondFilterResourceId]
//Only one filter in order position data, while there are two filters
def reOrderedFiltersPosition = [resolver.secondFilterResourceId]

when: 'POST is made to re-order filters position with invalid number of filters to re-order'
def reorderPOSTResult = reorderFilters(resolver.resourceId, reOrderedFiltersPosition)

then: 'Request completed successfully with 400'
reorderPOSTResult.statusCodeValue == 400

and: 'GET request is made to retrieve position order of filters'
def positionOrderResult = getFiltersPositionOrderFor(resolver.resourceId, List)

then: 'Original filters position order is retrieved'
positionOrderResult.body == originalFiltersPosition

and: "Request is made to retrieve the resolver with original filters"
def resolverResult = getResolver(resolver.resourceId)

then:
resolverResult.statusCodeValue == 200
resolverResult.body.metadataFilters.collect {it.resourceId} == originalFiltersPosition

and: 'POST is made to re-order filters position with invalid resource ids'
def reorderPOSTResult_2 = reorderFilters(resolver.resourceId, [resolver.secondFilterResourceId, 'invalid-id'])

then: 'Request completed successfully with 400'
reorderPOSTResult_2.statusCodeValue == 400
}

private createResolverWithTwoFilters() {
def resolver = generator.buildRandomMetadataResolverOfType('FileBacked')
resolver.metadataFilters = [generator.signatureValidationFilter(), generator.entityRoleWhitelistFilter()]
def resolverResourceId = resolver.resourceId
def firstFilterResourceId = resolver.metadataFilters[0].resourceId
def secondFilterResourceId = resolver.metadataFilters[1].resourceId
metadataResolverRepository.save(resolver)

[resourceId : resolverResourceId,
firstFilterResourceId : firstFilterResourceId,
secondFilterResourceId: secondFilterResourceId]
}

private getFiltersPositionOrderFor(String resourceId, responseType) {
this.restTemplate.getForEntity(resourceUriFor(resourceId), responseType)
}

private reorderFilters(String resourceId, List filterIdsPositionOrderList) {
this.restTemplate.postForEntity(resourceUriFor(resourceId), filterIdsPositionOrderList, null)
}

private getResolver(String resolverResourceId) {
this.restTemplate.getForEntity("$BASE_URI/$resolverResourceId", Object)
}

private static resourceUriFor(String resolverResourceId) {
String.format(RESOURCE_URI, resolverResourceId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package edu.internet2.tier.shibboleth.admin.ui.service

import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration
import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration
import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration
import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration
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 spock.lang.Specification

/**
* @author Bill Smith (wsmith@unicon.net)
*/
@DataJpaTest
@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, TestConfiguration, InternationalizationConfiguration])
@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"])
@EntityScan("edu.internet2.tier.shibboleth.admin.ui")
class IndexWriterServiceTests extends Specification {

@Autowired
IndexWriterService service

def "retrieving index writer for the same resource id multiple times results in the same index writer being returned"() {
given:
def resourceId = "12345"

when:
def firstIndexWriter = service.getIndexWriter(resourceId) // causes new index writer to be created and added to map
def secondIndexWriter = service.getIndexWriter(resourceId) // retrieves the same index writer from above

then:
firstIndexWriter == secondIndexWriter
}
}
59 changes: 59 additions & 0 deletions ui/src/app/contention/reducer/contention.reducer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { reducer, initialState as snapshot, getContention } from './contention.reducer';
import { ContentionActionTypes, ShowContentionAction, ResolveContentionAction, CancelContentionAction } from '../action/contention.action';

describe('Contention Reducer', () => {

const contention = {
base: {},
ours: {},
theirs: {},

rejectionObject: {},
resolutionObject: {},

ourChanges: {},
theirChanges: {},

handlers: {
resolve: (value: {}) => ({}),
reject: (value: {}) => ({})
}
};

const populated = { ...snapshot, contention: { ...contention } };

const resolution = { value: contention.ours, handlers: contention.handlers };

describe('undefined action', () => {
it('should return the default state', () => {
const result = reducer(snapshot, {} as any);

expect(result).toEqual(snapshot);
});
});

describe(`${ContentionActionTypes.SHOW_CONTENTION}`, () => {
it('should reset to initial state', () => {
expect(reducer(snapshot, new ShowContentionAction(contention))).toEqual(populated);
});
});

describe(`${ContentionActionTypes.RESOLVE_CONTENTION}`, () => {
it('should reset to initial state', () => {
expect(reducer(snapshot, new ResolveContentionAction(resolution))).toEqual(snapshot);
});
});

describe(`${ContentionActionTypes.CANCEL_CONTENTION}`, () => {
it('should reset to initial state', () => {
expect(reducer(snapshot, new CancelContentionAction(resolution))).toEqual(snapshot);
});
});

describe(`getContention method`, () => {
it('should return the contention object from the state', () => {
expect(getContention(snapshot)).toBe(snapshot.contention);
expect(getContention(populated)).toEqual(contention);
});
});
});
1 change: 1 addition & 0 deletions ui/src/app/core/model/query.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface QueryParams {
term: string;
resourceId?: string;
limit?: number;
offset?: number;
}
Loading

0 comments on commit 1179a46

Please sign in to comment.