Skip to content

Commit

Permalink
Merge 2980da7 into 45636d4
Browse files Browse the repository at this point in the history
  • Loading branch information
karolkalinski committed Aug 6, 2019
2 parents 45636d4 + 2980da7 commit 6d9d8ed
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ public Optional<ServiceContracts> find(String name, String version) {
return ofNullable(entityManager.find(ServiceContracts.class, new ServiceContracts.ServiceContractsId(name, version)));
}

@Override
public String getService(String name) {
return entityManager
.createQuery("select distinct o.id.name from " + ServiceContracts.class.getName() + " o where o.id.name = :name", String.class)
.setParameter("name", name)
.getSingleResult();

}

@Override
public List<ServiceContracts> find(String name) {
return entityManager
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package dev.hltech.dredd.domain.contracts;

import org.hibernate.annotations.NotFound;

import javax.persistence.EntityNotFoundException;
import java.util.List;
import java.util.Optional;

Expand All @@ -9,6 +12,8 @@ public interface ServiceContractsRepository {

Optional<ServiceContracts> find(String name, String version);

String getService(String name) throws EntityNotFoundException;

List<ServiceContracts> find(String name);

List<String> getServiceNames();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;

import javax.persistence.EntityNotFoundException;
import javax.persistence.NoResultException;
import java.util.List;
import java.util.Optional;

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

@RestController
@RequestMapping("/contracts")
@Transactional
public class ContractsController {

Expand All @@ -29,24 +35,16 @@ public ContractsController(ServiceContractsRepository serviceContractsRepository
this.mapper = mapper;
}

@PostMapping(value = "/contracts/{provider}/{version:.+}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Register contracts for a version of a service", nickname = "register contracts")
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Get registered contracts", nickname = "get names of services")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Success", response = ServiceContractsDto.class),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 500, message = "Failure")})
public ServiceContractsDto create(@PathVariable(name = "provider") String provider, @PathVariable(name = "version") String version, @RequestBody ServiceContractsForm form) {
return mapper.toDto(this.serviceContractsRepository.persist(
new ServiceContracts(
provider,
version,
mapper.mapCapabilitiesForm(form.getCapabilities()),
mapper.mapExpectationsForm(form.getExpectations())
)
));
@ApiResponse(code = 302, message = "Found")
})
public RedirectView getServicesEndpointDescription() {
return new RedirectView("contracts/services", true);
}

@GetMapping(value = "/contracts/services", produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "/services", produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Get names of services with registered contracts", nickname = "get names of services")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Success", response = String.class, responseContainer = "list"),
Expand All @@ -56,17 +54,24 @@ public List<String> getAvailableServiceNames() {
return serviceContractsRepository.getServiceNames();
}

@GetMapping(value = "/contracts", produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Get registered contracts", nickname = "get names of services")
@GetMapping(value = "/services/{serviceName}", produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Get details of a services with registered contracts", nickname = "get service details")
@ApiResponses(value = {
@ApiResponse(code = 302, message = "Found")
})
public RedirectView getServicesEndpointDescription() {
return new RedirectView("contracts/services", true);
@ApiResponse(code = 200, message = "Success", response = String.class),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 400, message = "Not found"),
@ApiResponse(code = 500, message = "Failure")})
public String getAvailableServiceNames(@PathVariable(name = "serviceName") String serviceName) {
return serviceContractsRepository.getService(serviceName);
}

