Katharsis with SpringBootTest - katharsis

I am having an interesting issue where API exposed by Katharsis are not found when I am trying to call them under SpringBootTest. I am able to connect to Spring MVC APIs using the same approach but Kartharsis end points are returning HTTP 404 inside the test. I am able to connect to Kartharsis end points while connecting via outside processes like invoking the same via a browser.
I have tried multiple solutions by replacing Spring TestRestTemplate with RestTemplate or OkHTTP all are having same issue.
Here are snippet of my code:
public class ProgramControllerTest_Search extends WebApiLayerIntegrationTest{
void "should get one program by id"() {
given:
Program program = new Program(programTitle: "Test Title")
program = this.programRepository.save(program)
when:
def response = this.testRestTemplate.getForEntity("jsonapi/programs/" + (program.id), Map.class)
then: "response status is ok"
noExceptionThrown()
response.statusCode == HttpStatus.OK
and: "response body has expected values"
response.body.data.id == program.id
response.body.data.attributes.programTitle == "Test Title"
}
}
#Slf4j
#Stepwise
#SpringBootTest(classes = [EscobarApplication.class],
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles(["integration-test", "my-local-test-config"])
public abstract class WebApiLayerIntegrationTest extends Specification {
#LocalServerPort
private int httpPort
#Autowired
protected TestRestTemplate testRestTemplate
#Autowired
private DataSourceProperties dsProp
#PostConstruct
void printTestEnvironmentInfo() {
log.info(">>> WebApiLayerIntegrationTest, using Datasource: url=${dsProp.getUrl()}, user=${dsProp.getUsername()}.")
}
protected int getHttpPort() {
return httpPort
}
}
#Entity
#Audited
#JsonApiResource(type = "programs")
public class Program extends BaseEntity {
#Id #JsonApiId
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Column(length = 128, name = "program_title")
#Size(min = 2, max = 128)
private String programTitle;
}

Related

#TransactionalEventListener annotated method not invoked in #Transactional test

I'm trying to implement domain event publishing from an entity by following the examples mentioned on the post below:
Example for #DomainEvents and #AfterDomainEventsPublication
However I haven't managed to have Spring calling my method annotated with #TransactionalEventListener.
See below the entity, service, event listener and test code:
#Entity
public class Book extends AbstractAggregateRoot<Book>
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(unique = true)
private String isbn;
#Column
private String name;
public Book(String isbn, String name)
{
this.isbn = isbn;
this.name = name;
}
public void purchase()
{
registerEvent(new BookPurchasedEvent(id));
}
// getters omitted for brevity
}
Service:
#Service
#Transactional
public class BookService
{
private final BookRepository bookRepository;
public BookService(BookRepository bookRepository)
{
this.bookRepository = bookRepository;
}
public void purchaseBook(Integer bookId)
{
Book book = bookRepository.findById(bookId)
.orElseThrow(NoSuchElementException::new);
book.purchase();
bookRepository.save(book);
}
}
Listener:
#Service
public class EventListener
{
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#TransactionalEventListener
public void handleEvent(BookPurchasedEvent event)
{
logger.info("Received event {}", event);
}
}
Test:
#RunWith(SpringRunner.class)
#SpringBootTest
#Transactional
public class BookEventsTest
{
#Autowired
private BookService bookService;
#Autowired
private EntityManager entityManager;
#Test
public void test()
{
Book book = new Book("abcd-efgh", "El Quijote");
book = entityManager.merge(book);
bookService.purchaseBook(book.getId());
}
}
The log message from the listener is not logged. It works though when deployed as a REST service and invoked e.g. via Postman
Got it. Since my test is annotated with #Transactional, the transaction wrapping the test method will be rolled back. Therefore the method annotated with #TransactionalEventListener won't be called, since by default it triggers at the phase TransactionPhase.AFTER_COMMIT (and I'm not interested in having it called unless the transaction is successful). So the working version of the test looks as follows:
#RunWith(SpringRunner.class)
#SpringBootTest
public class BookEventsTest
{
#Autowired
private BookService bookService;
#Autowired
private BookRepository bookRepository;
#MockBean
private EventListener eventListener;
private Book book;
#Before
public void init() {
book = bookRepository.save(new Book("abcd-efgh", "El Quijote"));
}
#After
public void clean() {
bookRepository.deleteAll();
}
#Test
public void testService()
{
bookService.purchaseBook(book.getId());
then(eventListener)
.should()
.handleEvent(any(BookPurchasedEvent.class));
}
}

