I have created a service with Spring Boot and Reactor. I try to test with Cucumber.
POM file (extract):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-console</artifactId>
<scope>test</scope>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-bom</artifactId>
<version>7.10.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.9.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
My cucumber configuration looks like this:
#Suite
class CucumberTestRunner
#CucumberContextConfiguration
#ContextConfiguration(classes = [CucumberBootstrap::class])
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [MyApplication::class])
class CucumberSpringContextConfig
#ComponentScan("com.mypackage")
#Configuration
class CucumberBootstrap
The step definition I'm working on is like this:
#ScenarioScope
class MyTestStepDefs(
private val service: MyService
) {
#Given("User is authenticated")
fun userIsAuthenticated() {
val auth = UsernamePasswordAuthenticationToken(
User("username", "password", emptyList()),
"credentials",
emptyList<GrantedAuthority>()
)
val context = ReactiveSecurityContextHolder.getContext().block()
//ReactiveSecurityContextHolder.getContext()
// .doOnNext { it.authentication = auth }
// .block()
}
...
}
I would like to set the authenticated user into the security context. But unfortunately, the security context is always null.
I have also regular Springt Boot Tests running, where the security context is correctly returned. Here is an extract:
#SpringBootTest(webEnvironment = RANDOM_PORT)
#Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyControllerTests #Autowired constructor(
private val client: WebTestClient
) {
#Test
#WithMockUser
fun `post instruction set`() {
val context = ReactiveSecurityContextHolder.getContext().block()
...
}
...
}
The service class is injected correctly into the step definitions class.
Does anyone have a hint what's missing in the configuration?
Thanks in advance for your help!
The tests that work use the #WithMockUser annotation which work (as described here) because this enables the WithSecurityContextTestExecutionListener.
Unfortunately you can't use this with Cucumber, Cucumber doesn't have a single test method. But ultimately this listener sets the context using:
this.securityContextHolderStrategyConverter.convert(testContext).setContext(supplier.get())
This then invokes (via some indirection):
TestSecurityContextHolder.setContext(context)
Which you could do inside Cucumber too. Though you may want to review the implementation of the WithSecurityContextTestExecutionListener for what exactly you're trying to reproduce.
Alternatively you can do your test end-to-end including the authentication.
Based on M.P. Korstanje's description, I implemented the step as follows:
#Given("User is authenticated")
fun userIsAuthenticated() {
val auth = UsernamePasswordAuthenticationToken(
User("username", "password", emptyList()),
"credentials",
emptyList<GrantedAuthority>()
)
val context = TestSecurityContextHolder.getContext()
context.authentication = auth
val newContext = ReactiveSecurityContextHolder.getContext().block()
assertEquals(auth, newContext?.authentication, "Wrong authentication stored in security context")
}
Related
I am building a simple example with Spring boot + GrapQL
package com.example.graphql
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.graphql.data.method.annotation.QueryMapping
import org.springframework.web.bind.annotation.RestController
#SpringBootApplication
class GraphqlApplication
fun main(args: Array<String>) {
runApplication<GraphqlApplication>(*args)
}
#RestController
class MyQueryController {
#QueryMapping
fun message(): Message = Message("1","some-text")
}
data class Message(var id: String, var text: String) {
}
then I got the schema under src/main/resources/graphql/schema.graphqls withe the following
type Query {
message: Message
}
type Message {
id: String
text: String
}
When I try to go to http://localhost:8080/graphql I got [95221568-1] Completed 405 METHOD_NOT_ALLOWED, headers={masked}
Any clue ?
this are dependencies in the pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
....
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.kotlin</groupId>
<artifactId>reactor-kotlin-extensions</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
....
As explained by #d.j.brown in the comments, Spring for GraphQL does not support GET queries on the "/graphql" endpoint, even though it can be supported by other implementations. Only POST HTTP requests are supported.
If you'd like to explore your API, you can enable the graphiql UI by enabling it in the configuration properties with spring.graphql.graphiql.enabled=true and browsing http://localhost:8080/graphiql. From there, you'll be able to craft and send GraphQL queries using this playground tool.
i'm trying to insert some entities in my H2 database to run some unit tests, for this im using a setup method decorated with the #BeforeAll annotation like so :
#RunWith(SpringRunner.class)
#DataJpaTest
public class ItemRepositoryTest {
#Autowired
private ItemRepository itemRepo;
#Autowired
private CategoryRepository catRepo;
#BeforeAll
public static void setup(#Autowired ItemRepository itemRepo) {
Category cat_0 = new Category(0L, "category_0");
Category cat_1 = new Category(1L, "category_1");
Category cat_2 = new Category(2L, "category_2");
Item item_0 = new Item(0L, "item_0", "...", 7.99, "0000000000000", "imgUrl", Set.of(cat_0, cat_1));
Item item_1 = new Item(1L, "item_1", "...", 8.99, "1111111111111", "imgUrl", Set.of(cat_2));
Item item_2 = new Item(2L, "item_2", "...", 9.99, "2222222222222", "imgUrl", Set.of(cat_0, cat_1, cat_2));
itemRepo.saveAll(List.of(item_0, item_1, item_2));
}
#AfterAll
public static void clean(#Autowired ItemRepository itemRepo, #Autowired CategoryRepository catRepo) {
itemRepo.deleteAll();
catRepo.deleteAll();
}
#Test
public void items_should_have_been_saved() {
List<Item> items = itemRepo.findAll();
assertThat(items).hasSize(3);
}
}
The 'items_should_have_been_saved' Test fails (Expected size: 3 but was: 0), it seems my 'setup' method does not work, can anybody tell me what im doing wrong ? (I'm using SpringBoot 2.5.0)
src/test/resources/aplication.properties:
#----------------------------------------------------------------------------------
# CONFIG H2
#----------------------------------------------------------------------------------
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE;IGNORECASE=TRUE;
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=sa
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.defer-datasource-initialization=true
#----------------------------------------------------------------------------------
server.error.include-message=always
spring.sql.init.platform=h2
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>net.kaczmarzyk</groupId>
<artifactId>specification-arg-resolver</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
When trying to cache a PageImpl response from a Spring Data JpaRepository using Spring Data Geode, it fails to cache the result with the following error:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.domain.PageImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.data.domain.PageImpl.<init>()
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:127) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
at org.springframework.data.convert.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:64) ~[spring-data-commons-2.0.7.RELEASE.jar:2.0.7.RELEASE]
at org.springframework.data.convert.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:86) ~[spring-data-commons-2.0.7.RELEASE.jar:2.0.7.RELEASE]
at org.springframework.data.gemfire.mapping.MappingPdxSerializer.fromData(MappingPdxSerializer.java:422) ~[spring-data-gemfire-2.0.7.RELEASE.jar:2.0.7.RELEASE]
at org.apache.geode.pdx.internal.PdxReaderImpl.basicGetObject(PdxReaderImpl.java:741) ~[geode-core-9.1.1.jar:?]
at org.apache.geode.pdx.internal.PdxReaderImpl.getObject(PdxReaderImpl.java:682) ~[geode-core-9.1.1.jar:?]
at org.apache.geode.internal.InternalDataSerializer.readPdxSerializable(InternalDataSerializer.java:3054) ~[geode-core-9.1.1.jar:?]
It looks like the MappingPdxSerializer looks for a default constructor but doesn't find it for a PageImpl class.
Here is maven pom for the dependencies I have:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.BUILD-SNAPSHOT</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-gemfire</artifactId>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
The JpaRepository I am using is:
#RepositoryRestResource
public interface RecordRepository extends JpaRepository<Record, Long>
{
#Override
#CacheEvict(cacheNames = { "Records" })
<S extends Record> S save(S s);
#Override
#Cacheable(value = "Records")
Optional<Record> findById(Long id);
#Override
#Cacheable(value = "Records", key = "#pageable.pageNumber + '.' + #pageable.pageSize + '.records'")
Page<Record> findAll(Pageable pageable);
#Override
#Cacheable(value = "Records")
Record getOne(Long aLong);
}
The code used to invoke a repository paged result is:
int PAGE=0,PAGE_SIZE=100;
Page<Record> recordPage;
do {
recordPage = recordRepository.findAll(PageRequest.of(PAGE, PAGE_SIZE));
log.info("Retrieved page: [{}]", recordPage);
} while (recordPage.hasNext());
I feel like it maybe a possible bug with the MappingPdxSerializer, but I'm not 100% sure. Any help in resolving this issue would be awesome!
Thanks
Why do you feel this is a possible bug with Spring Data Geode's (SDG) o.s.d.g.mapping.MappingPdxSerializer?
It is quite common, and even expected, that not all objects passed through SDG's MappingPdxSerializer will have a default (i.e. public, no-arg) constructor.
When using such types in your application (e.g. like the SD PageImpl class) and an instance of that type is read from Apache Geode (e.g. get(key)), the object is de-serialized and reconstructed on the (Region) data access operation (providing Apache Geode's read-serialized configuration attribute is not set to true; which cause you other problems and not recommended in this case), then you need to register an EntityInstantiator that informs SDG's MappingPdxSerializer how to instantiate the object, using an appropriate constructor.
The "appropriate" constructor is determined by the persistent entity's PreferredConstructor, which is evaluated during type evaluation by the SD Mapping Infrastructure, and can be specified with the #PersistenceContructor annotation, if necessary. This is useful in cases where you are using 1 of SD's canned EntityIntantiator types, e.g. ReflectionEntityInstantiator, and your application domain type has more than 1 non-default constructor.
Therefore, you can register 1 or more EntityInstantiator objects per application domain object by type using the EntityIntantiatiors composite class, perhaps with a "mapping" between application domain object Class type (e.g. Page) and EntityInstantiator, and then register the EntityInstantiators on SDG's MappingPdxSerializer.
Of course, you need to make sure that custom configured MappingPdxSerializer gets used by Apache Geode...
#Configuration
class ApacheGeodeConfiration {
#Bean
MappingPdxSerializer pdxSerializer() {
Map<Class<?>, EntityInstantiator> customInstantiators = new HashMap<>();
customInstantiators.put(Page.class, new MyPageEntityInstantiator());
customInstantiators.put...
MappingPdxSerializer pdxSerializer =
MappingPdxSerializer.newMappingPdxSerializer();
pdxSerializer.setGemfireInstantiators(
new EntityInstantiators(customInstantiators));
return pdxSerializer;
}
#Bean
CacheFactoryBean gemfireCache(MappingPdxSerializer pdxSerializer) {
CacheFactoryBean gemfireCache = new CacheFactoryBean();
gemfireCache.setPdxSerializer(pdxSerializer);
gemfireCache.set...
return gemfireCache;
}
...
}
Hope this helps!
-j
I'm using Spring Boot with Kotlin and Java8 time. Somehow the Jackson annotations on the data class fields are ignored.
import com.fasterxml.jackson.annotation.JsonProperty
import java.time.MonthDay
data class DataView(val id: String,
#get:JsonProperty("dayOfMonth") val monthDay: MonthDay) {}
Why would these annotations be ignored? The response still contains the field name "monthDay".
I know the annotation is correct, according to this discussion on discuss.kotlinlang.org.
I don't have any configurations that should affect this, as far as I know.
pom contains:
...
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>1.5.6.RELEASE</version>
<relativePath/>
</parent>
...
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hppc</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-json-org</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-afterburner</artifactId>
</dependency>
...
Very strange. I check this code local, and all looks fine
data class DataView(val id: String,
#get:JsonProperty("dayOfMonth") val monthDay: MonthDay)
data class MonthDay(val day: Int)
fun main(args: Array<String>) {
val objectMapper = ObjectMapper()
.registerKotlinModule()
val dataView = DataView("someId", MonthDay(1))
//{"id":"someId","dayOfMonth":{"day":1}}
println(objectMapper.writeValueAsString(dataView))
}
It works same even if I remove registerKotlinModule and change from #get to #field.
I Have a Repository and Controller,
#RestController
public class PersonController {
#Autowired
PersonRepository repository;
#GetMapping("/people")
public Iterable<Person> getPeople() {
return repository.findAll();
}
#PostMapping("/people")
public void addPerson(#RequestBody Person person) {
repository.save(person);
}
}
and I want to create endpoint /people but this doesn't work without the dependency Spring-Boot-Data-REST-Starter.
The question is : is it possible to create an endpoint without the Spring-Boot-Data-REST-Starter dependency?
here's my dependencies:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
I have a message : Started Application, but the process is finished with exit code 0, so nothing is up.
I should have add this dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>