@ExceptionHandler(NoResultException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
void notFound() {

}

@GetMapping(value = "/contracts/{serviceName}", produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "/services/{serviceName}/versions", produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Get versions of a service with registered contracts", nickname = "get versions of a service")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Success", response = String.class, responseContainer = "list"),
Expand All @@ -79,14 +84,30 @@ public List<String> getServiceVersions(@PathVariable(name = "serviceName") Strin
.collect(toList());
}

@GetMapping(value = "/contracts/{provider}/{version:.+}", produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "services/{serviceName}/versions/{version:.+}", produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Get contracts for a version of a service", nickname = "get contracts")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Success", response = ServiceContractsDto.class),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 500, message = "Failure")})
public ServiceContractsDto get(@PathVariable(name = "provider") String provider, @PathVariable(name = "version") String version) {
return mapper.toDto(this.serviceContractsRepository.find(provider, version).orElseThrow(ResourceNotFoundException::new));
public ServiceContractsDto get(@PathVariable(name = "serviceName") String serviceName, @PathVariable(name = "version") String version) {
return mapper.toDto(this.serviceContractsRepository.find(serviceName, version).orElseThrow(ResourceNotFoundException::new));
}

@PostMapping(value = "services/{serviceName}/versions/{version:.+}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Register contracts for a version of a service", nickname = "register contracts")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Success", response = ServiceContractsDto.class),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 500, message = "Failure")})
public ServiceContractsDto create(@PathVariable(name = "serviceName") String serviceName, @PathVariable(name = "version") String version, @RequestBody ServiceContractsForm form) {
return mapper.toDto(this.serviceContractsRepository.persist(
new ServiceContracts(
serviceName,
version,
mapper.mapCapabilitiesForm(form.getCapabilities()),
mapper.mapExpectationsForm(form.getExpectations())
)
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package dev.hltech.dredd.domain.contracts
import com.google.common.collect.Maps
import dev.hltech.dredd.domain.environment.EnvironmentAggregate

import javax.persistence.EntityNotFoundException
import javax.persistence.NoResultException

class InMemoryServiceContractsRepository implements ServiceContractsRepository {

private Map<EnvironmentAggregate.ServiceVersion, ServiceContracts> storage = Maps.newHashMap()
Expand All @@ -18,6 +21,19 @@ class InMemoryServiceContractsRepository implements ServiceContractsRepository {
return Optional.ofNullable(storage.get(new EnvironmentAggregate.ServiceVersion(name, version)))
}

@Override
String getService(String name) {
try {
return storage
.keySet()
.grep {it.name == name}
.first().name
} catch (NoSuchElementException nsee) {
throw new NoResultException("Not found")
}

}

@Override
List<ServiceContracts> find(String name) {
return storage.entrySet()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class ContractsControllerIT extends Specification {
def serviceName = randomAlphabetic(10)
def version = '1.0'
def response = mockMvc.perform(
get('/contracts/' + serviceName + '/' + version)
get(serviceNameVersionUrl(serviceName, version))
.contentType("application/json")
).andReturn().getResponse()
then: 'controller returns 404'
Expand All @@ -45,7 +45,7 @@ class ContractsControllerIT extends Specification {
def serviceName = randomAlphabetic(10)
def version = '1.0'
def response = mockMvc.perform(
post('/contracts/' + serviceName + '/' + version)
post(serviceNameVersionUrl(serviceName, version))
.contentType("application/json")
.content(objectMapper.writeValueAsString(randomServiceContractFormWithExpectationsAndCapabilities()))
).andReturn().getResponse()
Expand All @@ -55,19 +55,16 @@ class ContractsControllerIT extends Specification {
objectMapper.readValue(response.getContentAsString(), new TypeReference<ServiceContractsDto>() {})
}

private String serviceNameVersionUrl(String serviceName, String version) {
'/contracts/services/' + serviceName + '/versions/' + version
}

def 'should return 200 and json when get previously saved service contracts'() {
given: 'rest validatePacts url is hit'
def serviceName = randomAlphabetic(10)
def version = '1.0'
def serviceContractsForm = randomServiceContractFormWithExpectationsAndCapabilities()
mockMvc.perform(
post('/contracts/' + serviceName + '/' + version)
.contentType("application/json")
.content(objectMapper.writeValueAsString(serviceContractsForm))
).andReturn().getResponse()
def (serviceName, version) = createAService()
when:
def response = mockMvc.perform(
get('/contracts/' + serviceName + '/' + version)
get(serviceNameVersionUrl(serviceName, version))
.contentType("application/json")
).andReturn().getResponse()
then: 'controller returns dto response in json'
Expand All @@ -88,6 +85,31 @@ class ContractsControllerIT extends Specification {
objectMapper.readValue(response.getContentAsString(), new TypeReference<List<String>>() {})
}

def 'should successfully retrieve details of a service'() {
given:
def serviceName = createAService().get(0)
when:
def response = mockMvc.perform(
get('/contracts/services/{serviceName}', serviceName)
).andReturn().getResponse()
then:
response.getStatus() == 200
response.getContentType().contains("application/json")
response.getContentAsString() == serviceName
}

private def createAService() {
def version = '1.0'
def serviceName = randomAlphabetic(10)
mockMvc.perform(
post(serviceNameVersionUrl(serviceName, version))
.contentType("application/json")
.content(objectMapper.writeValueAsString(randomServiceContractFormWithExpectationsAndCapabilities()))
).andReturn().getResponse()
[serviceName, version]
}


def 'should calling /contracts redirect to /contracts/services to improve api discovery'() {
when:
def response = mockMvc.perform(
Expand All @@ -104,13 +126,13 @@ class ContractsControllerIT extends Specification {
def serviceName = randomAlphabetic(10)
def version = '1.0'
mockMvc.perform(
post('/contracts/' + serviceName + '/' + version)
post(serviceNameVersionUrl(serviceName, version))
.contentType("application/json")
.content(objectMapper.writeValueAsString(randomServiceContractFormWithExpectationsAndCapabilities()))
).andReturn().getResponse()
when:
def response = mockMvc.perform(
get('/contracts/' + serviceName)
get('/contracts/services/' + serviceName + "/versions")
).andReturn().getResponse()
then:
response.getStatus() == 200
Expand Down

0 comments on commit 6d9d8ed

Please sign in to comment.