How to add HTTP status code with error message in spring

I created one spring data jpa Application. In this application my method request is GET. but if I am trying to access that method Request url as post request. In this situation I want to know how to add HTTP status code 405(Method Not Allowed) with my custom error message.
Here is my code snippet
DepartmentModel
package com.demo.model;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "department")
public class DepartmentModel implements Serializable {
private static final long serialVersionUID = 1L;
#Id
public Integer ndeptid;
public String sdeptname ;
public Integer ninstid ;
public Boolean bislocked;
public String sclientdeptid;
public Integer nsurveymethodid;
public Boolean bisjointuse;
public Integer ntempdeptid;
public Boolean balternatejointusepercentage;
public Integer ndivid;
//getter and setter
DepartmentRepository
#Repository
public interface DepaertmentRepository extends JpaRepository<DepartmentModel, Integer>
{
#Query("select new map(dep.sdeptname as sdeptname)"
+ " from DepartmentModel as dep where dep.ninstid=60")
Set<DepartmentModel> findBySDepName();
}
DepartmentService
#Service
public class DepartmentService
{
#Autowired
DepaertmentRepository depRepo;
public Set<DepartmentModel> findDepName()
{
return depRepo.findBySDepName();
}
}
DepartmentController
#RestController
#RequestMapping("/SpaceStudy/SpaceAdmin")
public class DepartmentController {
#Autowired
DepartmentService depService;
#CrossOrigin(origins="*")
#GetMapping("AccountMaintenance/LoadDepartment")
//#ResponseStatus( value = HttpStatus.METHOD_NOT_ALLOWED)
public Set<DepartmentModel> findDepName() {
return depService.findDepName();
}
}
can any one help me how to add HTTP status code (405) with proper message when i am accessing GET request as post
You can override the method handleHttpRequestMethodNotSupported of ResponseEntityExceptionHandler and implement your own error message object. For example:
#Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest req) {
headers.setAllow(ex.getSupportedHttpMethods());
ErrorMessage errorMessage = ErrorMessage.of(
status.value(),
"You cannot make this request - the method is not allowed!",
ex.getMessage(),
((ServletWebRequest) req).getRequest().getServletPath()
);
return new ResponseEntity<>(errorMessage, headers, status);
}
#Value(staticConstructor = "of")
private static class ErrorMessage {
private Instant timestamp = Instant.now();
private Integer status;
private String error;
private String message;
private String path;
}
See my full demo for more info.
You can override not only this method of ResponseEntityExceptionHandler but all the remaining to get custom handling of other exceptions.
Note: you can use another approach to handle exceptions (or use both) - implement an exception handler.
UPDATE
It's necessary to add #ControllerAdvice annotation to the class that extended ResponseEntityExceptionHandler.

IllegalArgumentException: NamedQuery using Spring JPA

I am using namedquery for rest api using Spring JPA. The named query is implemented in my entity class:
#Entity
#Table(name="SPECIMEN_TB")
#NamedQueries({
#NamedQuery(name="SpecimenTb.findBySpecimenNo", query="select s from SpecimenTb s where s.specimenNo = :specimenNo"),
})
public class SpecimenTb implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SPECIMEN_TB_ROWID_GENERATOR")
#Column(name="ROW_ID")
private long rowId;
#Column(name="SPECIMEN_NO", unique = true)
private String specimenNo;
My controller looks like this:
#RestController
public class RistoreController {
#Autowired
private RistoreService ristoreService;
#RequestMapping(
value = "/ristore/foundation/{specno}",
method = RequestMethod.GET,
produces = "application/json")
public ResponseEntity<SpecimenTb> getFmSpecimen(#PathVariable("specno") String specno) {
List<SpecimenTb> specimens = ristoreService.findBySpecimenNo(specno);
if (specimens == null) {
return new ResponseEntity<SpecimenTb>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<SpecimenTb>(specimens.get(0), HttpStatus.OK);
}
I have a service bean which calls JPA repository findBySpecimenNo method.
#Service
public class RistoreServiceBean implements RistoreService {
#Autowired
private SpecimenRepository specimenRepository;
#Override
public List<SpecimenTb> findAll() {
List<SpecimenTb> specimens = specimenRepository.findAll();
return specimens;
}
#Override
public List<SpecimenTb> findBySpecimenNo(String specimenNo) {
List<SpecimenTb> specimens = specimenRepository.findBySpecimenNo(specimenNo);
return specimens;
}
When I start the Spring Boot Application and type in the url "http://localhost:8080/ristore/foundation/SKM1", I got the following error:
java.lang.IllegalArgumentException: Parameter with that position [1] did not exist
What did I do wrong?
Looks like you can't use a named parameter with the #NamedQuery based on the docs I read. Have you tried with ?1 instead?
Reason that named parameter doesn't work is that you also have to add the annotation on the method parameter so Spring knows which parameter matches to what placeholder in the query.

How to reference an entity with inheritance in Spring Data REST when POSTing new entity?

I have entities with joined inheritance:
Supporter
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "supporterType")
#JsonSubTypes({
#JsonSubTypes.Type(value = PersonSupporterEntity.class, name = "PERSON"),
#JsonSubTypes.Type(value = CompanySupporterEntity.class, name = "COMPANY")
})
#DiscriminatorColumn(name="supporter_type")
#Table(name = "supporter")
public class SupporterEntity extends UpdatableEntity {
private long id;
private SupporterType supporterType;
private PartnerEntity partner;
...
}
PersonSupporter
#Entity
#DiscriminatorValue("PERSON")
#Table(name = "person_supporter")
public class PersonSupporterEntity extends SupporterEntity {
...
}
CompanySupporter
#Entity
#DiscriminatorValue("COMPANY")
#Table(name = "company_supporter")
public class CompanySupporterEntity extends SupporterEntity {
...
}
I have another entity which references SupporterEntity
#Entity
#Table(name = "contact")
public class ContactEntity extends UpdatableEntity {
private long id;
private SupporterEntity supporter;
...
#ManyToOne // same error with #OneToOne
#JoinColumn(name = "supporter_id", referencedColumnName = "id", nullable = false)
public SupporterEntity getSupporter() {
return supporter;
}
...
}
Repositories
#Transactional
#RepositoryRestResource(collectionResourceRel = "supporters", path = "supporters")
public interface SupporterEntityRepository extends JpaRepository<SupporterEntity, Long> {
#Transactional(readOnly = true)
#RestResource(path = "by-partner", rel = "by-partner")
public Page<SupporterEntity> findByPartnerName(#Param("name") String name, Pageable pageable);
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "person_supporters", path = "person_supporters")
public interface PersonSupporterEntityRepository extends JpaRepository<PersonSupporterEntity, Long> {
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "company_supporters", path = "company_supporters")
public interface CompanySupporterEntityRepository extends JpaRepository<CompanySupporterEntity, Long> {
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "contacts", path = "contacts")
public interface ContactEntityRepository extends JpaRepository<ContactEntity, Long> {
#Transactional(readOnly = true)
#RestResource(path = "by-supporter", rel = "by-supporter")
public ContactEntity findBySupporterId(#Param("id") Long id);
}
I use Spring Boot, Spring Data REST, Spring Data JPA, Hibernate, Jackson. When I try to create a new ContactEntity with a post request like this:
{
"supporter":"/supporters/52",
"postcode":"1111",
"city":"Test City 1",
"address":"Test Address 1",
"email":"test1#email.com",
"newsletter":true
}
I get this exception:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_STRING), expected FIELD_NAME: missing property 'supporterType' that is to contain type id (for class com.facer.domain.supporter.SupporterEntity)
at [Source: HttpInputOverHTTP#4321c221; line: 1, column: 2] (through reference chain: com.facer.domain.supporter.ContactEntity["supporter"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148) ~[jackson-databind-2.4.4.jar:2.4.4]
After 2 days of debugging I found a way, but I kinda guessed it. So if I post it like this:
{
"supporter":{
"supporterType":"PERSON",
"id":"52"
},
"postcode":"1111",
"city":"Test City 1",
"address":"Test Address 1",
"email":"test1#email.com",
"newsletter":true
}
It works, but I don't know why. What's wrong with the other request? It works like that everywhere else when the referenced entity does not have inheritance.
Just another workaround using a RelProvider:
Do not use #JsonTypeInfo
Create a RelProvider for SupporterEntity sub-classes
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SupporterEntityRelProvider implements RelProvider {
#Override
public String getCollectionResourceRelFor(final Class<?> type) {
return "supporters";
}
#Override
public String getItemResourceRelFor(final Class<?> type) {
return "supporter";
}
#Override
public boolean supports(final Class<?> delimiter) {
return org.apache.commons.lang3.ClassUtils.isAssignable(delimiter, SupporterEntity.class);
}
}
See also:
https://jira.spring.io/browse/DATAREST-344
http://docs.spring.io/spring-hateoas/docs/current/reference/html/#configuration.at-enable
It looks like a Jackson problem. To be specific, it's the following code in com.fasterxml.jackson.databind.deser.SettableBeanProperty:
if (_valueTypeDeserializer != null) {
return _valueDeserializer.deserializeWithType(jp, ctxt, _valueTypeDeserializer);
}
return _valueDeserializer.deserialize(jp, ctxt);
Without inheritance _valueDeserializer.deserialize would be called which in turn runs some Spring code to convert the URI to a Supporter.
With inheritance _valueDeserializer.deserializeWithType is called and vanilla Jackson, of course, expects an object, not a URI.
If supporter was nullable you could first POST to /contacts and then PUT the supporter's URI to /contacts/xx/supporter. Unfortunately I am not aware of any other solution.
You should be able to workaround this by setting #JsonTypeInfo(use= JsonTypeInfo.Id.NONE) at the property/method level e.g.
Try with this:
#ManyToOne // same error with #OneToOne
#JoinColumn(name = "supporter_id", referencedColumnName = "id", nullable = false)
#JsonTypeInfo(use= JsonTypeInfo.Id.NONE)
public SupporterEntity getSupporter() {
return supporter;
}

Spring restful service test case fail HTTP status is 500

I want to implement test case for spring restful web services which return a json
MY controller test class is :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebAppContext.class,JpaTestConfiguration.class
})
#WebAppConfiguration
public class DominProfileRestControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testGetDomainProfile() throws Exception {
String profileId = domainProfile.getId().toString();
System.out.print("testing restful"+profileId);
mockMvc.perform(get("/service/domainprofile/get/{id}", profileId) )
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.city", is("Chandigrah")));
/* mockMvc.perform(get("/service/domainprofile/get/{id}",profileId).accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
.andExpect(content().string("hello Prashant"));
*/
}
My Controller class Is :
#RestController
#RequestMapping("/service/domainprofile")
public class DominProfileRestController {
#Autowired
private JpaDomainProfileRepository jpaDomainProfileRepository;
#RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
public DomainProfileResource getDomainProfile(#PathVariable String id) {
JpaDomainProfile domainProfile = jpaDomainProfileRepository.findOne(Long.valueOf(id));
DomainProfileResource domainProfileResource = new DomainProfileResource();
System.out.println("domainProfile.getCity()*************" + domainProfile.getCity());
System.out.println("domainProfile.getAddress()*************" + domainProfile.getAddress());
domainProfileResource.setCity(domainProfile.getCity());
domainProfileResource.setAddress(domainProfile.getAddress());
// return new ResponseEntity<DomainProfileResource>(domainProfileResource, HttpStatus.OK);
return domainProfileResource;
// return domainProfile;
}
}
When I run test case I got An error while we got values in domainprofile.city and domainprofile.address.
Error Is :
java.lang.AssertionError: Status
Expected :200
Actual :500
It Is Working Fine When I return a plain text
can you do this
mockMvc.perform(get("/service/domainprofile/get/{id}", profileId) )
.andDo(print());
this will print the full response with exception , now if you see HttpMessageNotWritableException which was the issue I was facing , you should try to serialize your object using jackson and see if it works (spring internally uses Jackson ). For example , If any of your fields are null the Serialization will fail.